Add test coverage to test run logic.

Test coverage % is now ~63%, above 60% average rate.

Also in this CL:
* Cleaned up log directory after test run.
* Mobly test result and logs will be reported even if an exception was
thrown earlier.

Test: TF unit test.
Bug: 159641617
Change-Id: I59ccf20810b11c7e5d3a6aa5a893de6511b5e27d
diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
index cd10ad8..67cd44c 100644
--- a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
@@ -146,9 +146,12 @@
                 continue;
             }
             parFile.setExecutable(true);
-            runSingleParFile(listener, parFile.getAbsolutePath());
-            processTestResults(listener, parFile.getName());
-            reportLogs(getLogDir(), listener);
+            try {
+                runSingleParFile(listener, parFile.getAbsolutePath());
+                processTestResults(listener, parFile.getName());
+            } finally {
+                reportLogs(getLogDir(), listener);
+            }
         }
     }
 
@@ -319,7 +322,6 @@
         yaml.dump(configMap, writer);
     }
 
-    // TODO(b/159369745): clean up tmp dir after test run.
     private File getLogDir() {
         if (mLogDir == null) {
             try {
@@ -375,6 +377,7 @@
                 }
             }
         }
+        FileUtil.deleteFile(logDir);
     }
 
     @VisibleForTesting
diff --git a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
index 0dac1b2..3b0e0f9 100644
--- a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
@@ -22,25 +22,38 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.core.StringStartsWith.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import com.android.tradefed.build.DeviceBuildInfo;
+import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
+import com.android.utils.FileUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.InputStream;
 import java.io.StringWriter;
 import java.io.Writer;
@@ -53,29 +66,152 @@
     private static final String LOG_PATH = "/log/dir/abs/path";
     private static final String DEVICE_SERIAL = "X123SER";
     private static final long DEFAULT_TIME_OUT = 30 * 1000L;
+    private static final String TEST_RESULT_FILE_NAME = "test_summary.yaml";
+    private static final String TEMP_DIR = "/tmp";
 
     private MoblyBinaryHostTest mSpyTest;
     private ITestDevice mMockDevice;
     private IRunUtil mMockRunUtil;
     private MoblyYamlResultParser mMockParser;
     private InputStream mMockSummaryInputStream;
+    private File mMoblyBinary; // used by python-binaries option
+    private File mMoblyBinary2; // used by par-file-name option
+    private DeviceBuildInfo mMockBuildInfo;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         mSpyTest = Mockito.spy(new MoblyBinaryHostTest());
         mMockDevice = Mockito.mock(ITestDevice.class);
         mMockRunUtil = Mockito.mock(IRunUtil.class);
+        mMockBuildInfo = Mockito.mock(DeviceBuildInfo.class);
         mSpyTest.setDevice(mMockDevice);
-        Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
         Mockito.doReturn(mMockRunUtil).when(mSpyTest).getRunUtil();
         Mockito.doReturn(DEFAULT_TIME_OUT).when(mSpyTest).getTestTimeout();
-        Mockito.doReturn(new CommandResult(CommandStatus.SUCCESS))
-                .when(mMockRunUtil)
-                .runTimedCmd(anyLong(), any());
+        Mockito.doReturn("not_adb").when(mSpyTest).getAdbPath();
+        mMoblyBinary = new File(TEMP_DIR, "mobly_binary.par");
+        FileUtils.createFile(mMoblyBinary, "");
+        mMoblyBinary2 = new File(TEMP_DIR, "mobly_binary_2.par");
+        FileUtils.createFile(mMoblyBinary2, "");
+        mSpyTest.setBuild(mMockBuildInfo);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteIfExists(mMoblyBinary);
+        FileUtils.deleteIfExists(mMoblyBinary2);
+    }
+
+    @Test
+    public void testRun_withPythonBinariesOption() throws Exception {
+        OptionSetter setter = new OptionSetter(mSpyTest);
+        setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+        File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME);
+        // Mimics the behavior of a successful test run.
+        Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), any()))
+                .thenAnswer(
+                        new Answer<CommandResult>() {
+                            @Override
+                            public CommandResult answer(InvocationOnMock invocation)
+                                    throws Throwable {
+                                FileUtils.createFile(testResult, "");
+                                FileUtils.createFile(
+                                        new File(mSpyTest.getLogDirAbsolutePath(), "log"),
+                                        "log content");
+                                return new CommandResult(CommandStatus.SUCCESS);
+                            }
+                        });
+
+        mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+        verify(mSpyTest.getRunUtil()).runTimedCmd(anyLong(), any());
+        assertFalse(new File(mSpyTest.getLogDirAbsolutePath()).exists());
+    }
+
+    @Test
+    public void testRun_withPythonBinariesOption_binaryNotFound() throws Exception {
+        OptionSetter setter = new OptionSetter(mSpyTest);
+        setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+        FileUtil.deleteFile(mMoblyBinary);
+
+        mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+        verify(mSpyTest, never()).reportLogs(any(), any());
+    }
+
+    @Test
+    public void testRun_withParFileNameOption() throws Exception {
+        OptionSetter setter = new OptionSetter(mSpyTest);
+        setter.setOptionValue("par-file-name", mMoblyBinary2.getName());
+        Mockito.doReturn(new File(TEMP_DIR)).when(mMockBuildInfo).getTestsDir();
+        File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME);
+        Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), any()))
+                .thenAnswer(
+                        new Answer<CommandResult>() {
+                            @Override
+                            public CommandResult answer(InvocationOnMock invocation)
+                                    throws Throwable {
+                                FileUtils.createFile(testResult, "");
+                                FileUtils.createFile(
+                                        new File(mSpyTest.getLogDirAbsolutePath(), "log"),
+                                        "log content");
+                                return new CommandResult(CommandStatus.SUCCESS);
+                            }
+                        });
+
+        mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+        verify(mSpyTest.getRunUtil()).runTimedCmd(anyLong(), any());
+        assertFalse(new File(mSpyTest.getLogDirAbsolutePath()).exists());
+    }
+
+    @Test
+    public void testRun_withParFileNameOption_binaryNotFound() throws Exception {
+        OptionSetter setter = new OptionSetter(mSpyTest);
+        setter.setOptionValue("par-file-name", mMoblyBinary2.getName());
+        Mockito.doReturn(new File(TEMP_DIR)).when(mMockBuildInfo).getTestsDir();
+        FileUtil.deleteFile(mMoblyBinary2);
+
+        try {
+            mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+            fail("Should have thrown an exception");
+        } catch (RuntimeException e) {
+            verify(mSpyTest, never()).reportLogs(any(), any());
+            assertEquals(
+                    e.getMessage(),
+                    String.format("Couldn't find a par file %s", mMoblyBinary2.getName()));
+        }
+    }
+
+    @Test
+    public void testRun_testResultIsMissing() throws Exception {
+        OptionSetter setter = new OptionSetter(mSpyTest);
+        setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+        // Test result and log files were not created for some reasons during test run.
+        Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), any()))
+                .thenAnswer(
+                        new Answer<CommandResult>() {
+                            @Override
+                            public CommandResult answer(InvocationOnMock invocation)
+                                    throws Throwable {
+                                return new CommandResult(CommandStatus.SUCCESS);
+                            }
+                        });
+
+        try {
+            mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+            fail("Should have thrown an exception");
+        } catch (RuntimeException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString(
+                            "Fail to find test summary file test_summary.yaml under directory"));
+            assertFalse(new File(mSpyTest.getLogDirAbsolutePath()).exists());
+        }
     }
 
     @Test
     public void testBuildCommandLineArrayWithOutConfig() throws Exception {
+        Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
         Mockito.doReturn(DEVICE_SERIAL).when(mMockDevice).getSerialNumber();
         Mockito.doReturn(LOG_PATH).when(mSpyTest).getLogDirAbsolutePath();
         List<String> expOptions = Arrays.asList("--option1", "--option2=test_option");
@@ -95,6 +231,7 @@
 
     @Test
     public void testBuildCommandLineArrayWithConfig() throws Exception {
+        Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
         Mockito.doReturn(DEVICE_SERIAL).when(mMockDevice).getSerialNumber();
         Mockito.doReturn(LOG_PATH).when(mSpyTest).getLogDirAbsolutePath();
         List<String> expOptions = Arrays.asList("--option1", "--option2=test_option");
@@ -116,6 +253,7 @@
 
     @Test
     public void testProcessYamlTestResultsSuccess() throws Exception {
+        Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
         mMockSummaryInputStream = Mockito.mock(InputStream.class);
         mMockParser = Mockito.mock(MoblyYamlResultParser.class);
         mSpyTest.processYamlTestResults(mMockSummaryInputStream, mMockParser);
@@ -124,6 +262,7 @@
 
     @Test
     public void testUpdateConfigFile() throws Exception {
+        Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
         Mockito.doReturn("testBedName").when(mSpyTest).getTestBed();
         String configString =
                 new StringBuilder()