def _git_fetch_for_comparison(remote: str, actual_branch: str, compare_branch: str, verbose: bool) -> prepared_env.PreparedEnv: """Fetches two branches including their common ancestor. Limits the depth of the fetch to avoid unnecessary work. Scales up the depth exponentially and tries again when the initial guess is not deep enough. Args: remote: The location of the remote repository, in a format that the git command will understand. actual_branch: A remote branch or ref to fetch, compare_branch: Another remote branch or ref to fetch, verbose: When set, more progress output is produced. Returns: A ComparableCommits containing the commit id of the actual branch and a the id of a commit to compare against (e.g. for when doing incremental checks). """ actual_id = '' base_id = '' optional_quiet = [] if verbose else ['--quiet'] for depth in [10, 100, 1000, None]: optional_depth = [] if depth is None else [f'--depth={depth}'] shell_tools.run( [ 'git', 'fetch', *optional_quiet, remote, actual_branch, *optional_depth ], log_run_to_stderr=verbose, ) actual_id = shell_tools.output_of(['git', 'rev-parse', 'FETCH_HEAD']) shell_tools.run( [ 'git', 'fetch', *optional_quiet, remote, compare_branch, *optional_depth ], log_run_to_stderr=verbose, ) base_id = shell_tools.output_of(['git', 'rev-parse', 'FETCH_HEAD']) try: base_id = shell_tools.output_of( ['git', 'merge-base', actual_id, base_id]) break except subprocess.CalledProcessError: # No common ancestor. We need to dig deeper. pass return prepared_env.PreparedEnv(None, actual_id, base_id, None, None)
def test_run_with_command_logging(): catch_stderr = io.StringIO() kw = {'stdout': subprocess.DEVNULL} with contextlib.redirect_stderr(catch_stderr): shell_tools.run(['echo', '-n', 'a', 'b'], **kw) assert catch_stderr.getvalue() == "run: ('echo', '-n', 'a', 'b')\n" catch_stderr = io.StringIO() with contextlib.redirect_stderr(catch_stderr): shell_tools.run(['echo', '-n', 'a', 'b'], abbreviate_non_option_arguments=True, **kw) assert catch_stderr.getvalue() == "run: ('echo', '-n', '[...]')\n"
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( [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
def create_virtual_env(venv_path: str, requirements_paths: Iterable[str], python_path: str, verbose: bool) -> None: """Creates a new virtual environment and then installs dependencies. Args: venv_path: Where to put the virtual environment's state. requirements_paths: Location of requirements files to -r install. python_path: The python binary to use. verbose: When set, more progress output is produced. """ optional_quiet = [] if verbose else ['--quiet'] shell_tools.run( ['virtualenv', *optional_quiet, '-p', python_path, venv_path], stdout=sys.stderr) pip_path = os.path.join(venv_path, 'bin', 'pip') for req_path in requirements_paths: shell_tools.run([pip_path, 'install', *optional_quiet, '-r', req_path], stdout=sys.stderr)
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(f"{env}/bin/pip list --format=json".split(), stdout=subprocess.PIPE) packages = json.loads(result.stdout) assert {"name": "flynt", "version": "0.64"} in packages assert {"astor", "flynt", "pip", "setuptools", "wheel"} == set(p['name'] for p in packages)
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( f"{env}/bin/pip install ./{module.root} ./cirq-core".split(), stderr=subprocess.PIPE, check=False, ) assert result.returncode == 0, f"Failed to install {module.name}:\n{result.stderr}" result = shell_tools.run( f"{env}/bin/pytest ./{module.root} --ignore ./cirq-core/cirq/contrib". split(), capture_output=True, check=False, ) assert result.returncode == 0, f"Failed isolated tests for {module.name}:\n{result.stdout}"
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(["virtualenv-clone", str(base_dir), str(clone_dir)]) return clone_dir
def run( *, script_file: str, tmpdir_factory: pytest.TempdirFactory, arg: str = '', setup: str = '', additional_intercepts: Iterable[str] = (), ) -> subprocess.CompletedProcess: """Invokes the given script within a temporary test environment.""" with open(script_file) as f: script_lines = f.readlines() # Create a unique temporary directory dir_path = tmpdir_factory.mktemp('tmp', numbered=True) file_path = os.path.join(dir_path, 'test-script.sh') intercepted = [ 'python', 'python3', 'pylint', 'env', 'pytest', 'mypy', 'black', *additional_intercepts, ] assert script_lines[0] == '#!/usr/bin/env bash\n' for e in intercepted: script_lines.insert(1, e + '() {\n echo INTERCEPTED ' + e + ' $@\n}\n') with open(file_path, 'w') as f: f.writelines(script_lines) cmd = r""" dir=$(git rev-parse --show-toplevel) cd {} git init --quiet --initial-branch master git config --local user.name 'Me' git config --local user.email '<>' git commit -m init --allow-empty --quiet --no-gpg-sign {} mkdir -p dev_tools touch dev_tools/pypath chmod +x ./test-script.sh ./test-script.sh {} """.format( dir_path, setup, arg ) return shell_tools.run( cmd, log_run_to_stderr=False, shell=True, check=False, capture_output=True )
def fetch_github_pull_request( destination_directory: str, repository: github_repository.GithubRepository, pull_request_number: int, verbose: bool, ) -> prepared_env.PreparedEnv: """Uses content from github to create a dir for testing and comparisons. Args: destination_directory: The location to fetch the contents into. repository: The github repository that the commit lives under. pull_request_number: The id of the pull request to clone. If None, then the master branch is cloned instead. verbose: When set, more progress output is produced. Returns: Commit ids corresponding to content to test/compare. """ branch = f'pull/{pull_request_number}/head' os.chdir(destination_directory) print('chdir', destination_directory, file=sys.stderr) optional_quiet = [] if verbose else ['--quiet'] shell_tools.run(['git', 'init', *optional_quiet], stdout=sys.stderr) result = _git_fetch_for_comparison( remote=repository.as_remote(), actual_branch=branch, compare_branch='master', verbose=verbose, ) optional_actual_commit_id = [] if result.actual_commit_id is None else [ result.actual_commit_id ] shell_tools.run( [ 'git', 'branch', *optional_quiet, 'compare_commit', result.compare_commit_id ], log_run_to_stderr=verbose, ) shell_tools.run( [ 'git', 'checkout', *optional_quiet, '-b', 'actual_commit', *optional_actual_commit_id ], log_run_to_stderr=verbose, ) return prepared_env.PreparedEnv( github_repo=repository, actual_commit_id=result.actual_commit_id, compare_commit_id=result.compare_commit_id, destination_directory=destination_directory, virtual_env_path=None, )
def test_notebooks_against_released_cirq(notebook_path): """Test that jupyter notebooks execute. In order to speed up the execution of these tests an auxiliary file may be supplied which performs substitutions on the notebook to make it faster. Specifically for a notebook file notebook.ipynb, one can supply a file notebook.tst which contains the substitutes. The substitutions are provide in the form `pattern->replacement` where the pattern is what is matched and replaced. While the pattern is compiled as a regular expression, it is considered best practice to not use complicated regular expressions. Lines in this file that do not have `->` are ignored. """ 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" rewritten_notebook_descriptor, rewritten_notebook_path = rewrite_notebook( notebook_path) papermill_flags = "--no-request-save-on-cell-execute --autosave-cell-every 0" cmd = f"""mkdir -p out/{notebook_rel_dir} papermill {rewritten_notebook_path} {out_path} {papermill_flags}""" result = shell_tools.run(cmd, log_run_to_stderr=False, shell=True, check=False, capture_output=True) if result.returncode != 0: print(result.stderr) pytest.fail( f"Notebook failure: {notebook_file}, please see {out_path} for the output " f"notebook (in Github Actions, you can download it from the workflow artifact" f" 'notebook-outputs')") if rewritten_notebook_descriptor: os.close(rewritten_notebook_descriptor)
def fetch_local_files(destination_directory: str, verbose: bool) -> prepared_env.PreparedEnv: """Uses local files to create a directory for testing and comparisons. Args: destination_directory: The directory where the copied files should go. verbose: When set, more progress output is produced. Returns: Commit ids corresponding to content to test/compare. """ staging_dir = destination_directory + '-staging' optional_quiet = [] if verbose else ['--quiet'] try: shutil.copytree(get_repo_root(), staging_dir) os.chdir(staging_dir) if verbose: print('chdir', staging_dir, file=sys.stderr) shell_tools.run(['git', 'add', '--all'], stdout=sys.stderr, log_run_to_stderr=verbose) shell_tools.run( [ 'git', 'commit', '-m', 'working changes', '--allow-empty', '--no-gpg-sign', *optional_quiet, ], stdout=sys.stderr, log_run_to_stderr=verbose, ) cur_commit = shell_tools.output_of(['git', 'rev-parse', 'HEAD']) os.chdir(destination_directory) if verbose: print('chdir', destination_directory, file=sys.stderr) shell_tools.run(['git', 'init', *optional_quiet], stdout=sys.stderr, log_run_to_stderr=verbose) result = _git_fetch_for_comparison(staging_dir, cur_commit, 'master', verbose=verbose) finally: shutil.rmtree(staging_dir, ignore_errors=True) optional_actual_commit_id = [] if result.actual_commit_id is None else [ result.actual_commit_id ] shell_tools.run( [ 'git', 'branch', *optional_quiet, 'compare_commit', result.compare_commit_id ], log_run_to_stderr=verbose, ) shell_tools.run( [ 'git', 'checkout', *optional_quiet, '-b', 'actual_commit', *optional_actual_commit_id ], log_run_to_stderr=verbose, ) return prepared_env.PreparedEnv( github_repo=None, actual_commit_id=result.actual_commit_id, compare_commit_id=result.compare_commit_id, destination_directory=destination_directory, virtual_env_path=None, )
def run(*args, **kwargs): return shell_tools.run(*args, log_run_to_stderr=False, **kwargs)