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