16
16
17
17
package com .google .devtools .mobileharness .platform .testbed .adhoc .controller ;
18
18
19
+ import static com .google .common .collect .ImmutableList .toImmutableList ;
19
20
import static com .google .common .collect .Multimaps .toMultimap ;
20
21
21
22
import com .google .common .annotations .VisibleForTesting ;
32
33
import com .google .devtools .mobileharness .api .model .error .MobileHarnessException ;
33
34
import com .google .devtools .mobileharness .api .model .proto .Test .TestResult ;
34
35
import com .google .devtools .mobileharness .infra .controller .test .local .annotation .DoNotSubscribeTestEvent ;
36
+ import com .google .devtools .mobileharness .shared .util .concurrent .Barrier ;
35
37
import com .google .devtools .mobileharness .shared .util .concurrent .ConcurrencyUtil ;
36
38
import com .google .devtools .mobileharness .shared .util .concurrent .ConcurrencyUtil .SubTask ;
37
39
import com .google .devtools .mobileharness .shared .util .logging .MobileHarnessLogTag ;
48
50
import java .util .ArrayList ;
49
51
import java .util .List ;
50
52
import java .util .Map ;
51
- import java .util .concurrent .BrokenBarrierException ;
52
- import java .util .concurrent .CyclicBarrier ;
53
53
import java .util .function .BiFunction ;
54
54
import java .util .stream .Collectors ;
55
55
import java .util .stream .Stream ;
@@ -80,6 +80,12 @@ public class AdhocTestbedDriver extends BaseDriver {
80
80
81
81
private static final FluentLogger logger = FluentLogger .forEnclosingClass ();
82
82
83
+ /**
84
+ * Test property used to indicate whether the main/secondary driver's driver barrier's run method
85
+ * is called.
86
+ */
87
+ private static final String TEST_PROP_DRIVER_BARRIER_RUN_CALLED = "driver_barrier_run_called" ;
88
+
83
89
/**
84
90
* A list of sub drivers which contains:
85
91
*
@@ -93,11 +99,23 @@ public class AdhocTestbedDriver extends BaseDriver {
93
99
*/
94
100
private final ImmutableList <Driver > subDrivers ;
95
101
96
- /** Lets all threads wait until all decorators' preRun() finish before running the main driver. */
97
- private final CyclicBarrier preDriverBarrier ;
102
+ /**
103
+ * Lets all threads wait until all decorators' preRun() finish before running the main driver.
104
+ *
105
+ * <p>Waiting threads may be woken up before all threads reach the barrier point if the barrier
106
+ * wakes them up, for example any decorator stacks try to skip the main driver execution, or
107
+ * awaiting threads are interrupted.
108
+ */
109
+ private final Barrier preDriverBarrier ;
98
110
99
- /** Lets all threads wait until the main driver finishes before running decorators' postRun(). */
100
- private final CyclicBarrier postDriverBarrier ;
111
+ /**
112
+ * Lets all threads wait until the main driver finishes before running decorators' postRun().
113
+ *
114
+ * <p>Waiting threads may be woken up before all threads reach the barrier point if the barrier
115
+ * wakes them up, for example any decorator stacks try to skip the main driver execution, or
116
+ * awaiting threads are interrupted.
117
+ */
118
+ private final Barrier postDriverBarrier ;
101
119
102
120
private final ListeningExecutorService threadPool ;
103
121
private final DriverFactory driverFactory ;
@@ -109,6 +127,8 @@ public class AdhocTestbedDriver extends BaseDriver {
109
127
@ Nullable
110
128
private final BiFunction <Driver , Class <? extends Decorator >, Decorator > decoratorExtender ;
111
129
130
+ private volatile boolean mainDriverSkipped = false ;
131
+
112
132
AdhocTestbedDriver (
113
133
List <Device > devices ,
114
134
TestInfo testInfo ,
@@ -124,8 +144,8 @@ public class AdhocTestbedDriver extends BaseDriver {
124
144
this .driverWrapper = driverWrapper ;
125
145
this .decoratorExtender = null ;
126
146
this .subDrivers = ImmutableList .copyOf (createSubDrivers (devices ));
127
- this .preDriverBarrier = new CyclicBarrier (devices .size ());
128
- this .postDriverBarrier = new CyclicBarrier (devices .size ());
147
+ this .preDriverBarrier = new Barrier (devices .size ());
148
+ this .postDriverBarrier = new Barrier (devices .size ());
129
149
130
150
// Sets up TestbedDevice.
131
151
testInfo .log ().atInfo ().alsoTo (logger ).log ("Setting up TestbedDevice" );
@@ -191,7 +211,7 @@ public void run(TestInfo testInfo) throws MobileHarnessException, InterruptedExc
191
211
threadPool ,
192
212
/* resultMerger= */ results -> null );
193
213
} finally {
194
- updateRootTestResultIfNeeded (testInfo );
214
+ updateRootTestResultIfNeeded (testInfo , mainDriverSkipped );
195
215
}
196
216
}
197
217
@@ -221,17 +241,28 @@ private String createTestNameOnSubDriver(Driver driver, TestInfo parentTest) {
221
241
* sponge can reflect it as expected.
222
242
*/
223
243
@ VisibleForTesting
224
- static void updateRootTestResultIfNeeded (TestInfo rootTestInfo ) {
244
+ static void updateRootTestResultIfNeeded (TestInfo rootTestInfo , boolean mainDriverSkipped ) {
225
245
TestResult rootTestResult = rootTestInfo .resultWithCause ().get ().type ();
226
246
if (rootTestResult .equals (TestResult .UNKNOWN )) {
227
- rootTestInfo
228
- .resultWithCause ()
229
- .setNonPassing (
230
- TestResult .ERROR ,
231
- new MobileHarnessException (
232
- ExtErrorId .MOBLY_TESTBED_ADHOC_DRIVER_END_WITH_UNKNOWN_RESULT ,
233
- "Set root test result to ERROR because adhoc testbed driver ends with UNKNOWN"
234
- + " test result. Maybe the primary driver has not been triggered." ));
247
+ if (mainDriverSkipped ) {
248
+ rootTestInfo
249
+ .resultWithCause ()
250
+ .setNonPassing (
251
+ TestResult .SKIP ,
252
+ new MobileHarnessException (
253
+ ExtErrorId .MOBLY_TESTBED_ADHOC_DRIVER_END_WITH_UNKNOWN_RESULT ,
254
+ "Set root test result to SKIP because adhoc testbed driver ends with UNKNOWN"
255
+ + " and the primary driver was skipped." ));
256
+ } else {
257
+ rootTestInfo
258
+ .resultWithCause ()
259
+ .setNonPassing (
260
+ TestResult .ERROR ,
261
+ new MobileHarnessException (
262
+ ExtErrorId .MOBLY_TESTBED_ADHOC_DRIVER_END_WITH_UNKNOWN_RESULT ,
263
+ "Set root test result to ERROR because adhoc testbed driver ends with UNKNOWN"
264
+ + " test result. Maybe the primary driver has not been triggered." ));
265
+ }
235
266
} else if (rootTestResult .equals (TestResult .PASS )) {
236
267
// Subtests in mobly tests may have different results, b/175287972
237
268
int errorTestsTotalCnt = 0 ;
@@ -347,7 +378,8 @@ private List<Driver> createSubDrivers(List<Device> devices) throws MobileHarness
347
378
getTest ().jobInfo ().subDeviceSpecs ().getSubDevice (i ).decorators ().getAll ());
348
379
subDriver .add (decoratedDriver );
349
380
}
350
- return subDriver ;
381
+ // Wraps each decorator stack with a decorator to handle barrier.
382
+ return subDriver .stream ().map (DecoratorBarrier ::new ).collect (toImmutableList ());
351
383
}
352
384
353
385
private List <Driver > createRawSubDrivers (List <Device > devices ) throws MobileHarnessException {
@@ -449,31 +481,54 @@ private DriverBarrier(Driver decorated) {
449
481
450
482
@ Override
451
483
public void run (TestInfo testInfo ) throws MobileHarnessException , InterruptedException {
484
+ testInfo .properties ().add (TEST_PROP_DRIVER_BARRIER_RUN_CALLED , "true" );
452
485
try {
453
486
testInfo
454
487
.log ()
455
488
.atInfo ()
456
489
.alsoTo (logger )
457
490
.log ("Waiting on pre-driver barrier, device=[%s]" , getDevice ().getDeviceId ());
458
- preDriverBarrier .await ();
459
-
460
- // The main "run" of AdhocTestbedDriver runs each stack using a sub-TestInfo specific to
461
- // each sub-device. We need to make sure the main driver is run against the TestInfo passed
462
- // to the AdhocTestbedDriver constructor.
463
- getDecorated ().run (AdhocTestbedDriver .this .getTest ());
464
-
491
+ boolean allPartiesInvokedOnPreDriverBarrier = preDriverBarrier .await ();
465
492
testInfo
466
493
.log ()
467
494
.atInfo ()
468
495
.alsoTo (logger )
469
- .log ("Waiting on post-driver barrier, device=[%s]" , getDevice ().getDeviceId ());
470
- postDriverBarrier .await ();
496
+ .log (
497
+ "All parties invoked on pre-driver barrier: %s, device=[%s]" ,
498
+ allPartiesInvokedOnPreDriverBarrier , getDevice ().getDeviceId ());
499
+ if (allPartiesInvokedOnPreDriverBarrier ) {
500
+ // The main "run" of AdhocTestbedDriver runs each stack using a sub-TestInfo specific to
501
+ // each sub-device. We need to make sure the main driver is run against the TestInfo
502
+ // passed to the AdhocTestbedDriver constructor.
503
+ getDecorated ().run (AdhocTestbedDriver .this .getTest ());
504
+
505
+ testInfo
506
+ .log ()
507
+ .atInfo ()
508
+ .alsoTo (logger )
509
+ .log ("Waiting on post-driver barrier, device=[%s]" , getDevice ().getDeviceId ());
510
+ boolean allPartiesInvokedOnPostDriverBarrier = postDriverBarrier .await ();
511
+ testInfo
512
+ .log ()
513
+ .atInfo ()
514
+ .alsoTo (logger )
515
+ .log (
516
+ "All parties invoked on post-driver barrier: %s, device=[%s]" ,
517
+ allPartiesInvokedOnPostDriverBarrier , getDevice ().getDeviceId ());
518
+ } else {
519
+ mainDriverSkipped = true ;
520
+ testInfo
521
+ .log ()
522
+ .atInfo ()
523
+ .alsoTo (logger )
524
+ .log ("Skipping driver with device [%s]" , getDevice ().getDeviceId ());
525
+ }
471
526
472
- // If non of the sub device decorators failed, set the device TestInfo result to PASS.
527
+ // If none of the sub device decorators failed, set the device TestInfo result to PASS.
473
528
if (testInfo .resultWithCause ().get ().type ().equals (TestResult .UNKNOWN )) {
474
529
testInfo .resultWithCause ().setPass ();
475
530
}
476
- } catch (BrokenBarrierException e ) {
531
+ } catch (InterruptedException e ) {
477
532
InterruptedException exception =
478
533
new InterruptedException (
479
534
String .format (
@@ -484,4 +539,31 @@ public void run(TestInfo testInfo) throws MobileHarnessException, InterruptedExc
484
539
}
485
540
}
486
541
}
542
+
543
+ @ DoNotSubscribeTestEvent
544
+ private class DecoratorBarrier extends BaseDecorator {
545
+
546
+ private DecoratorBarrier (Driver decorated ) {
547
+ super (decorated , decorated .getTest ());
548
+ }
549
+
550
+ @ Override
551
+ public void run (TestInfo testInfo ) throws MobileHarnessException , InterruptedException {
552
+ getDecorated ().run (testInfo );
553
+
554
+ boolean driverBarrierRunCalled =
555
+ testInfo .properties ().getBoolean (TEST_PROP_DRIVER_BARRIER_RUN_CALLED ).orElse (false );
556
+ if (!driverBarrierRunCalled ) {
557
+ testInfo
558
+ .log ()
559
+ .atInfo ()
560
+ .alsoTo (logger )
561
+ .log ("Driver barrier for device [%s] was not called" , getDevice ().getDeviceId ());
562
+ // The driver barrier (right before the driver) was not called for the current device's
563
+ // decorators stack, it's possible that a decorator decides to skip the test (b/384636099)
564
+ preDriverBarrier .stopAwaitations ();
565
+ postDriverBarrier .stopAwaitations ();
566
+ }
567
+ }
568
+ }
487
569
}
0 commit comments