1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.simpligility.maven.plugins.android;
18
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.IDevice;
21 import com.android.ddmlib.ShellCommandUnresponsiveException;
22 import com.android.ddmlib.TimeoutException;
23 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
24 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
25 import com.simpligility.maven.plugins.android.asm.AndroidTestFinder;
26 import com.simpligility.maven.plugins.android.common.DeviceHelper;
27 import com.simpligility.maven.plugins.android.configuration.Test;
28
29 import org.apache.commons.lang3.StringUtils;
30 import org.apache.maven.plugin.MojoExecutionException;
31 import org.apache.maven.plugin.MojoFailureException;
32 import org.apache.maven.plugins.annotations.Parameter;
33
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Map;
38
39
40
41
42
43
44
45
46 public abstract class AbstractInstrumentationMojo extends AbstractAndroidMojo
47 {
48
49
50
51
52 @Parameter( property = "maven.test.skip", defaultValue = "false", readonly = true )
53 private boolean mavenTestSkip;
54
55
56
57
58 @Parameter( property = "skipTests", defaultValue = "false", readonly = true )
59 private boolean mavenSkipTests;
60
61
62
63
64
65
66 @Parameter( property = "maven.test.failure.ignore", defaultValue = "false", readonly = true )
67 private boolean mavenIgnoreTestFailure;
68
69
70
71
72
73
74 @Parameter( property = "maven.test.error.ignore", defaultValue = "false", readonly = true )
75 private boolean mavenIgnoreTestError;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 @Parameter
104 private Test test;
105
106
107
108
109
110
111 @Parameter( property = "android.test.skip", defaultValue = "auto" )
112 private String testSkip;
113
114
115
116
117
118 @Parameter( property = "android.test.failsafe", defaultValue = "true" )
119 private Boolean testFailSafe;
120
121
122
123
124
125 @Parameter( property = "android.test.instrumentationPackage" )
126 private String testInstrumentationPackage;
127
128
129
130
131 @Parameter( property = "android.test.instrumentationRunner" )
132 private String testInstrumentationRunner;
133
134
135
136
137
138 @Parameter( property = "android.test.debug", defaultValue = "false" )
139 private Boolean testDebug;
140
141
142
143
144 @Parameter( property = "android.test.coverage", defaultValue = "false" )
145 private Boolean testCoverage;
146
147
148
149
150
151 @Parameter( property = "android.test.coverageFile" )
152 private String testCoverageFile;
153
154
155
156
157 @Parameter( property = "android.test.logonly", defaultValue = "false" )
158 private Boolean testLogOnly;
159
160
161
162
163
164
165
166
167 @Parameter( property = "android.test.testsize" )
168 private String testTestSize;
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192 @Parameter( property = "android.test.createreport", defaultValue = "true" )
193 private Boolean testCreateReport;
194
195
196
197
198
199
200
201
202
203
204 @Parameter( property = "android.test.packages" )
205 protected List<String> testPackages;
206
207
208
209
210
211
212
213
214
215
216 @Parameter( property = "android.test.classes" )
217 protected List<String> testClasses;
218
219
220
221
222
223
224
225
226
227
228
229 @Parameter( property = "android.test.annotations" )
230 protected List<String> testAnnotations;
231
232
233
234
235
236
237
238
239
240
241 @Parameter( property = "android.test.excludeAnnotations" )
242 protected List<String> testExcludeAnnotations;
243
244
245
246
247
248
249
250
251
252
253
254 @Parameter( property = "android.test.instrumentationArgs" )
255 protected List<String> testInstrumentationArgs;
256
257 private boolean classesExists;
258 private boolean packagesExists;
259
260
261 private String parsedSkip;
262 private String parsedInstrumentationPackage;
263 private String parsedInstrumentationRunner;
264 private List<String> parsedClasses;
265 private List<String> parsedPackages;
266 private List<String> parsedAnnotations;
267 private List<String> parsedExcludeAnnotations;
268 private Map<String, String> parsedInstrumentationArgs;
269 private String parsedTestSize;
270 private Boolean parsedCoverage;
271 private String parsedCoverageFile;
272 private Boolean parsedDebug;
273 private Boolean parsedLogOnly;
274 private Boolean parsedCreateReport;
275
276 private String packagesList;
277
278 protected void instrument() throws MojoExecutionException, MojoFailureException
279 {
280 parseConfiguration();
281
282 if ( parsedInstrumentationPackage == null )
283 {
284 parsedInstrumentationPackage = extractPackageNameFromAndroidManifest( destinationManifestFile );
285 }
286
287 if ( parsedInstrumentationRunner == null )
288 {
289 parsedInstrumentationRunner = extractInstrumentationRunnerFromAndroidManifest( destinationManifestFile );
290 }
291
292
293 packagesList = buildCommaSeparatedString( parsedPackages );
294 packagesExists = StringUtils.isNotBlank( packagesList );
295
296 if ( parsedClasses != null )
297 {
298 classesExists = parsedClasses.size() > 0;
299 }
300 else
301 {
302 classesExists = false;
303 }
304
305 if ( classesExists && packagesExists )
306 {
307
308 throw new MojoFailureException( "packages and classes are mutually exclusive. They cannot be specified at"
309 + " the same time. Please specify either packages or classes. For details, see "
310 + "http://developer.android.com/guide/developing/testing/testing_otheride.html" );
311 }
312
313 DeviceCallback instrumentationTestExecutor = new DeviceCallback()
314 {
315 public void doWithDevice( final IDevice device ) throws MojoExecutionException, MojoFailureException
316 {
317 String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device );
318
319 RemoteAndroidTestRunner remoteAndroidTestRunner = new RemoteAndroidTestRunner(
320 parsedInstrumentationPackage, parsedInstrumentationRunner, device );
321
322 if ( packagesExists )
323 {
324 for ( String str : packagesList.split( "," ) )
325 {
326 remoteAndroidTestRunner.setTestPackageName( str );
327 getLog().info( deviceLogLinePrefix + "Running tests for specified test package: " + str );
328 }
329 }
330
331 if ( classesExists )
332 {
333 remoteAndroidTestRunner
334 .setClassNames( parsedClasses.toArray( new String[ parsedClasses.size() ] ) );
335 getLog().info( deviceLogLinePrefix + "Running tests for specified test classes/methods: "
336 + parsedClasses );
337 }
338
339 if ( parsedAnnotations != null )
340 {
341 for ( String annotation : parsedAnnotations )
342 {
343 remoteAndroidTestRunner.addInstrumentationArg( "annotation", annotation );
344 }
345 }
346
347 if ( parsedExcludeAnnotations != null )
348 {
349 for ( String annotation : parsedExcludeAnnotations )
350 {
351 remoteAndroidTestRunner.addInstrumentationArg( "notAnnotation", annotation );
352 }
353
354 }
355
356 remoteAndroidTestRunner.setDebug( parsedDebug );
357 remoteAndroidTestRunner.setCoverage( parsedCoverage );
358 if ( StringUtils.isNotBlank( parsedCoverageFile ) )
359 {
360 remoteAndroidTestRunner.addInstrumentationArg( "coverageFile", parsedCoverageFile );
361 }
362 remoteAndroidTestRunner.setLogOnly( parsedLogOnly );
363
364 if ( StringUtils.isNotBlank( parsedTestSize ) )
365 {
366 IRemoteAndroidTestRunner.TestSize validSize = IRemoteAndroidTestRunner.TestSize
367 .getTestSize( parsedTestSize );
368 remoteAndroidTestRunner.setTestSize( validSize );
369 }
370
371 addAllInstrumentationArgs( remoteAndroidTestRunner, parsedInstrumentationArgs );
372
373 getLog().info( deviceLogLinePrefix + "Running instrumentation tests in "
374 + parsedInstrumentationPackage );
375 try
376 {
377 AndroidTestRunListener testRunListener = new AndroidTestRunListener( device, getLog(),
378 parsedCreateReport, false, "", "", targetDirectory );
379 remoteAndroidTestRunner.run( testRunListener );
380 if ( testRunListener.hasFailuresOrErrors() && !testFailSafe )
381 {
382 throw new MojoFailureException( deviceLogLinePrefix + "Tests failed on device." );
383 }
384 if ( testRunListener.testRunFailed() && !testFailSafe )
385 {
386 throw new MojoFailureException( deviceLogLinePrefix + "Test run failed to complete: "
387 + testRunListener.getTestRunFailureCause() );
388 }
389 if ( testRunListener.threwException() && !testFailSafe )
390 {
391 throw new MojoFailureException( deviceLogLinePrefix + testRunListener.getExceptionMessages() );
392 }
393 }
394 catch ( TimeoutException e )
395 {
396 throw new MojoExecutionException( deviceLogLinePrefix + "timeout", e );
397 }
398 catch ( AdbCommandRejectedException e )
399 {
400 throw new MojoExecutionException( deviceLogLinePrefix + "adb command rejected", e );
401 }
402 catch ( ShellCommandUnresponsiveException e )
403 {
404 throw new MojoExecutionException( deviceLogLinePrefix + "shell command " + "unresponsive", e );
405 }
406 catch ( IOException e )
407 {
408 throw new MojoExecutionException( deviceLogLinePrefix + "IO problem", e );
409 }
410 }
411 };
412
413 instrumentationTestExecutor = new ScreenshotServiceWrapper( instrumentationTestExecutor, project, getLog() );
414
415 doWithDevices( instrumentationTestExecutor );
416 }
417
418 private void addAllInstrumentationArgs(
419 final RemoteAndroidTestRunner remoteAndroidTestRunner,
420 final Map<String, String> parsedInstrumentationArgs )
421 {
422 for ( final Map.Entry<String, String> entry : parsedInstrumentationArgs.entrySet() )
423 {
424 remoteAndroidTestRunner.addInstrumentationArg( entry.getKey(), entry.getValue() );
425 }
426 }
427
428 private void parseConfiguration()
429 {
430
431 if ( test != null )
432 {
433 if ( StringUtils.isNotEmpty( test.getSkip() ) )
434 {
435 parsedSkip = test.getSkip();
436 }
437 else
438 {
439 parsedSkip = testSkip;
440 }
441 if ( StringUtils.isNotEmpty( test.getInstrumentationPackage() ) )
442 {
443 parsedInstrumentationPackage = test.getInstrumentationPackage();
444 }
445 else
446 {
447 parsedInstrumentationPackage = testInstrumentationPackage;
448 }
449 if ( StringUtils.isNotEmpty( test.getInstrumentationRunner() ) )
450 {
451 parsedInstrumentationRunner = test.getInstrumentationRunner();
452 }
453 else
454 {
455 parsedInstrumentationRunner = testInstrumentationRunner;
456 }
457 if ( test.getClasses() != null && ! test.getClasses().isEmpty() )
458 {
459 parsedClasses = test.getClasses();
460 }
461 else
462 {
463 parsedClasses = testClasses;
464 }
465 if ( test.getAnnotations() != null && ! test.getAnnotations().isEmpty() )
466 {
467 parsedAnnotations = test.getAnnotations();
468 }
469 else
470 {
471 parsedAnnotations = testAnnotations;
472 }
473 if ( test.getExcludeAnnotations() != null && ! test.getExcludeAnnotations().isEmpty() )
474 {
475 parsedExcludeAnnotations = test.getExcludeAnnotations();
476 }
477 else
478 {
479 parsedExcludeAnnotations = testExcludeAnnotations;
480 }
481 if ( test.getPackages() != null && ! test.getPackages().isEmpty() )
482 {
483 parsedPackages = test.getPackages();
484 }
485 else
486 {
487 parsedPackages = testPackages;
488 }
489 if ( StringUtils.isNotEmpty( test.getTestSize() ) )
490 {
491 parsedTestSize = test.getTestSize();
492 }
493 else
494 {
495 parsedTestSize = testTestSize;
496 }
497 if ( test.isCoverage() != null )
498 {
499 parsedCoverage = test.isCoverage();
500 }
501 else
502 {
503 parsedCoverage = testCoverage;
504 }
505 if ( test.getCoverageFile() != null )
506 {
507 parsedCoverageFile = test.getCoverageFile();
508 }
509 else
510 {
511 parsedCoverageFile = "";
512 }
513 if ( test.isDebug() != null )
514 {
515 parsedDebug = test.isDebug();
516 }
517 else
518 {
519 parsedDebug = testDebug;
520 }
521 if ( test.isLogOnly() != null )
522 {
523 parsedLogOnly = test.isLogOnly();
524 }
525 else
526 {
527 parsedLogOnly = testLogOnly;
528 }
529 if ( test.isCreateReport() != null )
530 {
531 parsedCreateReport = test.isCreateReport();
532 }
533 else
534 {
535 parsedCreateReport = testCreateReport;
536 }
537
538 parsedInstrumentationArgs = InstrumentationArgumentParser.parse( test.getInstrumentationArgs() );
539 }
540
541 else
542 {
543 parsedSkip = testSkip;
544 parsedInstrumentationPackage = testInstrumentationPackage;
545 parsedInstrumentationRunner = testInstrumentationRunner;
546 parsedClasses = testClasses;
547 parsedAnnotations = testAnnotations;
548 parsedExcludeAnnotations = testExcludeAnnotations;
549 parsedPackages = testPackages;
550 parsedTestSize = testTestSize;
551 parsedCoverage = testCoverage;
552 parsedCoverageFile = testCoverageFile;
553 parsedDebug = testDebug;
554 parsedLogOnly = testLogOnly;
555 parsedCreateReport = testCreateReport;
556 parsedInstrumentationArgs = InstrumentationArgumentParser.parse( testInstrumentationArgs );
557 }
558 }
559
560
561
562
563
564
565
566 protected boolean isEnableIntegrationTest() throws MojoFailureException, MojoExecutionException
567 {
568 parseConfiguration();
569 if ( mavenTestSkip )
570 {
571 getLog().info( "maven.test.skip set - skipping tests" );
572 return false;
573 }
574
575 if ( mavenSkipTests )
576 {
577 getLog().info( "maven.skip.tests set - skipping tests" );
578 return false;
579 }
580
581 if ( "true".equalsIgnoreCase( parsedSkip ) )
582 {
583 getLog().info( "android.test.skip set - skipping tests" );
584 return false;
585 }
586
587 if ( "false".equalsIgnoreCase( parsedSkip ) )
588 {
589 return true;
590 }
591
592 if ( parsedSkip == null || "auto".equalsIgnoreCase( parsedSkip ) )
593 {
594 if ( extractInstrumentationRunnerFromAndroidManifest( destinationManifestFile ) == null )
595 {
596 getLog().info( "No InstrumentationRunner found - skipping tests" );
597 return false;
598 }
599 return AndroidTestFinder.containsAndroidTests( projectOutputDirectory );
600 }
601
602 throw new MojoFailureException( "android.test.skip must be configured as 'true', 'false' or 'auto'." );
603
604 }
605
606
607
608
609
610
611
612
613 protected static String buildCommaSeparatedString( List<String> lines )
614 {
615 if ( lines == null || lines.size() == 0 )
616 {
617 return null;
618 }
619
620 List<String> strings = new ArrayList<String>( lines.size() );
621 for ( String str : lines )
622 {
623 if ( StringUtils.isNotBlank( str ) )
624 {
625 strings.add( StringUtils.trimToEmpty( str ) );
626 }
627 }
628
629 return StringUtils.join( strings, "," );
630 }
631
632 }