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.List;
9   import java.util.Properties;
10  import java.util.Map.Entry;
11  
12  import org.apache.commons.lang3.StringUtils;
13  
14  import com.android.ddmlib.AdbCommandRejectedException;
15  import com.android.ddmlib.IDevice;
16  import com.android.ddmlib.Log;
17  import com.android.ddmlib.ShellCommandUnresponsiveException;
18  import com.android.ddmlib.TimeoutException;
19  
20  /**
21   * Runs a UI Automator test command remotely and reports results.
22   */
23  public class UIAutomatorRemoteAndroidTestRunner
24  {
25  
26      private IDevice mRemoteDevice;
27      // default to no timeout
28      private int mMaxTimeToOutputResponse = 0;
29      private String mRunName = null;
30  
31      /** map of name-value instrumentation argument pairs */
32      private List< Entry< String, String >> mArgList;
33      private InstrumentationResultParser mParser;
34      private final String jarFile;
35      private boolean noHup;
36      private Object dumpFilePath;
37  
38      private static final String LOG_TAG = "RemoteAndroidTest";
39  
40      // defined instrumentation argument names
41      private static final String CLASS_ARG_NAME = "class";
42      private static final String DEBUG_ARG_NAME = "debug";
43  
44      public UIAutomatorRemoteAndroidTestRunner( String jarFile, IDevice remoteDevice )
45      {
46          this.jarFile = jarFile;
47          mRemoteDevice = remoteDevice;
48          mArgList = new ArrayList< Entry< String, String >>();
49      }
50  
51      /**
52       * {@inheritDoc}
53       */
54  
55      public void setTestClassOrMethods( String[] testClassOrMethods )
56      {
57          for ( String testClassOrMethod : testClassOrMethods )
58          {
59              addInstrumentationArg( CLASS_ARG_NAME, testClassOrMethod );
60          }
61      }
62  
63      /**
64       * {@inheritDoc}
65       */
66      public void addInstrumentationArg( String name, String value )
67      {
68          if ( name == null || value == null )
69          {
70              throw new IllegalArgumentException( "name or value arguments cannot be null" );
71          }
72          mArgList.add( new AbstractMap.SimpleImmutableEntry< String, String >( name, value ) );
73      }
74  
75      /**
76       * {@inheritDoc}
77       */
78  
79      public void addBooleanArg( String name, boolean value )
80      {
81          addInstrumentationArg( name, Boolean.toString( value ) );
82      }
83  
84      /**
85       * {@inheritDoc}
86       */
87  
88      /**
89       * {@inheritDoc}
90       */
91  
92      public void setDebug( boolean debug )
93      {
94          addBooleanArg( DEBUG_ARG_NAME, debug );
95      }
96  
97      public void setNoHup( boolean noHup )
98      {
99          this.noHup = noHup;
100     }
101 
102     public void setDumpFilePath( String dumpFilePath )
103     {
104         this.dumpFilePath = dumpFilePath;
105     }
106 
107     /**
108      * {@inheritDoc}
109      */
110 
111     public void setMaxtimeToOutputResponse( int maxTimeToOutputResponse )
112     {
113         mMaxTimeToOutputResponse = maxTimeToOutputResponse;
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119 
120     public void setRunName( String runName )
121     {
122         mRunName = runName;
123     }
124 
125     /**
126      * {@inheritDoc}
127      */
128 
129     public void run( ITestRunListener... listeners ) throws TimeoutException, AdbCommandRejectedException,
130             ShellCommandUnresponsiveException, IOException
131     {
132         run( Arrays.asList( listeners ) );
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     public void run( Collection< ITestRunListener > listeners ) throws TimeoutException, AdbCommandRejectedException,
139             ShellCommandUnresponsiveException, IOException
140     {
141         final String runCaseCommandStr = String.format( "uiautomator runtest %1$s %2$s", jarFile, buildArgsCommand() );
142         Log.i( LOG_TAG, String.format( "Running %1$s on %2$s", runCaseCommandStr, mRemoteDevice.getSerialNumber() ) );
143         mParser = new InstrumentationResultParser( mRunName, listeners );
144 
145         try
146         {
147             mRemoteDevice.executeShellCommand( runCaseCommandStr, mParser, mMaxTimeToOutputResponse );
148         }
149         catch ( IOException e )
150         {
151             Log.w( LOG_TAG, String.format( "IOException %1$s when running tests %2$s on %3$s", e.toString(), jarFile,
152                     mRemoteDevice.getSerialNumber() ) );
153             // rely on parser to communicate results to listeners
154             mParser.handleTestRunFailed( e.toString() );
155             throw e;
156         }
157         catch ( ShellCommandUnresponsiveException e )
158         {
159             Log.w( LOG_TAG,
160                     String.format( "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
161                             e.toString(), jarFile, mRemoteDevice.getSerialNumber() ) );
162             mParser.handleTestRunFailed( String.format( "Failed to receive adb shell test output within %1$d ms. "
163                     + "Test may have timed out, or adb connection to device became unresponsive",
164                     mMaxTimeToOutputResponse ) );
165             throw e;
166         }
167         catch ( TimeoutException e )
168         {
169             Log.w( LOG_TAG,
170                     String.format( "TimeoutException when running tests %1$s on %2$s", jarFile,
171                             mRemoteDevice.getSerialNumber() ) );
172             mParser.handleTestRunFailed( e.toString() );
173             throw e;
174         }
175         catch ( AdbCommandRejectedException e )
176         {
177             Log.w( LOG_TAG, String.format( "AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
178                     e.toString(), jarFile, mRemoteDevice.getSerialNumber() ) );
179             mParser.handleTestRunFailed( e.toString() );
180             throw e;
181         }
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     public void cancel()
188     {
189         if ( mParser != null )
190         {
191             mParser.cancel();
192         }
193     }
194 
195     /**
196      * Returns the full instrumentation command line syntax for the provided instrumentation arguments. Returns an empty
197      * string if no arguments were specified.
198      */
199     private String buildArgsCommand()
200     {
201         StringBuilder commandBuilder = new StringBuilder();
202         for ( Entry< String, String > argPair : mArgList )
203         {
204             final String argCmd = String.format( " -e %1$s %2$s", argPair.getKey(), argPair.getValue() );
205             commandBuilder.append( argCmd );
206         }
207 
208         if ( noHup )
209         {
210             commandBuilder.append( " --nohup" );
211         }
212 
213         if ( dumpFilePath != null )
214         {
215             commandBuilder.append( " dump " + dumpFilePath );
216         }
217         return commandBuilder.toString();
218     }
219     
220     /**
221      * Adds instrumentation arguments from userProperties from keys with the propertiesKeyPrefix prefix.
222      * 
223      * @param userProperties
224      * @param propertiesKeyPrefix
225      */
226     public void setUserProperties( Properties userProperties, String propertiesKeyPrefix )
227     {
228         if ( userProperties == null )
229         {
230             throw new IllegalArgumentException( "userProperties  cannot be null" );
231         }
232         
233         if ( StringUtils.isBlank( propertiesKeyPrefix ) )
234         {
235             //propertiesPrefix is blank, ignore all properties
236             return;
237         }
238         
239         for ( Entry< Object, Object > property : userProperties.entrySet() )
240         {
241             String name = (String) property.getKey();
242             
243             //Check if the key starts with the parameterPrefix
244             if ( StringUtils.startsWith( name, propertiesKeyPrefix ) )
245             {
246                 String value = (String) property.getValue();
247                 
248                 //Remove the prefix
249                 name = StringUtils.substring( name,
250                         StringUtils.length( propertiesKeyPrefix ),
251                         StringUtils.length( name ) );
252                 
253                 // Verify so the key isn't blank after substring
254                 if ( StringUtils.isNotBlank( name ) )
255                 {
256                     //Now its safe to add the parameter
257                     addInstrumentationArg( name, value );
258                 }
259             }
260         }
261     }
262 }