diff --git a/misc/output_test.py b/misc/output_test.py index 9691e68245..060b1474df 100755 --- a/misc/output_test.py +++ b/misc/output_test.py @@ -10,9 +10,9 @@ import subprocess import sys import tempfile +import typing as T import unittest from textwrap import dedent -import typing as T default_env = dict(os.environ) default_env.pop('NINJA_STATUS', None) @@ -20,6 +20,7 @@ default_env['TERM'] = '' NINJA_PATH = os.path.abspath('./ninja') + def remove_non_visible_lines(raw_output: bytes) -> str: # When running in a smart terminal, Ninja uses CR (\r) to # return the cursor to the start of the current line, prints @@ -120,7 +121,7 @@ def run( cwd=self.d.name, env=env) except subprocess.CalledProcessError as err: if print_err_output: - sys.stdout.buffer.write(err.output) + sys.stdout.buffer.write(err.output) err.cooked_output = remove_non_visible_lines(err.output) raise err @@ -152,14 +153,18 @@ class Output(unittest.TestCase): '', )) - def _test_expected_error(self, plan: str, flags: T.Optional[str], expected: str): + def _test_expected_error(self, plan: str, flags: T.Optional[str], expected: str, *args, **kwargs): """Run Ninja with a given plan and flags, and verify its cooked output against an expected content. + All *args and **kwargs are passed to the `run`, except `exit_code` """ actual = '' - try: - actual = run(plan, flags, print_err_output=False) - except subprocess.CalledProcessError as err: - actual = err.cooked_output + kwargs['print_err_output'] = False + exit_code = kwargs.pop("exit_code", None) + with self.assertRaises(subprocess.CalledProcessError) as cm: + run(plan, flags, *args, **kwargs) + actual = cm.exception.cooked_output + if exit_code is not None: + self.assertEqual(cm.exception.returncode, exit_code) self.assertEqual(expected, actual) def test_issue_1418(self) -> None: @@ -281,6 +286,75 @@ def test_issue_2048(self) -> None: except subprocess.CalledProcessError as err: self.fail("non-zero exit code with: " + err.output) + def test_pr_2540(self)->None: + py = sys.executable + plan = f'''\ +rule CUSTOM_COMMAND + command = $COMMAND + +build 124: CUSTOM_COMMAND + COMMAND = {py} -c 'exit(124)' + +build 127: CUSTOM_COMMAND + COMMAND = {py} -c 'exit(127)' + +build 130: CUSTOM_COMMAND + COMMAND = {py} -c 'exit(130)' + +build 137: CUSTOM_COMMAND + COMMAND = {py} -c 'exit(137)' + +build success: CUSTOM_COMMAND + COMMAND = sleep 0.3; echo success +''' + # Disable colors + env = default_env.copy() + env['TERM'] = 'dumb' + self._test_expected_error( + plan, '124', + f'''[1/1] {py} -c 'exit(124)' +FAILED: [code=124] 124 \n{py} -c 'exit(124)' +ninja: build stopped: subcommand failed. +''', + exit_code=124, env=env, + ) + self._test_expected_error( + plan, '127', + f'''[1/1] {py} -c 'exit(127)' +FAILED: [code=127] 127 \n{py} -c 'exit(127)' +ninja: build stopped: subcommand failed. +''', + exit_code=127, env=env, + ) + self._test_expected_error( + plan, '130', + 'ninja: build stopped: interrupted by user.\n', + exit_code=130, env=env, + ) + self._test_expected_error( + plan, '137', + f'''[1/1] {py} -c 'exit(137)' +FAILED: [code=137] 137 \n{py} -c 'exit(137)' +ninja: build stopped: subcommand failed. +''', + exit_code=137, env=env, + ) + self._test_expected_error( + plan, 'non-existent-target', + "ninja: error: unknown target 'non-existent-target'\n", + exit_code=1, env=env, + ) + self._test_expected_error( + plan, '-j2 success 127', + f'''[1/2] {py} -c 'exit(127)' +FAILED: [code=127] 127 \n{py} -c 'exit(127)' +[2/2] sleep 0.3; echo success +success +ninja: build stopped: subcommand failed. +''', + exit_code=127, env=env, + ) + def test_depfile_directory_creation(self) -> None: b = BuildDir('''\ rule touch