1 package com.android.ddmlib.testrunner;
2
3 import java.io.IOException;
4 import java.util.AbstractMap;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collection;
8 import java.util.HashMap;
9 import java.util.List;
10 import java.util.Map.Entry;
11
12 import com.android.ddmlib.AdbCommandRejectedException;
13 import com.android.ddmlib.IDevice;
14 import com.android.ddmlib.Log;
15 import com.android.ddmlib.MultiLineReceiver;
16 import com.android.ddmlib.ShellCommandUnresponsiveException;
17 import com.android.ddmlib.TimeoutException;
18
19
20
21
22 public class MonkeyTestRunner
23 {
24
25 private IDevice mRemoteDevice;
26
27 private int mMaxTimeToOutputResponse = 0;
28 private String mRunName = null;
29
30
31 private List< Entry< String, String >> mArgList;
32 private MonkeyResultParser mParser;
33 private final int eventCount;
34 private boolean ignoreCrashes;
35 private boolean debugNoEvents;
36 private boolean hprof;
37 private boolean ignoreTimeouts;
38 private boolean ignoreSecurityExceptions;
39 private boolean killProcessAfterError;
40 private boolean monitorNativeCrashes;
41
42 private static final String LOG_TAG = "RemoteAndroidTest";
43
44
45 private static final String SEED_ARG_NAME = "-s";
46 private static final String THROTTLE_ARG_NAME = "--throttle";
47 private static final String PERCENT_TOUCH_ARG_NAME = "--pct-touch";
48 private static final String PERCENT_MOTION_ARG_NAME = "--pct-motion";
49 private static final String PERCENT_TRACKBALL_ARG_NAME = "--pct-trackball";
50 private static final String PERCENT_NAV_ARG_NAME = "--pct-nav";
51 private static final String PERCENT_MAJORNAV_ARG_NAME = "--pct-majornav";
52 private static final String PERCENT_SYSKEYS_ARG_NAME = "--pct-syskeys";
53 private static final String PERCENT_APPSWITCH_ARG_NAME = "--pct-appswitch";
54 private static final String PERCENT_ANYEVENT_ARG_NAME = "--pct-anyevent";
55 private static final String PACKAGE_ARG_NAME = "-p";
56 private static final String CATEGORY_ARG_NAME = "-c";
57
58 public MonkeyTestRunner( int eventCount, IDevice remoteDevice )
59 {
60 this.eventCount = eventCount;
61 mRemoteDevice = remoteDevice;
62 mArgList = new ArrayList< Entry< String, String >>();
63 }
64
65
66
67
68 public void addArg( String name, String value )
69 {
70 if ( name == null || value == null )
71 {
72 throw new IllegalArgumentException( "name or value arguments cannot be null" );
73 }
74 mArgList.add( new AbstractMap.SimpleImmutableEntry< String, String >( name, value ) );
75 }
76
77
78
79
80
81 public void addBooleanArg( String name, boolean value )
82 {
83 addArg( name, Boolean.toString( value ) );
84 }
85
86
87
88
89 public void addLongArg( String name, long value )
90 {
91 addArg( name, Long.toString( value ) );
92 }
93
94 public void setSeed( long seed )
95 {
96 addLongArg( SEED_ARG_NAME, seed );
97 }
98
99 public void setThrottle( long throttle )
100 {
101 addLongArg( THROTTLE_ARG_NAME, throttle );
102 }
103
104 public void setPercentTouch( long percent )
105 {
106 addLongArg( PERCENT_TOUCH_ARG_NAME, percent );
107 }
108
109 public void setPercentMotion( long percent )
110 {
111 addLongArg( PERCENT_MOTION_ARG_NAME, percent );
112 }
113
114 public void setPercentTrackball( long percent )
115 {
116 addLongArg( PERCENT_TRACKBALL_ARG_NAME, percent );
117 }
118
119 public void setPercentNav( long percent )
120 {
121 addLongArg( PERCENT_NAV_ARG_NAME, percent );
122 }
123
124 public void setPercentMajorNav( long percent )
125 {
126 addLongArg( PERCENT_MAJORNAV_ARG_NAME, percent );
127 }
128
129 public void setPercentSyskeys( long percent )
130 {
131 addLongArg( PERCENT_SYSKEYS_ARG_NAME, percent );
132 }
133
134 public void setPercentAppswitch( long percent )
135 {
136 addLongArg( PERCENT_APPSWITCH_ARG_NAME, percent );
137 }
138
139 public void setPercentAnyEvent( int percent )
140 {
141 addLongArg( PERCENT_ANYEVENT_ARG_NAME, percent );
142 }
143
144 public void setPackages( String[] packages )
145 {
146 for ( String packageName : packages )
147 {
148 addArg( PACKAGE_ARG_NAME, packageName );
149 }
150 }
151
152 public void setCategories( String[] categories )
153 {
154 for ( String category : categories )
155 {
156 addArg( CATEGORY_ARG_NAME, category );
157 }
158 }
159
160 public void setDebugNoEvents( boolean debugNoEvents )
161 {
162 this.debugNoEvents = debugNoEvents;
163 }
164
165 public void setHprof( boolean hprof )
166 {
167 this.hprof = hprof;
168 }
169
170 public void setIgnoreCrashes( boolean ignoreCrashes )
171 {
172 this.ignoreCrashes = ignoreCrashes;
173 }
174
175 public void setIgnoreTimeouts( boolean ignoreTimeouts )
176 {
177 this.ignoreTimeouts = ignoreTimeouts;
178 }
179
180 public void setIgnoreSecurityExceptions( boolean ignoreSecurityExceptions )
181 {
182 this.ignoreSecurityExceptions = ignoreSecurityExceptions;
183 }
184
185 public void setKillProcessAfterError( boolean killProcessAfterError )
186 {
187 this.killProcessAfterError = killProcessAfterError;
188 }
189
190 public void setMonitorNativeCrash( boolean monitorNativeCrashes )
191 {
192 this.monitorNativeCrashes = monitorNativeCrashes;
193 }
194
195
196
197
198
199 public void setMaxtimeToOutputResponse( int maxTimeToOutputResponse )
200 {
201 mMaxTimeToOutputResponse = maxTimeToOutputResponse;
202 }
203
204
205
206
207
208 public void setRunName( String runName )
209 {
210 mRunName = runName;
211 }
212
213
214
215
216
217 public void run( ITestRunListener... listeners ) throws TimeoutException, AdbCommandRejectedException,
218 ShellCommandUnresponsiveException, IOException
219 {
220 run( Arrays.asList( listeners ) );
221 }
222
223
224
225
226 public void run( Collection< ITestRunListener > listeners ) throws TimeoutException, AdbCommandRejectedException,
227 ShellCommandUnresponsiveException, IOException
228 {
229 final String runCaseCommandStr = String.format( "monkey -v -v -v %1$s %2$s", buildArgsCommand(),
230 Long.toString( eventCount ) );
231 Log.i( LOG_TAG, String.format( "Running %1$s on %2$s", runCaseCommandStr, mRemoteDevice.getSerialNumber() ) );
232 mParser = new MonkeyResultParser( mRunName, listeners );
233
234 try
235 {
236 mRemoteDevice.executeShellCommand( runCaseCommandStr, mParser, mMaxTimeToOutputResponse );
237 }
238 catch ( IOException e )
239 {
240 Log.w( LOG_TAG,
241 String.format( "IOException %1$s when running monkey tests on %3$s", e.toString(),
242 mRemoteDevice.getSerialNumber() ) );
243
244 mParser.handleTestRunFailed( e.toString() );
245 throw e;
246 }
247 catch ( ShellCommandUnresponsiveException e )
248 {
249 Log.w( LOG_TAG,
250 String.format( "ShellCommandUnresponsiveException %1$s when running monkey tests on %3$s",
251 e.toString(), mRemoteDevice.getSerialNumber() ) );
252 mParser.handleTestRunFailed( String.format( "Failed to receive adb shell test output within %1$d ms. "
253 + "Test may have timed out, or adb connection to device became unresponsive",
254 mMaxTimeToOutputResponse ) );
255 throw e;
256 }
257 catch ( TimeoutException e )
258 {
259 Log.w( LOG_TAG,
260 String.format( "TimeoutException when running monkey tests on %2$s",
261 mRemoteDevice.getSerialNumber() ) );
262 mParser.handleTestRunFailed( e.toString() );
263 throw e;
264 }
265 catch ( AdbCommandRejectedException e )
266 {
267 Log.w( LOG_TAG,
268 String.format( "AdbCommandRejectedException %1$s when running monkey tests %2$s on %3$s",
269 e.toString(), mRemoteDevice.getSerialNumber() ) );
270 mParser.handleTestRunFailed( e.toString() );
271 throw e;
272 }
273 }
274
275
276
277
278 public void cancel()
279 {
280 if ( mParser != null )
281 {
282 mParser.cancel();
283 }
284 }
285
286
287
288
289
290 private String buildArgsCommand()
291 {
292 StringBuilder commandBuilder = new StringBuilder();
293 for ( Entry< String, String > argPair : mArgList )
294 {
295 final String argCmd = String.format( " %1$s %2$s", argPair.getKey(), argPair.getValue() );
296 commandBuilder.append( argCmd );
297 }
298
299 if ( debugNoEvents )
300 {
301 commandBuilder.append( " --dbg-no-events" );
302 }
303 if ( hprof )
304 {
305 commandBuilder.append( " --hprof" );
306 }
307 if ( ignoreCrashes )
308 {
309 commandBuilder.append( " --ignore-crashes" );
310 }
311 if ( ignoreTimeouts )
312 {
313 commandBuilder.append( " --ignore-timeouts" );
314 }
315 if ( ignoreSecurityExceptions )
316 {
317 commandBuilder.append( " --ignore-security-exceptions" );
318 }
319 if ( killProcessAfterError )
320 {
321 commandBuilder.append( " --kill-process-after-error" );
322 }
323 if ( monitorNativeCrashes )
324 {
325 commandBuilder.append( " --monitor-native-crashes" );
326 }
327 return commandBuilder.toString();
328 }
329
330 private class MonkeyResultParser extends MultiLineReceiver
331 {
332
333 private static final String CRASH_KEY = "// CRASH:";
334 private static final String SHORT_MESSAGE_KEY = "// Short Msg:";
335 private static final String LONG_MESSAGE_KEY = "// Long Msg:";
336 private static final String BUILD_LABEL_KEY = "// Build Label:";
337 private static final String BUILD_CHANGELIST_KEY = "// Build Changelist:";
338 private static final String BUILD_TIME_KEY = "// Build Time:";
339 private static final String EMPTY_KEY = "//";
340 private static final String SENDING_KEY = ":Sending";
341 private static final String SWITCHING_KEY = ":Switch";
342 private static final String MONKEY_KEY = ":Monkey:";
343
344 private final Collection< ITestRunListener > mTestListeners;
345
346 private String runName;
347 private boolean canceled;
348 private TestIdentifier mCurrentTestIndentifier;
349 private long elapsedTime;
350 private HashMap< String, String > runMetrics = new HashMap< String, String >();
351
352 private MonkeyResultParser( String runName, Collection< ITestRunListener > listeners )
353 {
354 this.runName = runName;
355 mTestListeners = new ArrayList< ITestRunListener >( listeners );
356 }
357
358 public void cancel()
359 {
360 canceled = true;
361 }
362
363 @Override
364 public boolean isCancelled()
365 {
366 return canceled;
367 }
368
369 @Override
370 public void done()
371 {
372 handleTestEnd();
373 handleTestRunEnded();
374 super.done();
375 }
376
377 @Override
378 public void processNewLines( String[] lines )
379 {
380 for ( int indexLine = 0; indexLine < lines.length; indexLine++ )
381 {
382 String line = lines[ indexLine ];
383 Log.v( "monkey receiver:" + runName, line );
384
385 if ( line.startsWith( MONKEY_KEY ) )
386 {
387 handleTestRunStarted();
388 }
389 if ( line.startsWith( SHORT_MESSAGE_KEY ) )
390 {
391 runMetrics.put( "ShortMsg", line.substring( SHORT_MESSAGE_KEY.length() - 1 ) );
392 }
393 if ( line.startsWith( LONG_MESSAGE_KEY ) )
394 {
395 runMetrics.put( "LongMsg", line.substring( LONG_MESSAGE_KEY.length() - 1 ) );
396 }
397 if ( line.startsWith( BUILD_LABEL_KEY ) )
398 {
399 runMetrics.put( "BuildLabel", line.substring( BUILD_LABEL_KEY.length() - 1 ) );
400 }
401 if ( line.startsWith( BUILD_CHANGELIST_KEY ) )
402 {
403 runMetrics.put( "BuildChangeList", line.substring( BUILD_CHANGELIST_KEY.length() - 1 ) );
404 }
405 if ( line.startsWith( BUILD_TIME_KEY ) )
406 {
407 runMetrics.put( "BuildTime", line.substring( BUILD_TIME_KEY.length() - 1 ) );
408 }
409
410 if ( line.startsWith( SENDING_KEY ) || line.startsWith( SWITCHING_KEY ) )
411 {
412 handleTestEnd();
413 handleTestStarted( line );
414 }
415
416 if ( line.startsWith( CRASH_KEY ) )
417 {
418 Log.d( "monkey received crash:", line );
419 indexLine = handleCrash( lines, indexLine );
420 handleTestEnd();
421 }
422 }
423 }
424
425 private void handleTestRunStarted()
426 {
427 elapsedTime = System.currentTimeMillis();
428 for ( ITestRunListener listener : mTestListeners )
429 {
430 listener.testRunStarted( mRunName, eventCount );
431 }
432 }
433
434 public void handleTestRunFailed( String error )
435 {
436 for ( ITestRunListener listener : mTestListeners )
437 {
438 listener.testRunFailed( error );
439 }
440 }
441
442 private void handleTestRunEnded()
443 {
444 elapsedTime = System.currentTimeMillis() - elapsedTime;
445
446 for ( ITestRunListener listener : mTestListeners )
447 {
448 listener.testRunEnded( elapsedTime, runMetrics );
449 }
450 }
451
452 private void handleTestStarted( String line )
453 {
454 mCurrentTestIndentifier = new TestIdentifier( "MonkeyTest", line );
455 for ( ITestRunListener listener : mTestListeners )
456 {
457 listener.testStarted( mCurrentTestIndentifier );
458 }
459 }
460
461 private void handleTestEnd()
462 {
463 if ( mCurrentTestIndentifier != null )
464 {
465 for ( ITestRunListener listener : mTestListeners )
466 {
467 listener.testEnded( mCurrentTestIndentifier, new HashMap< String, String >() );
468 }
469 mCurrentTestIndentifier = null;
470 }
471 }
472
473 private int handleCrash( String[] lines, int indexLine )
474 {
475 StringBuilder errorBuilder = new StringBuilder();
476 boolean errorEnd = false;
477 boolean errorStart = false;
478 do
479 {
480 String line = lines[ indexLine ];
481 if ( line.startsWith( BUILD_TIME_KEY ) )
482 {
483 errorStart = true;
484 }
485 indexLine++;
486 } while ( !errorStart );
487
488
489 int firstLine = indexLine;
490
491 do
492 {
493 String line = lines[ indexLine ];
494 if ( line.equals( EMPTY_KEY ) )
495 {
496 errorEnd = true;
497 }
498 else
499 {
500 String stackTraceLine = lines[ indexLine ];
501 stackTraceLine = stackTraceLine.substring( indexLine == firstLine ? 3 : 4 );
502 errorBuilder.append( stackTraceLine ).append( "\n" );
503 }
504 indexLine++;
505 } while ( !errorEnd );
506
507 String trace = errorBuilder.toString();
508
509 for ( ITestRunListener listener : mTestListeners )
510 {
511 listener.testFailed( mCurrentTestIndentifier, trace );
512 }
513 mCurrentTestIndentifier = null;
514 return indexLine;
515 }
516 }
517 }