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 package libcore.junit.util; 17 18 import static org.junit.Assert.fail; 19 20 import java.lang.annotation.ElementType; 21 import java.lang.annotation.Retention; 22 import java.lang.annotation.RetentionPolicy; 23 import java.lang.annotation.Target; 24 import java.lang.reflect.Method; 25 import org.junit.rules.TestRule; 26 import org.junit.runner.Description; 27 import org.junit.runners.model.Statement; 28 29 /** 30 * Allows tests to specify the target SDK version that they want to run as. 31 * 32 * <p>To use add the following to the test class. It will only change the behavior of a test method 33 * if it is annotated with {@link TargetSdkVersion}. 34 * 35 * <pre> 36 * @Rule 37 * public TestRule switchTargetSdkVersionRule = SwitchTargetSdkVersionRule.getInstance(); 38 * </pre> 39 * 40 * <p>Each test method that needs to run with a specific target SDK version needs to be annotated 41 * with {@link TargetSdkVersion} specifying the required SDK version. e.g. 42 * 43 * <pre> 44 * @Test 45 * @TargetSdkVersion(23) 46 * public void testAsIfTargetedAtSDK23() { 47 * assertEquals(23, VMRuntime.getRuntime().getTargetSdkVersion()); 48 * } 49 * </pre> 50 * 51 * <p>Within the body of the method the {@code VMRuntime.getTargetSdkVersion()} will be set to the 52 * value specified in the annotation. 53 * 54 * <p>If used on a platform that does not support the {@code dalvik.system.VMRuntime} class then any 55 * test annotated with {@link TargetSdkVersion} will fail with a message explaining that it is not 56 * supported. 57 */ 58 public abstract class SwitchTargetSdkVersionRule implements TestRule { 59 60 private static final TestRule INSTANCE; 61 62 private static final String VMRUNTIME = "dalvik.system.VMRuntime"; 63 64 // Use reflection so that this rule can compile and run against RI core libraries. 65 static { 66 TestRule rule; 67 try { 68 // Assume that VMRuntime is supported and create a rule instance that will use it. 69 rule = new SwitchVMRuntimeUsingReflection(); 70 } catch (ClassNotFoundException | NoSuchMethodException e) { 71 // VMRuntime is not supported. 72 rule = new VMRuntimeNotSupported(); 73 } 74 75 INSTANCE = rule; 76 } 77 getInstance()78 public static TestRule getInstance() { 79 return INSTANCE; 80 } 81 82 @Retention(RetentionPolicy.RUNTIME) 83 @Target(ElementType.METHOD) 84 public @interface TargetSdkVersion { value()85 int value(); 86 } 87 SwitchTargetSdkVersionRule()88 private SwitchTargetSdkVersionRule() { 89 } 90 91 @Override apply(final Statement statement, Description description)92 public Statement apply(final Statement statement, Description description) { 93 TargetSdkVersion targetSdkVersion = description.getAnnotation(TargetSdkVersion.class); 94 if (targetSdkVersion == null) { 95 return statement; 96 } 97 98 return createStatement(statement, targetSdkVersion.value()); 99 } 100 101 /** 102 * Create the {@link Statement} that will be run for the specific {@code targetSdkVersion}. 103 * 104 * @param statement the {@link Statement} encapsulating the test method. 105 * @param targetSdkVersion the target SDK version to use within the body of the test. 106 * @return the created {@link Statement}. 107 */ createStatement(Statement statement, int targetSdkVersion)108 protected abstract Statement createStatement(Statement statement, int targetSdkVersion); 109 110 /** 111 * Switch the value of {@code VMRuntime.getTargetSdkVersion()} for tests annotated with 112 * {@link TargetSdkVersion}. 113 * 114 * <p>Uses reflection so this class can compile and run on OpenJDK. 115 */ 116 private static class SwitchVMRuntimeUsingReflection extends SwitchTargetSdkVersionRule { 117 118 private final Method runtimeInstanceGetter; 119 private final Method targetSdkVersionGetter; 120 private final Method targetSdkVersionSetter; 121 SwitchVMRuntimeUsingReflection()122 private SwitchVMRuntimeUsingReflection() throws ClassNotFoundException, NoSuchMethodException { 123 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 124 Class<?> runtimeClass = classLoader.loadClass(VMRUNTIME); 125 126 this.runtimeInstanceGetter = runtimeClass.getMethod("getRuntime"); 127 this.targetSdkVersionGetter = runtimeClass.getMethod("getTargetSdkVersion"); 128 this.targetSdkVersionSetter = runtimeClass.getMethod("setTargetSdkVersion", int.class); 129 } 130 131 @Override createStatement(Statement statement, int targetSdkVersion)132 protected Statement createStatement(Statement statement, int targetSdkVersion) { 133 return new Statement() { 134 @Override 135 public void evaluate() throws Throwable { 136 Object runtime = runtimeInstanceGetter.invoke(null); 137 int oldTargetSdkVersion = (int) targetSdkVersionGetter.invoke(runtime); 138 targetSdkVersionSetter.invoke(runtime, targetSdkVersion); 139 try { 140 statement.evaluate(); 141 } finally { 142 targetSdkVersionSetter.invoke(runtime, oldTargetSdkVersion); 143 } 144 } 145 }; 146 } 147 } 148 149 /** 150 * VMRuntime is not supported on this platform so fail all tests that target a specific SDK 151 * version 152 */ 153 private static class VMRuntimeNotSupported extends SwitchTargetSdkVersionRule { 154 155 @Override 156 protected Statement createStatement(Statement statement, int targetSdkVersion) { 157 return new Statement() { 158 @Override 159 public void evaluate() { 160 fail("Targeting SDK version not supported as " + VMRUNTIME + " is not supported"); 161 } 162 }; 163 } 164 } 165 } 166