def write_mutant_cache_file(mutant: Mutant) -> None: """Create the cache file for the mutant on disk in __pycache__. Existing target cache files are removed to ensure clean overwrites. Reference: https://github.com/python/cpython/blob/master/Lib/py_compile.py#L157 Args: mutant: the mutant definition to create Returns: None, creates the cache file on disk. """ check_cache_invalidation_mode() bytecode = importlib._bootstrap_external._code_to_timestamp_pyc( # type: ignore mutant.mutant_code, mutant.source_stats["mtime"], mutant.source_stats["size"]) remove_existing_cache_files(mutant.src_file) create_cache_dirs(mutant.cfile) LOGGER.debug("Writing mutant cache file: %s", mutant.cfile) importlib._bootstrap_external._write_atomic(mutant.cfile, bytecode, mutant.mode) # type: ignore
def test_create_cache_dirs(tmp_path): """The __pycache__ directory should only be created once.""" cache_file = tmp_path / "__pycache__" / "cfile.pyc" # first run creates the __pycache__ directory create_cache_dirs(cache_file) # second run should do nothing since it already exists create_cache_dirs(cache_file) dir = list(tmp_path.iterdir()) # only one dirctory created, and should be __pycache__ assert len(dir) == 1 assert dir[0].parts[-1] == "__pycache__"
def create_mutation_run_parallelcache_trial( genome: Genome, target_idx: LocIndex, mutation_op: Any, test_cmds: List[str], max_runtime: float) -> MutantTrialResult: """Similar to run.create_mutation_run_trial() but using the parallel cache directory settings. This function requires Python 3.8 and does not run with Python 3.7. Importantly, it has the identical signature to run.create_mutation_run_trial() and is substituted in the run.mutation_sample_dispatch(). Args: genome: the genome to mutate target_idx: the mutation location mutation_op: the mutation operation test_cmds: the test commands to execute with the mutated code max_runtime: timeout for the subprocess trial Returns: MutantTrialResult Raises: EnvironmentError: if Python version is less than 3.8 """ if sys.version_info < (3, 8): raise EnvironmentError( "Python 3.8 is required to use PYTHONPYCACHEPREFIX.") # Note in coverage reports this shows as untested code due to the subprocess dispatching # the 'slow' tests in `test_run.py` cover this. cache.check_cache_invalidation_mode() # create the mutant without writing the cache mutant = genome.mutate(target_idx, mutation_op, write_cache=False) # set up parallel cache structure parallel_cache = Path.cwd() / PARALLEL_PYCACHE_DIR / uuid.uuid4().hex resolved_source_parts = genome.source_file.resolve().parent.parts[ 1:] # type: ignore parallel_cfile = parallel_cache.joinpath( *resolved_source_parts) / mutant.cfile.name bytecode = importlib._bootstrap_external._code_to_timestamp_pyc( # type: ignore mutant.mutant_code, mutant.source_stats["mtime"], mutant.source_stats["size"]) LOGGER.debug("Writing parallel mutant cache file: %s", parallel_cfile) cache.create_cache_dirs(parallel_cfile) importlib._bootstrap_external._write_atomic( # type: ignore parallel_cfile, bytecode, mutant.mode, ) copy_env = os.environ.copy() copy_env["PYTHONPYCACHEPREFIX"] = str(parallel_cache) try: mutant_trial = subprocess.run( test_cmds, env=copy_env, capture_output=capture_output(LOGGER.getEffectiveLevel()), timeout=max_runtime + MULTI_PROC_TIMEOUT_BUFFER, ) return_code = mutant_trial.returncode except subprocess.TimeoutExpired: return_code = 3 LOGGER.debug("Removing parallel cache file: %s", parallel_cache.parts[-1]) shutil.rmtree(parallel_cache) return MutantTrialResult( mutant=MutantReport(src_file=mutant.src_file, src_idx=mutant.src_idx, mutation=mutant.mutation), return_code=return_code, )