1 /* 2 * Copyright (C) 2019 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.server.pm.parsing.library; 18 19 import org.junit.Assume; 20 import org.junit.runner.Description; 21 import org.junit.runner.Runner; 22 import org.junit.runner.notification.RunNotifier; 23 import org.junit.runners.JUnit4; 24 import org.junit.runners.ParentRunner; 25 import org.junit.runners.model.InitializationError; 26 import org.junit.runners.model.Statement; 27 28 import java.lang.annotation.ElementType; 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.lang.annotation.Target; 32 import java.util.Collections; 33 import java.util.List; 34 35 /** 36 * Will run a test class, iff a class, specified by name in the {@link OptionalClass} annotation, 37 * exists on the class path. 38 * 39 * <p>It is an {@link InitializationError} if no {@link OptionalClass} annotation is specified on 40 * the class that has {@code @RunWith(OptionalClassRunner.class)}. 41 * 42 * <p>If the named class cannot be found then the test class is reported as having been ignored. 43 */ 44 public class OptionalClassRunner extends Runner { 45 46 private final Runner mDelegate; 47 OptionalClassRunner(Class<?> testClass)48 public OptionalClassRunner(Class<?> testClass) throws InitializationError { 49 OptionalClass annotation = testClass.getAnnotation(OptionalClass.class); 50 if (annotation == null) { 51 throw new InitializationError( 52 "No " + OptionalClass.class.getName() + " annotation found on " + testClass); 53 } 54 55 String className = annotation.value(); 56 Runner delegate; 57 try { 58 Class.forName(className); 59 // The class could be found so create a JUnit4 delegate for the class to run. 60 delegate = new JUnit4(testClass); 61 } catch (ClassNotFoundException e) { 62 // The class could not be found so create a Runner delegate that will treat the 63 // test as having failed a test assumption. 64 delegate = new ClassNotFoundRunner(testClass, className); 65 } 66 67 this.mDelegate = delegate; 68 } 69 70 @Override getDescription()71 public Description getDescription() { 72 return mDelegate.getDescription(); 73 } 74 75 @Override run(RunNotifier notifier)76 public void run(RunNotifier notifier) { 77 mDelegate.run(notifier); 78 } 79 80 @Retention(RetentionPolicy.RUNTIME) 81 @Target(ElementType.TYPE) 82 public @interface OptionalClass { value()83 String value(); 84 } 85 86 /** 87 * Emulates a class containing a single test that fails due to an invalid assumption caused by 88 * the missing class. 89 */ 90 private static class ClassNotFoundRunner extends ParentRunner<Runner> { 91 92 private List<Runner> mChildren; 93 ClassNotFoundRunner(Class<?> testClass, String className)94 ClassNotFoundRunner(Class<?> testClass, String className) 95 throws InitializationError { 96 super(testClass); 97 this.mChildren = Collections.singletonList(new ChildRunner(testClass, className)); 98 } 99 100 @Override getChildren()101 protected List<Runner> getChildren() { 102 return mChildren; 103 } 104 105 @Override describeChild(Runner child)106 protected Description describeChild(Runner child) { 107 return child.getDescription(); 108 } 109 110 @Override runChild(Runner child, RunNotifier notifier)111 protected void runChild(Runner child, RunNotifier notifier) { 112 child.run(notifier); 113 } 114 115 private class ChildRunner extends Runner { 116 117 private final Class<?> mTestClass; 118 119 private final String mClassName; 120 ChildRunner(Class<?> testClass, String className)121 ChildRunner(Class<?> testClass, String className) { 122 this.mTestClass = testClass; 123 this.mClassName = className; 124 } 125 126 @Override getDescription()127 public Description getDescription() { 128 return Description.createTestDescription(mTestClass, "classNotFound"); 129 } 130 131 @Override run(RunNotifier notifier)132 public void run(RunNotifier notifier) { 133 runLeaf(new Statement() { 134 @Override 135 public void evaluate() throws Throwable { 136 Assume.assumeTrue("Could not find class: " + mClassName, false); 137 } 138 }, getDescription(), notifier); 139 } 140 } 141 } 142 } 143