diff --git a/scripts/common.py b/scripts/common.py index c2ab32d01..7c2cbbbc1 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3676,6 +3676,13 @@ def run_cli(main: Callable[[], int | None]) -> None: for name in ("SIGTERM", "SIGHUP"): if hasattr(signal, name): signal.signal(getattr(signal, name), _exit_on_signal) + # Redirected, stdout is block-buffered and stderr is not: the --strict + # verdict landed on the first line of the log, before the report it + # closes. Line buffering keeps the two in the order they were written. + # A stream a caller swapped in (a test's StringIO) has nothing to set. + reconfigure = getattr(sys.stdout, "reconfigure", None) + if reconfigure is not None: + reconfigure(line_buffering=True) try: code = main() sys.stdout.flush() diff --git a/tests/test_tool_messages.py b/tests/test_tool_messages.py index 1b4799995..24483fa36 100644 --- a/tests/test_tool_messages.py +++ b/tests/test_tool_messages.py @@ -147,6 +147,31 @@ class ADamagedProfileNamesItself(unittest.TestCase): self.assertFalse(issubclass(common.SourceUnreadable, FileNotFoundError)) +class TheVerdictClosesARedirectedReport(unittest.TestCase): + """`verify.py --emulator fbneo --strict > log 2>&1` put the --strict + ERROR on the first line of the log: stdout redirected is block-buffered, + stderr is not, and the report it closes came after.""" + + def test_stderr_stays_after_what_stdout_wrote_first(self): + program = ( + "import sys\n" + "sys.path.insert(0, 'scripts')\n" + "from common import run_cli\n" + "def main():\n" + " print('report')\n" + " print('ERROR: verdict', file=sys.stderr)\n" + " return 1\n" + "run_cli(main)\n" + ) + env = {k: v for k, v in os.environ.items() if k != "PYTHONUNBUFFERED"} + proc = subprocess.run( + [sys.executable, "-c", program], cwd=REPO_ROOT, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=60, check=False, env=env, + ) + self.assertEqual(proc.returncode, 1) + self.assertEqual(proc.stdout.splitlines(), ["report", "ERROR: verdict"]) + + class AMistypedTargetAcrossAllPlatforms(unittest.TestCase): """`verify --all --target swich` ended on "no platform has it" after 17 INFO lines, two of which held the suggestion."""