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.wm;
18 
19 
20 import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
21 
22 import org.junit.After;
23 import org.junit.Before;
24 import org.junit.internal.runners.statements.RunAfters;
25 import org.junit.internal.runners.statements.RunBefores;
26 import org.junit.rules.TestRule;
27 import org.junit.runners.model.FrameworkMethod;
28 import org.junit.runners.model.InitializationError;
29 import org.junit.runners.model.Statement;
30 
31 import java.lang.annotation.ElementType;
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.lang.annotation.Target;
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /**
39  * A runner with support to bind additional operations with test method tightly.
40  *
41  * @see MethodWrapperRule
42  */
43 public class WindowTestRunner extends AndroidJUnit4ClassRunner {
44     private final List<FrameworkMethod> mBefores;
45     private final List<FrameworkMethod> mAfters;
46 
WindowTestRunner(Class<?> klass)47     public WindowTestRunner(Class<?> klass) throws InitializationError {
48         super(klass);
49         mBefores = getTestClass().getAnnotatedMethods(Before.class);
50         mAfters = getTestClass().getAnnotatedMethods(After.class);
51     }
52 
53     @Override
methodInvoker(FrameworkMethod method, Object test)54     protected Statement methodInvoker(FrameworkMethod method, Object test) {
55         return wrapStatement(super.methodInvoker(method, test), method, test);
56     }
57 
wrapStatement(Statement statement, FrameworkMethod method, Object target)58     private Statement wrapStatement(Statement statement, FrameworkMethod method, Object target) {
59         for (MethodWrapper wrapper : getMethodWrappers(target)) {
60             statement = wrapper.apply(statement, describeChild(method));
61         }
62         return statement;
63     }
64 
65     /**
66      * Constructs the test statement with {@link Before}.
67      *
68      * @param method The test method.
69      * @param target The instance of test class.
70      * @param statement The next statement. It is usually the test method.
71      */
72     @Override
withBefores(FrameworkMethod method, Object target, Statement statement)73     protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
74         if (mBefores.isEmpty()) {
75             return statement;
76         }
77 
78         final List<FrameworkMethod> befores = new ArrayList<>(mBefores.size());
79         for (FrameworkMethod before : mBefores) {
80             befores.add(wrapMethod(before, target));
81         }
82         return new RunBefores(statement, befores, target);
83     }
84 
85     /**
86      * Constructs the test statement with {@link After}.
87      *
88      * @param method The test method.
89      * @param target The instance of test class.
90      * @param statement The next statement. If there are "before" methods, then it is the
91      *                  before-statement for the next test.
92      */
93     @Override
withAfters(FrameworkMethod method, Object target, Statement statement)94     protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
95         if (mAfters.isEmpty()) {
96             return statement;
97         }
98 
99         final List<FrameworkMethod> afters = new ArrayList<>(mAfters.size());
100         for (FrameworkMethod after : mAfters) {
101             afters.add(wrapMethod(after, target));
102         }
103         return new RunAfters(statement, afters, target);
104     }
105 
wrapMethod(FrameworkMethod method, Object target)106     private FrameworkMethod wrapMethod(FrameworkMethod method, Object target) {
107         for (MethodWrapper wrapper : getMethodWrappers(target)) {
108             method = wrapper.apply(method);
109         }
110         return method;
111     }
112 
getMethodWrappers(Object target)113     private List<MethodWrapper> getMethodWrappers(Object target) {
114         return getTestClass().getAnnotatedFieldValues(
115                 target, MethodWrapperRule.class, MethodWrapper.class);
116     }
117 
118     /**
119      * If a {@link TestRule} is annotated with this, it can ensure the operation of the rule runs
120      * with the test method on the same path and thread.
121      * <p>
122      * The traditional {@link org.junit.Rule} may run on another thread if timeout is set. And if
123      * the rule will hold a lock which will be used in test method, it will cause deadlock such as
124      * "Instr: androidx.test.runner.AndroidJUnitRunner" and "Time-limited test" wait for each other.
125      * <p>
126      * This annotation only takes effect if the test runner is {@link WindowTestRunner}.
127      *
128      * @see org.junit.internal.runners.statements.FailOnTimeout
129      * @see org.junit.runners.BlockJUnit4ClassRunner#methodBlock
130      */
131     @Retention(RetentionPolicy.RUNTIME)
132     @Target({ ElementType.FIELD, ElementType.METHOD })
133     @interface MethodWrapperRule {}
134 
135     /**
136      * The interface to support wrapping test method, including {@link Before} and {@link After}.
137      */
138     interface MethodWrapper extends TestRule {
apply(FrameworkMethod base)139         FrameworkMethod apply(FrameworkMethod base);
140     }
141 }
142