Merge "Address ddms logger in verbose mode"
diff --git a/src/com/android/tradefed/device/INativeDevice.java b/src/com/android/tradefed/device/INativeDevice.java
index 79c6f8c..99a1b04 100644
--- a/src/com/android/tradefed/device/INativeDevice.java
+++ b/src/com/android/tradefed/device/INativeDevice.java
@@ -1313,4 +1313,16 @@
      * returned by {@link System#currentTimeMillis()}.
      */
     public long getLastExpectedRebootTimeMillis();
+
+    /**
+     * Fetch and return the list of tombstones from the devices. Requires root.
+     *
+     * <p>method is best-effort so if one tombstone fails to be pulled for any reason it will be
+     * missing from the list. Only a {@link DeviceNotAvailableException} will terminate the method
+     * early.
+     *
+     * @return A list of tombstone files, empty if no tombstone.
+     * @see <a href="https://source.android.com/devices/tech/debug">Tombstones documentation</a>
+     */
+    public List<File> getTombstones() throws DeviceNotAvailableException;
 }
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 5cd16be..92c2c83 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -183,6 +183,9 @@
     /** Pattern to find an executable file. */
     private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+");
 
+    /** Path of the device containing the tombstones */
+    private static final String TOMBSTONE_PATH = "/data/tombstones/";
+
     /** The time in ms to wait for a command to complete. */
     private long mCmdTimeout = 2 * 60 * 1000L;
     /** The time in ms to wait for a 'long' command to complete. */
@@ -4364,6 +4367,23 @@
         return mLastTradefedRebootTime;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public List<File> getTombstones() throws DeviceNotAvailableException {
+        List<File> tombstones = new ArrayList<>();
+        if (!isAdbRoot()) {
+            CLog.w("Device was not root, cannot collect tombstones.");
+            return tombstones;
+        }
+        for (String tombName : getChildren(TOMBSTONE_PATH)) {
+            File tombFile = pullFile(TOMBSTONE_PATH + tombName);
+            if (tombFile != null) {
+                tombstones.add(tombFile);
+            }
+        }
+        return tombstones;
+    }
+
     /** Validate that pid is an integer and not empty. */
     private boolean checkValidPid(String output) {
         if (output.isEmpty()) {
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index 3363053..8f5ecd6 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -251,7 +251,6 @@
         if (!anyDescription.is(Context.class)) {
             throw new RuntimeException("Expected Any description of type Context");
         }
-        log("Test module started proto");
         try {
             IInvocationContext moduleContext =
                     InvocationContext.fromProto(anyDescription.unpack(Context.class));
diff --git a/src/com/android/tradefed/sandbox/SandboxConfigUtil.java b/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
index 3caa7b1..34d3e30 100644
--- a/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
+++ b/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
@@ -87,7 +87,9 @@
         }
         CommandResult result = runUtil.runTimedCmd(DUMP_TIMEOUT, mCmdArgs.toArray(new String[0]));
         CLog.d("stdout: %s", result.getStdout());
-        CLog.d("stderr: %s", result.getStderr());
+        if (result.getStderr() != null && !result.getStderr().isEmpty()) {
+            CLog.d("stderr: %s", result.getStderr());
+        }
         if (CommandStatus.SUCCESS.equals(result.getStatus())) {
             return destination;
         }
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index dfa544c..92fe045 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -297,7 +297,8 @@
                 mEventParser = new SubprocessTestResultsParser(listener, true, context);
                 commandLine = commandLine + " --no-" + SandboxOptions.USE_PROTO_REPORTER;
             }
-            String[] args = QuotationAwareTokenizer.tokenizeLine(commandLine);
+            String[] args =
+                    QuotationAwareTokenizer.tokenizeLine(commandLine, /* No Logging */ false);
             mGlobalConfig = dumpGlobalConfig(config, new HashSet<>());
             try (InputStreamSource source = new FileInputStreamSource(mGlobalConfig)) {
                 listener.testLog("sandbox-global-config", LogDataType.XML, source);
diff --git a/src/com/android/tradefed/testtype/DeviceTestCase.java b/src/com/android/tradefed/testtype/DeviceTestCase.java
index 4914a91..16de6d0 100644
--- a/src/com/android/tradefed/testtype/DeviceTestCase.java
+++ b/src/com/android/tradefed/testtype/DeviceTestCase.java
@@ -15,7 +15,6 @@
  */
 package com.android.tradefed.testtype;
 
-import com.android.ddmlib.Log;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -49,7 +48,6 @@
                 ITestFilterReceiver,
                 ITestAnnotationFilterReceiver {
 
-    private static final String LOG_TAG = "DeviceTestCase";
     private ITestDevice mDevice;
     private TestFilterHelper mFilterHelper;
 
@@ -349,9 +347,6 @@
                 }
                 superClass = superClass.getSuperclass();
             }
-            if (mMethodNames.size() == 0) {
-                Log.w(LOG_TAG, String.format("No tests found in %s", theClass.getName()));
-            }
         }
         return mMethodNames;
     }
diff --git a/src/com/android/tradefed/testtype/HostTest.java b/src/com/android/tradefed/testtype/HostTest.java
index f6c9160..af30957 100644
--- a/src/com/android/tradefed/testtype/HostTest.java
+++ b/src/com/android/tradefed/testtype/HostTest.java
@@ -569,13 +569,16 @@
     }
 
     private void runTestCases(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        Set<String> skippedTests = new LinkedHashSet<>();
         for (Object obj : getTestMethods()) {
             if (IRemoteTest.class.isInstance(obj)) {
                 IRemoteTest test = (IRemoteTest) obj;
                 runRemoteTest(listener, test);
             } else if (TestSuite.class.isInstance(obj)) {
                 TestSuite junitTest = (TestSuite) obj;
-                runJUnit3Tests(listener, junitTest, junitTest.getName());
+                if (!runJUnit3Tests(listener, junitTest, junitTest.getName())) {
+                    skippedTests.add(junitTest.getName());
+                }
             } else if (Description.class.isInstance(obj)) {
                 // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test}
                 Description desc = (Description) obj;
@@ -587,6 +590,7 @@
                         String.format("%s is not a supported test", obj));
             }
         }
+        CLog.v("The following classes were skipped due to no test cases found: %s", skippedTests);
     }
 
     private void runRemoteTest(ITestInvocationListener listener, IRemoteTest test)
@@ -604,14 +608,16 @@
         test.run(listener);
     }
 
-    private void runJUnit3Tests(
+    /** Returns True if some tests were executed, false otherwise. */
+    private boolean runJUnit3Tests(
             ITestInvocationListener listener, TestSuite junitTest, String className)
             throws DeviceNotAvailableException {
         if (mCollectTestsOnly) {
             // Collect only mode, fake the junit test execution.
-            listener.testRunStarted(className, junitTest.countTestCases());
+            int testCount = junitTest.countTestCases();
+            listener.testRunStarted(className, testCount);
             HashMap<String, Metric> empty = new HashMap<>();
-            for (int i = 0; i < junitTest.countTestCases(); i++) {
+            for (int i = 0; i < testCount; i++) {
                 Test t = junitTest.testAt(i);
                 // Test does not have a getName method.
                 // using the toString format instead: <testName>(className)
@@ -622,8 +628,13 @@
             }
             HashMap<String, Metric> emptyMap = new HashMap<>();
             listener.testRunEnded(0, emptyMap);
+            if (testCount > 0) {
+                return true;
+            } else {
+                return false;
+            }
         } else {
-            JUnitRunUtil.runTest(listener, junitTest, className);
+            return JUnitRunUtil.runTest(listener, junitTest, className);
         }
     }
 
diff --git a/src/com/android/tradefed/testtype/JUnitRunUtil.java b/src/com/android/tradefed/testtype/JUnitRunUtil.java
index dd0c544..95180f2 100644
--- a/src/com/android/tradefed/testtype/JUnitRunUtil.java
+++ b/src/com/android/tradefed/testtype/JUnitRunUtil.java
@@ -16,7 +16,6 @@
 package com.android.tradefed.testtype;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.JUnitToInvocationResultForwarder;
@@ -33,16 +32,15 @@
  */
 public class JUnitRunUtil {
 
-    public static void runTest(ITestInvocationListener listener, Test junitTest)
+    public static boolean runTest(ITestInvocationListener listener, Test junitTest)
             throws DeviceNotAvailableException {
-        runTest(listener, junitTest, junitTest.getClass().getName());
+        return runTest(listener, junitTest, junitTest.getClass().getName());
     }
 
-    public static void runTest(ITestInvocationListener listener, Test junitTest,
-            String runName) throws DeviceNotAvailableException {
+    public static boolean runTest(ITestInvocationListener listener, Test junitTest, String runName)
+            throws DeviceNotAvailableException {
         if (junitTest.countTestCases() == 0) {
-            CLog.v("Skipping empty test case %s", runName);
-            return;
+            return false;
         }
         listener.testRunStarted(runName, junitTest.countTestCases());
         long startTime = System.currentTimeMillis();
@@ -60,5 +58,6 @@
             listener.testRunEnded(
                     System.currentTimeMillis() - startTime, new HashMap<String, Metric>());
         }
+        return true;
     }
 }
diff --git a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java b/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
index 8d54a51..b1b6c8d 100644
--- a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
+++ b/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
@@ -40,7 +40,7 @@
  * A {@link ResultForwarder} that will pull native coverage measurements off of the device and log
  * them as test artifacts.
  */
-final class NativeCodeCoverageListener extends ResultForwarder {
+public final class NativeCodeCoverageListener extends ResultForwarder {
 
     private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
     private static final String COVERAGE_FILE_LIST_COMMAND =
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index fd3c900..b44c3c8 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -833,11 +833,6 @@
         // to carry these extra data.
         cleanUpSuiteSetup();
 
-        /** Randomize all the modules to be ran if random-order is set while sharding.*/
-        if (mRandomOrder) {
-            randomizeTestModules(splitModules, mRandomSeed);
-        }
-
         // create an association of one ITestSuite <=> one ModuleDefinition as the smallest
         // execution unit supported.
         List<IRemoteTest> splitTests = new ArrayList<>();
diff --git a/tests/src/com/android/tradefed/device/NativeDeviceTest.java b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
index 28e24ab..27d5ce6 100644
--- a/tests/src/com/android/tradefed/device/NativeDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 
 import com.android.ddmlib.AdbCommandRejectedException;
@@ -2579,4 +2580,34 @@
                 };
         assertFalse(mTestDevice.isExecutable("/system"));
     }
+
+    /** Test {@link NativeDevice#getTombstones()}. */
+    @Test
+    public void testGetTombstones_notRoot() throws Exception {
+        TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+        doReturn(false).when(spy).isAdbRoot();
+
+        EasyMock.replay(mMockIDevice);
+        List<File> result = spy.getTombstones();
+        assertEquals(0, result.size());
+        EasyMock.verify(mMockIDevice);
+    }
+
+    /** Test {@link NativeDevice#getTombstones()}. */
+    @Test
+    public void testGetTombstones() throws Exception {
+        TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+        doReturn(true).when(spy).isAdbRoot();
+
+        String[] tombs = new String[] {"tomb1", "tomb2"};
+        doReturn(tombs).when(spy).getChildren("/data/tombstones/");
+
+        doReturn(new File("tomb1_test")).when(spy).pullFile("/data/tombstones/tomb1");
+        doReturn(new File("tomb2_test")).when(spy).pullFile("/data/tombstones/tomb2");
+
+        EasyMock.replay(mMockIDevice);
+        List<File> result = spy.getTombstones();
+        assertEquals(2, result.size());
+        EasyMock.verify(mMockIDevice);
+    }
 }