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