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