• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bedstead.harrier;
18 
19 import static com.android.bedstead.permissions.annotations.EnsureDoesNotHavePermissionKt.ensureDoesNotHavePermission;
20 import static com.android.bedstead.permissions.annotations.EnsureHasPermissionKt.ensureHasPermission;
21 
22 import com.android.bedstead.enterprise.annotations.CanSetPolicyTest;
23 import com.android.bedstead.enterprise.annotations.CannotSetPolicyTest;
24 import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
25 import com.android.bedstead.enterprise.annotations.MostImportantCoexistenceTest;
26 import com.android.bedstead.enterprise.annotations.MostRestrictiveCoexistenceTest;
27 import com.android.bedstead.enterprise.annotations.PolicyAppliesTest;
28 import com.android.bedstead.enterprise.annotations.PolicyDoesNotApplyTest;
29 import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile;
30 import com.android.bedstead.harrier.annotations.AnnotationCostRunPrecedence;
31 import com.android.bedstead.harrier.annotations.AnnotationPriorityRunPrecedence;
32 import com.android.bedstead.harrier.annotations.CrossUserTest;
33 import com.android.bedstead.harrier.annotations.EnumTestParameter;
34 import com.android.bedstead.harrier.annotations.HiddenApiTest;
35 import com.android.bedstead.harrier.annotations.IntTestParameter;
36 import com.android.bedstead.harrier.annotations.PermissionTest;
37 import com.android.bedstead.harrier.annotations.PolicyArgument;
38 import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
39 import com.android.bedstead.harrier.annotations.StringTestParameter;
40 import com.android.bedstead.harrier.annotations.UserPair;
41 import com.android.bedstead.harrier.annotations.UserTest;
42 import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
43 import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
44 import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
45 import com.android.bedstead.harrier.exceptions.RestartTestException;
46 import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser;
47 import com.android.bedstead.multiuser.annotations.EnsureHasCloneProfile;
48 import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile;
49 import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser;
50 import com.android.bedstead.multiuser.annotations.EnsureHasTvProfile;
51 import com.android.bedstead.multiuser.annotations.OtherUser;
52 import com.android.bedstead.multiuser.annotations.RequireNotHeadlessSystemUserMode;
53 import com.android.bedstead.multiuser.annotations.RequireRunOnAdditionalUser;
54 import com.android.bedstead.multiuser.annotations.RequireRunOnCloneProfile;
55 import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser;
56 import com.android.bedstead.multiuser.annotations.RequireRunOnPrivateProfile;
57 import com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser;
58 import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
59 import com.android.bedstead.multiuser.annotations.RequireRunOnTvProfile;
60 import com.android.bedstead.nene.exceptions.NeneException;
61 import com.android.bedstead.nene.types.OptionalBoolean;
62 import com.android.bedstead.performanceanalyzer.annotations.PerformanceTest;
63 import com.android.queryable.annotations.Query;
64 
65 import com.google.auto.value.AutoAnnotation;
66 import com.google.common.collect.ImmutableMap;
67 
68 import com.google.errorprone.annotations.CanIgnoreReturnValue;
69 import org.junit.Test;
70 import org.junit.rules.TestRule;
71 import org.junit.runner.Description;
72 import org.junit.runner.notification.RunNotifier;
73 import org.junit.runners.BlockJUnit4ClassRunner;
74 import org.junit.runners.model.FrameworkMethod;
75 import org.junit.runners.model.InitializationError;
76 import org.junit.runners.model.Statement;
77 import org.junit.runners.model.TestClass;
78 
79 import java.lang.annotation.Annotation;
80 import java.lang.reflect.InvocationTargetException;
81 import java.lang.reflect.Parameter;
82 import java.util.ArrayList;
83 import java.util.Arrays;
84 import java.util.Collections;
85 import java.util.Comparator;
86 import java.util.HashMap;
87 import java.util.HashSet;
88 import java.util.List;
89 import java.util.Map;
90 import java.util.Objects;
91 import java.util.Set;
92 import java.util.function.BiFunction;
93 import java.util.stream.Collectors;
94 import java.util.stream.Stream;
95 
96 /**
97  * A JUnit test runner for use with Bedstead.
98  */
99 // Annotating this class with @Query as a workaround to add this as a data type to a field
100 // in annotations that are called upon by @AutoAnnotation (for e.g. EnsureHasWorkProfile).
101 // @AutoAnnotation is not able to set default value for a field with an annotated data type,
102 // so we try to pass the default value explicitly that is accessed via reflection through this
103 // class.
104 @SuppressWarnings("AndroidJdkLibsChecker")
105 @Query
106 public final class BedsteadJUnit4 extends BlockJUnit4ClassRunner {
107 
108     private static final Set<TestLifecycleListener> sLifecycleListeners = new HashSet<>();
109 
110     private static final Map<Annotation, Integer> ANNOTATION_COST_CACHE = new HashMap<>();
111     private static final Map<Annotation, Integer> ANNOTATION_PRIORITY_CACHE = new HashMap<>();
112 
113     private static final String LOG_TAG = "BedsteadJUnit4";
114     private boolean mHasManualHarrierRule = false;
115 
116     @AutoAnnotation
requireRunOnSystemUser()117     private static RequireRunOnSystemUser requireRunOnSystemUser() {
118         return new AutoAnnotation_BedsteadJUnit4_requireRunOnSystemUser();
119     }
120 
requireRunOnPrimaryUser()121     private static RequireRunOnPrimaryUser requireRunOnPrimaryUser() {
122         return requireRunOnPrimaryUser(OptionalBoolean.ANY);
123     }
124 
125     @AutoAnnotation
requireRunOnPrimaryUser(OptionalBoolean switchedToUser)126     private static RequireRunOnPrimaryUser requireRunOnPrimaryUser(OptionalBoolean switchedToUser) {
127         return new AutoAnnotation_BedsteadJUnit4_requireRunOnPrimaryUser(switchedToUser);
128     }
129 
requireRunOnSecondaryUser()130     private static RequireRunOnSecondaryUser requireRunOnSecondaryUser() {
131         return requireRunOnSecondaryUser(OptionalBoolean.ANY);
132     }
133 
134     @AutoAnnotation
requireRunOnSecondaryUser( OptionalBoolean switchedToUser)135     private static RequireRunOnSecondaryUser requireRunOnSecondaryUser(
136             OptionalBoolean switchedToUser) {
137         return new AutoAnnotation_BedsteadJUnit4_requireRunOnSecondaryUser(switchedToUser);
138     }
139 
140     @AutoAnnotation
requireRunOnAdditionalUser()141     private static RequireRunOnAdditionalUser requireRunOnAdditionalUser() {
142         return new AutoAnnotation_BedsteadJUnit4_requireRunOnAdditionalUser();
143     }
144 
145     @AutoAnnotation
requireRunOnWorkProfile(Query dpc)146     private static RequireRunOnWorkProfile requireRunOnWorkProfile(Query dpc) {
147         return new AutoAnnotation_BedsteadJUnit4_requireRunOnWorkProfile(dpc);
148     }
149 
150     @AutoAnnotation
requireRunOnTvProfile()151     private static RequireRunOnTvProfile requireRunOnTvProfile() {
152         return new AutoAnnotation_BedsteadJUnit4_requireRunOnTvProfile();
153     }
154 
155     @AutoAnnotation
requireRunOnCloneProfile()156     private static RequireRunOnCloneProfile requireRunOnCloneProfile() {
157         return new AutoAnnotation_BedsteadJUnit4_requireRunOnCloneProfile();
158     }
159 
160     @AutoAnnotation
requireRunOnPrivateProfile()161     private static RequireRunOnPrivateProfile requireRunOnPrivateProfile() {
162         return new AutoAnnotation_BedsteadJUnit4_requireRunOnPrivateProfile();
163     }
164 
165     @AutoAnnotation
requireRunOnInitialUser(OptionalBoolean switchedToUser)166     static RequireRunOnInitialUser requireRunOnInitialUser(OptionalBoolean switchedToUser) {
167         return new AutoAnnotation_BedsteadJUnit4_requireRunOnInitialUser(switchedToUser);
168     }
169 
requireRunOnInitialUser()170     static RequireRunOnInitialUser requireRunOnInitialUser() {
171         return requireRunOnInitialUser(OptionalBoolean.TRUE);
172     }
173 
174     @AutoAnnotation
ensureHasSecondaryUser()175     private static EnsureHasSecondaryUser ensureHasSecondaryUser() {
176         return new AutoAnnotation_BedsteadJUnit4_ensureHasSecondaryUser();
177     }
178 
179     @AutoAnnotation
ensureHasAdditionalUser()180     private static EnsureHasAdditionalUser ensureHasAdditionalUser() {
181         return new AutoAnnotation_BedsteadJUnit4_ensureHasAdditionalUser();
182     }
183 
184     @AutoAnnotation
ensureHasWorkProfile(Query dpc)185     private static EnsureHasWorkProfile ensureHasWorkProfile(Query dpc) {
186         return new AutoAnnotation_BedsteadJUnit4_ensureHasWorkProfile(dpc);
187     }
188 
189     @AutoAnnotation
ensureHasTvProfile()190     private static EnsureHasTvProfile ensureHasTvProfile() {
191         return new AutoAnnotation_BedsteadJUnit4_ensureHasTvProfile();
192     }
193 
194     @AutoAnnotation
ensureHasCloneProfile()195     private static EnsureHasCloneProfile ensureHasCloneProfile() {
196         return new AutoAnnotation_BedsteadJUnit4_ensureHasCloneProfile();
197     }
198 
199     @AutoAnnotation
ensureHasPrivateProfile()200     private static EnsureHasPrivateProfile ensureHasPrivateProfile() {
201         return new AutoAnnotation_BedsteadJUnit4_ensureHasPrivateProfile();
202     }
203 
204     @AutoAnnotation
otherUser(UserType value)205     private static OtherUser otherUser(UserType value) {
206         return new AutoAnnotation_BedsteadJUnit4_otherUser(value);
207     }
208 
209     @AutoAnnotation
requireNotHeadlessSystemUserMode(String reason)210     private static RequireNotHeadlessSystemUserMode requireNotHeadlessSystemUserMode(String reason) {
211         return new AutoAnnotation_BedsteadJUnit4_requireNotHeadlessSystemUserMode(reason);
212     }
213 
214     // Get @Query annotation via BedsteadJunit4 class as a workaround to enable adding Query
215     // fields to annotations that rely on @AutoAnnotation (for e.g. @EnsureHasWorkProfile)
query()216     private static Query query() {
217         try {
218             return Class.forName("com.android.bedstead.harrier.BedsteadJUnit4")
219                     .getAnnotation(Query.class);
220         } catch (ClassNotFoundException e) {
221             throw new RuntimeException(
222                     "Unable to get BedsteadJunit4 class when trying to get "
223                             + "@Query annotation", e);
224         }
225     }
226 
227 
228     // These are annotations which are not included indirectly
229     private static final Set<String> sIgnoredAnnotationPackages = new HashSet<>();
230 
231     static {
232         sIgnoredAnnotationPackages.add("java.lang.annotation");
233         sIgnoredAnnotationPackages.add("com.android.bedstead.harrier.annotations.meta");
234         sIgnoredAnnotationPackages.add("kotlin.*");
235         sIgnoredAnnotationPackages.add("org.junit");
236     }
237 
238     /**
239      * Annotation sorter using the priority method added to an annotation,
240      * higher priority numbers are earlier in the list, if a priority is not provided
241      * {@link AnnotationPriorityRunPrecedence#PRECEDENCE_NOT_IMPORTANT} will be used
242      */
annotationSorter(Annotation a, Annotation b)243     public static int annotationSorter(Annotation a, Annotation b) {
244         return getAnnotationPriority(a) - getAnnotationPriority(b);
245     }
246 
getAnnotationCost(Annotation annotation)247     private static int getAnnotationCost(Annotation annotation) {
248         return ANNOTATION_COST_CACHE.computeIfAbsent(
249                 annotation, BedsteadJUnit4::computeAnnotationCost);
250     }
251 
getAnnotationPriority(Annotation annotation)252     private static int getAnnotationPriority(Annotation annotation) {
253         return ANNOTATION_PRIORITY_CACHE.computeIfAbsent(
254                 annotation, BedsteadJUnit4::computeAnnotationPriority);
255     }
256 
computeAnnotationCost(Annotation annotation)257     private static int computeAnnotationCost(Annotation annotation) {
258         try {
259             return (int) annotation.annotationType().getMethod("cost").invoke(annotation);
260         } catch (NoSuchMethodException e) {
261             // Default to MIDDLE if no cost is found on the annotation.
262             return AnnotationCostRunPrecedence.MIDDLE;
263         } catch (IllegalAccessException | InvocationTargetException e) {
264             throw new NeneException("Failed to invoke cost on this annotation: " + annotation, e);
265         }
266     }
267 
computeAnnotationPriority(Annotation annotation)268     private static int computeAnnotationPriority(Annotation annotation) {
269         if (annotation instanceof DynamicParameterizedAnnotation) {
270             // Special case, not important
271             return AnnotationPriorityRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
272         }
273 
274         try {
275             return (int) annotation.annotationType().getMethod("priority").invoke(annotation);
276         } catch (NoSuchMethodException e) {
277             // Default to PRECEDENCE_NOT_IMPORTANT if no priority is found on the annotation.
278             return AnnotationPriorityRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
279         } catch (IllegalAccessException | InvocationTargetException e) {
280             throw new NeneException(
281                     "Failed to invoke priority on this annotation: " + annotation, e);
282         }
283     }
284 
getParameterName(Annotation annotation)285     static String getParameterName(Annotation annotation) {
286         if (annotation instanceof DynamicParameterizedAnnotation) {
287             return ((DynamicParameterizedAnnotation) annotation).name();
288         }
289         return annotation.annotationType().getSimpleName();
290     }
291 
292     /**
293      * Resolves annotations recursively.
294      *
295      * @param parameterizedAnnotations The class of the parameterized annotations to expand, if any
296      */
resolveRecursiveAnnotations( List<Annotation> annotations, List<Annotation> parameterizedAnnotations)297     public void resolveRecursiveAnnotations(
298             List<Annotation> annotations, List<Annotation> parameterizedAnnotations) {
299         resolveRecursiveAnnotations(getHarrierRule(), annotations, parameterizedAnnotations);
300     }
301 
302     /**
303      * Resolves annotations recursively.
304      *
305      * @param parameterizedAnnotations The class of the parameterized annotation to expand, if any
306      */
resolveRecursiveAnnotations( HarrierRule harrierRule, List<Annotation> annotations, List<Annotation> parameterizedAnnotations)307     public static void resolveRecursiveAnnotations(
308             HarrierRule harrierRule,
309             List<Annotation> annotations,
310             List<Annotation> parameterizedAnnotations) {
311         int index = 0;
312         while (index < annotations.size()) {
313             Annotation annotation = annotations.get(index);
314             annotations.remove(index);
315             List<Annotation> replacementAnnotations =
316                     getReplacementAnnotations(harrierRule, annotation, parameterizedAnnotations);
317             replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
318             annotations.addAll(index, replacementAnnotations);
319             index += replacementAnnotations.size();
320         }
321     }
322 
isParameterizedAnnotation(Annotation annotation)323     private static boolean isParameterizedAnnotation(Annotation annotation) {
324         if (annotation instanceof DynamicParameterizedAnnotation) {
325             return true;
326         }
327 
328         return annotation.annotationType().getAnnotation(ParameterizedAnnotation.class) != null;
329     }
330 
isAnnotationClassParameterizedAnnotation(Annotation annotation)331     private static boolean isAnnotationClassParameterizedAnnotation(Annotation annotation) {
332         return annotation.annotationType() != null
333                 && annotation.annotationType().getAnnotation(ParameterizedAnnotation.class) != null;
334     }
335 
getIndirectAnnotations(Annotation annotation)336     private static Annotation[] getIndirectAnnotations(Annotation annotation) {
337         if (annotation instanceof DynamicParameterizedAnnotation) {
338             return ((DynamicParameterizedAnnotation) annotation).annotations();
339         }
340         return annotation.annotationType().getAnnotations();
341     }
342 
isRepeatingAnnotation(Annotation annotation)343     private static boolean isRepeatingAnnotation(Annotation annotation) {
344         if (annotation instanceof DynamicParameterizedAnnotation) {
345             return false;
346         }
347 
348         return annotation.annotationType().getAnnotation(RepeatingAnnotation.class) != null;
349     }
350 
351     private HarrierRule mHarrierRule;
352 
353     private static final ImmutableMap<
354                     Class<? extends Annotation>,
355                     BiFunction<HarrierRule, Annotation, Stream<Annotation>>>
356             ANNOTATION_REPLACEMENTS =
357                     ImmutableMap.of(
358                             RequireRunOnInitialUser.class,
359                             (harrierRule, a) -> {
360                                 RequireRunOnInitialUser requireRunOnInitialUserAnnotation =
361                                         (RequireRunOnInitialUser) a;
362 
363                                 if (harrierRule.isHeadlessSystemUserMode()) {
364                                     return Stream.of(
365                                             a,
366                                             ensureHasSecondaryUser(),
367                                             requireRunOnSecondaryUser(
368                                                     requireRunOnInitialUserAnnotation
369                                                             .switchedToUser()));
370                                 } else {
371                                     return Stream.of(
372                                             a,
373                                             requireRunOnPrimaryUser(
374                                                     requireRunOnInitialUserAnnotation
375                                                             .switchedToUser()));
376                                 }
377                             },
378                             RequireRunOnAdditionalUser.class,
379                             (harrierRule, a) -> {
380                                 RequireRunOnAdditionalUser requireRunOnAdditionalUserAnnotation =
381                                         (RequireRunOnAdditionalUser) a;
382                                 if (harrierRule.isHeadlessSystemUserMode()) {
383                                     return Stream.of(ensureHasSecondaryUser(), a);
384                                 } else {
385                                     return Stream.of(
386                                             a,
387                                             requireRunOnSecondaryUser(
388                                                     requireRunOnAdditionalUserAnnotation
389                                                             .switchedToUser()));
390                                 }
391                             });
392 
getReplacementAnnotations( HarrierRule harrierRule, Annotation annotation, List<Annotation> parameterizedAnnotations)393     static List<Annotation> getReplacementAnnotations(
394             HarrierRule harrierRule,
395             Annotation annotation,
396             List<Annotation> parameterizedAnnotations) {
397         BiFunction<HarrierRule, Annotation, Stream<Annotation>> specialReplaceFunction =
398                 ANNOTATION_REPLACEMENTS.get(annotation.annotationType());
399 
400         if (specialReplaceFunction != null) {
401             List<Annotation> replacement =
402                     specialReplaceFunction.apply(harrierRule, annotation)
403                             .collect(Collectors.toList());
404             return replacement;
405         }
406 
407         List<Annotation> replacementAnnotations = new ArrayList<>();
408 
409         if (isRepeatingAnnotation(annotation)) {
410             try {
411                 Annotation[] annotations =
412                         (Annotation[]) annotation.annotationType()
413                                 .getMethod("value").invoke(annotation);
414                 Collections.addAll(replacementAnnotations, annotations);
415                 return replacementAnnotations;
416             } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
417                 throw new NeneException("Error expanding repeated annotations", e);
418             }
419         }
420 
421         if (isParameterizedAnnotation(annotation)
422                 && !parameterizedAnnotations.contains(annotation)) {
423             return replacementAnnotations;
424         }
425 
426         for (Annotation indirectAnnotation : getIndirectAnnotations(annotation)) {
427             if (shouldSkipAnnotation(annotation)) {
428                 continue;
429             }
430 
431             replacementAnnotations.addAll(
432                     getReplacementAnnotations(
433                             harrierRule, indirectAnnotation, parameterizedAnnotations));
434         }
435 
436         if (!(annotation instanceof DynamicParameterizedAnnotation)) {
437             // We drop the fake annotation once it's replaced
438             replacementAnnotations.add(annotation);
439         }
440 
441         return replacementAnnotations;
442     }
443 
shouldSkipAnnotation(Annotation annotation)444     private static boolean shouldSkipAnnotation(Annotation annotation) {
445         if (annotation instanceof DynamicParameterizedAnnotation) {
446             return false;
447         }
448 
449         if(annotation.annotationType().equals(IncludeNone.class)) {
450             return true;
451         }
452 
453         String annotationPackage = annotation.annotationType().getPackage().getName();
454 
455         for (String ignoredPackage : sIgnoredAnnotationPackages) {
456             if (ignoredPackage.endsWith(".*")) {
457                 if (annotationPackage.startsWith(
458                         ignoredPackage.substring(0, ignoredPackage.length() - 2))) {
459                     return true;
460                 }
461             } else if (annotationPackage.equals(ignoredPackage)) {
462                 return true;
463             }
464         }
465 
466         return false;
467     }
468 
BedsteadJUnit4(Class<?> testClass)469     public BedsteadJUnit4(Class<?> testClass) throws InitializationError {
470         super(testClass);
471     }
472 
getBasicTests(TestClass testClass)473     private static List<FrameworkMethod> getBasicTests(TestClass testClass) {
474         Set<FrameworkMethod> methods = new HashSet<>();
475 
476         methods.addAll(testClass.getAnnotatedMethods(Test.class));
477         methods.addAll(testClass.getAnnotatedMethods(PolicyAppliesTest.class));
478         methods.addAll(testClass.getAnnotatedMethods(PolicyDoesNotApplyTest.class));
479         methods.addAll(testClass.getAnnotatedMethods(CanSetPolicyTest.class));
480         methods.addAll(testClass.getAnnotatedMethods(CannotSetPolicyTest.class));
481         methods.addAll(testClass.getAnnotatedMethods(UserTest.class));
482         methods.addAll(testClass.getAnnotatedMethods(CrossUserTest.class));
483         methods.addAll(testClass.getAnnotatedMethods(PermissionTest.class));
484         methods.addAll(testClass.getAnnotatedMethods(MostRestrictiveCoexistenceTest.class));
485         methods.addAll(testClass.getAnnotatedMethods(MostImportantCoexistenceTest.class));
486         methods.addAll(testClass.getAnnotatedMethods(HiddenApiTest.class));
487         methods.addAll(testClass.getAnnotatedMethods(PerformanceTest.class));
488 
489         return new ArrayList<>(methods);
490     }
491 
492     /**
493      * Groups list of annotations of type [ParameterizedAnnotation] by its [scope].
494      *
495      * @param parameterizedAnnotations the list of annotations of type [ParameterizedAnnotation]
496      * @return list of list of [ParameterizedAnnotation] where each sub list corresponds to
497      *     annotations of one scope.
498      */
getParameterizedAnnotationsGroupedByScope( Set<Annotation> parameterizedAnnotations)499     private List<List<Annotation>> getParameterizedAnnotationsGroupedByScope(
500             Set<Annotation> parameterizedAnnotations) {
501         Map<String, List<Annotation>> annotationsPerScope = new HashMap<>();
502         for (Annotation annotation : parameterizedAnnotations) {
503             if (isAnnotationClassParameterizedAnnotation(annotation)
504                     && !shouldSkipAnnotation(annotation)) {
505                 ParameterizedAnnotation parameterizedAnnotation =
506                         annotation.annotationType().getAnnotation(ParameterizedAnnotation.class);
507                 annotationsPerScope.putIfAbsent(
508                         parameterizedAnnotation.scope().name(), new ArrayList<>());
509                 annotationsPerScope.get(parameterizedAnnotation.scope().name()).add(annotation);
510             }
511         }
512 
513         return new ArrayList<>(annotationsPerScope.values());
514     }
515 
516     /**
517      * Generates a cartesian product of multiple sets of annotations. For example: If the
518      * [annotations] param has value [[A1, A2], [A3, A4]] then it will return [[A1, A3], [A1, A4],
519      * [A2, A3], [A2, A4]].
520      *
521      * @param annotations list of list of annotations whose cartesian product we want to generate.
522      * @return cartesian product of the annotation sets.
523      */
calculateCartesianProductOfAnnotationSets( List<List<Annotation>> annotations)524     private static List<List<Annotation>> calculateCartesianProductOfAnnotationSets(
525             List<List<Annotation>> annotations) {
526         List<List<Annotation>> result = new ArrayList<>();
527         if (!annotations.isEmpty()) {
528             generateCartesianProductOfAnnotationSets(annotations, 0, result, new ArrayList<>());
529         }
530         return result;
531     }
532 
533     /**
534      * Generates a cartesian product of multiple sets of annotations. This method is an internal
535      * helper method for {@code calculateCartesianProductOfAnnotationSets()}. Refer {@code
536      * calculateCartesianProductOfAnnotationSets()} for an example.
537      */
generateCartesianProductOfAnnotationSets( List<List<Annotation>> annotations, int position, List<List<Annotation>> result, List<Annotation> subResult)538     private static void generateCartesianProductOfAnnotationSets(
539             List<List<Annotation>> annotations,
540             int position,
541             List<List<Annotation>> result,
542             List<Annotation> subResult) {
543         if (position == annotations.size()) {
544             if (!subResult.isEmpty()) {
545                 result.add(new ArrayList<>(subResult));
546             }
547             return;
548         }
549         for (int i = 0; i < annotations.get(position).size(); i++) {
550             subResult.add(annotations.get(position).get(i));
551             generateCartesianProductOfAnnotationSets(annotations, position + 1, result, subResult);
552             subResult.remove(subResult.size() - 1);
553         }
554     }
555 
556     @Override
computeTestMethods()557     protected List<FrameworkMethod> computeTestMethods() {
558         // TODO: It appears that the annotations are computed up to 8 times per run. Figure out how
559         // to cut this out (this method only seems to be called once)
560         List<FrameworkMethod> basicTests = getBasicTests(getTestClass());
561         List<FrameworkMethod> modifiedTests = new ArrayList<>();
562 
563         for (FrameworkMethod m : basicTests) {
564             Set<Annotation> parameterizedAnnotations = getParameterizedAnnotations(m.getAnnotations());
565 
566             if (parameterizedAnnotations.isEmpty()) {
567                 // Unparameterized, just add the original
568                 modifiedTests.add(new BedsteadFrameworkMethod(this, m.getMethod()));
569                 continue;
570             }
571 
572             // Create [BedsteadFrameworkMethod] for parameterized annotation of instance {@Code
573             // DynamicParameterizedAnnotation}.
574             for (Annotation annotation : parameterizedAnnotations) {
575                 if (shouldSkipAnnotation(annotation)
576                         || isAnnotationClassParameterizedAnnotation(annotation)) {
577                     // Special case - does not generate a run
578                     continue;
579                 }
580                 modifiedTests.add(
581                         new BedsteadFrameworkMethod(this, m.getMethod(), List.of(annotation)));
582             }
583 
584             List<List<Annotation>> parametrizedAnnotationsGroupedByScope =
585                     getParameterizedAnnotationsGroupedByScope(parameterizedAnnotations);
586 
587             List<List<Annotation>> cartesianProductOfAnnotationSets =
588                     calculateCartesianProductOfAnnotationSets(
589                             parametrizedAnnotationsGroupedByScope);
590 
591             // Create [BedsteadFrameworkMethod] for each parameterized annotation of type
592             // [ParameterizedAnnotation].
593             for (List<Annotation> annotationsToApplyTogether : cartesianProductOfAnnotationSets) {
594                 modifiedTests.add(
595                         new BedsteadFrameworkMethod(
596                                 this, m.getMethod(), annotationsToApplyTogether));
597             }
598         }
599 
600         modifiedTests = generateGeneralParameterisationMethods(modifiedTests);
601 
602         sortMethodsByBedsteadAnnotations(modifiedTests);
603 
604         return modifiedTests;
605     }
606 
generateGeneralParameterisationMethods( List<FrameworkMethod> modifiedTests)607     private List<FrameworkMethod> generateGeneralParameterisationMethods(
608             List<FrameworkMethod> modifiedTests) {
609         return modifiedTests.stream()
610                 .flatMap(this::generateGeneralParameterisationMethods)
611                 .collect(Collectors.toList());
612     }
613 
generateGeneralParameterisationMethods(FrameworkMethod method)614     private Stream<FrameworkMethod> generateGeneralParameterisationMethods(FrameworkMethod method) {
615         Stream<FrameworkMethod> expandedMethods = Stream.of(method);
616         if (method.getMethod().getParameterCount() == 0) {
617             return expandedMethods;
618         }
619 
620         for (Parameter parameter : method.getMethod().getParameters()) {
621             List<Annotation> annotations =
622                     new ArrayList<>(Arrays.asList(parameter.getAnnotations()));
623             resolveRecursiveAnnotations(annotations, /* parameterizedAnnotations= */ List.of());
624 
625             boolean hasParameterised = false;
626 
627             for (Annotation annotation : annotations) {
628 
629                 if (annotation instanceof PolicyArgument) {
630                     if (hasParameterised) {
631                         throw new IllegalStateException(
632                                 "Each parameter can only have a single parameterised annotation");
633                     }
634                     hasParameterised = true;
635 
636                     HarrierToEnterpriseMediator mediator =
637                             HarrierToEnterpriseMediator.Companion.getMediatorOrThrowException(
638                                     "you can't use @PolicyArgument without the enterprise module"
639                             );
640                     expandedMethods = mediator.generatePolicyArgumentTests(method, expandedMethods);
641                 } else if (annotation instanceof StringTestParameter) {
642                     if (hasParameterised) {
643                         throw new IllegalStateException(
644                                 "Each parameter can only have a single parameterised annotation");
645                     }
646                     hasParameterised = true;
647 
648                     StringTestParameter stringTestParameter = (StringTestParameter) annotation;
649 
650                     expandedMethods = expandedMethods.flatMap(
651                             i -> applyStringTestParameter(i, stringTestParameter));
652                 } else if (annotation instanceof IntTestParameter) {
653                     if (hasParameterised) {
654                         throw new IllegalStateException(
655                                 "Each parameter can only have a single parameterised annotation");
656                     }
657                     hasParameterised = true;
658 
659                     IntTestParameter intTestParameter = (IntTestParameter) annotation;
660 
661                     expandedMethods = expandedMethods.flatMap(
662                             i -> applyIntTestParameter(i, intTestParameter));
663                 } else if (annotation instanceof EnumTestParameter) {
664                     if (hasParameterised) {
665                         throw new IllegalStateException(
666                                 "Each parameter can only have a single parameterised annotation");
667                     }
668                     hasParameterised = true;
669 
670                     EnumTestParameter enumTestParameter = (EnumTestParameter) annotation;
671 
672                     expandedMethods = expandedMethods.flatMap(
673                             i -> applyEnumTestParameter(i, enumTestParameter));
674                 }
675             }
676 
677             if (!hasParameterised) {
678                 throw new IllegalStateException(
679                         "Parameter " + parameter + " must be annotated as parameterised");
680             }
681         }
682 
683         return expandedMethods;
684     }
685 
applyStringTestParameter(FrameworkMethod frameworkMethod, StringTestParameter stringTestParameter)686     private static Stream<FrameworkMethod> applyStringTestParameter(FrameworkMethod frameworkMethod,
687             StringTestParameter stringTestParameter) {
688         return Stream.of(stringTestParameter.value()).map(
689                 (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
690         );
691     }
692 
applyIntTestParameter(FrameworkMethod frameworkMethod, IntTestParameter intTestParameter)693     private static Stream<FrameworkMethod> applyIntTestParameter(FrameworkMethod frameworkMethod,
694             IntTestParameter intTestParameter) {
695         return Arrays.stream(intTestParameter.value()).mapToObj(
696                 (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
697         );
698     }
699 
applyEnumTestParameter(FrameworkMethod frameworkMethod, EnumTestParameter enumTestParameter)700     private static Stream<FrameworkMethod> applyEnumTestParameter(FrameworkMethod frameworkMethod,
701             EnumTestParameter enumTestParameter) {
702         return Arrays.stream(enumTestParameter.value().getEnumConstants()).map(
703                 (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
704         );
705     }
706 
707     /**
708      * Sort methods by cost and group the ones with identical bedstead annotations together.
709      *
710      * <p>This will also ensure that all tests methods which are not annotated for bedstead will
711      * run before any tests which are annotated.
712      */
sortMethodsByBedsteadAnnotations(List<FrameworkMethod> modifiedTests)713     private void sortMethodsByBedsteadAnnotations(List<FrameworkMethod> modifiedTests) {
714         List<Annotation> bedsteadAnnotationsSortedByCost =
715                 bedsteadAnnotationsSortedByCost(modifiedTests);
716         Comparator<FrameworkMethod> comparator = ((o1, o2) -> {
717             for (Annotation annotation : bedsteadAnnotationsSortedByCost) {
718                 boolean o1HasAnnotation = o1.getAnnotation(annotation.annotationType()) != null;
719                 boolean o2HasAnnotation = o2.getAnnotation(annotation.annotationType()) != null;
720 
721                 if (o1HasAnnotation && !o2HasAnnotation) {
722                     // o1 goes to the start
723                     return -1;
724                 } else if (o2HasAnnotation && !o1HasAnnotation) {
725                     return 1;
726                 }
727             }
728             return 0;
729         });
730 
731         List<Annotation> bedsteadAnnotationsSortedByMostCommon =
732                 bedsteadAnnotationsSortedByMostCommon(modifiedTests);
733         var unused = comparator.thenComparing((o1, o2) -> {
734             for (Annotation annotation : bedsteadAnnotationsSortedByMostCommon) {
735                 boolean o1HasAnnotation = o1.getAnnotation(annotation.annotationType()) != null;
736                 boolean o2HasAnnotation = o2.getAnnotation(annotation.annotationType()) != null;
737 
738                 if (o1HasAnnotation && !o2HasAnnotation) {
739                     // o1 goes to the end
740                     return 1;
741                 } else if (o2HasAnnotation && !o1HasAnnotation) {
742                     return -1;
743                 }
744             }
745 
746             return 0;
747         });
748 
749         modifiedTests.sort(comparator);
750     }
751 
bedsteadAnnotationsSortedByCost(List<FrameworkMethod> methods)752     private List<Annotation> bedsteadAnnotationsSortedByCost(List<FrameworkMethod> methods) {
753         Map<Annotation, Integer> annotationCosts = mapAnnotationsCost(methods);
754 
755         List<Annotation> annotations = new ArrayList<>(annotationCosts.keySet());
756         annotations.sort(Comparator.comparingInt(annotationCosts::get));
757 
758         return annotations;
759     }
760 
bedsteadAnnotationsSortedByMostCommon(List<FrameworkMethod> methods)761     private List<Annotation> bedsteadAnnotationsSortedByMostCommon(List<FrameworkMethod> methods) {
762         Map<Annotation, Integer> annotationCounts = countAnnotations(methods);
763         List<Annotation> annotations = new ArrayList<>(annotationCounts.keySet());
764         annotations.sort(Comparator.comparingInt(annotationCounts::get));
765         Collections.reverse(annotations);
766 
767         return annotations;
768     }
769 
countAnnotations(List<FrameworkMethod> methods)770     private Map<Annotation, Integer> countAnnotations(List<FrameworkMethod> methods) {
771         Map<Annotation, Integer> annotationCounts = new HashMap<>();
772 
773         for (FrameworkMethod method : methods) {
774             for (Annotation annotation : method.getAnnotations()) {
775                 annotationCounts.put(
776                         annotation, annotationCounts.getOrDefault(annotation, 0) + 1);
777             }
778         }
779 
780         return annotationCounts;
781     }
782 
mapAnnotationsCost(List<FrameworkMethod> methods)783     private Map<Annotation, Integer> mapAnnotationsCost(List<FrameworkMethod> methods) {
784         Map<Annotation, Integer> annotationCosts = new HashMap<>();
785 
786         for (FrameworkMethod method : methods) {
787             for (Annotation annotation : method.getAnnotations()) {
788                 annotationCosts.put(annotation, getAnnotationCost(annotation));
789             }
790         }
791 
792         return annotationCosts;
793     }
794 
795     /**
796      * Filters array of annotations and returns only annotations of type
797      * {@link ParameterizedAnnotation} and {@link DynamicParameterizedAnnotation}.
798      *
799      * @param methodAnnotations the array of annotations of test method
800      */
801     @CanIgnoreReturnValue
getParameterizedAnnotations(Annotation[] methodAnnotations)802     public static Set<Annotation> getParameterizedAnnotations(Annotation[] methodAnnotations) {
803         Set<Annotation> parameterizedAnnotations = new HashSet<>();
804         List<Annotation> annotations = new ArrayList<>(Arrays.asList(methodAnnotations));
805 
806         parseEnterpriseAnnotations(annotations);
807         parsePermissionAnnotations(annotations);
808         parseUserAnnotations(annotations);
809 
810         for (Annotation annotation : annotations) {
811             if (isParameterizedAnnotation(annotation)) {
812                 parameterizedAnnotations.add(annotation);
813             }
814         }
815 
816         return parameterizedAnnotations;
817     }
818 
819     /**
820      * Parse enterprise-specific annotations.
821      *
822      * <p>To be used before general annotation processing.
823      */
parseEnterpriseAnnotations(List<Annotation> annotations)824     static void parseEnterpriseAnnotations(List<Annotation> annotations) {
825         HarrierToEnterpriseMediator mediator =
826                 HarrierToEnterpriseMediator.Companion.getMediatorOrNull();
827         if (mediator == null) {
828             System.out.println(LOG_TAG + " bedstead-enterprise module is not loaded, "
829                     + "parseEnterpriseAnnotations will not be executed");
830         } else {
831             mediator.parseEnterpriseAnnotations(annotations);
832         }
833     }
834 
835     /**
836      * Parse @PermissionTest annotations.
837      *
838      * <p>To be used before general annotation processing.
839      */
parsePermissionAnnotations(List<Annotation> annotations)840     static void parsePermissionAnnotations(List<Annotation> annotations) {
841         int index = 0;
842         while (index < annotations.size()) {
843             Annotation annotation = annotations.get(index);
844             if (annotation instanceof PermissionTest) {
845                 annotations.remove(index);
846 
847                 List<Annotation> replacementAnnotations = generatePermissionAnnotations(
848                         ((PermissionTest) annotation).value());
849                 replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
850 
851                 annotations.addAll(index, replacementAnnotations);
852                 index += replacementAnnotations.size();
853             } else {
854                 index++;
855             }
856         }
857     }
858 
generatePermissionAnnotations(String[] permissions)859     private static List<Annotation> generatePermissionAnnotations(String[] permissions) {
860         Set<String> allPermissions = new HashSet<>(Arrays.asList(permissions));
861         List<Annotation> replacementAnnotations = new ArrayList<>();
862 
863         for (String permission : permissions) {
864             allPermissions.remove(permission);
865             replacementAnnotations.add(
866                     new DynamicParameterizedAnnotation(
867                             permission,
868                             new Annotation[]{
869                                     ensureHasPermission(permission),
870                                     ensureDoesNotHavePermission(allPermissions.toArray(new String[]{}))
871                             }));
872             allPermissions.add(permission);
873         }
874 
875         return replacementAnnotations;
876     }
877 
878     /**
879      * Parse @UserTest and @CrossUserTest annotations.
880      *
881      * <p>To be used before general annotation processing.
882      */
parseUserAnnotations(List<Annotation> annotations)883     static void parseUserAnnotations(List<Annotation> annotations) {
884         int index = 0;
885         while (index < annotations.size()) {
886             Annotation annotation = annotations.get(index);
887             if (annotation instanceof UserTest) {
888                 annotations.remove(index);
889 
890                 List<Annotation> replacementAnnotations = generateUserAnnotations(
891                         ((UserTest) annotation).value());
892                 replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
893 
894                 annotations.addAll(index, replacementAnnotations);
895                 index += replacementAnnotations.size();
896             } else if (annotation instanceof CrossUserTest) {
897                 annotations.remove(index);
898 
899                 CrossUserTest crossUserTestAnnotation = (CrossUserTest) annotation;
900                 List<Annotation> replacementAnnotations = generateCrossUserAnnotations(
901                         crossUserTestAnnotation.value());
902                 replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
903 
904                 annotations.addAll(index, replacementAnnotations);
905                 index += replacementAnnotations.size();
906             } else {
907                 index++;
908             }
909         }
910     }
911 
generateUserAnnotations(UserType[] userTypes)912     private static List<Annotation> generateUserAnnotations(UserType[] userTypes) {
913         List<Annotation> replacementAnnotations = new ArrayList<>();
914 
915         for (UserType userType : userTypes) {
916             Annotation runOnUserAnnotation = getRunOnAnnotation(userType, "@UserTest");
917             replacementAnnotations.add(
918                     new DynamicParameterizedAnnotation(
919                             userType.name(),
920                             new Annotation[]{runOnUserAnnotation}));
921         }
922 
923         return replacementAnnotations;
924     }
925 
generateCrossUserAnnotations(UserPair[] userPairs)926     private static List<Annotation> generateCrossUserAnnotations(UserPair[] userPairs) {
927         List<Annotation> replacementAnnotations = new ArrayList<>();
928 
929         for (UserPair userPair : userPairs) {
930             Annotation[] annotations = new Annotation[]{
931                     getRunOnAnnotation(userPair.from(), "@CrossUserTest"),
932                     otherUser(userPair.to())
933             };
934             if (userPair.from() != userPair.to()) {
935                 Annotation hasUserAnnotation =
936                         getHasUserAnnotation(userPair.to(), "@CrossUserTest");
937                 if (hasUserAnnotation != null) {
938                     annotations = new Annotation[]{
939                             annotations[0],
940                             annotations[1],
941                             hasUserAnnotation};
942                 }
943             }
944 
945             replacementAnnotations.add(
946                     new DynamicParameterizedAnnotation(
947                             userPair.from().name() + "_to_" + userPair.to().name(),
948                             annotations));
949         }
950 
951         return replacementAnnotations;
952     }
953 
getRunOnAnnotation(UserType userType, String annotationName)954     private static Annotation getRunOnAnnotation(UserType userType, String annotationName) {
955         switch (userType) {
956             case SYSTEM_USER:
957                 return requireRunOnSystemUser();
958             case CURRENT_USER:
959                 return null; // No requirement, run on current user
960             case INITIAL_USER:
961                 return requireRunOnInitialUser();
962             case ADDITIONAL_USER:
963                 return requireRunOnAdditionalUser();
964             case PRIMARY_USER:
965                 return requireRunOnPrimaryUser();
966             case SECONDARY_USER:
967                 return requireRunOnSecondaryUser();
968             case WORK_PROFILE:
969                 return requireRunOnWorkProfile(query());
970             case TV_PROFILE:
971                 return requireRunOnTvProfile();
972             case CLONE_PROFILE:
973                 return requireRunOnCloneProfile();
974             case PRIVATE_PROFILE:
975                 return requireRunOnPrivateProfile();
976             default:
977                 throw new IllegalStateException(
978                         "UserType " + userType + " is not compatible with " + annotationName);
979         }
980     }
981 
getHasUserAnnotation(UserType userType, String annotationName)982     private static Annotation getHasUserAnnotation(UserType userType, String annotationName) {
983         switch (userType) {
984             case SYSTEM_USER:
985                 return null; // We always have a system user
986             case CURRENT_USER:
987                 return null; // We always have a current user
988             case INITIAL_USER:
989                 return null; // We always have an initial user
990             case ADDITIONAL_USER:
991                 return ensureHasAdditionalUser();
992             case PRIMARY_USER:
993                 return requireNotHeadlessSystemUserMode(
994                         "Headless System User Mode Devices do not have a primary user");
995             case SECONDARY_USER:
996                 return ensureHasSecondaryUser();
997             case WORK_PROFILE:
998                 return ensureHasWorkProfile(query());
999             case TV_PROFILE:
1000                 return ensureHasTvProfile();
1001             case CLONE_PROFILE:
1002                 return ensureHasCloneProfile();
1003             case PRIVATE_PROFILE:
1004                 return ensureHasPrivateProfile();
1005             default:
1006                 throw new IllegalStateException(
1007                         "UserType " + userType + " is not compatible with " + annotationName);
1008         }
1009     }
1010 
getHarrierRule()1011     HarrierRule getHarrierRule() {
1012         if (mHarrierRule == null) {
1013             var unused = classRules();
1014         }
1015         return mHarrierRule;
1016     }
1017 
1018     @Override
getTestRules(Object target)1019     protected List<TestRule> getTestRules(Object target) {
1020         var testRules = super.getTestRules(target);
1021         if (mHasManualHarrierRule) {
1022             return testRules;
1023         }
1024         var harrier = findHarrier(testRules);
1025         if (harrier == null) {
1026             testRules.add(getHarrierRule());
1027         }
1028         return testRules;
1029     }
1030 
1031     @Override
classRules()1032     protected List<TestRule> classRules() {
1033         List<TestRule> rules = super.classRules();
1034 
1035         mHarrierRule = findHarrier(rules);
1036         mHasManualHarrierRule = mHarrierRule != null;
1037 
1038         if (mHarrierRule == null) {
1039             mHarrierRule = new DeviceState();
1040         }
1041         if (!rules.contains(mHarrierRule)) {
1042             rules.add(mHarrierRule);
1043         }
1044 
1045         mHarrierRule.setSkipTestTeardown(true);
1046         mHarrierRule.setUsingBedsteadJUnit4(true);
1047 
1048         return rules;
1049     }
1050 
findHarrier(List<TestRule> rules)1051     private HarrierRule findHarrier(List<TestRule> rules) {
1052         for (TestRule rule : rules) {
1053             if (rule instanceof HarrierRule) {
1054                 return (HarrierRule) rule;
1055             }
1056         }
1057         return null;
1058     }
1059 
1060     /**
1061      * True if the test is running in debug mode.
1062      *
1063      * <p>This will result in additional debugging information being added which would otherwise
1064      * be dropped to improve test performance.
1065      *
1066      * <p>To enable this, pass the "bedstead-debug" instrumentation arg as "true"
1067      */
isDebug()1068     public static boolean isDebug() {
1069         try {
1070             Class instrumentationRegistryClass = Class.forName(
1071                         "androidx.test.platform.app.InstrumentationRegistry");
1072 
1073             Object arguments = instrumentationRegistryClass.getMethod("getArguments")
1074                     .invoke(null);
1075             return Boolean.parseBoolean((String) arguments.getClass()
1076                     .getMethod("getString", String.class, String.class)
1077                     .invoke(arguments, "bedstead-debug", "false"));
1078         } catch (ClassNotFoundException e) {
1079             return false; // Must be on the host so can't access debug information
1080         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1081             throw new IllegalStateException("Error getting isDebug", e);
1082         }
1083     }
1084 
1085     @Override
validateTestMethods(List<Throwable> errors)1086     protected void validateTestMethods(List<Throwable> errors) {
1087         // We do allow arguments - they will fail validation later on if not properly annotated
1088     }
1089 
1090     /**
1091      * Add a listener to be informed of test lifecycle events.
1092      */
addLifecycleListener(TestLifecycleListener listener)1093     public static void addLifecycleListener(TestLifecycleListener listener) {
1094         sLifecycleListeners.add(listener);
1095     }
1096 
1097     /**
1098      * Remove a listener being informed of test lifecycle events.
1099      */
removeLifecycleListener(TestLifecycleListener listener)1100     public static void removeLifecycleListener(TestLifecycleListener listener) {
1101         sLifecycleListeners.remove(listener);
1102     }
1103 
1104     @Override
runChild(final FrameworkMethod method, RunNotifier notifier)1105     protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
1106         Description description = describeChild(method);
1107         if (isIgnored(method)) {
1108             notifier.fireTestIgnored(description);
1109         } else {
1110             Statement statement = new Statement() {
1111                 @Override
1112                 public void evaluate() throws Throwable {
1113                     sLifecycleListeners.forEach(l -> l.testStarted(method.getName()));
1114                     while (true) {
1115                         try {
1116                             methodBlock(method).evaluate();
1117                             sLifecycleListeners.forEach(l -> l.testFinished(method.getName()));
1118                             return;
1119                         } catch (RestartTestException e) {
1120                             sLifecycleListeners.forEach(
1121                                     l -> l.testRestarted(method.getName(), e.getMessage()));
1122                             System.out.println(LOG_TAG + ": Restarting test(" + e.toString() + ")");
1123                         }
1124                     }
1125                 }
1126             };
1127             runLeaf(statement, description, notifier);
1128         }
1129     }
1130 }
1131