From 10b15e07feaf7f1f5ddc79afbf80ee5753740026 Mon Sep 17 00:00:00 2001 From: Balint Pato Date: Tue, 10 Aug 2021 01:15:12 -0400 Subject: [PATCH] Fix cirq-core dependencies (#4369) Some of the cirq-XXX modules were lacking the dependency on cirq-core. This PR adds tests in isolation for each of the modules where they are installed and pytested in a clean environment. Fixes #4361. --- .github/workflows/ci.yml | 14 +-- check/pytest | 15 +-- cirq-aqt/setup.py | 1 + cirq-ionq/requirements.txt | 1 + cirq-ionq/setup.py | 2 + cirq-pasqal/requirements.txt | 1 + cirq-pasqal/setup.py | 7 +- cirq-rigetti/setup.py | 7 +- cirq-web/setup.py | 1 - dev_tools/bash_scripts_test.py | 7 +- dev_tools/cloned_env_test.py | 42 +++++++++ dev_tools/conftest.py | 93 +++++++++++++++++++ dev_tools/modules.py | 4 + dev_tools/modules_test.py | 2 + dev_tools/notebooks/isolated_notebook_test.py | 41 +------- dev_tools/packaging/isolated_packages_test.py | 55 +++++++++++ dev_tools/packaging/packaging_test.sh | 2 +- dev_tools/requirements/cirq-only.env.txt | 2 - dev_tools/requirements/deps/pytest.txt | 4 + dev_tools/requirements/isolated-base.env.txt | 6 ++ .../requirements/isolated-notebooks.env.txt | 8 -- dev_tools/shell_tools_test.py | 8 +- dev_tools/test_utils.py | 21 +++++ 23 files changed, 258 insertions(+), 86 deletions(-) create mode 100644 dev_tools/cloned_env_test.py create mode 100644 dev_tools/packaging/isolated_packages_test.py delete mode 100644 dev_tools/requirements/cirq-only.env.txt create mode 100644 dev_tools/requirements/isolated-base.env.txt delete mode 100644 dev_tools/requirements/isolated-notebooks.env.txt create mode 100644 dev_tools/test_utils.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bc7a680b07..d6328598311 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,8 +130,8 @@ jobs: run: pip install -r dev_tools/requirements/deps/tensorflow-docs.txt - name: Doc check run: check/nbformat - cirq-only: - name: Pytest (cirq-only) Ubuntu + isolated-modules: + name: Isolated pytest Ubuntu runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 @@ -139,10 +139,10 @@ jobs: with: python-version: '3.8' architecture: 'x64' - - name: Install requirements - run: pip install -r dev_tools/requirements/cirq-only.env.txt - - name: Pytest check - run: check/pytest --cirq-only --ignore=cirq-core/cirq/contrib --actually-quiet + - name: Install dependencies + run: pip install -r dev_tools/requirements/isolated-base.env.txt + - name: Test each module in isolation + run: pytest -n auto -m slow dev_tools/packaging/isolated_packages_test.py pytest: name: Pytest Ubuntu strategy: @@ -267,7 +267,7 @@ jobs: python-version: '3.8' architecture: 'x64' - name: Install requirements - run: pip install -r dev_tools/requirements/isolated-notebooks.env.txt + run: pip install -r dev_tools/requirements/isolated-base.env.txt - name: Notebook tests run: check/pytest -n auto -m slow dev_tools/notebooks/isolated_notebook_test.py -k ${{matrix.partition}} - uses: actions/upload-artifact@v2 diff --git a/check/pytest b/check/pytest index 74c60636986..3aa396e2e15 100755 --- a/check/pytest +++ b/check/pytest @@ -4,11 +4,9 @@ # Runs pytest on the repository. # # Usage: -# check/pytest [--actually-quiet] [--cirq-only] [--flags for pytest] [file-paths-relative-to-repo-root] +# check/pytest [--actually-quiet] [--flags for pytest] [file-paths-relative-to-repo-root] # # The --actually-quiet argument filters out any progress output from pytest. -# If --cirq-only is specified, only cirq-core tests are executed other cirq modules won't be available on the -# PYTHONPATH, which is useful to test cirq-core's ability to function independently. # # You may specify pytest flags and specific files to test. The file paths # must be relative to the repository root. If no files are specified, everything @@ -21,24 +19,15 @@ cd "$(git rev-parse --show-toplevel)" PYTEST_ARGS=() ACTUALLY_QUIET="" -CIRQ_ONLY="" for arg in $@; do if [[ "${arg}" == "--actually-quiet" ]]; then ACTUALLY_QUIET=1 - elif [[ "${arg}" == "--cirq-only" ]]; then - CIRQ_ONLY=1 else PYTEST_ARGS+=("${arg}") fi done -if [ -z "${CIRQ_ONLY}" ]; then - source dev_tools/pypath -else - export PYTHONPATH=cirq-core - PYTEST_ARGS+=("./cirq-core") -fi - +source dev_tools/pypath PYTHON_VERSION=$(python -V 2>&1 | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/') if [ "$PYTHON_VERSION" -lt "37" ]; then PYTEST_ARGS+=("--ignore=cirq-rigetti") diff --git a/cirq-aqt/setup.py b/cirq-aqt/setup.py index 1b52e616a67..ff20b49631c 100644 --- a/cirq-aqt/setup.py +++ b/cirq-aqt/setup.py @@ -45,6 +45,7 @@ # Read in requirements requirements = open('requirements.txt').readlines() requirements = [r.strip() for r in requirements] +requirements += [f'cirq-core=={__version__}'] cirq_packages = ['cirq_aqt'] + [ 'cirq_aqt.' + package for package in find_packages(where='cirq_aqt') diff --git a/cirq-ionq/requirements.txt b/cirq-ionq/requirements.txt index e69de29bb2d..3d368bb39ab 100644 --- a/cirq-ionq/requirements.txt +++ b/cirq-ionq/requirements.txt @@ -0,0 +1 @@ +requests~=2.18 \ No newline at end of file diff --git a/cirq-ionq/setup.py b/cirq-ionq/setup.py index dfbb5d8fe2d..d8fb11a41cd 100644 --- a/cirq-ionq/setup.py +++ b/cirq-ionq/setup.py @@ -51,6 +51,8 @@ # Sanity check assert __version__, 'Version string cannot be empty' +requirements += [f'cirq-core=={__version__}'] + setup( name=name, version=__version__, diff --git a/cirq-pasqal/requirements.txt b/cirq-pasqal/requirements.txt index e69de29bb2d..3d368bb39ab 100644 --- a/cirq-pasqal/requirements.txt +++ b/cirq-pasqal/requirements.txt @@ -0,0 +1 @@ +requests~=2.18 \ No newline at end of file diff --git a/cirq-pasqal/setup.py b/cirq-pasqal/setup.py index 56b11c628ca..dc9ff2a6fc5 100644 --- a/cirq-pasqal/setup.py +++ b/cirq-pasqal/setup.py @@ -43,14 +43,15 @@ # Read in requirements requirements = open('requirements.txt').readlines() requirements = [r.strip() for r in requirements] +# Sanity check +assert __version__, 'Version string cannot be empty' + +requirements += [f'cirq-core=={__version__}'] cirq_packages = ['cirq_pasqal'] + [ 'cirq_pasqal.' + package for package in find_packages(where='cirq_pasqal') ] -# Sanity check -assert __version__, 'Version string cannot be empty' - setup( name=name, version=__version__, diff --git a/cirq-rigetti/setup.py b/cirq-rigetti/setup.py index 2a20d1334d4..ea0e34bf2c2 100644 --- a/cirq-rigetti/setup.py +++ b/cirq-rigetti/setup.py @@ -44,12 +44,15 @@ requirements = open('requirements.txt').readlines() requirements = [r.strip() for r in requirements] +# Sanity check +assert __version__, 'Version string cannot be empty' + +requirements += [f'cirq-core=={__version__}'] + cirq_packages = ['cirq_rigetti'] + [ 'cirq_rigetti.' + package for package in find_packages(where='cirq_rigetti') ] -# Sanity check -assert __version__, 'Version string cannot be empty' setup( name=name, diff --git a/cirq-web/setup.py b/cirq-web/setup.py index 7fb16efd672..6552565e9a8 100644 --- a/cirq-web/setup.py +++ b/cirq-web/setup.py @@ -46,7 +46,6 @@ # Sanity check assert __version__, 'Version string cannot be empty' -# This is a pure metapackage that installs all our packages requirements += [f'cirq-core=={__version__}'] # Gather all packages from cirq_web, and the dist/ folder from cirq_ts diff --git a/dev_tools/bash_scripts_test.py b/dev_tools/bash_scripts_test.py index fe2ddc35adc..b7822a7c31a 100644 --- a/dev_tools/bash_scripts_test.py +++ b/dev_tools/bash_scripts_test.py @@ -16,17 +16,12 @@ from typing import TYPE_CHECKING, Iterable from dev_tools import shell_tools +from dev_tools.test_utils import only_on_posix if TYPE_CHECKING: import _pytest.tmpdir -def only_on_posix(func): - if os.name != 'posix': - return None - return func - - def run( *, script_file: str, diff --git a/dev_tools/cloned_env_test.py b/dev_tools/cloned_env_test.py new file mode 100644 index 00000000000..7896a1aef7f --- /dev/null +++ b/dev_tools/cloned_env_test.py @@ -0,0 +1,42 @@ +# Copyright 2020 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests the cloned_env fixture in conftest.py""" +import json +import os +from unittest import mock + +import pytest + +from dev_tools import shell_tools +from dev_tools.test_utils import only_on_posix + + +# due to shell_tools dependencies windows builds break on this +# see https://github.com/quantumlib/Cirq/issues/4394 +@only_on_posix +# ensure that no cirq packages are on the PYTHONPATH, this is important, otherwise +# the "isolation" fails and all the cirq modules would be in the list +@mock.patch.dict(os.environ, {"PYTHONPATH": ""}) +@pytest.mark.parametrize('param', ['a', 'b', 'c']) +def test_isolated_env_cloning(cloned_env, param): + env = cloned_env("test_isolated", "flynt==0.64") + assert (env / "bin" / "pip").is_file() + + result = shell_tools.run_cmd( + *f"{env}/bin/pip list --format=json".split(), out=shell_tools.TeeCapture() + ) + packages = json.loads(result.out) + assert {"name": "flynt", "version": "0.64"} in packages + assert {"astor", "flynt", "pip", "setuptools", "wheel"} == set(p['name'] for p in packages) diff --git a/dev_tools/conftest.py b/dev_tools/conftest.py index 615f3b4566e..e9ff7355799 100644 --- a/dev_tools/conftest.py +++ b/dev_tools/conftest.py @@ -11,8 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os +import shutil +import sys +import tempfile +import uuid +from pathlib import Path +from typing import Tuple import pytest +from filelock import FileLock + +from dev_tools import shell_tools +from dev_tools.env_tools import create_virtual_env def pytest_configure(config): @@ -29,3 +40,85 @@ def pytest_collection_modifyitems(config, items): for item in items: if 'slow' in item.keywords: item.add_marker(skip_slow_marker) + + +@pytest.fixture(scope="session") +def cloned_env(testrun_uid, worker_id): + """Fixture to allow tests to run in a clean virtual env. + + It de-duplicates installation of base packages. Assuming `virtualenv-clone` exists on the PATH, + it creates first a prototype environment and then clones for each new request the same env. + This fixture is safe to use with parallel execution, i.e. pytest-xdist. The workers synchronize + via a file lock, the first worker will (re)create the prototype environment, the others will + reuse it via cloning. + + A group of tests that share the same base environment is identified by a name, `env_dir`, + which will become the directory within the temporary directory to hold the virtualenv. + + Usage: + + >>> def test_something_in_clean_env(cloned_env): + # base_env will point to a pathlib.Path containing the virtual env which will + # have quimb, jinja and whatever reqs.txt contained. + base_env = cloned_env("some_tests", "quimb", "jinja", "-r", "reqs.txt") + + # To install new packages (that are potentially different for each test instance) + # just run pip install from the virtual env + subprocess.run(f"{base_env}/bin/pip install something".split(" ")) + ... + + Returns: + a function to create the cloned base environment with signature + `def base_env_creator(env_dir: str, *pip_install_args: str) -> Path`. + Use `env_dir` to specify the directory name per shared base packages. + Use `pip_install_args` varargs to pass arguments to `pip install`, these + can be requirements files, e.g. `'-r','dev_tools/.../something.txt'` or + actual packages as well, e.g.`'quimb'`. + """ + base_dir = None + + def base_env_creator(env_dir_name: str, *pip_install_args: str) -> Path: + """The function to create a cloned base environment.""" + # get/create a temp directory shared by all workers + base_temp_path = Path(tempfile.gettempdir()) / "cirq-pytest" + os.makedirs(name=base_temp_path, exist_ok=True) + nonlocal base_dir + base_dir = base_temp_path / env_dir_name + with FileLock(str(base_dir) + ".lock"): + if _check_for_reuse_or_recreate(base_dir): + print(f"Pytest worker [{worker_id}] is reusing {base_dir} for '{env_dir_name}'.") + else: + print(f"Pytest worker [{worker_id}] is creating {base_dir} for '{env_dir_name}'.") + _create_base_env(base_dir, pip_install_args) + + clone_dir = base_temp_path / str(uuid.uuid4()) + shell_tools.run_cmd("virtualenv-clone", str(base_dir), str(clone_dir)) + return clone_dir + + def _check_for_reuse_or_recreate(env_dir: Path): + reuse = False + if env_dir.is_dir() and (env_dir / "testrun.uid").is_file(): + uid = open(env_dir / "testrun.uid").readlines()[0] + # if the dir is from this test session, let's reuse it + if uid == testrun_uid: + reuse = True + else: + # if we have a dir from a previous test session, recreate it + shutil.rmtree(env_dir) + return reuse + + def _create_base_env(base_dir: Path, pip_install_args: Tuple[str, ...]): + try: + create_virtual_env(str(base_dir), [], sys.executable, True) + with open(base_dir / "testrun.uid", mode="w") as f: + f.write(testrun_uid) + if pip_install_args: + shell_tools.run_cmd(f"{base_dir}/bin/pip", "install", *pip_install_args) + except BaseException as ex: + # cleanup on failure + if base_dir.is_dir(): + print(f"Removing {base_dir}, due to error: {ex}") + shutil.rmtree(base_dir) + raise + + return base_env_creator diff --git a/dev_tools/modules.py b/dev_tools/modules.py index 85f40f233f9..f47e89fcf6c 100644 --- a/dev_tools/modules.py +++ b/dev_tools/modules.py @@ -55,6 +55,7 @@ class Module: version: str = dataclasses.field(init=False) top_level_packages: List[str] = dataclasses.field(init=False) top_level_package_paths: List[Path] = dataclasses.field(init=False) + install_requires: List[str] = dataclasses.field(init=False) def __post_init__(self) -> None: self.name = self.raw_setup['name'] @@ -64,6 +65,9 @@ def __post_init__(self) -> None: self.top_level_packages = [] self.top_level_package_paths = [self.root / p for p in self.top_level_packages] self.version = self.raw_setup['version'] + self.install_requires = ( + [] if 'install_requires' not in self.raw_setup else self.raw_setup['install_requires'] + ) def list_modules( diff --git a/dev_tools/modules_test.py b/dev_tools/modules_test.py index 534f2a0d62d..5edf53b82f9 100644 --- a/dev_tools/modules_test.py +++ b/dev_tools/modules_test.py @@ -46,6 +46,7 @@ def test_modules(): assert mod1.version == '0.12.0.dev' assert mod1.top_level_packages == ['pack1'] assert mod1.top_level_package_paths == [Path('mod1') / 'pack1'] + assert mod1.install_requires == ['req1', 'req2'] mod2 = Module( root=Path('mod2'), raw_setup={'name': 'module2', 'version': '1.2.3', 'packages': ['pack2']} @@ -55,6 +56,7 @@ def test_modules(): assert mod2.version == '1.2.3' assert mod2.top_level_packages == ['pack2'] assert mod2.top_level_package_paths == [Path('mod2') / 'pack2'] + assert mod2.install_requires == [] assert modules.list_modules(search_dir=Path("dev_tools/modules_test_data")) == [mod1, mod2] parent = Module( diff --git a/dev_tools/notebooks/isolated_notebook_test.py b/dev_tools/notebooks/isolated_notebook_test.py index a509adfe1a9..9eefb4bad79 100644 --- a/dev_tools/notebooks/isolated_notebook_test.py +++ b/dev_tools/notebooks/isolated_notebook_test.py @@ -30,10 +30,8 @@ from typing import Set, List import pytest -from filelock import FileLock from dev_tools import shell_tools -from dev_tools.env_tools import create_virtual_env from dev_tools.notebooks import list_all_notebooks, filter_notebooks, rewrite_notebook # these notebooks rely on features that are not released yet @@ -86,7 +84,6 @@ # for running the notebooks "papermill", "jupyter", - "virtualenv-clone", # assumed to be part of colab "seaborn~=0.11.1", # https://github.com/nteract/papermill/issues/519 @@ -141,32 +138,6 @@ def _list_changed_notebooks() -> Set[str]: return set() -@pytest.mark.slow -@pytest.fixture(scope="session") -def base_env(tmp_path_factory, worker_id): - # get the temp directory shared by all workers - root_tmp_dir = tmp_path_factory.getbasetemp().parent.parent - proto_dir = root_tmp_dir / "proto_dir" - with FileLock(str(proto_dir) + ".lock"): - if proto_dir.is_dir(): - print(f"{worker_id} returning as {proto_dir} is a dir!") - print( - f"If all the notebooks are failing, the test framework might " - f"have left this directory around. Try 'rm -rf {proto_dir}'" - ) - else: - print(f"{worker_id} creating stuff...") - _create_base_env(proto_dir) - - return root_tmp_dir, proto_dir - - -def _create_base_env(proto_dir): - create_virtual_env(str(proto_dir), [], sys.executable, True) - pip_path = str(proto_dir / "bin" / "pip") - shell_tools.run_cmd(pip_path, "install", *PACKAGES) - - def _partitioned_test_cases(notebooks): n_partitions = int(os.environ.get("NOTEBOOK_PARTITIONS", "1")) return [(f"partition-{i%n_partitions}", notebook) for i, notebook in enumerate(notebooks)] @@ -177,7 +148,7 @@ def _partitioned_test_cases(notebooks): "partition, notebook_path", _partitioned_test_cases(filter_notebooks(_list_changed_notebooks(), SKIP_NOTEBOOKS)), ) -def test_notebooks_against_released_cirq(partition, notebook_path, base_env): +def test_notebooks_against_released_cirq(partition, notebook_path, cloned_env): """Tests the notebooks in isolated virtual environments. In order to speed up the execution of these tests an auxiliary file may be supplied which @@ -192,22 +163,19 @@ def test_notebooks_against_released_cirq(partition, notebook_path, base_env): notebook_file = os.path.basename(notebook_path) notebook_rel_dir = os.path.dirname(os.path.relpath(notebook_path, ".")) out_path = f"out/{notebook_rel_dir}/{notebook_file[:-6]}.out.ipynb" - tmpdir, proto_dir = base_env + notebook_env = cloned_env("isolated_notebook_tests", *PACKAGES) notebook_file = os.path.basename(notebook_path) - dir_name = notebook_file.rstrip(".ipynb") - - notebook_env = os.path.join(tmpdir, f"{dir_name}") rewritten_notebook_descriptor, rewritten_notebook_path = rewrite_notebook(notebook_path) cmd = f""" mkdir -p out/{notebook_rel_dir} -{proto_dir}/bin/virtualenv-clone {proto_dir} {notebook_env} cd {notebook_env} . ./bin/activate +pip list papermill {rewritten_notebook_path} {os.getcwd()}/{out_path}""" - _, stderr, status = shell_tools.run_shell( + stdout, stderr, status = shell_tools.run_shell( cmd=cmd, log_run_to_stderr=False, raise_on_fail=False, @@ -219,6 +187,7 @@ def test_notebooks_against_released_cirq(partition, notebook_path, base_env): ) if status != 0: + print(stdout) print(stderr) pytest.fail( f"Notebook failure: {notebook_file}, please see {out_path} for the output " diff --git a/dev_tools/packaging/isolated_packages_test.py b/dev_tools/packaging/isolated_packages_test.py new file mode 100644 index 00000000000..8b46c7c1816 --- /dev/null +++ b/dev_tools/packaging/isolated_packages_test.py @@ -0,0 +1,55 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +from unittest import mock + +import pytest + +from dev_tools import shell_tools +from dev_tools.modules import list_modules + +PACKAGES = [ + "-r", + "dev_tools/requirements/deps/pytest.txt", + "-r", + # one of the _compat_test.py tests uses flynt for testing metadata + "dev_tools/requirements/deps/flynt.txt", +] + + +@pytest.mark.slow +# ensure that no cirq packages are on the PYTHONPATH, this is important, otherwise +# the "isolation" fails and for example cirq-core would be on the PATH +@mock.patch.dict(os.environ, {"PYTHONPATH": ""}) +@pytest.mark.parametrize('module', list_modules(), ids=[m.name for m in list_modules()]) +def test_isolated_packages(cloned_env, module): + env = cloned_env("isolated_packages", *PACKAGES) + + if str(module.root) != "cirq-core": + assert f'cirq-core=={module.version}' in module.install_requires + + result = shell_tools.run_cmd( + *f"{env}/bin/pip install ./{module.root} ./cirq-core".split(), + err=shell_tools.TeeCapture(), + raise_on_fail=False, + ) + assert result.exit_code == 0, f"Failed to install {module.name}:\n{result.err}" + + result = shell_tools.run_cmd( + *f"{env}/bin/pytest ./{module.root} --ignore ./cirq-core/cirq/contrib".split(), + out=shell_tools.TeeCapture(), + err=shell_tools.TeeCapture(), + raise_on_fail=False, + ) + assert result.exit_code == 0, f"Failed isolated tests for {module.name}:\n{result.stdout}" diff --git a/dev_tools/packaging/packaging_test.sh b/dev_tools/packaging/packaging_test.sh index 430d5cb86fc..fd1d00c4dd4 100755 --- a/dev_tools/packaging/packaging_test.sh +++ b/dev_tools/packaging/packaging_test.sh @@ -34,7 +34,7 @@ export CIRQ_PRE_RELEASE_VERSION=$(dev_tools/packaging/generate-dev-version-id.sh out_dir=${tmp_dir}/dist dev_tools/packaging/produce-package.sh ${out_dir} $CIRQ_PRE_RELEASE_VERSION -# test installation +# test installation "${tmp_dir}/env/bin/python" -m pip install ${out_dir}/* echo =========================== diff --git a/dev_tools/requirements/cirq-only.env.txt b/dev_tools/requirements/cirq-only.env.txt deleted file mode 100644 index 1da3e75eb27..00000000000 --- a/dev_tools/requirements/cirq-only.env.txt +++ /dev/null @@ -1,2 +0,0 @@ --r ../../cirq-core/requirements.txt --r deps/dev-tools.txt \ No newline at end of file diff --git a/dev_tools/requirements/deps/pytest.txt b/dev_tools/requirements/deps/pytest.txt index 8c1fb6a9897..8f6b17f739f 100644 --- a/dev_tools/requirements/deps/pytest.txt +++ b/dev_tools/requirements/deps/pytest.txt @@ -16,3 +16,7 @@ importlib-metadata; python_version < '3.8' # codeowners test dependency codeowners; python_version >= '3.7' + +# for creating isolated environments +virtualenv +virtualenv-clone \ No newline at end of file diff --git a/dev_tools/requirements/isolated-base.env.txt b/dev_tools/requirements/isolated-base.env.txt new file mode 100644 index 00000000000..b84d3b97445 --- /dev/null +++ b/dev_tools/requirements/isolated-base.env.txt @@ -0,0 +1,6 @@ +# a minimal env for testing isolated notebooks and packages + +-r deps/pytest.txt + +# for shell_tools +requests \ No newline at end of file diff --git a/dev_tools/requirements/isolated-notebooks.env.txt b/dev_tools/requirements/isolated-notebooks.env.txt deleted file mode 100644 index 2a5e4c1e142..00000000000 --- a/dev_tools/requirements/isolated-notebooks.env.txt +++ /dev/null @@ -1,8 +0,0 @@ -# a minimal env for testing isolated notebooks, which pull their dependencies using -# the notebook itself. When testing notebooks, use only this requirements file. - --r deps/pytest.txt - -virtualenv -# for shell_tools -requests \ No newline at end of file diff --git a/dev_tools/shell_tools_test.py b/dev_tools/shell_tools_test.py index d1d2eeb94ab..8b9d6b2fb7c 100644 --- a/dev_tools/shell_tools_test.py +++ b/dev_tools/shell_tools_test.py @@ -13,17 +13,11 @@ # limitations under the License. import subprocess -import os import pytest from dev_tools import shell_tools - - -def only_on_posix(func): - if os.name != 'posix': - return None - return func +from dev_tools.test_utils import only_on_posix def run_cmd(*args, **kwargs): diff --git a/dev_tools/test_utils.py b/dev_tools/test_utils.py new file mode 100644 index 00000000000..792a6daff1e --- /dev/null +++ b/dev_tools/test_utils.py @@ -0,0 +1,21 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +import pytest + + +def only_on_posix(func): + """Only run test on posix.""" + return pytest.mark.skipif(os.name != 'posix', reason=f"os {os.name} is not posix")(func)