""" noxfile ~~~~~~~ Nox configuration script """ # pylint: disable=resource-leakage,3rd-party-module-not-gated import contextlib import datetime import glob import gzip import json import os import pathlib import shutil import sqlite3 import sys import tarfile import tempfile import nox.command # fmt: off if __name__ == "__main__": sys.stderr.write( "Do not execute this file directly. Use nox instead, it will know how to handle this file\n" ) sys.stderr.flush() exit(1) # fmt: on import nox # isort:skip from nox.command import CommandFailed # isort:skip REPO_ROOT = pathlib.Path(__file__).resolve().parent ENV_FILE = REPO_ROOT / ".ci-env" if ENV_FILE.exists(): print("Found .ci-env file. Updating environment...", flush=True) for key, value in json.loads(ENV_FILE.read_text()).items(): print(f" {key}={value}", flush=True) os.environ[key] = value print("Deleting .ci-env file", flush=True) ENV_FILE.unlink() # Be verbose when running under a CI context CI_RUN = ( os.environ.get("JENKINS_URL") or os.environ.get("CI") or os.environ.get("DRONE") is not None ) PIP_INSTALL_SILENT = CI_RUN is False PRINT_TEST_SELECTION = os.environ.get("PRINT_TEST_SELECTION") if PRINT_TEST_SELECTION is None: PRINT_TEST_SELECTION = CI_RUN else: PRINT_TEST_SELECTION = PRINT_TEST_SELECTION == "1" PRINT_TEST_PLAN_ONLY = os.environ.get("PRINT_TEST_PLAN_ONLY", "0") == "1" PRINT_SYSTEM_INFO = os.environ.get("PRINT_SYSTEM_INFO") if PRINT_SYSTEM_INFO is None: PRINT_SYSTEM_INFO = CI_RUN else: PRINT_SYSTEM_INFO = PRINT_SYSTEM_INFO == "1" PRINT_SYSTEM_INFO_ONLY = os.environ.get("PRINT_SYSTEM_INFO_ONLY", "0") == "1" SKIP_REQUIREMENTS_INSTALL = os.environ.get("SKIP_REQUIREMENTS_INSTALL", "0") == "1" EXTRA_REQUIREMENTS_INSTALL = os.environ.get("EXTRA_REQUIREMENTS_INSTALL") COVERAGE_REQUIREMENT = os.environ.get("COVERAGE_REQUIREMENT") # Global Path Definitions REPO_ROOT = pathlib.Path(os.path.dirname(__file__)).resolve() ARTIFACTS_DIR = REPO_ROOT / "artifacts" COVERAGE_OUTPUT_DIR = ARTIFACTS_DIR / "coverage" COVERAGE_FILE = os.environ.get("COVERAGE_FILE") if COVERAGE_FILE is None: COVERAGE_FILE = str(COVERAGE_OUTPUT_DIR / ".coverage") IS_DARWIN = sys.platform.lower().startswith("darwin") IS_WINDOWS = sys.platform.lower().startswith("win") IS_FREEBSD = sys.platform.lower().startswith("freebsd") IS_LINUX = sys.platform.lower().startswith("linux") ONEDIR_ARTIFACT_PATH = ARTIFACTS_DIR / "salt" if IS_WINDOWS: ONEDIR_PYTHON_PATH = ONEDIR_ARTIFACT_PATH / "Scripts" / "python.exe" else: ONEDIR_PYTHON_PATH = ONEDIR_ARTIFACT_PATH / "bin" / "python3" # Python versions to run against _PYTHON_VERSIONS = ("3", "3.8", "3.9", "3.10", "3.11") # Nox options # Reuse existing virtualenvs nox.options.reuse_existing_virtualenvs = True # Change current directory to REPO_ROOT os.chdir(str(REPO_ROOT)) RUNTESTS_LOGFILE = ARTIFACTS_DIR.joinpath( "logs", "runtests-{}.log".format(datetime.datetime.now().strftime("%Y%m%d%H%M%S.%f")), ) # Prevent Python from writing bytecode os.environ["PYTHONDONTWRITEBYTECODE"] = "1" def session_warn(session, message): try: session.warn(message) except AttributeError: session.log(f"WARNING: {message}") def session_run_always(session, *command, **kwargs): """ Patch nox to allow running some commands which would be skipped if --install-only is passed. """ try: # Guess we weren't the only ones wanting this # https://github.com/theacodes/nox/pull/331 return session.run_always(*command, **kwargs) except AttributeError: old_install_only_value = session._runner.global_config.install_only try: # Force install only to be false for the following chunk of code # For additional information as to why see: # https://github.com/theacodes/nox/pull/181 session._runner.global_config.install_only = False return session.run(*command, **kwargs) finally: session._runner.global_config.install_only = old_install_only_value def find_session_runner(session, name, python_version, onedir=False, **kwargs): if onedir: name += f"-onedir-{ONEDIR_PYTHON_PATH}" else: name += f"-{python_version}" for s, _ in session._runner.manifest.list_all_sessions(): if name not in s.signatures: continue for signature in s.signatures: for key, value in kwargs.items(): param = f"{key}={value!r}" if param not in signature: break else: return s continue session.error( "Could not find a nox session by the name {!r} with the following keyword arguments: {!r}".format( name, kwargs ) ) def _create_ci_directories(): ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True) # Allow other users to write to this directory. # This helps when some tests run under a different name and yet # they need access to this path, for example, code coverage. ARTIFACTS_DIR.chmod(0o777) COVERAGE_OUTPUT_DIR.mkdir(exist_ok=True) COVERAGE_OUTPUT_DIR.chmod(0o777) ARTIFACTS_DIR.joinpath("xml-unittests-output").mkdir(exist_ok=True) def _get_session_python_version_info(session): try: version_info = session._runner._real_python_version_info except AttributeError: session_py_version = session_run_always( session, "python", "-c", 'import sys; sys.stdout.write("{}.{}.{}".format(*sys.version_info))', stderr=None, silent=True, log=False, ) version_info = tuple( int(part) for part in session_py_version.strip().split(".") if part.isdigit() ) session._runner._real_python_version_info = version_info return version_info def _get_pydir(session): version_info = _get_session_python_version_info(session) if version_info < (3, 8): session.error("Only Python >= 3.8 is supported") return "py{}.{}".format(*version_info) def _get_pip_requirements_file(session, crypto=None, requirements_type="ci"): assert requirements_type in ("ci", "pkg") pydir = _get_pydir(session) if IS_WINDOWS: if crypto is None: _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "windows.txt" ) if os.path.exists(_requirements_file): return _requirements_file _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "windows-crypto.txt" ) if os.path.exists(_requirements_file): return _requirements_file session.error(f"Could not find a windows requirements file for {pydir}") elif IS_DARWIN: if crypto is None: _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "darwin.txt" ) if os.path.exists(_requirements_file): return _requirements_file _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "darwin-crypto.txt" ) if os.path.exists(_requirements_file): return _requirements_file session.error(f"Could not find a darwin requirements file for {pydir}") elif IS_FREEBSD: if crypto is None: _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "freebsd.txt" ) if os.path.exists(_requirements_file): return _requirements_file _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "freebsd-crypto.txt" ) if os.path.exists(_requirements_file): return _requirements_file session.error(f"Could not find a freebsd requirements file for {pydir}") else: if crypto is None: _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "linux.txt" ) if os.path.exists(_requirements_file): return _requirements_file _requirements_file = os.path.join( "requirements", "static", requirements_type, pydir, "linux-crypto.txt" ) if os.path.exists(_requirements_file): return _requirements_file session.error(f"Could not find a linux requirements file for {pydir}") def _upgrade_pip_setuptools_and_wheel(session, upgrade=True): if SKIP_REQUIREMENTS_INSTALL: session.log( "Skipping Python Requirements because SKIP_REQUIREMENTS_INSTALL was found in the environ" ) return False env = os.environ.copy() env["PIP_CONSTRAINT"] = str(REPO_ROOT / "requirements" / "constraints.txt") install_command = [ "python", "-m", "pip", "install", "--progress-bar=off", ] if upgrade: install_command.append("-U") install_command.extend(["setuptools", "pip", "wheel"]) session_run_always(session, *install_command, silent=PIP_INSTALL_SILENT, env=env) return True def _install_requirements( session, *extra_requirements, requirements_type="ci", onedir=False, ): if onedir and IS_LINUX: session_run_always(session, "python3", "-m", "relenv", "toolchain", "fetch") if not _upgrade_pip_setuptools_and_wheel(session): return False # Install requirements env = os.environ.copy() env["PIP_CONSTRAINT"] = str(REPO_ROOT / "requirements" / "constraints.txt") requirements_file = _get_pip_requirements_file( session, requirements_type=requirements_type ) install_command = ["--progress-bar=off", "-r", requirements_file] session.install(*install_command, silent=PIP_INSTALL_SILENT, env=env) if extra_requirements: install_command = ["--progress-bar=off"] install_command += list(extra_requirements) session.install(*install_command, silent=PIP_INSTALL_SILENT, env=env) if EXTRA_REQUIREMENTS_INSTALL: session.log( "Installing the following extra requirements because the" " EXTRA_REQUIREMENTS_INSTALL environment variable was set: %s", EXTRA_REQUIREMENTS_INSTALL, ) # We pass --constraint in this step because in case any of these extra dependencies has a requirement # we're already using, we want to maintain the locked version install_command = ["--progress-bar=off", "--constraint", requirements_file] install_command += EXTRA_REQUIREMENTS_INSTALL.split() session.install(*install_command, silent=PIP_INSTALL_SILENT, env=env) return True def _install_coverage_requirement(session): if SKIP_REQUIREMENTS_INSTALL is False: env = os.environ.copy() env["PIP_CONSTRAINT"] = str(REPO_ROOT / "requirements" / "constraints.txt") coverage_requirement = COVERAGE_REQUIREMENT if coverage_requirement is None: coverage_requirement = "coverage==7.3.1" if IS_LINUX: distro_slug = os.environ.get("TOOLS_DISTRO_SLUG") if distro_slug is not None and distro_slug in ( "centos-7", "debian-10", "photonos-3", ): # Keep the old coverage requirement version since the new one, on these # Plaforms turns the test suite quite slow. # Unit tests don't finish before the 5 hours timeout when they should # finish within 1 to 2 hours. coverage_requirement = "coverage==5.5" session.install( "--progress-bar=off", coverage_requirement, silent=PIP_INSTALL_SILENT, env=env, ) def _run_with_coverage(session, *test_cmd, env=None, on_rerun=False): _install_coverage_requirement(session) if on_rerun is False: session.run("coverage", "erase") if env is None: env = {} sitecustomize_dir = session.run( "salt-factories", "--coverage", silent=True, log=True, stderr=None ) if sitecustomize_dir is not None: sitecustomize_dir = pathlib.Path(sitecustomize_dir.strip()).resolve() if not sitecustomize_dir.exists(): session.error( f"The path to 'sitecustomize.py', '{str(sitecustomize_dir)}', does not exist." ) if sitecustomize_dir: try: relative_sitecustomize_dir = sitecustomize_dir.relative_to(REPO_ROOT) except ValueError: relative_sitecustomize_dir = sitecustomize_dir log_msg = f"Discovered salt-factories coverage 'sitecustomize.py' path: {relative_sitecustomize_dir}" try: session.debug(log_msg) except AttributeError: # Older nox session.log(log_msg) python_path_env_var = os.environ.get("PYTHONPATH") or None if python_path_env_var is None: python_path_env_var = str(sitecustomize_dir) else: python_path_entries = python_path_env_var.split(os.pathsep) if str(sitecustomize_dir) in python_path_entries: python_path_entries.remove(str(sitecustomize_dir)) python_path_entries.insert(0, str(sitecustomize_dir)) python_path_env_var = os.pathsep.join(python_path_entries) env.update( { # The updated python path so that sitecustomize is importable "PYTHONPATH": python_path_env_var, # Instruct sub processes to also run under coverage "COVERAGE_PROCESS_START": str(REPO_ROOT / ".coveragerc"), # The full path to the .coverage data file. Makes sure we always write # them to the same directory "COVERAGE_FILE": COVERAGE_FILE, } ) session.run(*test_cmd, env=env) def _report_coverage( session, combine=True, cli_report=True, html_report=False, xml_report=False, json_report=False, ): _install_coverage_requirement(session) if not any([combine, cli_report, html_report, xml_report, json_report]): session.error( "At least one of combine, cli_report, html_report, xml_report, json_report needs to be True" ) env = { # The full path to the .coverage data file. Makes sure we always write # them to the same directory "COVERAGE_FILE": COVERAGE_FILE, } report_section = None if session.posargs: report_section = session.posargs.pop(0) if report_section not in ("salt", "tests"): session.error( f"The report section can only be one of 'salt', 'tests', not: {report_section}" ) if session.posargs: session.error( "Only one argument can be passed to the session, which is optional " "and is one of 'salt', 'tests'." ) if combine is True: coverage_db_files = glob.glob(f"{COVERAGE_FILE}.*") if coverage_db_files: with contextlib.suppress(CommandFailed): # Sometimes some of the coverage files are corrupt which would trigger a CommandFailed # exception session.run("coverage", "combine", env=env) elif os.path.exists(COVERAGE_FILE): session_warn(session, "Coverage files already combined.") if os.path.exists(COVERAGE_FILE) and not IS_WINDOWS: # Some coverage files might have come from a windows machine, fix paths with sqlite3.connect(COVERAGE_FILE) as db: res = db.execute(r"SELECT * FROM file WHERE path LIKE '%salt\%'") if res.fetchone(): session_warn( session, "Replacing backwards slashes with forward slashes on file " "paths in the coverage database", ) db.execute( r"UPDATE OR IGNORE file SET path=replace(path, '\', '/');" ) if not os.path.exists(COVERAGE_FILE): session.error("No coverage files found.") if report_section == "salt": json_coverage_file = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "salt.json" xml_coverage_file = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "salt.xml" html_coverage_dir = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "html" / "salt" cmd_args = [ "--omit=tests/*,tests/pytests/pkg/*", "--include=salt/*", ] elif report_section == "tests": json_coverage_file = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "tests.json" xml_coverage_file = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "tests.xml" html_coverage_dir = ( COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "html" / "tests" ) cmd_args = [ "--omit=salt/*", "--include=tests/*,tests/pytests/pkg/*", ] else: json_coverage_file = ( COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "coverage.json" ) xml_coverage_file = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "coverage.xml" html_coverage_dir = COVERAGE_OUTPUT_DIR.relative_to(REPO_ROOT) / "html" / "full" cmd_args = [ "--include=salt/*,tests/*,tests/pytests/pkg/*", ] if cli_report: session.run( "coverage", "report", "--precision=2", *cmd_args, env=env, ) if html_report: session.run( "coverage", "html", "-d", str(html_coverage_dir), "--show-contexts", "--precision=2", *cmd_args, env=env, ) if xml_report: try: session.run( "coverage", "xml", "-o", str(xml_coverage_file), *cmd_args, env=env, ) except CommandFailed: session_warn( session, "Failed to generate the source XML code coverage report" ) if json_report: session.run( "coverage", "json", "-o", str(json_coverage_file), "--show-contexts", *cmd_args, env=env, ) @nox.session(python=_PYTHON_VERSIONS, name="test-parametrized") @nox.parametrize("coverage", [False, True]) @nox.parametrize("transport", ["zeromq", "tcp"]) @nox.parametrize("crypto", [None, "m2crypto", "pycryptodome"]) def test_parametrized(session, coverage, transport, crypto): """ DO NOT CALL THIS NOX SESSION DIRECTLY """ # Install requirements if _install_requirements(session): if crypto: session_run_always( session, "pip", "uninstall", "-y", "m2crypto", "pycrypto", "pycryptodome", "pycryptodomex", silent=True, ) install_command = [ "--progress-bar=off", "--constraint", _get_pip_requirements_file(session, crypto=True), ] install_command.append(crypto) session.install(*install_command, silent=PIP_INSTALL_SILENT) cmd_args = [ f"--transport={transport}", ] + session.posargs _pytest(session, coverage=coverage, cmd_args=cmd_args) @nox.session(python=_PYTHON_VERSIONS) @nox.parametrize("coverage", [False, True]) def test(session, coverage): """ pytest session with zeromq transport and default crypto """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto=None, transport="zeromq", ) ) @nox.session(python=_PYTHON_VERSIONS) @nox.parametrize("coverage", [False, True]) def pytest(session, coverage): """ pytest session with zeromq transport and default crypto """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-tcp") @nox.parametrize("coverage", [False, True]) def test_tcp(session, coverage): """ pytest session with TCP transport and default crypto """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto=None, transport="tcp", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-tcp") @nox.parametrize("coverage", [False, True]) def pytest_tcp(session, coverage): """ pytest session with TCP transport and default crypto """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-zeromq") @nox.parametrize("coverage", [False, True]) def test_zeromq(session, coverage): """ pytest session with zeromq transport and default crypto """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto=None, transport="zeromq", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-zeromq") @nox.parametrize("coverage", [False, True]) def pytest_zeromq(session, coverage): """ pytest session with zeromq transport and default crypto """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-m2crypto") @nox.parametrize("coverage", [False, True]) def test_m2crypto(session, coverage): """ pytest session with zeromq transport and m2crypto """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto="m2crypto", transport="zeromq", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-m2crypto") @nox.parametrize("coverage", [False, True]) def pytest_m2crypto(session, coverage): """ pytest session with zeromq transport and m2crypto """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-tcp-m2crypto") @nox.parametrize("coverage", [False, True]) def test_tcp_m2crypto(session, coverage): """ pytest session with TCP transport and m2crypto """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto="m2crypto", transport="tcp", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-tcp-m2crypto") @nox.parametrize("coverage", [False, True]) def pytest_tcp_m2crypto(session, coverage): """ pytest session with TCP transport and m2crypto """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-zeromq-m2crypto") @nox.parametrize("coverage", [False, True]) def test_zeromq_m2crypto(session, coverage): """ pytest session with zeromq transport and m2crypto """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto="m2crypto", transport="zeromq", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-zeromq-m2crypto") @nox.parametrize("coverage", [False, True]) def pytest_zeromq_m2crypto(session, coverage): """ pytest session with zeromq transport and m2crypto """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-pycryptodome") @nox.parametrize("coverage", [False, True]) def test_pycryptodome(session, coverage): """ pytest session with zeromq transport and pycryptodome """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto="pycryptodome", transport="zeromq", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-pycryptodome") @nox.parametrize("coverage", [False, True]) def pytest_pycryptodome(session, coverage): """ pytest session with zeromq transport and pycryptodome """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-tcp-pycryptodome") @nox.parametrize("coverage", [False, True]) def test_tcp_pycryptodome(session, coverage): """ pytest session with TCP transport and pycryptodome """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto="pycryptodome", transport="tcp", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-tcp-pycryptodome") @nox.parametrize("coverage", [False, True]) def pytest_tcp_pycryptodome(session, coverage): """ pytest session with TCP transport and pycryptodome """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-zeromq-pycryptodome") @nox.parametrize("coverage", [False, True]) def test_zeromq_pycryptodome(session, coverage): """ pytest session with zeromq transport and pycryptodome """ session.notify( find_session_runner( session, "test-parametrized", session.python, coverage=coverage, crypto="pycryptodome", transport="zeromq", ) ) @nox.session(python=_PYTHON_VERSIONS, name="pytest-zeromq-pycryptodome") @nox.parametrize("coverage", [False, True]) def pytest_zeromq_pycryptodome(session, coverage): """ pytest session with zeromq transport and pycryptodome """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-cloud") @nox.parametrize("coverage", [False, True]) def test_cloud(session, coverage): """ pytest cloud tests session """ pydir = _get_pydir(session) if pydir == "py3.5": session.error( "Due to conflicting and unsupported requirements the cloud tests only run on Py3.6+" ) # Install requirements if _upgrade_pip_setuptools_and_wheel(session): linux_requirements_file = os.path.join( "requirements", "static", "ci", pydir, "linux.txt" ) cloud_requirements_file = os.path.join( "requirements", "static", "ci", pydir, "cloud.txt" ) install_command = [ "--progress-bar=off", "-r", linux_requirements_file, "-r", cloud_requirements_file, ] session.install(*install_command, silent=PIP_INSTALL_SILENT) cmd_args = [ "--run-expensive", "-k", "cloud", ] + session.posargs _pytest(session, coverage=coverage, cmd_args=cmd_args) @nox.session(python=_PYTHON_VERSIONS, name="pytest-cloud") @nox.parametrize("coverage", [False, True]) def pytest_cloud(session, coverage): """ pytest cloud tests session """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) @nox.session(python=_PYTHON_VERSIONS, name="test-tornado") @nox.parametrize("coverage", [False, True]) def test_tornado(session, coverage): """ pytest tornado tests session """ # Install requirements if _upgrade_pip_setuptools_and_wheel(session): _install_requirements(session) session.install( "--progress-bar=off", "tornado==5.0.2", silent=PIP_INSTALL_SILENT ) session.install( "--progress-bar=off", "pyzmq==17.0.0", silent=PIP_INSTALL_SILENT ) _pytest(session, coverage=coverage, cmd_args=session.posargs) @nox.session(python=_PYTHON_VERSIONS, name="pytest-tornado") @nox.parametrize("coverage", [False, True]) def pytest_tornado(session, coverage): """ pytest tornado tests session """ try: session_name = session.name except AttributeError: session_name = session._runner.friendly_name session_warn( session, "This nox session is deprecated, please call {!r} instead".format( session_name.replace("pytest-", "test-") ), ) session.notify(session_name.replace("pytest-", "test-")) def _pytest(session, coverage, cmd_args, env=None, on_rerun=False): # Create required artifacts directories _create_ci_directories() if env is None: env = {} env["CI_RUN"] = "1" if CI_RUN else "0" args = [ "--rootdir", str(REPO_ROOT), "--log-file-level=debug", "--show-capture=no", "-ra", "-s", "-vv", "--showlocals", ] for arg in cmd_args: if arg == "--log-file" or arg.startswith("--log-file="): break else: args.append(f"--log-file={RUNTESTS_LOGFILE}") args.extend(cmd_args) if PRINT_SYSTEM_INFO_ONLY and "--sys-info-and-exit" not in args: args.append("--sys-info-and-exit") session.run("python", "-m", "pytest", *args, env=env) return if PRINT_SYSTEM_INFO and "--sysinfo" not in args: args.append("--sysinfo") if PRINT_TEST_SELECTION: # We'll print out the collected tests on CI runs. # This will show a full list of what tests are going to run, in the right order, which, in case # of a test suite hang, helps us pinpoint which test is hanging session.run( "python", "-m", "pytest", *(args + ["--collect-only", "-qqq"]), env=env ) if PRINT_TEST_PLAN_ONLY: return if coverage is True: _coverage_cmd_args = [] if "COVERAGE_CONTEXT" in os.environ: _coverage_cmd_args.append(f"--context={os.environ['COVERAGE_CONTEXT']}") _run_with_coverage( session, "python", "-m", "coverage", "run", *_coverage_cmd_args, "-m", "pytest", *args, env=env, on_rerun=on_rerun, ) else: session.run("python", "-m", "pytest", *args, env=env) def _ci_test(session, transport, onedir=False): # Install requirements _install_requirements(session, onedir=onedir) env = {} if onedir: env["ONEDIR_TESTRUN"] = "1" chunks = { "pkg": [ "tests/pytests/pkg", ], "unit": [ "tests/unit", "tests/pytests/unit", ], "functional": [ "tests/pytests/functional", ], "scenarios": [ "tests/pytests/scenarios", ], } test_group_number = os.environ.get("TEST_GROUP") or "1" if not session.posargs: chunk_cmd = [] junit_report_filename = "test-results" runtests_log_filename = "runtests" else: chunk = session.posargs.pop(0) if chunk in ["unit", "functional", "integration", "scenarios", "all"]: if chunk == "all": chunk_cmd = [] junit_report_filename = "test-results" runtests_log_filename = "runtests" elif chunk == "integration": chunk_cmd = [] for values in chunks.values(): for value in values: chunk_cmd.append(f"--ignore={value}") junit_report_filename = f"test-results-{chunk}-grp{test_group_number}" runtests_log_filename = f"runtests-{chunk}-grp{test_group_number}" else: chunk_cmd = chunks[chunk] junit_report_filename = f"test-results-{chunk}-grp{test_group_number}" runtests_log_filename = f"runtests-{chunk}-grp{test_group_number}" if session.posargs: if session.posargs[0] == "--": session.posargs.pop(0) chunk_cmd.extend(session.posargs) else: chunk_cmd = [chunk] + session.posargs junit_report_filename = f"test-results-grp{test_group_number}" runtests_log_filename = f"runtests-grp{test_group_number}" rerun_failures = os.environ.get("RERUN_FAILURES", "0") == "1" track_code_coverage = os.environ.get("SKIP_CODE_COVERAGE", "0") == "0" common_pytest_args = [ "--color=yes", "--ssh-tests", "--sys-stats", "--run-destructive", f"--output-columns={os.environ.get('OUTPUT_COLUMNS') or 120}", ] try: pytest_args = ( common_pytest_args[:] + [ f"--junitxml=artifacts/xml-unittests-output/{junit_report_filename}.xml", f"--log-file=artifacts/logs/{runtests_log_filename}.log", ] + chunk_cmd ) _pytest(session, coverage=track_code_coverage, cmd_args=pytest_args, env=env) except CommandFailed: if rerun_failures is False: raise # Don't print the system information, not the test selection on reruns global PRINT_TEST_SELECTION global PRINT_SYSTEM_INFO PRINT_TEST_SELECTION = False PRINT_SYSTEM_INFO = False pytest_args = ( common_pytest_args[:] + [ "--lf", f"--junitxml=artifacts/xml-unittests-output/{junit_report_filename}-rerun.xml", f"--log-file=artifacts/logs/{runtests_log_filename}-rerun.log", ] + chunk_cmd ) _pytest( session, coverage=track_code_coverage, cmd_args=pytest_args, env=env, on_rerun=True, ) @nox.session(python=_PYTHON_VERSIONS, name="ci-test") def ci_test(session): transport = os.environ.get("SALT_TRANSPORT") or "zeromq" valid_transports = ("zeromq", "tcp") if transport not in valid_transports: session.error( "The value for the SALT_TRANSPORT environment variable can only be " f"one of: {', '.join(valid_transports)}" ) _ci_test(session, transport) @nox.session(python=_PYTHON_VERSIONS, name="ci-test-tcp") def ci_test_tcp(session): _ci_test(session, "tcp") @nox.session( python=str(ONEDIR_PYTHON_PATH), name="ci-test-onedir", venv_params=["--system-site-packages"], ) def ci_test_onedir(session): if not ONEDIR_ARTIFACT_PATH.exists(): session.error( "The salt onedir artifact, expected to be in '{}', was not found".format( ONEDIR_ARTIFACT_PATH.relative_to(REPO_ROOT) ) ) transport = os.environ.get("SALT_TRANSPORT") or "zeromq" valid_transports = ("zeromq", "tcp") if transport not in valid_transports: session.error( "The value for the SALT_TRANSPORT environment variable can only be " f"one of: {', '.join(valid_transports)}" ) _ci_test(session, "zeromq", onedir=True) @nox.session( python=str(ONEDIR_PYTHON_PATH), name="ci-test-onedir-tcp", venv_params=["--system-site-packages"], ) def ci_test_onedir_tcp(session): if not ONEDIR_ARTIFACT_PATH.exists(): session.error( "The salt onedir artifact, expected to be in '{}', was not found".format( ONEDIR_ARTIFACT_PATH.relative_to(REPO_ROOT) ) ) _ci_test(session, "tcp", onedir=True) @nox.session(python="3", name="report-coverage") def report_coverage(session): _report_coverage(session, combine=True, cli_report=True) @nox.session(python="3", name="coverage-report") def coverage_report(session): _report_coverage(session, combine=True, cli_report=True) @nox.session(python=False, name="decompress-dependencies") def decompress_dependencies(session): if not session.posargs: session.error( "The 'decompress-dependencies' session target needs " "two arguments, ' '." ) try: platform = session.posargs.pop(0) arch = session.posargs.pop(0) if session.posargs: session.error( "The 'decompress-dependencies' session target only accepts " "two arguments, ' '." ) except IndexError: session.error( "The 'decompress-dependencies' session target needs " "two arguments, ' '." ) if platform == "windows": extension = "tar.gz" scripts_dir_name = "Scripts" else: extension = "tar.xz" scripts_dir_name = "bin" nox_dependencies_tarball = f"nox.{platform}.{arch}.{extension}" nox_dependencies_tarball_path = REPO_ROOT / nox_dependencies_tarball if not nox_dependencies_tarball_path.exists(): session.error( f"The {nox_dependencies_tarball} file " "does not exist. Not decompressing anything." ) session_run_always(session, "tar", "xpf", nox_dependencies_tarball) if os.environ.get("DELETE_NOX_ARCHIVE", "0") == "1": nox_dependencies_tarball_path.unlink() session.log("Finding broken 'python' symlinks under '.nox/' ...") for dirname in os.scandir(REPO_ROOT / ".nox"): scan_path = REPO_ROOT.joinpath(".nox", dirname, scripts_dir_name) script_paths = {str(p): p for p in os.scandir(scan_path)} fixed_shebang = f"#!{scan_path / 'python'}" for key in sorted(script_paths): path = script_paths[key] if path.is_symlink(): broken_link = pathlib.Path(path) resolved_link = os.readlink(path) if not os.path.isabs(resolved_link): # Relative symlinks, resolve them resolved_link = os.path.join(scan_path, resolved_link) prefix_check = False if platform == "windows": prefix_check = resolved_link.startswith("\\\\?") if not os.path.exists(resolved_link) or prefix_check: session.log("The symlink %r looks to be broken", resolved_link) # This is a broken link, fix it resolved_link_suffix = resolved_link.split( f"artifacts{os.sep}salt{os.sep}" )[-1] fixed_link = REPO_ROOT.joinpath( "artifacts", "salt", resolved_link_suffix ) session.log( "Fixing broken symlink in nox virtualenv %r, from %r to %r", dirname.name, resolved_link, str(fixed_link.relative_to(REPO_ROOT)), ) broken_link.unlink() broken_link.symlink_to(fixed_link) continue if not path.is_file(): continue if platform != "windows": # Let's try to fix shebang's try: fpath = pathlib.Path(path) contents = fpath.read_text(encoding="utf-8").splitlines() if ( contents[0].startswith("#!") and contents[0].endswith("python") and contents[0] != fixed_shebang ): session.log( "Fixing broken shebang in %r", str(fpath.relative_to(REPO_ROOT)), ) fpath.write_text( "\n".join([fixed_shebang] + contents[1:]), encoding="utf-8" ) except UnicodeDecodeError: pass @nox.session(python=False, name="compress-dependencies") def compress_dependencies(session): if not session.posargs: session.error( "The 'compress-dependencies' session target needs " "two arguments, ' '." ) try: platform = session.posargs.pop(0) arch = session.posargs.pop(0) if session.posargs: session.error( "The 'compress-dependencies' session target only accepts " "two arguments, ' '." ) except IndexError: session.error( "The 'compress-dependencies' session target needs " "two arguments, ' '." ) if platform == "windows": extension = "tar.gz" else: extension = "tar.xz" nox_dependencies_tarball = f"nox.{platform}.{arch}.{extension}" nox_dependencies_tarball_path = REPO_ROOT / nox_dependencies_tarball if nox_dependencies_tarball_path.exists(): session_warn( session, f"Found existing {nox_dependencies_tarball}. Deleting it." ) nox_dependencies_tarball_path.unlink() session_run_always( session, "tar", "-caf", nox_dependencies_tarball, "--exclude=.nox/pre-archive-cleanup", ".nox", ) @nox.session( python=str(ONEDIR_PYTHON_PATH), name="pre-archive-cleanup", ) @nox.parametrize("pkg", [False, True]) def pre_archive_cleanup(session, pkg): """ Call `tools pkg pre-archive-cleanup ` """ if session.posargs: session.error("No additional arguments can be passed to 'pre-archive-cleanup'") version_info = _get_session_python_version_info(session) if version_info < (3, 10): session.error( "The nox session 'pre-archive-cleanup' needs Python 3.10+ to run." ) if _upgrade_pip_setuptools_and_wheel(session): requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "tools.txt" ) install_command = ["--progress-bar=off", "-r", requirements_file] session.install(*install_command, silent=PIP_INSTALL_SILENT) cmdline = [ "tools", "pkg", "pre-archive-cleanup", ] if pkg: cmdline.append("--pkg") cmdline.append(".nox") session_run_always(session, *cmdline) @nox.session(python="3", name="combine-coverage") def combine_coverage(session): _report_coverage(session, combine=True, cli_report=False) @nox.session( python=str(ONEDIR_PYTHON_PATH), name="combine-coverage-onedir", venv_params=["--system-site-packages"], ) def combine_coverage_onedir(session): _report_coverage(session, combine=True, cli_report=False) @nox.session(python="3", name="create-html-coverage-report") def create_html_coverage_report(session): _report_coverage(session, combine=True, cli_report=False, html_report=True) def _create_xml_coverage_reports(session): if session.posargs: session.error("No arguments are acceptable to this nox session.") session.posargs.append("salt") _report_coverage(session, combine=True, cli_report=False, xml_report=True) session.posargs.append("tests") _report_coverage(session, combine=True, cli_report=False, xml_report=True) @nox.session(python="3", name="create-xml-coverage-reports") def create_xml_coverage_reports(session): _create_xml_coverage_reports(session) @nox.session( python=str(ONEDIR_PYTHON_PATH), name="create-xml-coverage-reports-onedir", venv_params=["--system-site-packages"], ) def create_xml_coverage_reports_onedir(session): _create_xml_coverage_reports(session) @nox.session(python="3", name="create-json-coverage-reports") def create_json_coverage_reports(session): _report_coverage(session, combine=True, cli_report=False, json_report=True) @nox.session( python=str(ONEDIR_PYTHON_PATH), name="create-json-coverage-reports-onedir", venv_params=["--system-site-packages"], ) def create_json_coverage_reports_onedir(session): _report_coverage(session, combine=True, cli_report=False, json_report=True) class Tee: """ Python class to mimic linux tee behaviour """ def __init__(self, first, second): self._first = first self._second = second def write(self, b): wrote = self._first.write(b) self._first.flush() self._second.write(b) self._second.flush() def fileno(self): return self._first.fileno() def _lint(session, rcfile, flags, paths, upgrade_setuptools_and_pip=True): if _upgrade_pip_setuptools_and_wheel(session, upgrade=upgrade_setuptools_and_pip): linux_requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "linux.txt" ) lint_requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "lint.txt" ) install_command = [ "--progress-bar=off", "-r", linux_requirements_file, "-r", lint_requirements_file, ] session.install(*install_command, silent=PIP_INSTALL_SILENT) cmd_args = ["pylint", f"--rcfile={rcfile}"] + list(flags) + list(paths) cmd_kwargs = {"env": {"PYTHONUNBUFFERED": "1"}} session.run(*cmd_args, **cmd_kwargs) def _lint_pre_commit(session, rcfile, flags, paths): if "VIRTUAL_ENV" not in os.environ: session.error( "This should be running from within a virtualenv and " "'VIRTUAL_ENV' was not found as an environment variable." ) if "pre-commit" not in os.environ["VIRTUAL_ENV"]: session.error( "This should be running from within a pre-commit virtualenv and " "'VIRTUAL_ENV'({}) does not appear to be a pre-commit virtualenv.".format( os.environ["VIRTUAL_ENV"] ) ) from nox.virtualenv import VirtualEnv # Let's patch nox to make it run inside the pre-commit virtualenv session._runner.venv = VirtualEnv( os.environ["VIRTUAL_ENV"], interpreter=session._runner.func.python, reuse_existing=True, venv=True, ) _lint( session, rcfile, flags, paths, upgrade_setuptools_and_pip=False, ) @nox.session(python="3") def lint(session): """ Run PyLint against Salt and it's test suite. """ session.notify(f"lint-salt-{session.python}") session.notify(f"lint-tests-{session.python}") @nox.session(python="3", name="lint-salt") def lint_salt(session): """ Run PyLint against Salt. """ flags = ["--disable=I"] if session.posargs: paths = session.posargs else: # TBD replace paths entries when implement pyproject.toml paths = ["setup.py", "noxfile.py", "salt/", "tools/"] _lint(session, ".pylintrc", flags, paths) @nox.session(python="3", name="lint-tests") def lint_tests(session): """ Run PyLint against Salt and it's test suite. """ flags = ["--disable=I"] if session.posargs: paths = session.posargs else: paths = ["tests/"] _lint(session, ".pylintrc", flags, paths) @nox.session(python=False, name="lint-salt-pre-commit") def lint_salt_pre_commit(session): """ Run PyLint against Salt. """ flags = ["--disable=I"] if session.posargs: paths = session.posargs else: paths = ["setup.py", "noxfile.py", "salt/", "tools/"] _lint_pre_commit(session, ".pylintrc", flags, paths) @nox.session(python=False, name="lint-tests-pre-commit") def lint_tests_pre_commit(session): """ Run PyLint against Salt and it's test suite. """ flags = ["--disable=I"] if session.posargs: paths = session.posargs else: paths = ["tests/"] _lint_pre_commit(session, ".pylintrc", flags, paths) @nox.session(python="3") @nox.parametrize("clean", [False, True]) @nox.parametrize("update", [False, True]) @nox.parametrize("compress", [False, True]) def docs(session, compress, update, clean): """ Build Salt's Documentation """ session.notify(f"docs-html-{session.python}(compress={compress})") session.notify( find_session_runner( session, "docs-man", session.python, compress=compress, update=update, clean=clean, ) ) @nox.session(name="docs-html", python="3") @nox.parametrize("clean", [False, True]) @nox.parametrize("compress", [False, True]) def docs_html(session, compress, clean): """ Build Salt's HTML Documentation """ if _upgrade_pip_setuptools_and_wheel(session): linux_requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "linux.txt" ) base_requirements_file = os.path.join("requirements", "base.txt") zeromq_requirements_file = os.path.join("requirements", "zeromq.txt") docs_requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "docs.txt" ) install_command = [ "--progress-bar=off", "--constraint", linux_requirements_file, "-r", base_requirements_file, "-r", zeromq_requirements_file, "-r", docs_requirements_file, ] session.install(*install_command, silent=PIP_INSTALL_SILENT) os.chdir("doc/") if clean: session.run("make", "clean", external=True) session.run("make", "html", "SPHINXOPTS=-W", external=True) if compress: session.run("tar", "-cJvf", "html-archive.tar.xz", "_build/html", external=True) os.chdir("..") @nox.session(name="docs-man", python="3") @nox.parametrize("clean", [False, True]) @nox.parametrize("update", [False, True]) @nox.parametrize("compress", [False, True]) def docs_man(session, compress, update, clean): """ Build Salt's Manpages Documentation """ if _upgrade_pip_setuptools_and_wheel(session): linux_requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "linux.txt" ) base_requirements_file = os.path.join("requirements", "base.txt") zeromq_requirements_file = os.path.join("requirements", "zeromq.txt") docs_requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "docs.txt" ) install_command = [ "--progress-bar=off", "--constraint", linux_requirements_file, "-r", base_requirements_file, "-r", zeromq_requirements_file, "-r", docs_requirements_file, ] session.install(*install_command, silent=PIP_INSTALL_SILENT) os.chdir("doc/") if clean: session.run("make", "clean", external=True) session.run("make", "man", "SPHINXOPTS=-W", external=True) if update: session.run("rm", "-rf", "man/", external=True) session.run("cp", "-Rp", "_build/man", "man/", external=True) if compress: session.run("tar", "-cJvf", "man-archive.tar.xz", "_build/man", external=True) os.chdir("..") @nox.session(name="changelog", python="3") @nox.parametrize("draft", [False, True]) @nox.parametrize("force", [False, True]) def changelog(session, draft, force): """ Generate salt's changelog """ session_warn( session, "Please stop using this nox session and start using the 'tools' command shown below.", ) if _upgrade_pip_setuptools_and_wheel(session): requirements_file = os.path.join( "requirements", "static", "ci", _get_pydir(session), "tools.txt" ) install_command = ["--progress-bar=off", "-r", requirements_file] session.install(*install_command, silent=PIP_INSTALL_SILENT) cmd = ["tools", "changelog", "update-changelog-md"] if draft: cmd.append("--draft") cmd.append(session.posargs[0]) session.run(*cmd) class Recompress: """ Helper class to re-compress a ``.tag.gz`` file to make it reproducible. """ def __init__(self, mtime): self.mtime = int(mtime) def tar_reset(self, tarinfo): """ Reset user, group, mtime, and mode to create reproducible tar. """ tarinfo.uid = tarinfo.gid = 0 tarinfo.uname = tarinfo.gname = "root" tarinfo.mtime = self.mtime if tarinfo.type == tarfile.DIRTYPE: tarinfo.mode = 0o755 else: tarinfo.mode = 0o644 if tarinfo.pax_headers: raise ValueError(tarinfo.name, tarinfo.pax_headers) return tarinfo def recompress(self, targz): """ Re-compress the passed path. """ tempd = pathlib.Path(tempfile.mkdtemp()).resolve() d_src = tempd.joinpath("src") d_src.mkdir() d_tar = tempd.joinpath(targz.stem) d_targz = tempd.joinpath(targz.name) with tarfile.open(d_tar, "w|") as wfile: with tarfile.open(targz, "r:gz") as rfile: rfile.extractall(d_src) # nosec extracted_dir = next(pathlib.Path(d_src).iterdir()) for name in sorted(extracted_dir.rglob("*")): wfile.add( str(name), filter=self.tar_reset, recursive=False, arcname=str(name.relative_to(d_src)), ) with open(d_tar, "rb") as rfh: with gzip.GzipFile( fileobj=open(d_targz, "wb"), mode="wb", filename="", mtime=self.mtime ) as gz: # pylint: disable=invalid-name while True: chunk = rfh.read(1024) if not chunk: break gz.write(chunk) targz.unlink() shutil.move(str(d_targz), str(targz)) @nox.session(python="3") def build(session): """ Build source and binary distributions based off the current commit author date UNIX timestamp. The reason being, reproducible packages. .. code-block: shell git show -s --format=%at HEAD """ shutil.rmtree("dist/", ignore_errors=True) if SKIP_REQUIREMENTS_INSTALL is False: session.install( "--progress-bar=off", "-r", "requirements/build.txt", silent=PIP_INSTALL_SILENT, ) timestamp = session.run( "git", "show", "-s", "--format=%at", "HEAD", silent=True, log=False, stderr=None, ).strip() env = {"SOURCE_DATE_EPOCH": str(timestamp)} session.run( "python", "-m", "build", "--sdist", str(REPO_ROOT), env=env, ) # Recreate sdist to be reproducible recompress = Recompress(timestamp) for targz in REPO_ROOT.joinpath("dist").glob("*.tar.gz"): session.log("Re-compressing %s...", targz.relative_to(REPO_ROOT)) recompress.recompress(targz) sha256sum = shutil.which("sha256sum") if sha256sum: packages = [ str(pkg.relative_to(REPO_ROOT)) for pkg in REPO_ROOT.joinpath("dist").iterdir() ] session.run("sha256sum", *packages, external=True) session.run("python", "-m", "twine", "check", "dist/*") @nox.session( python=str(ONEDIR_PYTHON_PATH), name="ci-test-onedir-pkgs", venv_params=["--system-site-packages"], ) def ci_test_onedir_pkgs(session): from nox.virtualenv import VirtualEnv session_warn(session, "Replacing VirtualEnv instance...") ci_test_onedir_path = REPO_ROOT / ".nox" / "ci-test-onedir" if hasattr(session._runner.venv, "venv_or_virtualenv"): venv = session._runner.venv.venv_or_virtualenv == "venv" session._runner.venv = VirtualEnv( str(ci_test_onedir_path.relative_to(REPO_ROOT)), interpreter=session._runner.func.python, reuse_existing=True, venv=venv, venv_params=session._runner.venv.venv_params, ) else: venv = session._runner.venv.venv_backend in ("venv", "virtualenv") session._runner.venv = VirtualEnv( # pylint: disable=unexpected-keyword-arg str(ci_test_onedir_path.relative_to(REPO_ROOT)), interpreter=session._runner.func.python, reuse_existing=True, venv_backend=session._runner.venv.venv_backend, venv_params=session._runner.venv.venv_params, ) os.environ["VIRTUAL_ENV"] = session._runner.venv.location session._runner.venv.create() if not ONEDIR_ARTIFACT_PATH.exists(): session.error( "The salt onedir artifact, expected to be in '{}', was not found".format( ONEDIR_ARTIFACT_PATH.relative_to(REPO_ROOT) ) ) common_pytest_args = [ "--color=yes", "--sys-stats", "--run-destructive", f"--output-columns={os.environ.get('OUTPUT_COLUMNS') or 120}", "--pkg-system-service", ] chunks = { "install": [], "upgrade": [ "--upgrade", "--no-uninstall", ], "downgrade": [ "--downgrade", "--no-uninstall", ], "download-pkgs": [ "--download-pkgs", ], } if not session.posargs or session.posargs[0] not in chunks: chunk = "install" session.log("Choosing default 'install' test type") else: chunk = session.posargs.pop(0) cmd_args = chunks[chunk] for arg in session.posargs: if arg.startswith("tests/pytests/pkg/"): # The user is passing test paths cmd_args.pop() break if IS_LINUX: # Fetch the toolchain session_run_always(session, "python3", "-m", "relenv", "toolchain", "fetch") # Install requirements if _upgrade_pip_setuptools_and_wheel(session): _install_requirements(session, "pyzmq") env = { "ONEDIR_TESTRUN": "1", "PKG_TEST_TYPE": chunk, } pytest_args = ( common_pytest_args[:] + cmd_args[:] + [ f"--junitxml=artifacts/xml-unittests-output/test-results-{chunk}.xml", f"--log-file=artifacts/logs/runtests-{chunk}.log", ] + session.posargs ) append_tests_path = True test_paths = ( "tests/pytests/pkg/", str(REPO_ROOT / "tests" / "pytests" / "pkg"), ) for arg in session.posargs: if arg.startswith(test_paths): append_tests_path = False break if append_tests_path: pytest_args.append("tests/pytests/pkg/") try: _pytest(session, coverage=False, cmd_args=pytest_args, env=env) except CommandFailed: if os.environ.get("RERUN_FAILURES", "0") == "0": # Don't rerun on failures return # Don't print the system information, not the test selection on reruns global PRINT_TEST_SELECTION global PRINT_SYSTEM_INFO PRINT_TEST_SELECTION = False PRINT_SYSTEM_INFO = False pytest_args = ( common_pytest_args[:] + cmd_args[:] + [ f"--junitxml=artifacts/xml-unittests-output/test-results-{chunk}-rerun.xml", f"--log-file=artifacts/logs/runtests-{chunk}-rerun.log", "--lf", ] + session.posargs ) if append_tests_path: pytest_args.append("tests/pytests/pkg/") _pytest( session, coverage=False, cmd_args=pytest_args, env=env, on_rerun=True, ) if chunk not in ("install", "download-pkgs"): cmd_args = chunks[chunk] pytest_args = ( common_pytest_args[:] + cmd_args[:] + [ "--junitxml=artifacts/xml-unittests-output/test-results-install.xml", "--log-file=artifacts/logs/runtests-install.log", ] + session.posargs ) if "downgrade" in chunk: pytest_args.append("--use-prev-version") if append_tests_path: pytest_args.append("tests/pytests/pkg/") try: _pytest(session, coverage=False, cmd_args=pytest_args, env=env) except CommandFailed: if os.environ.get("RERUN_FAILURES", "0") == "0": # Don't rerun on failures return cmd_args = chunks[chunk] pytest_args = ( common_pytest_args[:] + cmd_args[:] + [ "--junitxml=artifacts/xml-unittests-output/test-results-install-rerun.xml", "--log-file=artifacts/logs/runtests-install-rerun.log", "--lf", ] + session.posargs ) if "downgrade" in chunk: pytest_args.append("--use-prev-version") if append_tests_path: pytest_args.append("tests/pytests/pkg/") _pytest( session, coverage=False, cmd_args=pytest_args, env=env, on_rerun=True, ) sys.exit(0)