Avoid triggering rerunners when there is no more tests

If there is no tests left, do not attempt rerun. Previously
attempting the rerun would run all the tests in the
instrumentation due to no filter being applied.

Test: unit tests
Bug: 112190992
Change-Id: I571d7dff2a6dde90c4e9a7ce37c8749281773726
(cherry picked from commit 3445dbafd280e50d9eebb457d8cb242a2d6a29b8)
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index db2179a..0427473 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -106,6 +106,9 @@
      */
     private LogcatItem extractLogcat(ITestDevice device, long startTime) {
         try (InputStreamSource logSource = device.getLogcatSince(startTime)) {
+            if (logSource.size() == 0L) {
+                return null;
+            }
             String message = StreamUtil.getStringFromStream(logSource.createInputStream());
             LogcatParser parser = new LogcatParser();
             List<String> lines = Arrays.asList(message.split("\n"));
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/src/com/android/tradefed/testtype/InstrumentationTest.java
index 984a964..ae2b8c7 100644
--- a/src/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationTest.java
@@ -940,6 +940,10 @@
     private void rerunTests(
             Collection<TestDescription> expectedTests, final ITestInvocationListener listener)
             throws DeviceNotAvailableException {
+        if (expectedTests.isEmpty()) {
+            CLog.d("No tests to re-run, all tests executed at least once.");
+            return;
+        }
         if (mRebootBeforeReRun) {
             mDevice.reboot();
         }
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index e172149..492700b 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -37,8 +37,10 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.ITestLifeCycleReceiver;
+import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.util.ListInstrumentationParser;
 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
@@ -695,6 +697,66 @@
         inOrder.verify(mMockListener).testRunEnded(1, EMPTY_STRING_MAP);
     }
 
+    /** Test that after the first run if there is no tests to re-run we don't launch a rerun. */
+    @Test
+    public void testRun_noMoreTests() throws Exception {
+        doReturn(mock(IRemoteTest.class))
+                .when(mInstrumentationTest)
+                .getTestReRunner(anyCollectionOf(TestDescription.class));
+
+        // We collect successfully 1 tests
+        RunInstrumentationTestsAnswer collected =
+                (runner, listener) -> {
+                    listener.testRunStarted("fakeName", 1);
+                    for (int i = 0; i < 1; i++) {
+                        TestDescription tid = new TestDescription("fakeclass", "fakemethod" + i);
+                        listener.testStarted(tid, 5);
+                        listener.testEnded(tid, 15, EMPTY_STRING_MAP);
+                    }
+                    listener.testRunEnded(500, EMPTY_STRING_MAP);
+                    return true;
+                };
+
+        // We attempt to run the test and it crash
+        RunInstrumentationTestsAnswer partialRun =
+                (runner, listener) -> {
+                    listener.testRunStarted("fakeName", 1);
+                    TestDescription tid = new TestDescription("fakeclass", "fakemethod0");
+                    listener.testStarted(tid, 0L);
+                    listener.testFailed(
+                            tid, "Instrumentation run failed due to 'Process crashed.'");
+                    listener.testEnded(tid, 15L, EMPTY_STRING_MAP);
+                    listener.testRunFailed("Instrumentation run failed due to 'Process crashed.'");
+                    listener.testRunEnded(1, EMPTY_STRING_MAP);
+                    return true;
+                };
+
+        doAnswer(collected)
+                .doAnswer(partialRun)
+                .when(mMockTestDevice)
+                .runInstrumentationTests(
+                        any(IRemoteAndroidTestRunner.class), any(ITestLifeCycleReceiver.class));
+
+        InputStreamSource source = new ByteArrayInputStreamSource("".getBytes());
+        doReturn(source).when(mMockTestDevice).getLogcatSince(anyLong());
+
+        mInstrumentationTest.run(mMockListener);
+
+        // Ensure no rerunner is requested since there is no more tests.
+        verify(mInstrumentationTest, times(0)).getTestReRunner(any());
+        // The reported number of tests is the one from the collected output
+        InOrder inOrder = Mockito.inOrder(mMockListener);
+        inOrder.verify(mMockListener).testRunStarted("fakeName", 1);
+        TestDescription tid = new TestDescription("fakeclass", "fakemethod0");
+        inOrder.verify(mMockListener).testStarted(tid, 0L);
+        inOrder.verify(mMockListener)
+                .testFailed(tid, "Instrumentation run failed due to 'Process crashed.'");
+        inOrder.verify(mMockListener).testEnded(tid, 15L, EMPTY_STRING_MAP);
+        inOrder.verify(mMockListener)
+                .testRunFailed("Instrumentation run failed due to 'Process crashed.'");
+        inOrder.verify(mMockListener).testRunEnded(1, EMPTY_STRING_MAP);
+    }
+
     @Test
     public void testAddScreenshotListener_enabled() {
         mInstrumentationTest.setScreenshotOnFailure(true);