def test_executable_relative(): """Validate |Executable| accepts relative paths.""" if platform.system() != "Windows": scripty_path = "./scripty.sh" with open(scripty_path, "w") as scripty: scripty.write("#!/bin/sh\necho 'hi, my name is scripty :)'\n") chmod = which("chmod", log_calls=False) chmod("+x", scripty_path) scripty = Executable(scripty_path, log_calls=False) proc = scripty(stdout=PIPE, stderr=PIPE) assert proc.returncode == 0 assert proc.stderr == b"" assert proc.stdout.decode("utf-8") == "hi, my name is scripty :)\n" rm_rf(scripty_path)
def test_executable_failures(capsys): """Validate failing executables error as expected.""" git = which("git", log_calls=False) # By default, failed invocations terminate. Make sure this happens. # NOTE: as with test_executable_logging, capsys doesn't capture subprocess.run. # Check for `git` failure message in non-terminating tests below where we capture # using PIPE. pipe = {"stdout": PIPE, "stderr": PIPE} with pytest.raises(SystemExit) as se_excinfo: git("log", "--petty=%B", **pipe) captured = capsys.readouterr() assert re.match(r".*non-zero exit status (\d+)\.?", captured.err).group(1) == "128" assert se_excinfo.value.code == 128 # Using check=False tells subprocess.run not to raise an Exception. proc = git("log", "--petty=%B", check=False, **pipe) assert proc.returncode == 128 assert proc.stdout == b"" assert b"fatal" in proc.stderr assert b"unrecognized argument" in proc.stderr assert b"--petty=%B" in proc.stderr # Clear capsys before this test. captured = capsys.readouterr() # Test that invalid kwargs to subprocess.run fail. with pytest.raises(SystemExit) as se_excinfo: git("status", not_valid_subprocess_kwarg=True) assert se_excinfo.value.code == 1 captured = capsys.readouterr() assert captured.out == "" assert "Executable.__call__: invalid kwarg(s) for subprocess.run" in captured.err assert "unexpected keyword argument 'not_valid_subprocess_kwarg'" in captured.err
def test_which(capsys): """Validate that |which| finds or does not find executables.""" # Make sure ci_exec.core.which and shutil.which agree (how could then not? xD). git = which("git") git_path = shutil.which("git") assert git.exe_path == git_path # This command should not exist. Right? no_cmd = "ja" * 22 with pytest.raises(SystemExit) as se_excinfo: which(no_cmd) assert se_excinfo.value.code == 1 captured = capsys.readouterr() assert captured.out == "" prefix = colorize("[X] ", color=Colors.Red, style=Styles.Bold) expected_error_message = "{prefix}Could not find '{no_cmd}' in $PATH.\n".format( prefix=prefix, no_cmd=no_cmd) assert captured.err == expected_error_message # Test manual $PATH override / make sure same python is found. actual_python = Path(sys.executable) python_name = actual_python.name python_dir = str(actual_python.parent) python = which(python_name, path=python_dir, log_calls=False) assert python.exe_path == str(actual_python) # Throwing in the __str__ test here because it doesn't deserve its own test method. assert str(python) == "Executable('{py}')".format(py=str(actual_python)) proc = python("-c", "import sys; print(sys.version_info)", stdout=PIPE, stderr=PIPE) assert proc.returncode == 0 assert proc.stderr == b"" assert proc.stdout.decode("utf-8").strip() == "{v}".format( v=sys.version_info) # :) with pytest.raises(TypeError) as te_excinfo: which("git", log_callz=False) assert "unexpected keyword argument 'log_callz'" in str(te_excinfo.value)
def test_executable_logging(capsys): """Validate |Executable| runs and logs as expected.""" # NOTE: capsys is not able to capture subprocess.run() output directly, so what we # do instead is run with PIPEs and print it to simulate how it would run normally. pipe = {"stdout": PIPE, "stderr": PIPE} def run_and_print(exe: Executable, *args, **kwargs) -> str: """Run the executable and print to stdout / stderr, return expected logging.""" proc = exe(*args, **kwargs) assert proc.returncode == 0 print(proc.stderr.decode("utf-8"), file=sys.stderr, end="") print(proc.stdout.decode("utf-8"), end="") if exe.log_calls: popen_args = (exe.exe_path, *args) message = f"{exe.log_prefix}{' '.join(popen_args)}" if exe.log_color: message = colorize(message, color=exe.log_color, style=exe.log_style) return message else: return "" def startswith(out: str, prefix: str, color: str, style: str) -> bool: """External check for cross-validating run_and_print.""" colored_prefix = colorize(prefix, color=color, style=style) colored = colored_prefix.split(Ansi.Clear)[0] return out.startswith(colored) git = which("git") log_template = "{logged}\norigin\n" # Test default logging (bold cyan). logged = run_and_print(git, "remote", **pipe) captured = capsys.readouterr() assert captured.err == "" assert captured.out == log_template.format(logged=logged) assert startswith(captured.out, "$ ", Colors.Cyan, Styles.Bold) # Test custom log color. git.log_color = Colors.Magenta logged = run_and_print(git, "remote", **pipe) captured = capsys.readouterr() assert captured.err == "" assert captured.out == log_template.format(logged=logged) assert startswith(captured.out, "$ ", Colors.Magenta, Styles.Bold) # Test custom log style. git.log_style = Styles.BoldInverted logged = run_and_print(git, "remote", **pipe) captured = capsys.readouterr() assert captured.err == "" assert captured.out == log_template.format(logged=logged) assert startswith(captured.out, "$ ", Colors.Magenta, Styles.BoldInverted) # Test custom log prefix. git.log_prefix = ">>> " logged = run_and_print(git, "remote", **pipe) captured = capsys.readouterr() assert captured.err == "" assert captured.out == log_template.format(logged=logged) assert startswith(captured.out, ">>> ", Colors.Magenta, Styles.BoldInverted) # Test log without colors. git.log_color = None logged = run_and_print(git, "remote", **pipe) captured = capsys.readouterr() assert captured.err == "" assert captured.out.startswith(">>> ") # Test log turned off only outputs command. git.log_calls = False run_and_print(git, "remote", **pipe) captured = capsys.readouterr() assert captured.err == "" assert captured.out == "origin\n"