Merge "Notify clearcut when an invocation is started"
diff --git a/common_util/com/android/tradefed/result/LogDataType.java b/common_util/com/android/tradefed/result/LogDataType.java
index 9547659..da1eb9e 100644
--- a/common_util/com/android/tradefed/result/LogDataType.java
+++ b/common_util/com/android/tradefed/result/LogDataType.java
@@ -38,6 +38,7 @@
CLANG_COVERAGE("profdata", "text/plain", false, false), // LLVM indexed profile data
PB("pb", "application/octet-stream", true, false), // Binary proto file
TEXTPB("textproto", "text/plain", false, true), // Text proto file
+ PERFETTO("pb", "application/octet-stream", true, false), // binary proto perfetto trace file
/* Specific text file types */
BUGREPORT("txt", "text/plain", false, true),
BUGREPORTZ("zip", "application/zip", true, false),
diff --git a/src/com/android/tradefed/device/DeviceManager.java b/src/com/android/tradefed/device/DeviceManager.java
index 7e08c4f..a653c1e 100644
--- a/src/com/android/tradefed/device/DeviceManager.java
+++ b/src/com/android/tradefed/device/DeviceManager.java
@@ -916,7 +916,7 @@
* @return std output if the command succeedm null otherwise.
*/
public String executeGlobalAdbCommand(String... cmdArgs) {
- String[] fullCmd = ArrayUtil.buildArray(new String[] {"adb"}, cmdArgs);
+ String[] fullCmd = ArrayUtil.buildArray(new String[] {getAdbPath()}, cmdArgs);
CommandResult result = getRunUtil().runTimedCmd(FASTBOOT_CMD_TIMEOUT, fullCmd);
if (CommandStatus.SUCCESS.equals(result.getStatus())) {
return result.getStdout();
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 3e4ea4e..3e6a77d 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -474,18 +474,15 @@
if (gceAvd == null || gceAvd.hostAndPort() == null) {
return null;
}
- String output = "";
- // Retry a couple of time because adb might not be started for that user.
- // FIXME: See if we can use vsoc-01 directly to avoid this
- for (int i = 0; i < 3; i++) {
- output =
- remoteSshCommandExec(
- gceAvd, options, runUtil, "./bin/adb", "shell", "bugreportz");
- Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
- if (match.find()) {
- break;
- }
- }
+ String output =
+ remoteSshCommandExec(
+ gceAvd,
+ options,
+ runUtil,
+ "./bin/adb",
+ "wait-for-device",
+ "shell",
+ "bugreportz");
Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
if (!match.find()) {
CLog.e("Something went wrong during bugreportz collection: '%s'", output);
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index fdd0a4b..0efc7a4 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -26,6 +26,7 @@
import com.android.tradefed.config.DynamicRemoteFileResolver;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.device.DeviceManager;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.FreeDeviceState;
@@ -1059,6 +1060,12 @@
}
if (countPhysicalLost > 0) {
addInvocationMetric(InvocationMetricKey.DEVICE_LOST_DETECTED, countPhysicalLost);
+ if (GlobalConfiguration.getDeviceManagerInstance() instanceof DeviceManager) {
+ String adbOutput =
+ ((DeviceManager) GlobalConfiguration.getDeviceManagerInstance())
+ .executeGlobalAdbCommand("devices");
+ CLog.e("'adb devices' output:\n%s", adbOutput);
+ }
}
return devicesStates;
}
diff --git a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
index 1c68f50..257e9e5 100644
--- a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
@@ -278,7 +278,7 @@
}
} else {
- testLog(metricFile.getName(), LogDataType.PB, source);
+ testLog(metricFile.getName(), LogDataType.PERFETTO, source);
}
}
diff --git a/test_framework/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java
index 444a278..41c5dac 100644
--- a/test_framework/com/android/tradefed/testtype/GTest.java
+++ b/test_framework/com/android/tradefed/testtype/GTest.java
@@ -428,6 +428,7 @@
// Insert the coverage listener if code coverage collection is enabled.
listener = addNativeCoverageListenerIfEnabled(listener);
listener = addClangCoverageListenerIfEnabled(listener);
+ listener = getGTestListener(listener);
NativeCodeCoverageFlusher flusher =
new NativeCodeCoverageFlusher(mDevice, getCoverageOptions().getCoverageProcesses());
diff --git a/test_framework/com/android/tradefed/testtype/GTestBase.java b/test_framework/com/android/tradefed/testtype/GTestBase.java
index 194199c..3ee7179 100644
--- a/test_framework/com/android/tradefed/testtype/GTestBase.java
+++ b/test_framework/com/android/tradefed/testtype/GTestBase.java
@@ -163,6 +163,11 @@
+ "the same name as the binary with the .json extension.")
private String mTestFilterKey = null;
+ @Option(
+ name = "disable-duplicate-test-check",
+ description = "If set to true, it will not check that a method is only run once.")
+ private boolean mDisableDuplicateCheck = false;
+
// GTest flags...
protected static final String GTEST_FLAG_PRINT_TIME = "--gtest_print_time";
protected static final String GTEST_FLAG_FILTER = "--gtest_filter";
@@ -673,4 +678,17 @@
}
return new CoverageOptions();
}
+
+ /**
+ * Returns the {@link GTestListener} that provides extra debugging info, like detects and
+ * reports duplicate tests if mDisabledDuplicateCheck is false. Otherwise, returns the passed-in
+ * listener.
+ */
+ protected ITestInvocationListener getGTestListener(ITestInvocationListener listener) {
+ if (mDisableDuplicateCheck) {
+ return listener;
+ }
+
+ return new GTestListener(listener);
+ }
}
diff --git a/test_framework/com/android/tradefed/testtype/GTestListener.java b/test_framework/com/android/tradefed/testtype/GTestListener.java
new file mode 100644
index 0000000..c967d06
--- /dev/null
+++ b/test_framework/com/android/tradefed/testtype/GTestListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.testtype;
+
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Internal listener to Trade Federation for {@link GTest}. Detect and report if duplciated tests
+ * are run.
+ */
+final class GTestListener extends ResultForwarder {
+
+ private Set<TestDescription> mTests = new HashSet<>();
+ private Set<TestDescription> mDuplicateTests = new HashSet<>();
+
+ public GTestListener(ITestInvocationListener... listeners) {
+ super(listeners);
+ }
+
+ @Override
+ public void testStarted(TestDescription test, long startTime) {
+ super.testStarted(test, startTime);
+ if (!mTests.add(test)) {
+ mDuplicateTests.add(test);
+ }
+ }
+
+ @Override
+ public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
+ if (!mDuplicateTests.isEmpty()) {
+ FailureDescription error =
+ FailureDescription.create(
+ String.format(
+ "The following tests ran more than once: %s.",
+ mDuplicateTests));
+ error.setFailureStatus(FailureStatus.TEST_FAILURE);
+ super.testRunFailed(error);
+ }
+ super.testRunEnded(elapsedTime, runMetrics);
+ }
+}
diff --git a/test_framework/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java
index c4958b7..43d599e 100644
--- a/test_framework/com/android/tradefed/testtype/HostGTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostGTest.java
@@ -246,6 +246,7 @@
String.format("%s is not executable!", gTestFile.getAbsolutePath()));
}
+ listener = getGTestListener(listener);
// TODO: Need to support XML test output based on isEnableXmlOutput
IShellOutputReceiver resultParser = createResultParser(gTestFile.getName(), listener);
String flags = getAllGTestFlags(gTestFile.getName());
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index ad894a9..ed1ba81 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -257,6 +257,7 @@
import com.android.tradefed.testtype.DeviceTestSuiteTest;
import com.android.tradefed.testtype.FakeTestTest;
import com.android.tradefed.testtype.GTestListTestParserTest;
+import com.android.tradefed.testtype.GTestListenerTest;
import com.android.tradefed.testtype.GTestResultParserTest;
import com.android.tradefed.testtype.GTestTest;
import com.android.tradefed.testtype.GTestXmlResultParserTest;
@@ -728,6 +729,7 @@
GoogleBenchmarkResultParserTest.class,
GoogleBenchmarkTestTest.class,
GTestListTestParserTest.class,
+ GTestListenerTest.class,
GTestResultParserTest.class,
GTestTest.class,
GTestXmlResultParserTest.class,
diff --git a/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java b/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java
index 5cf6f64..f3bdced 100644
--- a/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java
@@ -756,6 +756,7 @@
EasyMock.anyObject(),
EasyMock.eq("root@127.0.0.1"),
EasyMock.eq("./bin/adb"),
+ EasyMock.eq("wait-for-device"),
EasyMock.eq("shell"),
EasyMock.eq("bugreportz")))
.andReturn(res);
diff --git a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
index cca825c..24418a6 100644
--- a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
@@ -113,7 +113,7 @@
Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
Mockito.any(), Mockito.any(), Mockito.any());
Mockito.verify(mMockListener)
- .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PB), Mockito.any());
+ .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PERFETTO), Mockito.any());
assertTrue("Expected two metrics that includes success status",
currentMetrics.get("perfetto_trace_extractor_status").getMeasurements()
.getSingleString().equals("1"));
@@ -184,7 +184,7 @@
Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
Mockito.any(), Mockito.any(), Mockito.any());
Mockito.verify(mMockListener)
- .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PB), Mockito.any());
+ .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PERFETTO), Mockito.any());
assertTrue("Expected two metrics that includes failure status",
currentMetrics.get("perfetto_trace_extractor_status").getMeasurements()
.getSingleString().equals("0"));
diff --git a/tests/src/com/android/tradefed/testtype/GTestListenerTest.java b/tests/src/com/android/tradefed/testtype/GTestListenerTest.java
new file mode 100644
index 0000000..392ee75
--- /dev/null
+++ b/tests/src/com/android/tradefed/testtype/GTestListenerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.testtype;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.HashMap;
+
+/** Unit tests for {@link GTestListener}. */
+@RunWith(JUnit4.class)
+public class GTestListenerTest {
+ private ITestInvocationListener mMockListener = null;
+
+ /** Helper to initialize the various EasyMocks we'll need. */
+ @Before
+ public void setUp() throws Exception {
+ mMockListener = EasyMock.createMock(ITestInvocationListener.class);
+ }
+
+ /** Helper that replays all mocks. */
+ private void replayMocks() {
+ EasyMock.replay(mMockListener);
+ }
+
+ /** Helper that verifies all mocks. */
+ private void verifyMocks() {
+ EasyMock.verify(mMockListener);
+ }
+
+ /** Verify test passes without duplicate tests */
+ @Test
+ public void testNoDuplicateTests() throws DeviceNotAvailableException {
+ String moduleName = "testNoDuplicateTest";
+ String testClass = "testClass";
+ String testName1 = "testName1";
+ TestDescription testId1 = new TestDescription(testClass, testName1);
+ String testName2 = "testName2";
+ TestDescription testId2 = new TestDescription(testClass, testName2);
+
+ mMockListener.testRunStarted(EasyMock.eq(moduleName), EasyMock.eq(2));
+ mMockListener.testStarted(EasyMock.eq(testId1), EasyMock.anyLong());
+ mMockListener.testEnded(
+ EasyMock.eq(testId1),
+ EasyMock.anyLong(),
+ (HashMap<String, Metric>) EasyMock.anyObject());
+ mMockListener.testStarted(EasyMock.eq(testId2), EasyMock.anyLong());
+ mMockListener.testEnded(
+ EasyMock.eq(testId2),
+ EasyMock.anyLong(),
+ (HashMap<String, Metric>) EasyMock.anyObject());
+ mMockListener.testRunEnded(
+ EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
+ replayMocks();
+
+ HashMap<String, Metric> emptyMap = new HashMap<>();
+ GTestListener listener = new GTestListener(mMockListener);
+ listener.testRunStarted(moduleName, 2);
+ listener.testStarted(testId1);
+ listener.testEnded(testId1, emptyMap);
+ listener.testStarted(testId2);
+ listener.testEnded(testId2, emptyMap);
+ listener.testRunEnded(0, emptyMap);
+ verifyMocks();
+ }
+
+ /** Verify test with duplicate tests fails if option enabled */
+ @Test
+ public void testDuplicateTestsFailsWithOptionEnabled() throws DeviceNotAvailableException {
+ String moduleName = "testWithDuplicateTests";
+ String testClass = "testClass";
+ String testName1 = "testName1";
+ String duplicateTestsMessage = "The following tests ran more than once: ";
+ TestDescription testId1 = new TestDescription(testClass, testName1);
+
+ mMockListener.testRunStarted(EasyMock.eq(moduleName), EasyMock.eq(2));
+ mMockListener.testStarted(EasyMock.eq(testId1), EasyMock.anyLong());
+ mMockListener.testEnded(
+ EasyMock.eq(testId1),
+ EasyMock.anyLong(),
+ (HashMap<String, Metric>) EasyMock.anyObject());
+ mMockListener.testStarted(EasyMock.eq(testId1), EasyMock.anyLong());
+ mMockListener.testEnded(
+ EasyMock.eq(testId1),
+ EasyMock.anyLong(),
+ (HashMap<String, Metric>) EasyMock.anyObject());
+ Capture<FailureDescription> capturedFailureDescription = new Capture<>();
+ mMockListener.testRunFailed(EasyMock.capture(capturedFailureDescription));
+ mMockListener.testRunEnded(
+ EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
+ replayMocks();
+
+ HashMap<String, Metric> emptyMap = new HashMap<>();
+ GTestListener listener = new GTestListener(mMockListener);
+ listener.testRunStarted(moduleName, 2);
+ listener.testStarted(testId1);
+ listener.testEnded(testId1, emptyMap);
+ listener.testStarted(testId1);
+ listener.testEnded(testId1, emptyMap);
+ listener.testRunEnded(0, emptyMap);
+ FailureDescription failureDescription = capturedFailureDescription.getValue();
+ assertTrue(failureDescription.getErrorMessage().contains(duplicateTestsMessage));
+ assertTrue(FailureStatus.TEST_FAILURE.equals(failureDescription.getFailureStatus()));
+ verifyMocks();
+ }
+}