def checked_run(argv):
     """
     Run a process with the given arguments. Log its output and raise an
     error if it fails.
     """
     p = Run(argv)
     if p.status != 0:
         logging.error('Command failed: %s',
                       ' '.join(quote_arg(arg) for arg in argv))
         logging.error('Output:\n' + p.out)
         raise RuntimeError
    def setup_result_dir(self):
        """Create the output directory in which the results are stored."""
        if os.path.isdir(self.old_output_dir):
            rm(self.old_output_dir, True)
        if os.path.isdir(self.output_dir):
            mv(self.output_dir, self.old_output_dir)
        mkdir(self.output_dir)

        if self.main.args.dump_environ:
            with open(os.path.join(self.output_dir, 'environ.sh'), 'w') as f:
                for var_name in sorted(os.environ):
                    f.write('export %s=%s\n' %
                            (var_name, quote_arg(os.environ[var_name])))
Beispiel #3
0
    def setup_result_dir(self) -> None:
        """Create the output directory in which the results are stored."""
        assert self.main.args

        if os.path.isdir(self.old_output_dir):
            rm(self.old_output_dir, True)
        if os.path.isdir(self.output_dir):
            mv(self.output_dir, self.old_output_dir)
        mkdir(self.output_dir)

        if self.main.args.dump_environ:
            with open(os.path.join(self.output_dir, "environ.sh"), "w") as f:
                for var_name in sorted(os.environ):
                    f.write("export {}={}\n".format(
                        var_name, quote_arg(os.environ[var_name])))
Beispiel #4
0
def run_alr(*args, **kwargs):
    """
    Run "alr" with the given arguments.

    :param bool complain_on_error: If true and the subprocess exits with a
        non-zero status code, print information on the standard output (for
        debugging) and raise a CalledProcessError (to abort the test).
    :param bool quiet: If true (which is the default), append "-q" to the
        command line.
    :param bool debug: If true (which is the default), append "-d" to the
        command line. This ensures uncaught exceptions are logged instead
        of presenting a sanitized error intended for final users.
    :rtype: ProcessResult
    """

    complain_on_error = kwargs.pop('complain_on_error', True)
    debug = kwargs.pop('debug', True)
    force = kwargs.pop('force', False)
    quiet = kwargs.pop('quiet', True)
    if kwargs:
        first_unknown_kwarg = sorted(kwargs)[0]
        raise ValueError('Invalid argument: {}'.format(first_unknown_kwarg))

    argv = ['alr']
    argv.insert(1, '-n')  # always non-interactive
    if debug:
        argv.insert(1, '-d')
    if force:
        argv.insert(1, '-f')
    if quiet:
        argv.insert(1, '-q')
    argv.extend(args)
    p = Run(argv)
    if p.status != 0 and complain_on_error:
        print('The following command:')
        print('  {}'.format(' '.join(quote_arg(arg) for arg in argv)))
        print('Exitted with status code {}'.format(p.status))
        print('Output:')
        print(p.out)
        raise CalledProcessError('alr returned non-zero status code')

    # Convert CRLF line endings (Windows-style) to LF (Unix-style). This
    # canonicalization is necessary to make output comparison work on all
    # platforms.
    return ProcessResult(p.status, p.out.replace('\r\n', '\n'))
Beispiel #5
0
    def setup_result_dirs(self) -> None:
        """Create the output directory in which the results are stored."""
        assert self.main.args
        args = self.main.args

        # Both the actual new/old directories to use depend on both
        # --output-dir and --old-output-dir options.
        d = os.path.abspath(args.output_dir)
        if args.old_output_dir:
            self.output_dir = d
            old_output_dir = os.path.abspath(args.old_output_dir)
        else:
            self.output_dir = os.path.join(d, "new")
            old_output_dir = os.path.join(d, "old")

        # Rotate results directories if requested. In both cases, make sure the
        # new results dir is clean.
        if args.rotate_output_dirs:
            if os.path.isdir(old_output_dir):
                rm(old_output_dir, recursive=True)
            if os.path.isdir(self.output_dir):
                mv(self.output_dir, old_output_dir)
        elif os.path.isdir(self.output_dir):
            rm(self.output_dir, recursive=True)
        mkdir(self.output_dir)

        # Remember about the old output directory only if it exists and does
        # contain results. If not, this info will be unused at best, or lead to
        # incorrect behavior.
        self.old_output_dir = None
        if (os.path.exists(old_output_dir) and os.path.exists(
                os.path.join(old_output_dir, ReportIndex.INDEX_FILENAME))):
            self.old_output_dir = old_output_dir

        if args.dump_environ:
            with open(os.path.join(self.output_dir, "environ.sh"), "w") as f:
                for var_name in sorted(os.environ):
                    f.write("export {}={}\n".format(
                        var_name, quote_arg(os.environ[var_name])))
Beispiel #6
0
 def write_comment_file(self, comment_file: IO[str]) -> None:
     # Sensible default: just write the command line used to run the
     # testsuite. Testsuites can override or reuse this.
     quoted_cmdline = " ".join(quote_arg(arg) for arg in sys.argv)
     comment_file.write(f"Testsuite options:" f"\n  {quoted_cmdline}" f"\n")
Beispiel #7
0
    def shell(self,
              args: List[str],
              cwd: Optional[str] = None,
              env: Optional[Dict[str, str]] = None,
              catch_error: bool = True,
              analyze_output: bool = True,
              timeout: Optional[int] = None,
              encoding: Optional[str] = None,
              truncate_logs_threshold: Optional[int] = None) -> ProcessResult:
        """Run a subprocess.

        :param args: Arguments for the subprocess to run.
        :param cwd: Current working directory for the subprocess. By default
            (i.e. if None), use the test working directory.
        :param env: Environment to pass to the subprocess.
        :param catch_error: If True, consider that an error status code leads
            to a test failure. In that case, abort the testcase.
        :param analyze_output: If True, add the subprocess output to the
            ``self.output`` log.
        :param timeout: Timeout (in seconds) for the subprocess. Use
            ``self.default_timeout`` if left to None.
        :param encoding: Encoding to use when decoding the subprocess' output
            stream. If None, use the default enocding for this test
            (``self.default_encoding``, from the ``encoding`` entry in
            test.yaml).  If "binary", leave the output undecoded as a bytes
            string.
        :param truncate_logs_threshold: Threshold to truncate the subprocess
            output in ``self.result.log``. See
            ``e3.testsuite.result.truncated``'s ``line_count`` argument. If
            left to None, use the testsuite's ``--truncate-logs`` option.
        """
        # By default, run the subprocess in the test working directory
        if cwd is None:
            cwd = self.test_env["working_dir"]

        if timeout is None:
            timeout = self.default_process_timeout

        if truncate_logs_threshold is None:
            truncate_logs_threshold = self.testsuite_options.truncate_logs

        # Run the subprocess and log it
        def format_header(label: str, value: Any) -> str:
            return "{}{}{}: {}{}\n".format(
                self.Style.RESET_ALL + self.Style.BRIGHT,
                label,
                self.Style.RESET_ALL,
                self.Style.DIM,
                value,
            )

        self.result.log += format_header(
            "Running",
            "{} (cwd={}{}{})".format(" ".join(quote_arg(a) for a in args),
                                     self.Style.RESET_ALL, cwd,
                                     self.Style.DIM))

        process_info = {"cmd": args, "cwd": cwd}
        self.result.processes.append(process_info)

        # Python2's subprocess module does not handle timeout, so re-implement
        # e3.os.process's rlimit-based implementation of timeouts.
        if timeout is not None:
            args = [get_rlimit(), str(timeout)] + args

        # We cannot use e3.os.process.Run as this API forces the use of text
        # streams, whereas testsuite sometimes need to deal with binary data
        # (or unknown encodings, which is equivalent).
        subp = subprocess.Popen(args,
                                cwd=cwd,
                                env=env,
                                stdin=subprocess.DEVNULL,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT)
        stdout: Union[str, bytes]
        stdout, _ = subp.communicate()
        assert isinstance(stdout, bytes)
        encoding = encoding or self.default_encoding
        if encoding != "binary":
            try:
                stdout = stdout.decode(encoding)
            except UnicodeDecodeError as exc:
                raise TestAbortWithError(
                    "cannot decode process output ({}: {})".format(
                        type(exc).__name__, exc))

        p = ProcessResult(subp.returncode, stdout)

        self.result.log += format_header("Status code", p.status)
        process_info["status"] = p.status
        process_info["output"] = Log(stdout)

        self.result.log += format_header(
            "Output", "\n" +
            truncated(str(process_info["output"]), truncate_logs_threshold))

        # If requested, use its output for analysis
        if analyze_output:
            self.output += stdout

        if catch_error and p.status != 0:
            raise TestAbortWithFailure("non-zero status code")

        return p
Beispiel #8
0
    def shell(
        self,
        args: List[str],
        cwd: Optional[str] = None,
        env: Optional[Dict[str, str]] = None,
        catch_error: bool = True,
        analyze_output: bool = True,
        timeout: Optional[int] = None,
        encoding: Optional[str] = None,
        truncate_logs_threshold: Optional[int] = None,
        ignore_environ: bool = True,
    ) -> ProcessResult:
        """Run a subprocess.

        :param args: Arguments for the subprocess to run.
        :param cwd: Current working directory for the subprocess. By default
            (i.e. if None), use the test working directory.
        :param env: Environment to pass to the subprocess.
        :param catch_error: If True, consider that an error status code leads
            to a test failure. In that case, abort the testcase.
        :param analyze_output: If True, add the subprocess output to the
            ``self.output`` log.
        :param timeout: Timeout (in seconds) for the subprocess. Use
            ``self.default_timeout`` if left to None.
        :param encoding: Encoding to use when decoding the subprocess' output
            stream. If None, use the default enocding for this test
            (``self.default_encoding``, from the ``encoding`` entry in
            test.yaml).  If "binary", leave the output undecoded as a bytes
            string.
        :param truncate_logs_threshold: Threshold to truncate the subprocess
            output in ``self.result.log``. See
            ``e3.testsuite.result.truncated``'s ``line_count`` argument. If
            left to None, use the testsuite's ``--truncate-logs`` option.
        :param ignore_environ: Applies only when ``env`` is not None.
            When True (the default), pass exactly environment variables
            in ``env``. When False, pass a copy of ``os.environ`` that is
            augmented with variables in ``env``.
        """
        # By default, run the subprocess in the test working directory
        if cwd is None:
            cwd = self.test_env["working_dir"]

        if timeout is None:
            timeout = self.default_process_timeout

        if truncate_logs_threshold is None:
            truncate_logs_threshold = self.testsuite_options.truncate_logs

        # Run the subprocess and log it
        def format_header(label: str, value: Any) -> str:
            return "{}{}{}: {}{}\n".format(
                self.Style.RESET_ALL + self.Style.BRIGHT,
                label,
                self.Style.RESET_ALL,
                self.Style.DIM,
                value,
            )

        self.result.log += format_header(
            "Running",
            "{} (cwd={}{}{})".format(
                " ".join(quote_arg(a) for a in args),
                self.Style.RESET_ALL,
                cwd,
                self.Style.DIM,
            ),
        )

        process_info = {"cmd": args, "cwd": cwd}
        self.result.processes.append(process_info)

        subp = Run(
            cmds=args,
            cwd=cwd,
            output=PIPE,
            error=STDOUT,
            input=DEVNULL,
            timeout=timeout,
            env=env,
            ignore_environ=ignore_environ,
        )

        # Testsuites sometimes need to deal with binary data (or unknown
        # encodings, which is equivalent), so always use subp.raw_out.
        stdout: Union[str, bytes]
        stdout = subp.raw_out
        assert isinstance(stdout, bytes)
        encoding = encoding or self.default_encoding
        if encoding != "binary":
            try:
                stdout = stdout.decode(encoding)
            except UnicodeDecodeError as exc:
                raise TestAbortWithError(
                    "cannot decode process output ({}: {})".format(
                        type(exc).__name__, exc))

        # We run subprocesses in foreground mode, so by the time Run's
        # constructor has returned, the subprocess is supposed to have
        # completed, and thus we are supposed to have an exit status code.
        assert subp.status is not None
        p = ProcessResult(subp.status, stdout)

        self.result.log += format_header("Status code", p.status)
        process_info["status"] = p.status
        process_info["output"] = Log(stdout)

        self.result.log += format_header(
            "Output",
            "\n" +
            truncated(str(process_info["output"]), truncate_logs_threshold),
        )

        # If requested, use its output for analysis
        if analyze_output:
            self.output += stdout

        if catch_error and p.status != 0:
            raise TestAbortWithFailure("non-zero status code")

        return p