def run_reduction_on_report( # pylint: disable=too-many-locals; test_dir: Path, reports_dir: Path, binary_manager: binaries_util.BinaryManager, settings: Settings, ) -> None: test = test_util.metadata_read(test_dir) check( bool(test.device and test.device.name), AssertionError( f"Cannot reduce {str(test_dir)}; " f"device must be specified in {str(test_util.get_metadata_path(test_dir))}" ), ) source_dir = test_util.get_source_dir(test_dir) try: run_reduction( source_dir_to_reduce=source_dir, reduction_output_dir=test_util.get_reductions_dir( test_dir, test.device.name), binary_manager=binary_manager, settings=settings, ) except fuzz_glsl_test.ReductionFailedError as ex: # Create a symlink to the failed reduction so it is easy to investigate failed reductions. link_to_failed_reduction_path = ( reports_dir / "failed_reductions" / f"{test_dir.name}_{ex.reduction_work_dir.name}") util.make_directory_symlink( new_symlink_file_path=link_to_failed_reduction_path, existing_dir=ex.reduction_work_dir, )
def run_reduction_on_report( test_dir: Path, reports_dir: Path, binary_manager: binaries_util.BinaryManager, settings: Settings, ) -> None: test = test_util.metadata_read(test_dir) if not test.device or not test.device.name: raise AssertionError( f"Cannot reduce {str(test_dir)}; device must be specified") try: run_reduction( source_dir_to_reduce=test_util.get_source_dir(test_dir), reduction_output_dir=test_util.get_reductions_dir( test_dir, test.device.name), binary_manager=binary_manager, settings=settings, ) except ReductionFailedError as ex: # Create a symlink to the failed reduction so it is easy to investigate failed reductions. link_to_failed_reduction_path = ( reports_dir / "failed_reductions" / f"{test_dir.name}_{ex.reduction_work_dir.name}") util.make_directory_symlink( new_symlink_file_path=link_to_failed_reduction_path, existing_dir=ex.reduction_work_dir, )
def should_reduce_report(settings: Settings, test_dir: Path) -> bool: test = test_util.metadata_read(test_dir) status = test.expected_status signature = test.crash_signature if not settings.reduce_tool_crashes and status == fuzz.STATUS_TOOL_CRASH: return False if ( not settings.reduce_crashes and status == fuzz.STATUS_CRASH and signature != signature_util.BAD_IMAGE_SIGNATURE ): return False if ( not settings.reduce_bad_images and status == fuzz.STATUS_CRASH and signature == signature_util.BAD_IMAGE_SIGNATURE ): return False if ( settings.only_reduce_signature_regex and re.fullmatch(settings.only_reduce_signature_regex, signature) is None ): return False return True
def create_summary_and_reproduce( test_dir: Path, binary_manager: binaries_util.BinaryManager) -> None: util.mkdirs_p(test_dir / "summary") test_metadata = test_util.metadata_read(test_dir) # noinspection PyTypeChecker if test_metadata.HasField("glsl") or test_metadata.HasField("spirv_fuzz"): fuzz_glsl_test.create_summary_and_reproduce(test_dir, binary_manager) else: raise AssertionError("Unrecognized test type")
def run( test_dir: Path, binary_manager: binaries_util.BinaryManager, device: Optional[Device] = None, ) -> str: test: Test = test_util.metadata_read(test_dir) if not device: device = test.device result_output_dir = run_shader_job( source_dir=test_util.get_source_dir(test_dir), output_dir=test_util.get_results_directory(test_dir, device.name), binary_manager=binary_manager, device=device, ) return result_util.get_status(result_output_dir)
def create_summary_and_reproduce( test_dir: Path, binary_manager: binaries_util.BinaryManager) -> None: test_metadata = test_util.metadata_read(test_dir) summary_dir = test_dir / "summary" unreduced = util.copy_dir(test_util.get_source_dir(test_dir), summary_dir / "unreduced") reduced_test_dir = test_util.get_reduced_test_dir( test_dir, test_metadata.device.name, fuzz.BEST_REDUCTION_NAME) reduced_source_dir = test_util.get_source_dir(reduced_test_dir) reduced: Optional[Path] = None if reduced_source_dir.exists(): reduced = util.copy_dir(reduced_source_dir, summary_dir / "reduced") run_shader_job( source_dir=unreduced, output_dir=(summary_dir / "unreduced_result"), binary_manager=binary_manager, ) variant_reduced_glsl_result: Optional[Path] = None if reduced: variant_reduced_glsl_result = run_shader_job( source_dir=reduced, output_dir=(summary_dir / "reduced_result"), binary_manager=binary_manager, ) # Some post-processing for common error types. if variant_reduced_glsl_result: status = result_util.get_status(variant_reduced_glsl_result) if status == fuzz.STATUS_TOOL_CRASH: tool_crash_summary_bug_report_dir( reduced_source_dir, variant_reduced_glsl_result, summary_dir, binary_manager, )
def run_reduction_on_report( test_dir: Path, reports_dir: Path, binary_manager: binaries_util.BinaryManager) -> None: test = test_util.metadata_read(test_dir) try: reduced_test = test_dir reduced_test = run_reduction( test_dir_reduction_output=test_dir, test_dir_to_reduce=reduced_test, preserve_semantics=True, binary_manager=binary_manager, reduction_name="1", ) if test.crash_signature != signature_util.BAD_IMAGE_SIGNATURE: reduced_test = run_reduction( test_dir_reduction_output=test_dir, test_dir_to_reduce=reduced_test, preserve_semantics=False, binary_manager=binary_manager, reduction_name="2", ) device_name = test.device.name # Create a symlink to the "best" reduction. best_reduced_test_link = test_util.get_reduced_test_dir( test_dir, device_name, fuzz.BEST_REDUCTION_NAME) util.make_directory_symlink( new_symlink_file_path=best_reduced_test_link, existing_dir=reduced_test) except ReductionFailedError as ex: # Create a symlink to the failed reduction so it is easy to investigate failed reductions. link_to_failed_reduction_path = ( reports_dir / "failed_reductions" / f"{test_dir.name}_{ex.reduction_name}") util.make_directory_symlink( new_symlink_file_path=link_to_failed_reduction_path, existing_dir=ex.reduction_work_dir, )
def run_reduction( test_dir_reduction_output: Path, test_dir_to_reduce: Path, preserve_semantics: bool, binary_manager: binaries_util.BinaryManager, reduction_name: str = "reduction1", ) -> Path: test = test_util.metadata_read(test_dir_to_reduce) if not test.device or not test.device.name: raise AssertionError( f"Cannot reduce {str(test_dir_to_reduce)}; " f"device must be specified in {str(test_util.get_metadata_path(test_dir_to_reduce))}" ) if not test.crash_signature: raise AssertionError( f"Cannot reduce {str(test_dir_to_reduce)} because there is no crash string specified." ) # E.g. reports/crashes/no_signature/d50c96e8_opt_rand2_test_phone_ABC/results/phone_ABC/reductions/1 # Will contain work/ and source/ reduced_test_dir = test_util.get_reduced_test_dir( test_dir_reduction_output, test.device.name, reduction_name) source_dir = test_util.get_source_dir(test_dir_to_reduce) shader_jobs = tool.get_shader_jobs(source_dir) # TODO: if needed, this could become a parameter to this function. name_of_shader_to_reduce = shader_jobs[0].name if len(shader_jobs) > 1: check( len(shader_jobs) == 2 and shader_jobs[1].name == test_util.VARIANT_DIR, AssertionError( "Can only reduce tests with shader jobs reference and variant, or just variant." ), ) name_of_shader_to_reduce = shader_jobs[1].name reduction_work_variant_dir = run_glsl_reduce( source_dir=source_dir, name_of_shader_to_reduce=name_of_shader_to_reduce, output_dir=test_util.get_reduction_work_directory( reduced_test_dir, name_of_shader_to_reduce), binary_manager=binary_manager, preserve_semantics=preserve_semantics, ) final_reduced_shader_job_path = get_final_reduced_shader_job_path( reduction_work_variant_dir) check( final_reduced_shader_job_path.exists(), ReductionFailedError("Reduction failed.", reduction_name, reduction_work_variant_dir), ) # Finally, create the source_dir so the returned directory can be used as a test_dir. util.copy_dir(source_dir, test_util.get_source_dir(reduced_test_dir)) shader_job_util.copy( final_reduced_shader_job_path, test_util.get_shader_job_path(reduced_test_dir, name_of_shader_to_reduce), ) return reduced_test_dir
def maybe_add_report( # pylint: disable=too-many-locals; test_dir: Path, reports_dir: Path, device: Device, settings: Settings) -> Optional[Path]: result_output_dir = test_util.get_results_directory(test_dir, device.name) status = result_util.get_status(result_output_dir) report_subdirectory_name = "" if status == fuzz.STATUS_CRASH: report_subdirectory_name = "crashes" elif status == fuzz.STATUS_TOOL_CRASH: report_subdirectory_name = "tool_crashes" elif status == fuzz.STATUS_UNRESPONSIVE: report_subdirectory_name = "unresponsive" if not report_subdirectory_name: return None log_path = result_util.get_log_path(result_output_dir) log_contents = util.file_read_text(log_path) signature = signature_util.get_signature_from_log_contents(log_contents) signature_dir = reports_dir / report_subdirectory_name / signature util.mkdirs_p(signature_dir) # If the signature_dir contains a NOT_INTERESTING file, then don't bother creating a report. if (signature_dir / "NOT_INTERESTING").exists(): return None if signature != signature_util.BAD_IMAGE_SIGNATURE: # If we have reached the maximum number of crashes per signature for this device, don't create a report. num_duplicates = [ report_dir for report_dir in signature_dir.iterdir() if report_dir.is_dir() and report_dir.name.endswith(f"_{device.name}") ] if len(num_duplicates) >= settings.maximum_duplicate_crashes: return None # We include the device name in the directory name because it is possible that this test crashes on two # different devices but gives the same crash signature in both cases (e.g. for generic signatures # like "compile_error"). This would lead to two test copies having the same path. # It also means we can limit duplicates per device using the directory name. test_dir_in_reports = signature_dir / f"{test_dir.name}_{device.name}" util.copy_dir(test_dir, test_dir_in_reports) if signature != signature_util.BAD_IMAGE_SIGNATURE: # If we found a crash, rename the directories for all shaders other than the variant. Thus, only the variant # shader will run. bad_shader_name = result_util.get_status_bad_shader_name( test_util.get_results_directory(test_dir_in_reports, device.name)) # TODO: Could possibly improve this. Could try scanning the Amber log to figure out which shader failed? if not bad_shader_name: log("WARNING: assuming that the bad shader is the variant") bad_shader_name = test_util.VARIANT_DIR shader_jobs = tool.get_shader_jobs( test_util.get_source_dir(test_dir_in_reports)) found_bad_shader = False for shader_job in shader_jobs: if shader_job.name == bad_shader_name: found_bad_shader = True else: shader_job.shader_job.parent.rename( shader_job.shader_job.parent.parent / f"_{shader_job.name}") check( found_bad_shader, AssertionError( f"Could not find bad shader at: {test_util.get_source_dir(test_dir_in_reports) / bad_shader_name}" ), ) test_metadata = test_util.metadata_read(test_dir_in_reports) test_metadata.crash_signature = signature test_metadata.device.CopyFrom(device) test_metadata.expected_status = status test_util.metadata_write(test_metadata, test_dir_in_reports) return test_dir_in_reports
def create_summary_and_reproduce( # pylint: disable=too-many-locals; test_dir: Path, binary_manager: binaries_util.BinaryManager, settings: Settings ) -> None: test_metadata = test_util.metadata_read(test_dir) summary_dir = test_dir / "summary" summary_source_dirs: List[Path] = [] unreduced = util.copy_dir( test_util.get_source_dir(test_dir), summary_dir / "unreduced" ) summary_source_dirs.append(unreduced) # For the `summary/reduced_1/` directory. reduction_output_dir_1 = test_util.get_reduced_test_dir( test_dir, test_metadata.device.name, "1" ) reduced_1: Optional[Path] = None if reduction_output_dir_1.is_dir(): reduction_output_source_dir_1 = test_util.get_source_dir(reduction_output_dir_1) if reduction_output_source_dir_1.is_dir(): reduced_1 = util.copy_dir( reduction_output_source_dir_1, summary_dir / "reduced_1" ) summary_source_dirs.append(reduced_1) # For the `summary/reduced_2/` directory. reduction_output_dir_2 = test_util.get_reduced_test_dir( test_dir, test_metadata.device.name, "2" ) if reduction_output_dir_2.is_dir(): reduction_output_source_dir_2 = test_util.get_source_dir(reduction_output_dir_2) if reduction_output_source_dir_2.is_dir(): reduced_2 = util.copy_dir( reduction_output_source_dir_2, summary_dir / "reduced_2" ) summary_source_dirs.append(reduced_2) # If this test was generated from a stable shader... if test_metadata.derived_from.startswith("stable_") and reduced_1: # Before running the reduced_1 source dir, find any renamed shader jobs (e.g. reference/ -> _reference/) # and rename them back. Thus, the modified test becomes a wrong image test once again, even though # the actual bug was probably a crash bug. renamed_shader_jobs = list(reduced_1.glob("_*")) renamed_shader_jobs = [ r for r in renamed_shader_jobs if (r / test_util.SHADER_JOB).is_file() ] if renamed_shader_jobs: for renamed_shader_job in renamed_shader_jobs: util.move_dir( renamed_shader_job, renamed_shader_job.with_name(renamed_shader_job.name[1:]), ) # Also, if this is a spirv_fuzz test then try to create a variant_2 shader job that is even more similar to the # variant than the reference shader job. if test_metadata.HasField("spirv_fuzz"): fuzz_spirv_test.create_spirv_fuzz_variant_2( reduced_1, binary_manager, settings ) # Run every source dir that we added to the summary dir. for summary_source_dir in summary_source_dirs: run_shader_job( source_dir=summary_source_dir, output_dir=(summary_dir / f"{summary_source_dir.name}_result"), binary_manager=binary_manager, )
def run_reduction_on_report( # pylint: disable=too-many-locals; test_dir: Path, reports_dir: Path, binary_manager: binaries_util.BinaryManager) -> None: test = test_util.metadata_read(test_dir) check( bool(test.device and test.device.name), AssertionError( f"Cannot reduce {str(test_dir)}; " f"device must be specified in {str(test_util.get_metadata_path(test_dir))}" ), ) check( bool(test.crash_signature), AssertionError( f"Cannot reduce {str(test_dir)} because there is no crash string specified." ), ) source_dir = test_util.get_source_dir(test_dir) shader_jobs = tool.get_shader_jobs(source_dir) # TODO: if needed, this could become a parameter to this function. shader_job_to_reduce = shader_jobs[0] if len(shader_jobs) > 1: check( len(shader_jobs) == 2 and shader_jobs[1].name == test_util.VARIANT_DIR, AssertionError( "Can only reduce tests with shader jobs reference and variant, or just variant." ), ) shader_job_to_reduce = shader_jobs[1] shader_transformation_suffixes = shader_job_util.get_related_suffixes_that_exist( shader_job_to_reduce.shader_job, language_suffix=(shader_job_util.SUFFIX_TRANSFORMATIONS, ), ) shader_spv_suffixes = shader_job_util.get_related_suffixes_that_exist( shader_job_to_reduce.shader_job, language_suffix=(shader_job_util.SUFFIX_SPIRV, )) try: reduced_test = test_dir for index, suffix in enumerate(shader_transformation_suffixes): # E.g. .frag.transformations -> .frag extension_to_reduce = str(Path(suffix).with_suffix("")) reduced_test = run_reduction( test_dir_reduction_output=test_dir, test_dir_to_reduce=reduced_test, shader_job_name_to_reduce=shader_job_to_reduce.name, extension_to_reduce=extension_to_reduce, preserve_semantics=True, binary_manager=binary_manager, reduction_name=f"0_{index}_{suffix.split('.')[1]}", ) if test.crash_signature != signature_util.BAD_IMAGE_SIGNATURE: for index, suffix in enumerate(shader_spv_suffixes): # E.g. .frag.spv -> .frag extension_to_reduce = str(Path(suffix).with_suffix("")) reduced_test = run_reduction( test_dir_reduction_output=test_dir, test_dir_to_reduce=reduced_test, shader_job_name_to_reduce=shader_job_to_reduce.name, extension_to_reduce=extension_to_reduce, preserve_semantics=False, binary_manager=binary_manager, reduction_name=f"1_{index}_{suffix.split('.')[1]}", ) device_name = test.device.name # Create a symlink to the "best" reduction. best_reduced_test_link = test_util.get_reduced_test_dir( test_dir, device_name, fuzz.BEST_REDUCTION_NAME) util.make_directory_symlink( new_symlink_file_path=best_reduced_test_link, existing_dir=reduced_test) except ReductionFailedError as ex: # Create a symlink to the failed reduction so it is easy to investigate failed reductions. link_to_failed_reduction_path = ( reports_dir / "failed_reductions" / f"{test_dir.name}_{ex.reduction_name}") util.make_directory_symlink( new_symlink_file_path=link_to_failed_reduction_path, existing_dir=ex.reduction_work_dir, )
def run_reduction( test_dir_reduction_output: Path, test_dir_to_reduce: Path, shader_job_name_to_reduce: str, extension_to_reduce: str, preserve_semantics: bool, binary_manager: binaries_util.BinaryManager, reduction_name: str = "reduction1", ) -> Path: test = test_util.metadata_read(test_dir_to_reduce) check( bool(test.device and test.device.name), AssertionError( f"Cannot reduce {str(test_dir_to_reduce)}; " f"device must be specified in {str(test_util.get_metadata_path(test_dir_to_reduce))}" ), ) check( bool(test.crash_signature), AssertionError( f"Cannot reduce {str(test_dir_to_reduce)} because there is no crash string specified." ), ) # E.g. reports/crashes/no_signature/d50c96e8_opt_rand2_test_phone_ABC/results/phone_ABC/reductions/1 # Will contain work/ and source/ reduced_test_dir = test_util.get_reduced_test_dir( test_dir_reduction_output, test.device.name, reduction_name) source_dir = test_util.get_source_dir(test_dir_to_reduce) output_dir = test_util.get_reduction_work_directory( reduced_test_dir, shader_job_name_to_reduce) if preserve_semantics: final_shader_path = run_spirv_reduce_or_shrink( source_dir=source_dir, name_of_shader_job_to_reduce=shader_job_name_to_reduce, extension_to_reduce=extension_to_reduce, output_dir=output_dir, preserve_semantics=preserve_semantics, binary_manager=binary_manager, ) else: final_shader_path = run_spirv_reduce_or_shrink( source_dir=source_dir, name_of_shader_job_to_reduce=shader_job_name_to_reduce, extension_to_reduce=extension_to_reduce, output_dir=output_dir, preserve_semantics=preserve_semantics, binary_manager=binary_manager, ) check( final_shader_path.exists(), ReductionFailedError("Reduction failed.", reduction_name, output_dir), ) # Finally, create the source_dir so the returned directory can be used as a test_dir. # Copy the original source directory. util.copy_dir(source_dir, test_util.get_source_dir(reduced_test_dir)) # And then replace the shader. # Destination file. E.g. reductions/source/variant/shader.frag.spv output_shader_prefix = ( test_util.get_source_dir(reduced_test_dir) / shader_job_name_to_reduce / test_util.SHADER_JOB).with_suffix(extension_to_reduce + shader_job_util.SUFFIX_SPIRV) util.copy_file( final_shader_path.with_suffix(shader_job_util.SUFFIX_SPIRV), output_shader_prefix.with_suffix(shader_job_util.SUFFIX_SPIRV), ) if preserve_semantics: util.copy_file( final_shader_path.with_suffix( shader_job_util.SUFFIX_TRANSFORMATIONS), output_shader_prefix.with_suffix( shader_job_util.SUFFIX_TRANSFORMATIONS), ) util.copy_file( final_shader_path.with_suffix( shader_job_util.SUFFIX_TRANSFORMATIONS_JSON), output_shader_prefix.with_suffix( shader_job_util.SUFFIX_TRANSFORMATIONS_JSON), ) return reduced_test_dir