def run_reduction_part(
    reduction_part_output_dir: Path,
    source_dir_to_reduce: Path,
    preserve_semantics: bool,
    binary_manager: binaries_util.BinaryManager,
    settings: Settings,
) -> Path:
    test = test_util.metadata_read_from_source_dir(source_dir_to_reduce)

    if not test.device or not test.device.name:
        raise AssertionError(
            f"Cannot reduce {str(source_dir_to_reduce)}; "
            f"device must be specified in {str(test_util.get_metadata_path_from_source_dir(source_dir_to_reduce))}"
        )

    if not test.crash_signature:
        raise AssertionError(
            f"Cannot reduce {str(source_dir_to_reduce)} because there is no crash string specified."
        )

    shader_jobs = tool.get_shader_jobs(source_dir_to_reduce)

    # 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_to_reduce,
        name_of_shader_to_reduce=name_of_shader_to_reduce,
        output_dir=test_util.get_reduction_work_directory(
            reduction_part_output_dir, name_of_shader_to_reduce),
        binary_manager=binary_manager,
        preserve_semantics=preserve_semantics,
        extra_args=list(settings.extra_graphics_fuzz_reduce_args)
        if settings.extra_graphics_fuzz_reduce_args else None,
    )

    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_work_variant_dir),
    )

    # Finally, create the output source_dir.

    util.copy_dir(source_dir_to_reduce,
                  test_util.get_source_dir(reduction_part_output_dir))

    shader_job_util.copy(
        final_reduced_shader_job_path,
        test_util.get_shader_job_path(reduction_part_output_dir,
                                      name_of_shader_to_reduce),
    )

    if not settings.keep_reduction_work:
        shutil.rmtree(reduction_work_variant_dir)

    return test_util.get_source_dir(reduction_part_output_dir)
示例#2
0
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
示例#3
0
def run_shader_job(  # pylint: disable=too-many-return-statements,too-many-branches, too-many-locals, too-many-statements;
    source_dir: Path,
    output_dir: Path,
    binary_manager: binaries_util.BinaryManager,
    test: Optional[Test] = None,
    device: Optional[Device] = None,
    ignore_test_and_device_binaries: bool = False,
    shader_job_overrides: Iterable[tool.NameAndShaderJob] = (),
    shader_job_shader_overrides: Optional[
        tool.ShaderJobNameToShaderOverridesMap] = None,
) -> Path:

    if not shader_job_shader_overrides:
        shader_job_shader_overrides = {}

    with util.file_open_text(output_dir / "log.txt", "w") as log_file:
        try:
            gflogging.push_stream_for_logging(log_file)

            # TODO: Find amber path. NDK or host.

            # TODO: If Amber is going to be used, check if Amber can use Vulkan debug layers now, and if not, pass that
            #  info down via a bool.

            if not test:
                test = test_util.metadata_read_from_path(
                    source_dir / test_util.TEST_METADATA)

            if not device:
                device = test.device

            log(f"Running test on device:\n{device.name}")

            # We will create a binary_manager child with a restricted set of binaries so that we only use the binaries
            # specified in the test and by the device; if some required binaries are not specified by the test nor the
            # device, there will be an error instead of falling back to our default binaries. But we keep a reference to
            # the parent so we can still access certain "test-independent" binaries like Amber.

            binary_manager_parent = binary_manager

            if not ignore_test_and_device_binaries:
                binary_manager = binary_manager.get_child_binary_manager(
                    list(device.binaries) + list(test.binaries))

            spirv_opt_hash: Optional[str] = None
            spirv_opt_args: Optional[List[str]] = None
            if test.glsl.spirv_opt_args or test.spirv_fuzz.spirv_opt_args:
                spirv_opt_hash = binary_manager.get_binary_by_name(
                    binaries_util.SPIRV_OPT_NAME).version
                spirv_opt_args = (list(test.glsl.spirv_opt_args)
                                  if test.glsl.spirv_opt_args else list(
                                      test.spirv_fuzz.spirv_opt_args))

            shader_jobs = tool.get_shader_jobs(source_dir,
                                               overrides=shader_job_overrides)

            combined_spirv_shader_jobs: List[tool.SpirvCombinedShaderJob] = []

            for shader_job in shader_jobs:
                try:
                    shader_overrides = shader_job_shader_overrides.get(
                        shader_job.name, None)
                    combined_spirv_shader_jobs.append(
                        tool.compile_shader_job(
                            name=shader_job.name,
                            input_json=shader_job.shader_job,
                            work_dir=output_dir / shader_job.name,
                            binary_paths=binary_manager,
                            spirv_opt_args=spirv_opt_args,
                            shader_overrides=shader_overrides,
                        ))
                except subprocess.CalledProcessError:
                    result_util.write_status(output_dir,
                                             fuzz.STATUS_TOOL_CRASH,
                                             shader_job.name)
                    return output_dir
                except subprocess.TimeoutExpired:
                    result_util.write_status(output_dir,
                                             fuzz.STATUS_TOOL_TIMEOUT,
                                             shader_job.name)
                    return output_dir

            # Device types: |preprocess| and |shader_compiler| don't need an AmberScript file.

            # noinspection PyTypeChecker
            if device.HasField("preprocess"):
                # The "preprocess" device type just needs to get this far, so this is a success.
                result_util.write_status(output_dir, fuzz.STATUS_SUCCESS)
                return output_dir

            # noinspection PyTypeChecker
            if device.HasField("shader_compiler"):
                for combined_spirv_shader_job in combined_spirv_shader_jobs:
                    try:
                        shader_compiler_util.run_shader_job(
                            device.shader_compiler,
                            combined_spirv_shader_job.spirv_shader_job,
                            output_dir,
                            binary_manager=binary_manager,
                        )
                    except subprocess.CalledProcessError:
                        result_util.write_status(
                            output_dir,
                            fuzz.STATUS_CRASH,
                            combined_spirv_shader_job.name,
                        )
                        return output_dir
                    except subprocess.TimeoutExpired:
                        result_util.write_status(
                            output_dir,
                            fuzz.STATUS_TIMEOUT,
                            combined_spirv_shader_job.name,
                        )
                        return output_dir

                # The shader compiler succeeded on all files; this is a success.
                result_util.write_status(output_dir, fuzz.STATUS_SUCCESS)
                return output_dir

            # Other device types need an AmberScript file.

            amber_converter_shader_job_files = [
                amber_converter.ShaderJobFile(
                    name_prefix=combined_spirv_shader_job.name,
                    asm_spirv_shader_job_json=combined_spirv_shader_job.
                    spirv_asm_shader_job,
                    glsl_source_json=combined_spirv_shader_job.
                    glsl_source_shader_job,
                    processing_info="",
                ) for combined_spirv_shader_job in combined_spirv_shader_jobs
            ]

            # Check if the first is the reference shader; if so, pull it out into its own variable.

            reference: Optional[amber_converter.ShaderJobFile] = None
            variants = amber_converter_shader_job_files

            if (amber_converter_shader_job_files[0].name_prefix ==
                    test_util.REFERENCE_DIR):
                reference = amber_converter_shader_job_files[0]
                variants = variants[1:]
            elif len(variants) > 1:
                raise AssertionError(
                    "More than one variant, but no reference. This is unexpected."
                )

            amber_script_file = amber_converter.spirv_asm_shader_job_to_amber_script(
                shader_job_file_amber_test=amber_converter.
                ShaderJobFileBasedAmberTest(reference_asm_spirv_job=reference,
                                            variants_asm_spirv_job=variants),
                output_amber_script_file_path=output_dir / "test.amber",
                amberfy_settings=amber_converter.AmberfySettings(
                    spirv_opt_args=spirv_opt_args,
                    spirv_opt_hash=spirv_opt_hash),
            )

            is_compute = bool(
                shader_job_util.get_related_files(
                    combined_spirv_shader_jobs[0].spirv_shader_job,
                    [shader_job_util.EXT_COMP],
                ))

            # noinspection PyTypeChecker
            if device.HasField("host") or device.HasField("swift_shader"):
                icd: Optional[Path] = None

                # noinspection PyTypeChecker
                if device.HasField("swift_shader"):
                    icd = binary_manager.get_binary_path_by_name(
                        binaries_util.SWIFT_SHADER_NAME).path

                # Run the test on the host using Amber.
                host_device_util.run_amber(
                    amber_script_file,
                    output_dir,
                    amber_path=binary_manager_parent.get_binary_path_by_name(
                        binaries_util.AMBER_NAME).path,
                    dump_image=(not is_compute),
                    dump_buffer=is_compute,
                    icd=icd,
                )
                return output_dir

            # noinspection PyTypeChecker
            if device.HasField("android"):

                android_device.run_amber_on_device(
                    amber_script_file,
                    output_dir,
                    dump_image=(not is_compute),
                    dump_buffer=is_compute,
                    serial=device.android.serial,
                )
                return output_dir

            # TODO: For a remote device (which we will probably need to support), use log_a_file to output the
            #  "amber_log.txt" file.

            raise AssertionError(f"Unhandled device type:\n{str(device)}")

        finally:
            gflogging.pop_stream_for_logging()
示例#4
0
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
示例#5
0
def run_reduction(
    source_dir_to_reduce: Path,
    reduction_output_dir: Path,
    binary_manager: binaries_util.BinaryManager,
    settings: Settings,
) -> Path:
    test = test_util.metadata_read_from_source_dir(source_dir_to_reduce)
    shader_jobs = tool.get_shader_jobs(source_dir_to_reduce)

    # 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, ))

    reduced_source_dir = source_dir_to_reduce

    for index, suffix in enumerate(shader_transformation_suffixes):
        # E.g. .frag.transformations -> .frag
        extension_to_reduce = str(Path(suffix).with_suffix(""))
        reduced_source_dir = run_reduction_part(
            reduction_part_output_dir=reduction_output_dir /
            f"0_{index}_{suffix.split('.')[1]}",
            source_dir_to_reduce=reduced_source_dir,
            shader_job_name_to_reduce=shader_job_to_reduce.name,
            extension_to_reduce=extension_to_reduce,
            preserve_semantics=True,
            binary_manager=binary_manager,
            settings=settings,
        )

    if (test.crash_signature != signature_util.BAD_IMAGE_SIGNATURE
            and not settings.skip_spirv_reduce):
        for index, suffix in enumerate(shader_spv_suffixes):
            # E.g. .frag.spv -> .frag
            extension_to_reduce = str(Path(suffix).with_suffix(""))
            reduced_source_dir = run_reduction_part(
                reduction_part_output_dir=reduction_output_dir /
                f"1_{index}_{suffix.split('.')[1]}",
                source_dir_to_reduce=reduced_source_dir,
                shader_job_name_to_reduce=shader_job_to_reduce.name,
                extension_to_reduce=extension_to_reduce,
                preserve_semantics=False,
                binary_manager=binary_manager,
                settings=settings,
            )

    # Create and return a symlink to the "best" reduction.
    return util.make_directory_symlink(
        new_symlink_file_path=reduction_output_dir / fuzz.BEST_REDUCTION_NAME,
        existing_dir=reduced_source_dir.parent,
    )
示例#6
0
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,
        )