View Javadoc
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   * Runs a Monkey test command remotely and reports results.
21   */
22  public class MonkeyTestRunner
23  {
24  
25      private IDevice mRemoteDevice;
26      // default to no timeout
27      private int mMaxTimeToOutputResponse = 0;
28      private String mRunName = null;
29  
30      /** map of name-value instrumentation argument pairs */
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      // defined instrumentation argument names
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       * {@inheritDoc}
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       * {@inheritDoc}
79       */
80  
81      public void addBooleanArg( String name, boolean value )
82      {
83          addArg( name, Boolean.toString( value ) );
84      }
85  
86      /**
87       * {@inheritDoc}
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      * {@inheritDoc}
197      */
198 
199     public void setMaxtimeToOutputResponse( int maxTimeToOutputResponse )
200     {
201         mMaxTimeToOutputResponse = maxTimeToOutputResponse;
202     }
203 
204     /**
205      * {@inheritDoc}
206      */
207 
208     public void setRunName( String runName )
209     {
210         mRunName = runName;
211     }
212 
213     /**
214      * {@inheritDoc}
215      */
216 
217     public void run( ITestRunListener... listeners ) throws TimeoutException, AdbCommandRejectedException,
218             ShellCommandUnresponsiveException, IOException
219     {
220         run( Arrays.asList( listeners ) );
221     }
222 
223     /**
224      * {@inheritDoc}
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             // rely on parser to communicate results to listeners
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      * {@inheritDoc}
277      */
278     public void cancel()
279     {
280         if ( mParser != null )
281         {
282             mParser.cancel();
283         }
284     }
285 
286     /**
287      * Returns the full instrumentation command line syntax for the provided instrumentation arguments. Returns an empty
288      * string if no arguments were specified.
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             // indexLine point to the first line of the stack trace now
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 }