import logging import os import re import shlex import subprocess # # Helper functions. # # Get exit code or other stored data about the latest command run by # runcmd_run. def _runcmd_get(ctx, name): ns = ctx.declare("_runcmd") return ns[name] def runcmd_get_exit_code(ctx): return _runcmd_get(ctx, "exit") def runcmd_get_stdout(ctx): return _runcmd_get(ctx, "stdout") def runcmd_get_stdout_raw(ctx): return _runcmd_get(ctx, "stdout.raw") def runcmd_get_stderr(ctx): return _runcmd_get(ctx, "stderr") def runcmd_get_stderr_raw(ctx): return _runcmd_get(ctx, "stderr.raw") def runcmd_get_argv(ctx): return _runcmd_get(ctx, "argv") # Run a command, given an argv and other arguments for subprocess.Popen. # # This is meant to be a helper function, not bound directly to a step. The # stdout, stderr, and exit code are stored in the "_runcmd" namespace in the # ctx context. def runcmd_run(ctx, argv, **kwargs): log_value = globals()["log_value"] ns = ctx.declare("_runcmd") # The Subplot Python template empties os.environ at startup, modulo a small # number of variables with carefully chosen values. Here, we don't need to # care about what those variables are, but we do need to not overwrite # them, so we just add anything in the env keyword argument, if any, to # os.environ. env = dict(os.environ) for key, arg in kwargs.pop("env", {}).items(): env[key] = arg pp = ns.get("path-prefix") if pp: env["PATH"] = pp + ":" + env["PATH"] kwargs["stdout"] = subprocess.PIPE kwargs["stderr"] = subprocess.PIPE logging.debug("runcmd_run: running command") log_value("argv", 1, dict(enumerate(argv))) log_value("kwargs:", 1, kwargs) p = subprocess.Popen(argv, env=env, **kwargs) stdout, stderr = p.communicate("") ns["argv"] = argv ns["stdout.raw"] = stdout ns["stderr.raw"] = stderr ns["stdout"] = stdout.decode("utf-8") ns["stderr"] = stderr.decode("utf-8") ns["exit"] = p.returncode log_value("ns", 1, ns.as_dict()) # Step: prepend srcdir to PATH whenever runcmd runs a command. def runcmd_helper_srcdir_path(ctx): srcdir = globals()["srcdir"] runcmd_prepend_to_path(ctx, srcdir) # Step: This creates a helper script. def runcmd_helper_script(ctx, script=None): get_file = globals()["get_file"] with open(script, "wb") as f: f.write(get_file(script)) # # Step functions for running commands. # def runcmd_prepend_to_path(ctx, dirname=None): ns = ctx.declare("_runcmd") pp = ns.get("path-prefix", "") if pp: pp = f"{pp}:{dirname}" else: pp = dirname ns["path-prefix"] = pp def runcmd_step(ctx, argv0=None, args=None): runcmd_try_to_run(ctx, argv0=argv0, args=args) runcmd_exit_code_is_zero(ctx) def runcmd_step_in(ctx, dirname=None, argv0=None, args=None): runcmd_try_to_run_in(ctx, dirname=dirname, argv0=argv0, args=args) runcmd_exit_code_is_zero(ctx) def runcmd_try_to_run(ctx, argv0=None, args=None): runcmd_try_to_run_in(ctx, dirname=None, argv0=argv0, args=args) def runcmd_try_to_run_in(ctx, dirname=None, argv0=None, args=None): argv = [shlex.quote(argv0)] + shlex.split(args) runcmd_run(ctx, argv, cwd=dirname) # # Step functions for examining exit codes. # def runcmd_exit_code_is_zero(ctx): runcmd_exit_code_is(ctx, exit=0) def runcmd_exit_code_is(ctx, exit=None): assert_eq = globals()["assert_eq"] assert_eq(runcmd_get_exit_code(ctx), int(exit)) def runcmd_exit_code_is_nonzero(ctx): runcmd_exit_code_is_not(ctx, exit=0) def runcmd_exit_code_is_not(ctx, exit=None): assert_ne = globals()["assert_ne"] assert_ne(runcmd_get_exit_code(ctx), int(exit)) # # Step functions and helpers for examining output in various ways. # def runcmd_stdout_is(ctx, text=None): _runcmd_output_is(runcmd_get_stdout(ctx), text) def runcmd_stdout_isnt(ctx, text=None): _runcmd_output_isnt(runcmd_get_stdout(ctx), text) def runcmd_stderr_is(ctx, text=None): _runcmd_output_is(runcmd_get_stderr(ctx), text) def runcmd_stderr_isnt(ctx, text=None): _runcmd_output_isnt(runcmd_get_stderr(ctx), text) def _runcmd_output_is(actual, wanted): assert_eq = globals()["assert_eq"] log_lines = globals()["log_lines"] indent = " " * 4 wanted = bytes(wanted, "utf8").decode("unicode_escape") logging.debug("_runcmd_output_is:") logging.debug(" actual:") log_lines(indent, actual) logging.debug(" wanted:") log_lines(indent, wanted) assert_eq(actual, wanted) def _runcmd_output_isnt(actual, wanted): assert_ne = globals()["assert_ne"] log_lines = globals()["log_lines"] indent = " " * 4 wanted = bytes(wanted, "utf8").decode("unicode_escape") logging.debug("_runcmd_output_isnt:") logging.debug(" actual:") log_lines(indent, actual) logging.debug(" wanted:") log_lines(indent, wanted) assert_ne(actual, wanted) def runcmd_stdout_contains(ctx, text=None): _runcmd_output_contains(runcmd_get_stdout(ctx), text) def runcmd_stdout_doesnt_contain(ctx, text=None): _runcmd_output_doesnt_contain(runcmd_get_stdout(ctx), text) def runcmd_stderr_contains(ctx, text=None): _runcmd_output_contains(runcmd_get_stderr(ctx), text) def runcmd_stderr_doesnt_contain(ctx, text=None): _runcmd_output_doesnt_contain(runcmd_get_stderr(ctx), text) def _runcmd_output_contains(actual, wanted): assert_eq = globals()["assert_eq"] log_lines = globals()["log_lines"] indent = " " * 4 wanted = bytes(wanted, "utf8").decode("unicode_escape") logging.debug("_runcmd_output_contains:") logging.debug(" actual:") log_lines(indent, actual) logging.debug(" wanted:") log_lines(indent, wanted) assert_eq(wanted in actual, True) def _runcmd_output_doesnt_contain(actual, wanted): assert_ne = globals()["assert_ne"] log_lines = globals()["log_lines"] indent = " " * 4 wanted = bytes(wanted, "utf8").decode("unicode_escape") logging.debug("_runcmd_output_doesnt_contain:") logging.debug(" actual:") log_lines(indent, actual) logging.debug(" wanted:") log_lines(indent, wanted) assert_ne(wanted in actual, True) def runcmd_stdout_matches_regex(ctx, regex=None): _runcmd_output_matches_regex(runcmd_get_stdout(ctx), regex) def runcmd_stdout_doesnt_match_regex(ctx, regex=None): _runcmd_output_doesnt_match_regex(runcmd_get_stdout(ctx), regex) def runcmd_stderr_matches_regex(ctx, regex=None): _runcmd_output_matches_regex(runcmd_get_stderr(ctx), regex) def runcmd_stderr_doesnt_match_regex(ctx, regex=None): _runcmd_output_doesnt_match_regex(runcmd_get_stderr(ctx), regex) def _runcmd_output_matches_regex(actual, regex): assert_ne = globals()["assert_ne"] log_lines = globals()["log_lines"] indent = " " * 4 r = re.compile(regex) m = r.search(actual) logging.debug("_runcmd_output_matches_regex:") logging.debug(f" actual: {actual!r}") log_lines(indent, actual) logging.debug(f" regex: {regex!r}") logging.debug(f" match: {m}") assert_ne(m, None) def _runcmd_output_doesnt_match_regex(actual, regex): assert_eq = globals()["assert_eq"] log_lines = globals()["log_lines"] indent = " " * 4 r = re.compile(regex) m = r.search(actual) logging.debug("_runcmd_output_doesnt_match_regex:") logging.debug(f" actual: {actual!r}") log_lines(indent, actual) logging.debug(f" regex: {regex!r}") logging.debug(f" match: {m}") assert_eq(m, None)