• 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 
17 package android.platform.test.longevity;
18 
19 import static java.lang.Math.max;
20 
21 import android.os.Bundle;
22 import android.os.SystemClock;
23 import android.platform.test.longevity.proto.Configuration.Scenario;
24 import android.platform.test.longevity.proto.Configuration.Scenario.ExtraArg;
25 import androidx.annotation.VisibleForTesting;
26 import androidx.test.InstrumentationRegistry;
27 
28 import java.util.List;
29 
30 import org.junit.rules.TestRule;
31 import org.junit.rules.Timeout;
32 import org.junit.runner.notification.RunNotifier;
33 import org.junit.runners.BlockJUnit4ClassRunner;
34 import org.junit.runners.model.FrameworkMethod;
35 import org.junit.runners.model.InitializationError;
36 import org.junit.runners.model.Statement;
37 
38 /**
39  * A {@link BlockJUnit4ClassRunner} that runs a test class with a specified timeout and optionally
40  * performs an idle before teardown (staying inside the app for Android CUJs).
41  */
42 public class ScheduledScenarioRunner extends LongevityClassRunner {
43     @VisibleForTesting static final long ENDTIME_LEEWAY_MS = 3000;
44 
45     private final Scenario mScenario;
46     private final long mTotalTimeoutMs;
47     private final boolean mShouldIdle;
48     private final Bundle mArguments;
49 
50     private long mStartTimeMs;
51 
ScheduledScenarioRunner( Class<?> klass, Scenario scenario, long timeout, boolean shouldIdle)52     public ScheduledScenarioRunner(
53             Class<?> klass, Scenario scenario, long timeout, boolean shouldIdle)
54             throws InitializationError {
55         this(klass, scenario, timeout, shouldIdle, InstrumentationRegistry.getArguments());
56     }
57 
58     @VisibleForTesting
ScheduledScenarioRunner( Class<?> klass, Scenario scenario, long timeout, boolean shouldIdle, Bundle arguments)59     ScheduledScenarioRunner(
60             Class<?> klass, Scenario scenario, long timeout, boolean shouldIdle, Bundle arguments)
61             throws InitializationError {
62         super(klass, arguments);
63         mScenario = scenario;
64         // Ensure that the timeout is non-negative.
65         mTotalTimeoutMs = max(timeout, 0);
66         mShouldIdle = shouldIdle;
67         mArguments = arguments;
68     }
69 
70     @Override
getTestRules(Object target)71     protected List<TestRule> getTestRules(Object target) {
72         List<TestRule> rules = super.getTestRules(target);
73         // Ensure that the timeout rule has a non-negative timeout.
74         rules.add(0, Timeout.millis(max(mTotalTimeoutMs - ENDTIME_LEEWAY_MS, 0)));
75         return rules;
76     }
77 
78     @Override
withAfters(FrameworkMethod method, Object target, Statement statement)79     protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
80         Statement withIdle =
81                 new Statement() {
82                     @Override
83                     public void evaluate() throws Throwable {
84                         try {
85                             // Run the underlying test and report exceptions.
86                             statement.evaluate();
87                         } finally {
88                             // If there is time left for idling (i.e. more than ENDTIME_LEEWAY_MS),
89                             // and the scenario is set to stay in app, idle for the remainder of
90                             // its timeout window until ENDTIME_LEEWAY_MS before the start time of
91                             // the next scenario, before executing the scenario's @After methods.
92                             // The above does not apply if current scenario is the last one, in
93                             // which case the idle is never performed regardless of its after_test
94                             // policy.
95                             if (mShouldIdle
96                                     && mScenario
97                                             .getAfterTest()
98                                             .equals(Scenario.AfterTest.STAY_IN_APP)) {
99                                 performIdleBeforeTeardown(
100                                         max(getTimeRemaining() - ENDTIME_LEEWAY_MS, 0));
101                             }
102                         }
103                     }
104                 };
105         return super.withAfters(method, target, withIdle);
106     }
107 
108     @Override
runChild(final FrameworkMethod method, RunNotifier notifier)109     protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
110         mStartTimeMs = System.currentTimeMillis();
111         // Keep a copy of the bundle arguments for restoring later.
112         Bundle modifiedArguments = mArguments.deepCopy();
113         for (ExtraArg argPair : mScenario.getExtrasList()) {
114             if (argPair.getKey() == null || argPair.getValue() == null) {
115                 throw new IllegalArgumentException(
116                         String.format(
117                                 "Each extra arg entry in scenario must have both a key and a value,"
118                                         + " but scenario is %s.",
119                                 mScenario.toString()));
120             }
121             modifiedArguments.putString(argPair.getKey(), argPair.getValue());
122         }
123         InstrumentationRegistry.registerInstance(
124                 InstrumentationRegistry.getInstrumentation(), modifiedArguments);
125         super.runChild(method, notifier);
126         // Restore the arguments to the state prior to the scenario.
127         InstrumentationRegistry.registerInstance(
128                 InstrumentationRegistry.getInstrumentation(), mArguments);
129         // If there are remaining scenarios, idle until the next one starts.
130         if (mShouldIdle) {
131             performIdleBeforeNextScenario(getTimeRemaining());
132         }
133     }
134 
135     /**
136      * Get the duration to idle after the current scenario. If the current scenario is the last one
137      * in the profile, returns 0.
138      */
getTimeRemaining()139     private long getTimeRemaining() {
140         // The idle time is total time minus time elapsed since the current scenario started.
141         return max(mTotalTimeoutMs - (System.currentTimeMillis() - mStartTimeMs), 0);
142     }
143 
144     @VisibleForTesting
performIdleBeforeTeardown(long durationMs)145     protected void performIdleBeforeTeardown(long durationMs) {
146         idleWithSystemClockSleep(durationMs);
147     }
148 
149     @VisibleForTesting
performIdleBeforeNextScenario(long durationMs)150     protected void performIdleBeforeNextScenario(long durationMs) {
151         // TODO (b/119386011): Change this idle method to using a sleep test; for now, using the
152         // same idling logic as {@link performIdleBeforeTeardown}.
153         idleWithSystemClockSleep(durationMs);
154     }
155 
idleWithSystemClockSleep(long durationMs)156     private void idleWithSystemClockSleep(long durationMs) {
157         if (durationMs <= 0) {
158             return;
159         }
160         SystemClock.sleep(durationMs);
161     }
162 }
163