def cleanup_for_venv(venv): within_parallel = PARALLEL_ENV_VAR_KEY in os.environ # if the directory exists and it doesn't look like a virtualenv, produce # an error if venv.path.exists(): dir_items = set(os.listdir(str(venv.path))) - {".lock", "log"} dir_items = {p for p in dir_items if not p.startswith(".tox-")} else: dir_items = set() if not ( # doesn't exist => OK not venv.path.exists() # does exist, but it's empty => OK or not dir_items # it exists and we're on windows with Lib and Scripts => OK or (INFO.IS_WIN and dir_items > {"Scripts", "Lib"}) # non-windows, with lib and bin => OK or dir_items > {"bin", "lib"}): venv.status = "error" reporter.error( "cowardly refusing to delete `envdir` (it does not look like a virtualenv): " "{}".format(venv.path)) raise SystemExit(2) if within_parallel: if venv.path.exists(): # do not delete the log folder as that's used by parent for content in venv.path.listdir(): if not content.basename == "log": content.remove(rec=1, ignore_errors=True) else: ensure_empty_dir(venv.path)
def tox_testenv_create(venv, action): """ Python is already installed, we just need to handle dependency installs, there are two major phases, we take care of 1 here: 1. extend base image -> create env image 1. install pip/setuptools/wheel 2. install dependencies 3. develop -> copy folder - install via -e non-develop -> copy sdist - install 2. run: 1. start container from env image and mount {toxinidir} under 2. install package (either develop or sdist) 3. run commands one by one 4. stop and remove container """ with safe_package_view(venv) as context: cwd = os.getcwd() os.chdir(context) try: image_id = build_image(venv, context, action) except Exception as exception: reporter.error(f"could not build image {exception}") # pragma: no cover raise # pragma: no cover finally: os.chdir(cwd) image = set_docker_image_tag(image_id, venv) if venv.envconfig.image is not None: # already had a previous image (e.g. different python) try: CLIENT.images.remove(image=venv.envconfig.image.id, force=True) except Exception as exception: # pragma: no cover reporter.warning(f"could not delete image {exception}") # pragma: no cover venv.envconfig.image = image return True
def get_build_info(folder): toml_file = folder.join("pyproject.toml") # as per https://www.python.org/dev/peps/pep-0517/ def abort(message): reporter.error("{} inside {}".format(message, toml_file)) raise SystemExit(1) if not toml_file.exists(): reporter.error("missing {}".format(toml_file)) raise SystemExit(1) config_data = get_py_project_toml(toml_file) if "build-system" not in config_data: abort("build-system section missing") build_system = config_data["build-system"] if "requires" not in build_system: abort("missing requires key at build-system section") if "build-backend" not in build_system: abort("missing build-backend key at build-system section") requires = build_system["requires"] if not isinstance(requires, list) or not all( isinstance(i, six.text_type) for i in requires): abort("requires key at build-system section must be a list of string") backend = build_system["build-backend"] if not isinstance(backend, six.text_type): abort("build-backend key at build-system section must be a string") args = backend.split(":") module = args[0] obj = args[1] if len(args) > 1 else "" backend_paths = build_system.get("backend-path", []) if not isinstance(backend_paths, list): abort( "backend-path key at build-system section must be a list, if specified" ) backend_paths = [folder.join(p) for p in backend_paths] normalized_folder = os.path.normcase(str(folder.realpath())) normalized_paths = (os.path.normcase(str(path.realpath())) for path in backend_paths) if not all( os.path.commonprefix((normalized_folder, path)) == normalized_folder for path in normalized_paths): abort("backend-path must exist in the project root") return BuildInfo(requires, module, obj, backend_paths)
def tox_runtest(venv, redirect): _init_pipenv_environ() pipfile_path = _clone_pipfile(venv) action = venv.new_action("runtests") with wrap_pipenv_environment(venv, pipfile_path): action.setactivity( "runtests", "PYTHONHASHSEED=%r" % os.environ.get("PYTHONHASHSEED") ) for i, argv in enumerate(venv.envconfig.commands): # have to make strings as _pcall changes argv[0] to a local() # happens if the same environment is invoked twice cwd = venv.envconfig.changedir message = "commands[%s] | %s" % (i, " ".join([str(x) for x in argv])) action.setactivity("runtests", message) # check to see if we need to ignore the return code # if so, we need to alter the command line arguments if argv[0].startswith("-"): ignore_ret = True if argv[0] == "-": del argv[0] else: argv[0] = argv[0].lstrip("-") else: ignore_ret = False args = [sys.executable, "-m", "pipenv", "run"] + argv try: venv._pcall( args, venv=False, cwd=cwd, action=action, redirect=redirect, ignore_ret=ignore_ret ) except tox.exception.InvocationError as err: if venv.envconfig.ignore_outcome: reporter.warning( "command failed but result from testenv is ignored\n" " cmd: %s" % (str(err),) ) venv.status = "ignored failed command" continue # keep processing commands reporter.error(str(err)) venv.status = "commands failed" if not venv.envconfig.ignore_errors: break # Don't process remaining commands except KeyboardInterrupt: venv.status = "keyboardinterrupt" reporter.error(venv.status) raise return True
def run_system_cmd(cmd): """Helper to report running command and also to actually run the command""" reporter.line("external_build: running command: {}".format(cmd)) p_cmd = Popen(cmd, shell=True, stderr=STDOUT) stdout, _ = p_cmd.communicate() if p_cmd.returncode != 0: reporter.error( "external_build returned: {} stdout+stderr: {}".format( p_cmd.returncode, stdout)) raise ExternalBuildNonZeroReturn( "'{}' exited with return code: {}".format( cmd, p_cmd.returncode))
def setupenv(self): if self.envconfig._missing_subs: self.status = ( "unresolvable substitution(s):\n {}\n" "Environment variables are missing or defined recursively.".format( "\n ".join( [ "{}: '{}'".format(section_key, exc.name) for section_key, exc in sorted(self.envconfig._missing_subs.items()) ], ), ) ) return if not self.matching_platform(): self.status = "platform mismatch" return # we simply omit non-matching platforms with self.new_action("getenv", self.envconfig.envdir) as action: self.status = 0 default_ret_code = 1 envlog = self.env_log try: status = self.update(action=action) except IOError as e: if e.args[0] != 2: raise status = ( "Error creating virtualenv. Note that spaces in paths are " "not supported by virtualenv. Error details: {!r}".format(e) ) except tox.exception.InvocationError as e: status = e except tox.exception.InterpreterNotFound as e: status = e if self.envconfig.config.option.skip_missing_interpreters == "true": default_ret_code = 0 except KeyboardInterrupt: self.status = "keyboardinterrupt" raise if status: str_status = str(status) command_log = envlog.get_commandlog("setup") command_log.add_command(["setup virtualenv"], str_status, default_ret_code) self.status = status if default_ret_code == 0: reporter.skip(str_status) else: reporter.error(str_status) return False command_path = self.getcommandpath("python") envlog.set_python_info(command_path) return True
def getvenv(self, name): if name in self.existing_venvs: return self.existing_venvs[name] env_config = self.config.envconfigs.get(name, None) if env_config is None: reporter.error("unknown environment {!r}".format(name)) raise LookupError(name) elif env_config.envdir == self.config.toxinidir: reporter.error("venv {!r} in {} would delete project".format(name, env_config.envdir)) raise tox.exception.ConfigError("envdir must not equal toxinidir") env_log = self.resultlog.get_envlog(name) venv = VirtualEnv(envconfig=env_config, popen=self.popen, env_log=env_log) self.existing_venvs[name] = venv return venv
def evaluate_cmd(self, input_file_handler, process, redirect): try: if self.generate_tox_log and not redirect: if process.stderr is not None: # prevent deadlock raise ValueError("stderr must not be piped here") # we read binary from the process and must write using a binary stream buf = getattr(sys.stdout, "buffer", sys.stdout) last_time = time.time() while True: # we have to read one byte at a time, otherwise there # might be no output for a long time with slow tests data = input_file_handler.read(1) if data: buf.write(data) if b"\n" in data or (time.time() - last_time) > 1: # we flush on newlines or after 1 second to # provide quick enough feedback to the user # when printing a dot per test buf.flush() last_time = time.time() elif process.poll() is not None: if process.stdout is not None: process.stdout.close() break else: time.sleep(0.1) # the seek updates internal read buffers input_file_handler.seek(0, 1) input_file_handler.close() out, _ = process.communicate() # wait to finish except KeyboardInterrupt as exception: reporter.error("got KeyboardInterrupt signal") main_thread = is_main_thread() while True: try: if main_thread: # spin up a new thread to disable further interrupt on main thread stopper = Thread(target=self.handle_interrupt, args=(process, )) stopper.start() stopper.join() else: self.handle_interrupt(process) except KeyboardInterrupt: continue break raise exception return out
def setupenv(self): if self.envconfig.missing_subs: self.status = ( "unresolvable substitution(s): {}. " "Environment variables are missing or defined recursively.". format(",".join( ["'{}'".format(m) for m in self.envconfig.missing_subs]))) return if not self.matching_platform(): self.status = "platform mismatch" return # we simply omit non-matching platforms with self.new_action("getenv", self.envconfig.envdir) as action: self.status = 0 default_ret_code = 1 envlog = self.env_log try: status = self.update(action=action) except IOError as e: if e.args[0] != 2: raise status = ( "Error creating virtualenv. Note that spaces in paths are " "not supported by virtualenv. Error details: {!r}".format( e)) except tox.exception.InvocationError as e: status = ( "Error creating virtualenv. Note that some special characters (e.g. ':' and " "unicode symbols) in paths are not supported by virtualenv. Error details: " "{!r}".format(e)) except tox.exception.InterpreterNotFound as e: status = e if self.envconfig.config.option.skip_missing_interpreters == "true": default_ret_code = 0 if status: str_status = str(status) command_log = envlog.get_commandlog("setup") command_log.add_command(["setup virtualenv"], str_status, default_ret_code) self.status = status if default_ret_code == 0: reporter.skip(str_status) else: reporter.error(str_status) return False command_path = self.getcommandpath("python") envlog.set_python_info(command_path) return True
def make_sdist(config, session): setup = config.setupdir.join("setup.py") pyproject = config.setupdir.join("pyproject.toml") setup_check = setup.check() if not setup_check and not pyproject.check(): reporter.error( "No pyproject.toml or setup.py file found. The expected locations are:\n" " {pyproject} or {setup}\n" "You can\n" " 1. Create one:\n" " https://tox.readthedocs.io/en/latest/example/package.html\n" " 2. Configure tox to avoid running sdist:\n" " https://tox.readthedocs.io/en/latest/example/general.html\n" " 3. Configure tox to use an isolated_build".format( pyproject=pyproject, setup=setup), ) raise SystemExit(1) if not setup_check: reporter.error( "pyproject.toml file found.\n" "To use a PEP 517 build-backend you are required to " "configure tox to use an isolated_build:\n" "https://tox.readthedocs.io/en/latest/example/package.html\n", ) raise SystemExit(1) with session.newaction("GLOB", "packaging") as action: action.setactivity("sdist-make", setup) ensure_empty_dir(config.distdir) build_log = action.popen( [ sys.executable, setup, "sdist", "--formats=zip", "--dist-dir", config.distdir, ], cwd=config.setupdir, returnout=True, ) reporter.verbosity2(build_log) try: return config.distdir.listdir()[0] except py.error.ENOENT: # check if empty or comment only data = [] with open(str(setup)) as fp: for line in fp: if line and line[0] == "#": continue data.append(line) if not "".join(data).strip(): reporter.error("setup.py is empty") raise SystemExit(1) reporter.error( "No dist directory found. Please check setup.py, e.g with:\n" " python setup.py sdist", ) raise SystemExit(1)
def _build_venvs(self): try: need_to_run = OrderedDict((v, self.getvenv(v)) for v in self._evaluated_env_list) try: venv_order = stable_topological_sort( OrderedDict((name, v.envconfig.depends) for name, v in need_to_run.items()), ) venvs = OrderedDict((v, need_to_run[v]) for v in venv_order) return venvs except ValueError as exception: reporter.error("circular dependency detected: {}".format(exception)) except LookupError: pass except tox.exception.ConfigError as exception: reporter.error(str(exception)) raise SystemExit(1)
def wheel_build_legacy(config, session, venv): setup = config.setupdir.join("setup.py") if not setup.check(): reporter.error( "No setup.py file found. The expected location is: {}".format( setup)) raise SystemExit(1) with session.newaction(venv.name, "packaging") as action: with patch(venv, "is_allowed_external", partial(wheel_is_allowed_external, venv=venv)): venv.update(action=action) if not (session.config.option.wheel_dirty or venv.envconfig.wheel_dirty): action.setactivity("wheel-make", "cleaning up build directory ...") ensure_empty_dir(config.setupdir.join("build")) ensure_empty_dir(config.distdir) venv.test( name="wheel-make", commands=[[ "python", setup, "bdist_wheel", "--dist-dir", config.distdir ]], redirect=False, ignore_outcome=False, ignore_errors=False, display_hash_seed=False, ) try: dists = config.distdir.listdir() except py.error.ENOENT: # check if empty or comment only data = [] with open(str(setup)) as fp: for line in fp: if line and line[0] == "#": continue data.append(line) if not "".join(data).strip(): reporter.error("setup.py is empty") raise SystemExit(1) reporter.error( "No dist directory found. Please check setup.py, e.g with:\n" " python setup.py bdist_wheel") raise SystemExit(1) else: if not dists: reporter.error( "No distributions found in the dist directory found. Please check setup.py, e.g with:\n" " python setup.py bdist_wheel") raise SystemExit(1) return dists[0]
def acquire_package(config, session): """acquire a source distribution (either by loading a local file or triggering a build)""" if not config.option.sdistonly and (config.sdistsrc or config.option.installpkg): path = get_local_package(config) else: try: path = build_package(config, session) except tox.exception.InvocationError as exception: error("FAIL could not package project - v = {!r}".format(exception)) return None sdist_file = config.distshare.join(path.basename) if sdist_file != path: info("copying new sdistfile to {!r}".format(str(sdist_file))) try: sdist_file.dirpath().ensure(dir=1) except py.error.Error: warning("could not copy distfile to {}".format(sdist_file.dirpath())) else: path.copy(sdist_file) return path
def start_container(image, mount_local=None, mount_to=None): try: container = CLIENT.containers.create( image, command=["sleep", "infinity"], auto_remove=False, detach=True, network_mode="host", volumes={mount_local: {"bind": str(mount_to), "mode": "Z"}} if mount_to and mount_local else None, ) except Exception as exception: reporter.error(repr(exception)) raise reporter.verbosity1( f"start container via {container.short_id} ({container.attrs['Name']}) based on {image}" ) container.start() return container
def main(args): setup_reporter(args) try: config = load_config(args) update_default_reporter(config.option.quiet_level, config.option.verbose_level) reporter.using("tox.ini: {}".format(config.toxinipath)) config.logdir.ensure(dir=1) ensure_empty_dir(config.logdir) with set_os_env_var("TOX_WORK_DIR", config.toxworkdir): retcode = build_session(config).runcommand() if retcode is None: retcode = 0 raise SystemExit(retcode) except KeyboardInterrupt: raise SystemExit(2) except (tox.exception.MinVersionError, tox.exception.MissingRequirement) as exception: reporter.error(str(exception)) raise SystemExit(1)
def wheel_build_pep517(config, session, venv): pyproject = config.setupdir.join("pyproject.toml") if not pyproject.check(): reporter.error( "No pyproject.toml file found. The expected location is: {}". format(pyproject)) raise SystemExit(1) with session.newaction(venv.name, "packaging") as action: venv.update(action=action) ensure_empty_dir(config.distdir) venv.test( name="wheel-make", commands=[[ "pip", "wheel", config.setupdir, "--no-deps", "--use-pep517", "--wheel-dir", config.distdir ]], redirect=False, ignore_outcome=False, ignore_errors=False, display_hash_seed=False, ) try: dists = config.distdir.listdir() except py.error.ENOENT: reporter.error( "No dist directory found. Please check pyproject.toml, e.g with:\n" " pip wheel . --use-pep517") raise SystemExit(1) else: if not dists: reporter.error( "No distributions found in the dist directory found. Please check pyproject.toml, e.g with:\n" " pip wheel . --use-pep517") raise SystemExit(1) return dists[0]
def get_build_info(folder): toml_file = folder.join("pyproject.toml") # as per https://www.python.org/dev/peps/pep-0517/ def abort(message): reporter.error("{} inside {}".format(message, toml_file)) raise SystemExit(1) if not toml_file.exists(): reporter.error("missing {}".format(toml_file)) raise SystemExit(1) config_data = get_py_project_toml(toml_file) if "build-system" not in config_data: abort("build-system section missing") build_system = config_data["build-system"] if "requires" not in build_system: abort("missing requires key at build-system section") if "build-backend" not in build_system: abort("missing build-backend key at build-system section") requires = build_system["requires"] if not isinstance(requires, list) or not all( isinstance(i, six.text_type) for i in requires ): abort("requires key at build-system section must be a list of string") backend = build_system["build-backend"] if not isinstance(backend, six.text_type): abort("build-backend key at build-system section must be a string") args = backend.split(":") module = args[0] obj = args[1] if len(args) > 1 else "" return BuildInfo(requires, module, obj)
def feed_stdin(self, fin, process, redirect): try: if self.generate_tox_log and not redirect: if process.stderr is not None: # prevent deadlock raise ValueError("stderr must not be piped here") # we read binary from the process and must write using a binary stream buf = getattr(sys.stdout, "buffer", sys.stdout) out = None last_time = time.time() while True: # we have to read one byte at a time, otherwise there # might be no output for a long time with slow tests data = fin.read(1) if data: buf.write(data) if b"\n" in data or (time.time() - last_time) > 1: # we flush on newlines or after 1 second to # provide quick enough feedback to the user # when printing a dot per test buf.flush() last_time = time.time() elif process.poll() is not None: if process.stdout is not None: process.stdout.close() break else: time.sleep(0.1) # the seek updates internal read buffers fin.seek(0, 1) fin.close() else: out, err = process.communicate() except KeyboardInterrupt: reporter.error("KEYBOARDINTERRUPT") process.wait() raise return out
def build_image(venv, context, action): create_docker_file(context, venv) # host -> Linux access local pip generator = CLIENT.api.build(path=str(context), rm=True, network_mode="host") image_id = None while True: output = None try: output = next(generator) message = "" for fragment in output.decode().split("\r\n"): if fragment: msg = json.loads(fragment) if "stream" in msg: msg = msg["stream"] match = re.search( r"(^Successfully built |sha256:)([0-9a-f]+)$", msg) if match: image_id = match.group(2) else: msg = fragment message += msg message = "".join(message).strip("\n") reporter.verbosity1(message) except StopIteration: reporter.info("Docker image build complete.") break except ValueError: # pragma: no cover reporter.error( "Error parsing output from docker image build: {}".format( output)) # pragma: no cover if image_id is None: raise InvocationError("docker image build failed") # pragma: no cover _BUILT_IMAGES.add(image_id) return image_id
def popen( self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False, capture_err=True, ): """this drives an interaction with a subprocess""" cmd_args = [str(x) for x in args] cmd_args_shell = " ".join(pipes.quote(i) for i in cmd_args) stream_getter = self._get_standard_streams(capture_err, cmd_args_shell, redirect, returnout) cwd = os.getcwd() if cwd is None else cwd with stream_getter as (fin, out_path, stderr, stdout): try: args = self._rewrite_args(cwd, args) process = self.via_popen( args, stdout=stdout, stderr=stderr, cwd=str(cwd), env=os.environ.copy() if env is None else env, universal_newlines=True, shell=False, ) except OSError as e: reporter.error( "invocation failed (errno {:d}), args: {}, cwd: {}".format( e.errno, cmd_args_shell, cwd)) raise reporter.log_popen(cwd, out_path, cmd_args_shell) output = self.feed_stdin(fin, process, redirect) exit_code = process.wait() if exit_code and not ignore_ret: invoked = " ".join(map(str, args)) if out_path: reporter.error( "invocation failed (exit code {:d}), logfile: {}".format( exit_code, out_path)) output = out_path.read() reporter.error(output) self.command_log.add_command(args, output, exit_code) raise InvocationError(invoked, exit_code, out_path) else: raise InvocationError(invoked, exit_code) if not output and out_path: output = out_path.read() self.command_log.add_command(args, output, exit_code) return output
def make_sdist(config, session): setup = config.setupdir.join("setup.py") if not setup.check(): reporter.error( "No setup.py file found. The expected location is:\n" " {}\n" "You can\n" " 1. Create one:\n" " https://packaging.python.org/tutorials/distributing-packages/#setup-py\n" " 2. Configure tox to avoid running sdist:\n" " https://tox.readthedocs.io/en/latest/example/general.html" "#avoiding-expensive-sdist".format(setup) ) raise SystemExit(1) with session.newaction("GLOB", "packaging") as action: action.setactivity("sdist-make", setup) ensure_empty_dir(config.distdir) build_log = action.popen( [sys.executable, setup, "sdist", "--formats=zip", "--dist-dir", config.distdir], cwd=config.setupdir, returnout=True, ) reporter.verbosity2(build_log) try: return config.distdir.listdir()[0] except py.error.ENOENT: # check if empty or comment only data = [] with open(str(setup)) as fp: for line in fp: if line and line[0] == "#": continue data.append(line) if not "".join(data).strip(): reporter.error("setup.py is empty") raise SystemExit(1) reporter.error( "No dist directory found. Please check setup.py, e.g with:\n" " python setup.py sdist" ) raise SystemExit(1)
def popen( self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False, capture_err=True, callback=None, report_fail=True, ): """this drives an interaction with a subprocess""" cwd = py.path.local() if cwd is None else cwd cmd_args = [str(x) for x in self._rewrite_args(cwd, args)] cmd_args_shell = " ".join(pipes.quote(i) for i in cmd_args) stream_getter = self._get_standard_streams( capture_err, cmd_args_shell, redirect, returnout, cwd, ) exit_code, output = None, None with stream_getter as (fin, out_path, stderr, stdout): try: process = self.via_popen( cmd_args, stdout=stdout, stderr=stderr, cwd=str(cwd), env=os.environ.copy() if env is None else env, universal_newlines=True, shell=False, creationflags=( subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0 # needed for Windows signal send ability (CTRL+C) ), ) except OSError as exception: exit_code = exception.errno else: if callback is not None: callback(process) reporter.log_popen(cwd, out_path, cmd_args_shell, process.pid) output = self.evaluate_cmd(fin, process, redirect) exit_code = process.returncode finally: if out_path is not None and out_path.exists(): lines = out_path.read_text("UTF-8").split("\n") # first three lines are the action, cwd, and cmd - remove it output = "\n".join(lines[3:]) try: if exit_code and not ignore_ret: if report_fail: msg = "invocation failed (exit code {:d})".format( exit_code) if out_path is not None: msg += ", logfile: {}".format(out_path) if not out_path.exists(): msg += " warning log file missing" reporter.error(msg) if out_path is not None and out_path.exists(): reporter.separator("=", "log start", Verbosity.QUIET) reporter.quiet(output) reporter.separator("=", "log end", Verbosity.QUIET) raise InvocationError(cmd_args_shell, exit_code, output) finally: self.command_log.add_command(cmd_args, output, exit_code) return output
def sigterm_handler(signum, frame): reporter.error("Got SIGTERM, handling it as a KeyboardInterrupt") raise KeyboardInterrupt()
def test( self, redirect=False, name="run-test", commands=None, ignore_outcome=None, ignore_errors=None, display_hash_seed=False, ): if commands is None: commands = self.envconfig.commands if ignore_outcome is None: ignore_outcome = self.envconfig.ignore_outcome if ignore_errors is None: ignore_errors = self.envconfig.ignore_errors with self.new_action(name) as action: cwd = self.envconfig.changedir if display_hash_seed: env = self._get_os_environ(is_test_command=True) # Display PYTHONHASHSEED to assist with reproducibility. action.setactivity( name, "PYTHONHASHSEED={!r}".format(env.get("PYTHONHASHSEED"))) for i, argv in enumerate(commands): # have to make strings as _pcall changes argv[0] to a local() # happens if the same environment is invoked twice message = "commands[{}] | {}".format( i, " ".join([pipes.quote(str(x)) for x in argv])) action.setactivity(name, message) # check to see if we need to ignore the return code # if so, we need to alter the command line arguments if argv[0].startswith("-"): ignore_ret = True if argv[0] == "-": del argv[0] else: argv[0] = argv[0].lstrip("-") else: ignore_ret = False try: self._pcall( argv, cwd=cwd, action=action, redirect=redirect, ignore_ret=ignore_ret, is_test_command=True, ) except tox.exception.InvocationError as err: if ignore_outcome: msg = "command failed but result from testenv is ignored\ncmd:" reporter.warning("{} {}".format(msg, err)) self.status = "ignored failed command" continue # keep processing commands reporter.error(str(err)) self.status = "commands failed" if not ignore_errors: break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" raise
def abort(message): reporter.error("{} inside {}".format(message, toml_file)) raise SystemExit(1)