xref: /aosp_15_r20/cts/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsVerifierTestActivity.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2 
3  * Copyright (C) 2014 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.cts.verifier.sensors.base;
19 
20 import android.hardware.cts.helpers.reporting.ISensorTestNode;
21 
22 import com.android.cts.verifier.sensors.reporting.SensorTestDetails;
23 import com.android.cts.verifier.sensors.reporting.SensorTestDetails.ResultCode;
24 
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.List;
31 
32 /**
33  * An Activity that provides a test execution engine for Sensor CtsVerifier tests. The tests are
34  * able to interact with an operator.
35  *
36  * Sub-classes reuse its own class definition to 'load' tests at runtime through reflection.
37  */
38 public abstract class SensorCtsVerifierTestActivity extends BaseSensorTestActivity {
39     private volatile int mTestPassedCounter;
40     private volatile int mTestSkippedCounter;
41     private volatile int mTestFailedCounter;
42     private volatile ISensorTestNode mCurrentTestNode;
43     private volatile boolean mEnableRetry = false;
44 
45     /**
46      * {@inheritDoc}
47      */
SensorCtsVerifierTestActivity( Class<? extends SensorCtsVerifierTestActivity> testClass)48     protected SensorCtsVerifierTestActivity(
49             Class<? extends SensorCtsVerifierTestActivity> testClass) {
50         super(testClass);
51     }
52 
53     /**
54      * {@inheritDoc}
55      * Constructor to be used by subclasses.
56      *
57      * @param testClass   The class that contains the tests. It is dependant on test executor
58      *                    implemented by subclasses.
59      * @param enableRetry Subclass can enable retry mechanism for subtests.
60      */
SensorCtsVerifierTestActivity( Class<? extends SensorCtsVerifierTestActivity> testClass, boolean enableRetry)61     protected SensorCtsVerifierTestActivity(
62         Class<? extends SensorCtsVerifierTestActivity> testClass, boolean enableRetry) {
63         super(testClass);
64         mEnableRetry = enableRetry;
65     }
66 
67     /**
68      * Executes Semi-automated Sensor tests.
69      * Execution is driven by this class, and allows discovery of tests using reflection.
70      */
71     @Override
executeTests()72     protected SensorTestDetails executeTests() throws InterruptedException {
73         // TODO: use reporting to log individual test results
74         Iterator<Method> testMethodIt = findTestMethods().iterator();
75         while (testMethodIt.hasNext()) {
76             Method testMethod = testMethodIt.next();
77             boolean isLastSubtest = !testMethodIt.hasNext();
78             getTestLogger().logTestStart(testMethod.getName());
79             SensorTestDetails testDetails = executeTest(testMethod);
80             getTestLogger().logTestDetails(testDetails);
81 
82             // If tests enable retry and get failed result, trigger the retry process.
83             while (mEnableRetry && testDetails.getResultCode().equals(ResultCode.FAIL)) {
84                 if (isLastSubtest) {
85                     waitForUserToFinish();
86                 } else {
87                     waitForUserToRetry();
88                 }
89                 if (!getShouldRetry()) {
90                     break;
91                 }
92                 mTestFailedCounter--;
93                 testDetails = executeTest(testMethod);
94                 getTestLogger().logTestDetails(testDetails);
95             }
96         }
97         return new SensorTestDetails(
98                 getApplicationContext(),
99                 getTestClassName(),
100                 mTestPassedCounter,
101                 mTestSkippedCounter,
102                 mTestFailedCounter);
103     }
104 
getCurrentTestNode()105     protected ISensorTestNode getCurrentTestNode() {
106         return mCurrentTestNode;
107     }
108 
findTestMethods()109     private List<Method> findTestMethods() {
110         ArrayList<Method> testMethods = new ArrayList<>();
111         for (Method method : mTestClass.getDeclaredMethods()) {
112             if (Modifier.isPublic(method.getModifiers())
113                     && method.getParameterTypes().length == 0
114                     && method.getName().startsWith("test")
115                     && method.getReturnType().equals(String.class)) {
116                 testMethods.add(method);
117             }
118         }
119         return testMethods;
120     }
121 
executeTest(Method testMethod)122     private SensorTestDetails executeTest(Method testMethod) throws InterruptedException {
123         String testMethodName = testMethod.getName();
124         String testName = String.format("%s#%s", getTestClassName(), testMethodName);
125         mCurrentTestNode = new TestNode(testMethod);
126 
127         SensorTestDetails testDetails;
128         try {
129             String testSummary = (String) testMethod.invoke(this);
130             testDetails =
131                     new SensorTestDetails(testName, SensorTestDetails.ResultCode.PASS, testSummary);
132         } catch (InvocationTargetException e) {
133             // get the inner exception, because we use reflection APIs to execute the test
134             testDetails = new SensorTestDetails(testName, "TestExecution", e.getCause());
135         } catch (Throwable e) {
136             testDetails = new SensorTestDetails(testName, "TestInfrastructure", e);
137         }
138 
139         SensorTestDetails.ResultCode resultCode = testDetails.getResultCode();
140         switch(resultCode) {
141             case PASS:
142             // Warning should still be treated as a pass, but with more detail to the test runner.
143             case WARNING:
144                 ++mTestPassedCounter;
145                 break;
146             case SKIPPED:
147                 ++mTestSkippedCounter;
148                 break;
149             case INTERRUPTED:
150                 throw new InterruptedException();
151             case FAIL:
152                 ++mTestFailedCounter;
153                 break;
154             default:
155                 throw new IllegalStateException("Unknown ResultCode: " + resultCode);
156         }
157 
158         return testDetails;
159     }
160 
161     private class TestNode implements ISensorTestNode {
162         private final Method mTestMethod;
163 
TestNode(Method testMethod)164         public TestNode(Method testMethod) {
165             mTestMethod = testMethod;
166         }
167 
168         @Override
getName()169         public String getName() {
170             return mTestClass.getSimpleName() + "_" + mTestMethod.getName();
171         }
172     }
173 
174     /**
175      * Show the instruction for the first time execution and wait for user to begin the test.
176      *
177      * @param descriptionResId The description for the first time execution.
178      */
setFirstExecutionInstruction(int ... descriptionResId)179     protected void setFirstExecutionInstruction(int ... descriptionResId) throws Throwable {
180         if (!getShouldRetry()) {
181             SensorTestLogger logger = getTestLogger();
182             for (int id : descriptionResId) {
183                 logger.logInstructions(id);
184             }
185             waitForUserToBegin();
186         }
187     }
188 }
189