1 /*
2  * Copyright (C) 2020 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 com.android.tradefed.testtype;
17 
18 import com.android.tradefed.device.DeviceNotAvailableException;
19 import com.android.tradefed.device.ITestDevice;
20 import com.android.tradefed.log.LogUtil.CLog;
21 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
22 import com.android.tradefed.result.FailureDescription;
23 import com.android.tradefed.result.ITestInvocationListener;
24 import com.android.tradefed.result.LogcatCrashResultForwarder;
25 import com.android.tradefed.result.TestDescription;
26 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
27 import com.android.tradefed.util.ProcessInfo;
28 
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.LinkedHashSet;
33 import java.util.Set;
34 
35 /**
36  * Internal listener to Trade Federation for {@link InstrumentationTest}. It allows to collect extra
37  * information needed for easier debugging.
38  */
39 final class InstrumentationListener extends LogcatCrashResultForwarder {
40 
41     // Message from ddmlib InstrumentationResultParser for interrupted instrumentation.
42     private static final String DDMLIB_INSTRU_FAILURE_MSG = "Test run failed to complete";
43 
44     private Set<TestDescription> mTests = new HashSet<>();
45     private Set<TestDescription> mDuplicateTests = new HashSet<>();
46     private final Collection<TestDescription> mExpectedTests;
47     private boolean mDisableDuplicateCheck = false;
48     private boolean mReportUnexecutedTests = false;
49     private ProcessInfo mSystemServerProcess = null;
50 
51     /**
52      * @param device
53      * @param listeners
54      */
InstrumentationListener( ITestDevice device, Collection<TestDescription> expectedTests, ITestInvocationListener... listeners)55     public InstrumentationListener(
56             ITestDevice device,
57             Collection<TestDescription> expectedTests,
58             ITestInvocationListener... listeners) {
59         super(device, listeners);
60         mExpectedTests = expectedTests;
61     }
62 
63     /** Whether or not to disable the duplicate test method check. */
setDisableDuplicateCheck(boolean disable)64     public void setDisableDuplicateCheck(boolean disable) {
65         mDisableDuplicateCheck = disable;
66     }
67 
setOriginalSystemServer(ProcessInfo info)68     public void setOriginalSystemServer(ProcessInfo info) {
69         mSystemServerProcess = info;
70     }
71 
setReportUnexecutedTests(boolean enable)72     public void setReportUnexecutedTests(boolean enable) {
73         mReportUnexecutedTests = enable;
74     }
75 
76     @Override
testRunStarted(String runName, int testCount)77     public void testRunStarted(String runName, int testCount) {
78         // In case of crash, run will attempt to report with 0
79         if (testCount == 0 && !mExpectedTests.isEmpty()) {
80             CLog.e("Run reported 0 tests while we collected %s", mExpectedTests.size());
81             super.testRunStarted(runName, mExpectedTests.size());
82         } else {
83             super.testRunStarted(runName, testCount);
84         }
85     }
86 
87     @Override
testStarted(TestDescription test, long startTime)88     public void testStarted(TestDescription test, long startTime) {
89         super.testStarted(test, startTime);
90         if (!mTests.add(test)) {
91             mDuplicateTests.add(test);
92         }
93     }
94 
95     @Override
testRunFailed(FailureDescription error)96     public void testRunFailed(FailureDescription error) {
97         if (error.getErrorMessage().startsWith(DDMLIB_INSTRU_FAILURE_MSG)) {
98             Set<TestDescription> expected = new LinkedHashSet<>(mExpectedTests);
99             expected.removeAll(mTests);
100             String helpMessage = String.format("The following tests didn't run: %s", expected);
101             error.setDebugHelpMessage(helpMessage);
102             error.setFailureStatus(FailureStatus.TEST_FAILURE);
103             if (mSystemServerProcess != null) {
104                 boolean restarted = false;
105                 try {
106                     restarted = getDevice().deviceSoftRestarted(mSystemServerProcess);
107                 } catch (DeviceNotAvailableException e) {
108                     // Ignore
109                 }
110                 if (restarted) {
111                     error.setFailureStatus(FailureStatus.SYSTEM_UNDER_TEST_CRASHED);
112                 }
113             }
114         }
115         super.testRunFailed(error);
116     }
117 
118     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)119     public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
120         if (!mDuplicateTests.isEmpty() && !mDisableDuplicateCheck) {
121             FailureDescription error =
122                     FailureDescription.create(
123                             String.format(
124                                     "The following tests ran more than once: %s. Check "
125                                             + "your run configuration, you might be "
126                                             + "including the same test class several "
127                                             + "times.",
128                                     mDuplicateTests));
129             error.setFailureStatus(FailureStatus.TEST_FAILURE);
130             super.testRunFailed(error);
131         } else if (mReportUnexecutedTests && mExpectedTests.size() > mTests.size()) {
132             Set<TestDescription> missingTests = new LinkedHashSet<>(mExpectedTests);
133             missingTests.removeAll(mTests);
134             for (TestDescription miss : missingTests) {
135                 super.testStarted(miss);
136                 FailureDescription failure =
137                         FailureDescription.create(
138                                 "test did not run due to instrumentation issue. See run level "
139                                         + "error for reason.",
140                                 FailureStatus.NOT_EXECUTED);
141                 super.testFailed(miss, failure);
142                 super.testEnded(miss, new HashMap<String, Metric>());
143             }
144         }
145         super.testRunEnded(elapsedTime, runMetrics);
146     }
147 }
148