Add a listener to communicate with a test.
If EVENT_FILE_ROBOLECTRIC exist, create AtestRunListener to inform atest about test status.
Bug: 124421998
Test: source build/envsetup.sh ; lunch
m atest && atest-dev RunStorageManagerRoboTests RunBluetoothRoboTests
Change-Id: I9b8f9d4df6e1d9b68f642ac1060755db5c6f34d8
diff --git a/libraries/junitxml/src/com/android/junitxml/AtestRunListener.java b/libraries/junitxml/src/com/android/junitxml/AtestRunListener.java
new file mode 100644
index 0000000..67f0dd0
--- /dev/null
+++ b/libraries/junitxml/src/com/android/junitxml/AtestRunListener.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 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.junitxml;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link RunListener} to write JUnit4 test status to File, then atest could use this file to get
+ * real time status}.
+ */
+public class AtestRunListener extends RunListener {
+
+ private static final String CLASSNAME_KEY = "className";
+ private static final String TESTNAME_KEY = "testName";
+ private static final String TRACE_KEY = "trace";
+ private static final String RUNNAME_KEY = "runName";
+ private static final String TESTCOUNT_KEY = "testCount";
+ private static final String ATTEMPT_KEY = "runAttempt";
+ private static final String TIME_KEY = "time";
+ private static final String START_TIME_KEY = "start_time";
+ private static final String END_TIME_KEY = "end_time";
+ private static final String MODULE_NAME_KEY = "moduleName";
+
+ /** Relevant test status keys. */
+ public static class StatusKeys {
+ public static final String TEST_ENDED = "TEST_ENDED";
+ public static final String TEST_FAILED = "TEST_FAILED";
+ public static final String TEST_IGNORED = "TEST_IGNORED";
+ public static final String TEST_STARTED = "TEST_STARTED";
+ public static final String TEST_RUN_ENDED = "TEST_RUN_ENDED";
+ public static final String TEST_RUN_STARTED = "TEST_RUN_STARTED";
+ public static final String TEST_MODULE_STARTED = "TEST_MODULE_STARTED";
+ public static final String TEST_MODULE_ENDED = "TEST_MODULE_ENDED";
+ }
+
+ private final int mTotalCount;
+
+ // the file where to log the events.
+ private final File mReportFile;
+
+ private final String mSuiteName;
+
+ public AtestRunListener(String suiteName, File reportFile, int totalCount) {
+ mSuiteName = suiteName;
+ mReportFile = reportFile;
+ mTotalCount = totalCount;
+ }
+
+ @Override
+ public void testRunStarted(Description description) {
+ Map<String, Object> moduleStartEventData = new HashMap<>();
+ moduleStartEventData.put(MODULE_NAME_KEY, mSuiteName);
+ printEvent(StatusKeys.TEST_MODULE_STARTED, moduleStartEventData);
+
+ Map<String, Object> testRunEventData = new HashMap<>();
+ testRunEventData.put(TESTCOUNT_KEY, mTotalCount);
+ testRunEventData.put(ATTEMPT_KEY, 0);
+ testRunEventData.put(RUNNAME_KEY, mSuiteName);
+ printEvent(StatusKeys.TEST_RUN_STARTED, testRunEventData);
+ }
+
+ @Override
+ public void testRunFinished(Result result) {
+ Map<String, Object> eventData = new HashMap<>();
+ eventData.put(TIME_KEY, result.getRunTime());
+ printEvent(StatusKeys.TEST_RUN_ENDED, eventData);
+ printEvent(StatusKeys.TEST_MODULE_ENDED, new HashMap<>());
+ }
+
+ @Override
+ public void testFailure(Failure failure) {
+ Description description = failure.getDescription();
+ Map<String, Object> eventData = new HashMap<>();
+ eventData.put(CLASSNAME_KEY, description.getClassName());
+ eventData.put(TESTNAME_KEY, description.getMethodName());
+ eventData.put(TRACE_KEY, failure.getTrace());
+ printEvent(StatusKeys.TEST_FAILED, eventData);
+ }
+
+ @Override
+ public void testStarted(Description description) {
+ Map<String, Object> eventData = new HashMap<>();
+ eventData.put(START_TIME_KEY, System.currentTimeMillis());
+ eventData.put(CLASSNAME_KEY, description.getClassName());
+ eventData.put(TESTNAME_KEY, description.getMethodName());
+ printEvent(StatusKeys.TEST_STARTED, eventData);
+ }
+
+ @Override
+ public void testFinished(Description description) {
+ Map<String, Object> eventData = new HashMap<>();
+ eventData.put(END_TIME_KEY, System.currentTimeMillis());
+ eventData.put(CLASSNAME_KEY, description.getClassName());
+ eventData.put(TESTNAME_KEY, description.getMethodName());
+ printEvent(StatusKeys.TEST_ENDED, eventData);
+ }
+
+ @Override
+ public void testIgnored(Description description) {
+ Map<String, Object> eventData = new HashMap<>();
+ eventData.put(TESTNAME_KEY, description.getMethodName());
+ eventData.put(CLASSNAME_KEY, description.getClassName());
+ eventData.put(START_TIME_KEY, System.currentTimeMillis());
+ eventData.put(END_TIME_KEY, System.currentTimeMillis());
+ printEvent(StatusKeys.TEST_STARTED, eventData);
+ printEvent(StatusKeys.TEST_IGNORED, eventData);
+ printEvent(StatusKeys.TEST_ENDED, eventData);
+ }
+
+ private void printEvent(String key, Map<String, Object> event) {
+ if (mReportFile.canWrite()) {
+ try {
+ try (FileWriter fw = new FileWriter(mReportFile, true)) {
+ String eventLog = String.format("%s %s\n\n", key, toJson(event));
+ fw.append(eventLog);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new RuntimeException(
+ String.format(
+ "report file: %s is not writable", mReportFile.getAbsolutePath()));
+ }
+ }
+
+ private String toJson(Map<String, Object> data) {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, Object> entry : data.entrySet()) {
+ sb.append(",");
+ sb.append("\"").append(entry.getKey()).append("\":");
+ if (entry.getValue() instanceof Number) {
+ sb.append(entry.getValue().toString());
+ } else {
+ sb.append("\"").append(entry.getValue()).append("\"");
+ }
+ }
+ sb.replace(0, 1, "{").append("}");
+ return sb.toString();
+ }
+}
diff --git a/libraries/junitxml/src/com/android/junitxml/JUnitXmlRunner.java b/libraries/junitxml/src/com/android/junitxml/JUnitXmlRunner.java
index 82f1c1f..70f602d 100644
--- a/libraries/junitxml/src/com/android/junitxml/JUnitXmlRunner.java
+++ b/libraries/junitxml/src/com/android/junitxml/JUnitXmlRunner.java
@@ -16,13 +16,16 @@
package com.android.junitxml;
+import org.junit.Test;
import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.util.stream.Stream;
/**
@@ -54,24 +57,45 @@
return null;
}
+ private static AtestRunListener getAtestRunListener(int count) {
+ String outputFileStr = System.getenv("EVENT_FILE_ROBOLECTRIC");
+ String suiteName = System.getenv("TEST_WORKSPACE");
+ if (outputFileStr != null && outputFileStr.length() > 0) {
+ File outputFile = new File(outputFileStr);
+ if (outputFile.exists()) {
+ return new AtestRunListener(suiteName, outputFile, count);
+ }
+ }
+ return null;
+ }
+
public static void main(String... args) {
JUnitCore core = new JUnitCore();
try {
+ Class[] as =
+ Stream.of(args)
+ .map(
+ test -> {
+ try {
+ return Class.forName(test);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .toArray(Class[]::new);
TextListener textListener = new TextListener(System.out);
core.addListener(textListener);
XmlRunListener xmlListener = getRunListener();
if (xmlListener != null) {
core.addListener(xmlListener);
}
- Result result = core.run(Stream.of(args)
- .map(test -> {
- try {
- return Class.forName(test);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
- }
- })
- .toArray(Class[]::new));
+
+ // Add AtestRunListener to communicate with ATest.
+ AtestRunListener atestRunListener = getAtestRunListener(calcTestCount(as));
+ if (atestRunListener != null) {
+ core.addListener(atestRunListener);
+ }
+ Result result = core.run(as);
if (xmlListener != null) {
xmlListener.endTestSuite();
}
@@ -80,5 +104,18 @@
throw new RuntimeException(e);
}
}
+
+ private static int calcTestCount(Class[] as) {
+ int count = 0;
+ for (Class cls : as) {
+ Method[] declaredMethods = cls.getMethods();
+ for (Method method : declaredMethods) {
+ if (method.isAnnotationPresent(Test.class)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
}