Always capture test output for the report.

Before, the build process was shown on the terminal and the final
report only said "build failed". This patch makes the execution more
succinct and displays all the messages at the end.

Change-Id: I57228fe2006652870b25a32789e11e337b046732
diff --git a/tests/ndk.py b/tests/ndk.py
index bb8a515..c7689d0 100644
--- a/tests/ndk.py
+++ b/tests/ndk.py
@@ -17,9 +17,10 @@
 """Interface to NDK build information."""
 import os
 import re
-import subprocess
 import sys
 
+import util
+
 
 THIS_DIR = os.path.dirname(os.path.realpath(__file__))
 NDK_ROOT = os.path.realpath(os.path.join(THIS_DIR, '..'))
@@ -49,7 +50,7 @@
 
 def build(build_flags):
     ndk_build_path = os.path.join(NDK_ROOT, 'ndk-build')
-    return subprocess.call([ndk_build_path] + build_flags)
+    return util.call_output([ndk_build_path] + build_flags)
 
 
 def expand_app_abi(abi):
diff --git a/tests/tests.py b/tests/tests.py
index fa1e869..7febd12 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -401,10 +401,11 @@
             test_env['APP_PLATFORM'] = platform
         assert toolchain is not None
         test_env['NDK_TOOLCHAIN_VERSION'] = toolchain
-        if subprocess.call(build_cmd, env=test_env) == 0:
+        rc, out = util.call_output(build_cmd, env=test_env)
+        if rc == 0:
             return Success(test_name)
         else:
-            return Failure(test_name, 'build failed')
+            return Failure(test_name, out)
 
 
 def _run_ndk_build_test(test_name, build_dir, test_dir, build_flags, abi,
@@ -418,10 +419,11 @@
         ]
         if platform is not None:
             args.append('APP_PLATFORM=' + platform)
-        if ndk.build(build_flags + args) == 0:
+        rc, out = ndk.build(build_flags + args)
+        if rc == 0:
             return Success(test_name)
         else:
-            return Failure(test_name, 'build failed')
+            return Failure(test_name, out)
 
 
 class ShellBuildTest(Test):
@@ -489,7 +491,7 @@
         # This was the case with the old shell based script too. I'm trying not
         # to change too much in the translation.
         lib_path = os.path.join(abi_dir, test_file)
-        print('Pushing {} to {}'.format(lib_path, device_dir))
+        print('\tPushing {} to {}...'.format(lib_path, device_dir))
         adb.push(lib_path, device_dir)
 
         # TODO(danalbert): Sync data.
@@ -548,7 +550,7 @@
 
                 cmd = 'cd {} && LD_LIBRARY_PATH={} ./{}'.format(
                     device_dir, device_dir, case)
-                print('Executing test: {}'.format(cmd))
+                print('\tExecuting {}...'.format(case_name))
                 result, out = adb.shell(cmd)
 
                 config, bug = self.check_subtest_broken(case)
diff --git a/tests/util.py b/tests/util.py
index f38f378..c1b1775 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -15,6 +15,7 @@
 #
 import contextlib
 import os
+import subprocess
 
 
 def color_string(string, color):
@@ -35,3 +36,20 @@
         yield
     finally:
         os.chdir(curdir)
+
+
+def call_output(cmd, *args, **kwargs):
+    """Invoke the specified command and return exit code and output.
+
+    This is the missing subprocess.call_output, which is the combination of
+    subprocess.call and subprocess.check_output. Like call, it returns an exit
+    code rather than raising an exception. Like check_output, it returns the
+    output of the program. Unlike check_output, it returns the output even on
+    failure.
+
+    Returns: Tuple of (exit_code, output).
+    """
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT, *args, **kwargs)
+    out, _ = proc.communicate()
+    return proc.returncode, out