def test_make_subprocess_output_error__non_ascii_command_arg(monkeypatch): """ Test a command argument with a non-ascii character. """ cmd_args = ['foo', 'déf'] if sys.version_info[0] == 2: # Check in Python 2 that the str (bytes object) with the non-ascii # character has the encoding we expect. (This comes from the source # code encoding at the top of the file.) assert cmd_args[1].decode('utf-8') == 'déf' # We need to monkeypatch so the encoding will be correct on Windows. monkeypatch.setattr(locale, 'getpreferredencoding', lambda: 'utf-8') actual = make_subprocess_output_error( cmd_args=cmd_args, cwd='/path/to/cwd', lines=[], exit_status=1, ) expected = dedent("""\ Command errored out with exit status 1: command: foo 'déf' cwd: /path/to/cwd Complete output (0 lines): ----------------------------------------""") assert actual == expected, f'actual: {actual}'
def test_make_subprocess_output_error__non_ascii_cwd_python_3(monkeypatch): """ Test a str (text) cwd with a non-ascii character in Python 3. """ cmd_args = ['test'] cwd = '/path/to/cwd/déf' actual = make_subprocess_output_error( cmd_args=cmd_args, cwd=cwd, lines=[], exit_status=1, ) expected = dedent("""\ Command errored out with exit status 1: command: test cwd: /path/to/cwd/déf Complete output (0 lines): ----------------------------------------""") assert actual == expected, f'actual: {actual}'
def test_make_subprocess_output_error__non_ascii_line(): """ Test a line with a non-ascii character. """ lines = ['curly-quote: \u2018\n'] actual = make_subprocess_output_error( cmd_args=['test'], cwd='/path/to/cwd', lines=lines, exit_status=1, ) expected = dedent("""\ Command errored out with exit status 1: command: test cwd: /path/to/cwd Complete output (1 lines): curly-quote: \u2018 ----------------------------------------""") assert actual == expected, f'actual: {actual}'
def test_make_subprocess_output_error(): cmd_args = ['test', 'has space'] cwd = '/path/to/cwd' lines = ['line1\n', 'line2\n', 'line3\n'] actual = make_subprocess_output_error( cmd_args=cmd_args, cwd=cwd, lines=lines, exit_status=3, ) expected = dedent("""\ Command errored out with exit status 3: command: test 'has space' cwd: /path/to/cwd Complete output (3 lines): line1 line2 line3 ----------------------------------------""") assert actual == expected, f'actual: {actual}'
def test_make_subprocess_output_error() -> None: cmd_args = ["test", "has space"] cwd = "/path/to/cwd" lines = ["line1\n", "line2\n", "line3\n"] actual = make_subprocess_output_error( cmd_args=cmd_args, cwd=cwd, lines=lines, exit_status=3, ) expected = dedent("""\ Command errored out with exit status 3: command: test 'has space' cwd: /path/to/cwd Complete output (3 lines): line1 line2 line3 ----------------------------------------""") assert actual == expected, f"actual: {actual}"
def test_make_subprocess_output_error__non_ascii_command_arg(monkeypatch): """ Test a command argument with a non-ascii character. """ cmd_args = ['foo', 'déf'] # We need to monkeypatch so the encoding will be correct on Windows. monkeypatch.setattr(locale, 'getpreferredencoding', lambda: 'utf-8') actual = make_subprocess_output_error( cmd_args=cmd_args, cwd='/path/to/cwd', lines=[], exit_status=1, ) expected = dedent("""\ Command errored out with exit status 1: command: foo 'déf' cwd: /path/to/cwd Complete output (0 lines): ----------------------------------------""") assert actual == expected, f'actual: {actual}'
def test_make_subprocess_output_error__non_ascii_cwd_python_2( monkeypatch, encoding, ): """ Test a str (bytes object) cwd with a non-ascii character in Python 2. """ cmd_args = ['test'] cwd = u'/path/to/cwd/déf'.encode(encoding) monkeypatch.setattr(sys, 'getfilesystemencoding', lambda: encoding) actual = make_subprocess_output_error( cmd_args=cmd_args, cwd=cwd, lines=[], exit_status=1, ) expected = dedent(u"""\ Command errored out with exit status 1: command: test cwd: /path/to/cwd/déf Complete output (0 lines): ----------------------------------------""") assert actual == expected, u'actual: {}'.format(actual)
def test_make_subprocess_output_error__non_ascii_command_arg( monkeypatch: pytest.MonkeyPatch, ) -> None: """ Test a command argument with a non-ascii character. """ cmd_args = ["foo", "déf"] # We need to monkeypatch so the encoding will be correct on Windows. monkeypatch.setattr(locale, "getpreferredencoding", lambda: "utf-8") actual = make_subprocess_output_error( cmd_args=cmd_args, cwd="/path/to/cwd", lines=[], exit_status=1, ) expected = dedent("""\ Command errored out with exit status 1: command: foo 'déf' cwd: /path/to/cwd Complete output (0 lines): ----------------------------------------""") assert actual == expected, f"actual: {actual}"
def call_subprocess( cmd, # type: Union[List[str], CommandArgs] cwd=None, # type: Optional[str] extra_environ=None, # type: Optional[Mapping[str, Any]] extra_ok_returncodes=None, # type: Optional[Iterable[int]] log_failed_cmd=True, # type: Optional[bool] ): # type: (...) -> Text """ Args: extra_ok_returncodes: an iterable of integer return codes that are acceptable, in addition to 0. Defaults to None, which means []. log_failed_cmd: if false, failed commands are not logged, only raised. """ if extra_ok_returncodes is None: extra_ok_returncodes = [] # log the subprocess output at DEBUG level. log_subprocess = subprocess_logger.debug env = os.environ.copy() if extra_environ: env.update(extra_environ) # Whether the subprocess will be visible in the console. showing_subprocess = True command_desc = format_command_args(cmd) try: proc = subprocess.Popen( # Convert HiddenText objects to the underlying str. reveal_command_args(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, ) if proc.stdin: proc.stdin.close() except Exception as exc: if log_failed_cmd: subprocess_logger.critical( "Error %s while executing command %s", exc, command_desc, ) raise all_output = [] while True: # The "line" value is a unicode string in Python 2. line = None if proc.stdout: line = console_to_str(proc.stdout.readline()) if not line: break line = line.rstrip() all_output.append(line + "\n") # Show the line immediately. log_subprocess(line) try: proc.wait() finally: if proc.stdout: proc.stdout.close() proc_had_error = proc.returncode and proc.returncode not in extra_ok_returncodes if proc_had_error: if not showing_subprocess and log_failed_cmd: # Then the subprocess streams haven't been logged to the # console yet. msg = make_subprocess_output_error( cmd_args=cmd, cwd=cwd, lines=all_output, exit_status=proc.returncode, ) subprocess_logger.error(msg) exc_msg = ( "Command errored out with exit status {}: {} " "Check the logs for full command output." ).format(proc.returncode, command_desc) raise SubProcessError(exc_msg) return "".join(all_output)