Reland "Run-test: Refactor stdout/stderr capture"
Test: ./art/test.py -r --optimizing --64 --all-target
Change-Id: Iae5e26f7e1e084ef88b1f24ed7f8defcd878453a
diff --git a/test/default_run.py b/test/default_run.py
index de6b8fe..27855f8 100755
--- a/test/default_run.py
+++ b/test/default_run.py
@@ -16,7 +16,7 @@
import sys, os, shutil, shlex, re, subprocess, glob
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
from os import path
-from os.path import isfile, isdir
+from os.path import isfile, isdir, basename
from typing import List
from subprocess import DEVNULL, PIPE, STDOUT
from tempfile import NamedTemporaryFile
@@ -207,8 +207,6 @@
def run(cmdline: str,
env={},
- stdout_file=None,
- stderr_file=None,
check=True,
parse_exit_code_from_stdout=False,
expected_exit_code=0,
@@ -230,6 +228,7 @@
cmdline = "true" # We still need to run some command, so run the no-op "true" binary instead.
proc = subprocess.run([cmdline],
shell=True,
+ executable="/bin/bash",
env=env,
encoding="utf8",
capture_output=True)
@@ -245,14 +244,6 @@
proc.stdout = proc.stdout[:found.start(0)] # Remove the exit code from stdout.
proc.returncode = int(found.group(1)) # Use it as if it was the process exit code.
- # Save copy of the output on disk.
- if stdout_file:
- with open(stdout_file, "a") as f:
- f.write(proc.stdout)
- if stderr_file:
- with open(stderr_file, "a") as f:
- f.write(proc.stderr)
-
# Check the exit code.
if (check and proc.returncode != expected_exit_code) or VERBOSE:
print("$ " + cmdline)
@@ -264,10 +255,19 @@
suffix = " (TIME OUT)"
elif expected_exit_code != 0:
suffix = " (expected {})".format(expected_exit_code)
- raise Exception("Command returned exit code {}{}".format(proc.returncode, suffix))
+ print("Command returned exit code {}{}".format(proc.returncode, suffix), file=sys.stderr)
+ sys.exit(1)
return proc
+ # Store copy of stdout&stderr of command in files so that we can diff them later.
+ # This may run under 'adb shell' so we are limited only to 'sh' shell feature set.
+ def tee(cmd: str):
+ # 'tee' works on stdout only, so we need to temporarily swap stdout and stderr.
+ cmd = f"({cmd} | tee -a {DEX_LOCATION}/{basename(args.stdout_file)}) 3>&1 1>&2 2>&3"
+ cmd = f"({cmd} | tee -a {DEX_LOCATION}/{basename(args.stderr_file)}) 3>&1 1>&2 2>&3"
+ return f"set -o pipefail; {cmd}" # Use exit code of first failure in piped command.
+
class Adb():
def __init__(self):
@@ -290,6 +290,9 @@
def push(self, src: str, dst: str, **kwargs) -> None:
run(f"adb push {src} {dst}", self.env, **kwargs)
+ def pull(self, src: str, dst: str, **kwargs) -> None:
+ run(f"adb pull {src} {dst}", self.env, **kwargs)
+
adb = Adb()
local_path = os.path.dirname(__file__)
@@ -725,10 +728,8 @@
print(f"Runnable test script written to {pwd}/runit.sh")
return
else:
- run(cmdline,
+ run(tee(cmdline),
env,
- stdout_file=args.stdout_file,
- stderr_file=args.stderr_file,
expected_exit_code=args.expected_exit_code)
return
@@ -1095,20 +1096,20 @@
# b/24664297
dalvikvm_cmdline = f"{INVOKE_WITH} {GDB} {ANDROID_ART_BIN_DIR}/{DALVIKVM} \
- {GDB_ARGS} \
- {FLAGS} \
- {DEX_VERIFY} \
- -XXlib:{LIB} \
- {DEX2OAT} \
- {DALVIKVM_ISA_FEATURES_ARGS} \
- {ZYGOTE} \
- {JNI_OPTS} \
- {INT_OPTS} \
- {DEBUGGER_OPTS} \
- {QUOTED_DALVIKVM_BOOT_OPT} \
- {TMP_DIR_OPTION} \
- -XX:DumpNativeStackOnSigQuit:false \
- -cp {DALVIKVM_CLASSPATH} {MAIN} {ARGS}"
+ {GDB_ARGS} \
+ {FLAGS} \
+ {DEX_VERIFY} \
+ -XXlib:{LIB} \
+ {DEX2OAT} \
+ {DALVIKVM_ISA_FEATURES_ARGS} \
+ {ZYGOTE} \
+ {JNI_OPTS} \
+ {INT_OPTS} \
+ {DEBUGGER_OPTS} \
+ {QUOTED_DALVIKVM_BOOT_OPT} \
+ {TMP_DIR_OPTION} \
+ -XX:DumpNativeStackOnSigQuit:false \
+ -cp {DALVIKVM_CLASSPATH} {MAIN} {ARGS}"
if SIMPLEPERF:
dalvikvm_cmdline = f"simpleperf record {dalvikvm_cmdline} && simpleperf report"
@@ -1267,12 +1268,13 @@
# Create a script with the command. The command can get longer than the longest
# allowed adb command and there is no way to get the exit status from a adb shell command.
with NamedTemporaryFile(mode="w") as cmdfile:
+ cmdfile.write("echo '$$ {}'\n".format(cmdline.replace("'", r"'\''")))
cmdfile.write(cmdline)
cmdfile.flush()
adb.push(
cmdfile.name, f"{CHROOT_DEX_LOCATION}/cmdline.sh", save_cmd=False)
run('echo cmdline.sh "' + cmdline.replace('"', '\\"') + '"')
- chroot_prefix = f"chroot {CHROOT} " if CHROOT else ""
+ chroot_prefix = f"chroot {CHROOT}" if CHROOT else ""
return adb.shell(f"{chroot_prefix} sh {DEX_LOCATION}/cmdline.sh", **kwargs)
if VERBOSE and (USE_GDB or USE_GDBSERVER):
@@ -1280,17 +1282,21 @@
run_cmd(f"rm -rf {DEX_LOCATION}/dalvik-cache/")
run_cmd(f"mkdir -p {mkdir_locations}")
+ # Restore stdout/stderr from previous run (the directory might have been cleared).
+ adb.push(args.stdout_file, f"{CHROOT}{DEX_LOCATION}/{basename(args.stdout_file)}")
+ adb.push(args.stderr_file, f"{CHROOT}{DEX_LOCATION}/{basename(args.stderr_file)}")
run_cmd(f"{profman_cmdline}", env)
run_cmd(f"{dex2oat_cmdline}", env)
run_cmd(f"{dm_cmdline}", env)
run_cmd(f"{vdex_cmdline}", env)
run_cmd(f"{strip_cmdline}")
run_cmd(f"{sync_cmdline}")
- run_cmd(f"{timeout_prefix} {dalvikvm_cmdline}",
+ run_cmd(tee(f"{timeout_prefix} {dalvikvm_cmdline}"),
env,
- stdout_file=args.stdout_file,
- stderr_file=args.stderr_file,
expected_exit_code=args.expected_exit_code)
+ # Copy the on-device stdout/stderr to host.
+ adb.pull(f"{CHROOT}{DEX_LOCATION}/{basename(args.stdout_file)}", args.stdout_file)
+ adb.pull(f"{CHROOT}{DEX_LOCATION}/{basename(args.stderr_file)}", args.stderr_file)
else:
# Host run.
if USE_ZIPAPEX or USE_EXRACTED_ZIPAPEX:
@@ -1409,10 +1415,8 @@
subprocess.run(cmdline, env=env, shell=True)
else:
if TIME_OUT != "gdb":
- run(cmdline,
+ run(tee(cmdline),
env,
- stdout_file=args.stdout_file,
- stderr_file=args.stderr_file,
expected_exit_code=args.expected_exit_code)
else:
# With a thread dump that uses gdb if a timeout.