HostGTest: Merge stdout and stderr output and send to result parser

Adds the ability to configure RunUtil to merge stderr into stderr through
ProcessBuilder.redirectErrorStream(), and uses that in HostGTest so that stderr
is properly interleaved with the main test output.

Bug: 123529934
Bug: 120174634
Test: m -j tradefed-all &&
tools/tradefederation/core/tests/run_tradefed_aosp_presubmit.sh

Change-Id: I2e7afd32f12d7f14457d25b8436db8f9af282541
Merged-In: I2e7afd32f12d7f14457d25b8436db8f9af282541
diff --git a/src/com/android/tradefed/util/IRunUtil.java b/src/com/android/tradefed/util/IRunUtil.java
index 5066d90..45620aa 100644
--- a/src/com/android/tradefed/util/IRunUtil.java
+++ b/src/com/android/tradefed/util/IRunUtil.java
@@ -77,6 +77,15 @@
     public void unsetEnvVariable(String key);
 
     /**
+     * Set the standard error stream to redirect to the standard output stream when running system
+     * commands. Initial value is false.
+     *
+     * @param redirect new value for whether or not to redirect
+     * @see ProcessBuilder#redirectErrorStream(boolean)
+     */
+    public void setRedirectStderrToStdout(boolean redirect);
+
+    /**
      * Helper method to execute a system command, and aborting if it takes longer than a specified
      * time.
      *
diff --git a/src/com/android/tradefed/util/RunUtil.java b/src/com/android/tradefed/util/RunUtil.java
index 03bf49e..20100a7 100644
--- a/src/com/android/tradefed/util/RunUtil.java
+++ b/src/com/android/tradefed/util/RunUtil.java
@@ -55,6 +55,7 @@
     private Map<String, String> mEnvVariables = new HashMap<String, String>();
     private Set<String> mUnsetEnvVariables = new HashSet<String>();
     private EnvPriority mEnvVariablePriority = EnvPriority.UNSET;
+    private boolean mRedirectStderr = false;
 
     private final CommandInterrupter mInterrupter;
 
@@ -123,6 +124,16 @@
         mUnsetEnvVariables.add(key);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void setRedirectStderrToStdout(boolean redirect) {
+        if (this.equals(sDefaultInstance)) {
+            throw new UnsupportedOperationException(
+                    "Cannot setRedirectStderrToStdout on default RunUtil");
+        }
+        mRedirectStderr = redirect;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -199,6 +210,7 @@
                 processBuilder.environment().putAll(mEnvVariables);
             }
         }
+        processBuilder.redirectErrorStream(mRedirectStderr);
         return processBuilder.command(commandList);
     }
 
diff --git a/tests/src/com/android/tradefed/util/FakeShellOutputReceiver.java b/tests/src/com/android/tradefed/util/FakeShellOutputReceiver.java
new file mode 100644
index 0000000..a060905
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/FakeShellOutputReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import com.android.ddmlib.IShellOutputReceiver;
+
+import java.io.ByteArrayOutputStream;
+
+/** Fake implementation of {@link IShellOutputReceiver} for use in unit tests. */
+public class FakeShellOutputReceiver implements IShellOutputReceiver {
+    private final ByteArrayOutputStream mStream = new ByteArrayOutputStream();
+
+    public byte[] getReceivedOutput() {
+        return mStream.toByteArray();
+    }
+
+    @Override
+    public void addOutput(byte[] data, int offset, int length) {
+        mStream.write(data, offset, length);
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public boolean isCancelled() {
+        return false;
+    }
+}
diff --git a/tests/src/com/android/tradefed/util/RunUtilTest.java b/tests/src/com/android/tradefed/util/RunUtilTest.java
index 78b6e14..9e74e66 100644
--- a/tests/src/com/android/tradefed/util/RunUtilTest.java
+++ b/tests/src/com/android/tradefed/util/RunUtilTest.java
@@ -321,7 +321,7 @@
             fail("Failed to create output files: " + e.getMessage());
         }
         RunUtil spyUtil = new SpyRunUtil(false);
-        String[] command = {"echo", "TEST"};
+        String[] command = {"unused", "cmd"};
         CommandResult result =
                 spyUtil.runTimedCmd(LONG_TIMEOUT_MS, stdoutStream, stderrStream, command);
         assertEquals(CommandStatus.SUCCESS, result.getStatus());
@@ -332,8 +332,8 @@
         assertTrue(stdout.exists());
         assertTrue(stderr.exists());
         try {
-            assertEquals("TEST\n", FileUtil.readStringFromFile(stdout));
-            assertEquals("", FileUtil.readStringFromFile(stderr));
+            assertEquals("TEST STDOUT\n", FileUtil.readStringFromFile(stdout));
+            assertEquals("TEST STDERR\n", FileUtil.readStringFromFile(stderr));
         } catch (IOException e) {
             fail(e.getMessage());
         } finally {
@@ -349,11 +349,11 @@
      */
     public void testRuntimedCmd_regularOutput_fileNull() {
         RunUtil spyUtil = new SpyRunUtil(false);
-        String[] command = {"echo", "TEST"};
+        String[] command = {"unused", "cmd"};
         CommandResult result = spyUtil.runTimedCmd(LONG_TIMEOUT_MS, null, null, command);
         assertEquals(CommandStatus.SUCCESS, result.getStatus());
-        assertEquals(result.getStdout(), "TEST\n");
-        assertEquals(result.getStderr(), "");
+        assertEquals(result.getStdout(), "TEST STDOUT\n");
+        assertEquals(result.getStderr(), "TEST STDERR\n");
     }
 
     /**
@@ -376,7 +376,7 @@
             fail("Failed to create output files: " + e.getMessage());
         }
         RunUtil spyUtil = new SpyRunUtil(false);
-        String[] command = {"echo", "TEST"};
+        String[] command = {"unused", "cmd"};
         CommandResult result =
                 spyUtil.runTimedCmd(SHORT_TIMEOUT_MS, stdoutStream, stderrStream, command);
         assertEquals(CommandStatus.SUCCESS, result.getStatus());
@@ -387,8 +387,20 @@
         assertTrue(stdout.exists());
         assertTrue(stderr.exists());
         try {
-            assertEquals("TEST\n", FileUtil.readStringFromFile(stdout));
-            assertEquals("", FileUtil.readStringFromFile(stderr));
+// <<<<<<< HEAD
+//             assertEquals("TEST\n", FileUtil.readStringFromFile(stdout));
+//             assertEquals("", FileUtil.readStringFromFile(stderr));
+// =======
+            assertEquals(CommandStatus.SUCCESS, result.getStatus());
+            assertEquals(
+                    result.getStdout(), "redirected to " + stdoutStream.getClass().getSimpleName());
+            assertEquals(
+                    result.getStderr(), "redirected to " + stderrStream.getClass().getSimpleName());
+            assertTrue(stdout.exists());
+            assertTrue(stderr.exists());
+            assertEquals("TEST STDOUT\n", FileUtil.readStringFromFile(stdout));
+            assertEquals("TEST STDERR\n", FileUtil.readStringFromFile(stderr));
+// >>>>>>> 7f6b1bf55... HostGTest: Merge stdout and stderr output and send to result parser
         } catch (IOException e) {
             fail(e.getMessage());
         } finally {
@@ -490,9 +502,22 @@
         assertEquals(2, (int) result.getExitCode());
     }
 
+    public void testSetRedirectStderrToStdout() {
+        RunUtil testRunUtil = new RunUtil();
+        testRunUtil.setRedirectStderrToStdout(true);
+        CommandResult result =
+                testRunUtil.runTimedCmd(
+                        VERY_LONG_TIMEOUT_MS,
+                        "/bin/bash",
+                        "-c",
+                        "echo 'TEST STDOUT'; echo 'TEST STDERR' >&2");
+        assertEquals("TEST STDOUT\nTEST STDERR\n", result.getStdout());
+        assertEquals("", result.getStderr());
+    }
+
     /**
-     * Implementation of {@link Process} to simulate a success of 'echo Test' without actually
-     * calling the underlying system.
+     * Implementation of {@link Process} to simulate a success of a command that echos to both
+     * stdout and stderr without actually calling the underlying system.
      */
     private class FakeProcess extends Process {
 
@@ -508,13 +533,12 @@
 
         @Override
         public InputStream getInputStream() {
-            ByteArrayInputStream stream = new ByteArrayInputStream("TEST\n".getBytes());
-            return stream;
+            return new ByteArrayInputStream("TEST STDOUT\n".getBytes());
         }
 
         @Override
         public InputStream getErrorStream() {
-            return new ByteArrayInputStream("".getBytes());
+            return new ByteArrayInputStream("TEST STDERR\n".getBytes());
         }
 
         @Override