• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * &#64;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  *   &#64;Test
45  *   &#64;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