diff --git a/test/fuzzing/meson.build b/test/fuzzing/meson.build index 2ce9725db..5bd48fb9a 100644 --- a/test/fuzzing/meson.build +++ b/test/fuzzing/meson.build @@ -6,8 +6,7 @@ tests = [ 'hb-repacker-fuzzer.cc', ] -run_fuzzer_tests = find_program('run-fuzzer-tests.py', required: true) - +# Build the binaries foreach file_name : tests test_name = file_name.split('.')[0] @@ -36,60 +35,60 @@ foreach file_name : tests set_variable('@0@_exe'.format(test_name.underscorify()), exe) endforeach -test('shape-fuzzer', run_fuzzer_tests, - args: [ - hb_shape_fuzzer_exe, - meson.current_source_dir() / 'fonts', - ], - workdir: meson.current_build_dir() / '..' / '..', - priority: 1, - suite: ['fuzzing'], -) - -test('repacker-fuzzer', run_fuzzer_tests, - args: [ - hb_repacker_fuzzer_exe, - meson.current_source_dir() / 'graphs', - ], - workdir: meson.current_build_dir() / '..' / '..', - priority: 1, - suite: ['fuzzing'], -) - -test('draw-fuzzer', run_fuzzer_tests, - args: [ - hb_draw_fuzzer_exe, - meson.current_source_dir() / 'fonts', - ], - workdir: meson.current_build_dir() / '..' / '..', - suite: ['fuzzing'], -) - glob_cmd = find_program('glob.py', required: true) -# Subset fuzzer: feed the fuzzer directly, without run-fuzzer-tests.py -font_dirs = [ - meson.current_source_dir() / 'fonts', - meson.current_source_dir() / '..' / 'subset' / 'data' / 'fonts', -] -glob = run_command(glob_cmd, font_dirs, check:true).stdout().strip().split('\n') -# Chunk glob and call runner for each chunk +fonts_glob = run_command(glob_cmd, meson.current_source_dir() / 'fonts', check:true).stdout().strip().split('\n') +subset_fonts_glob = run_command(glob_cmd, meson.current_source_dir() / '..' / 'subset' / 'data' / 'fonts', check:true).stdout().strip().split('\n') +graphs_glob = run_command(glob_cmd, meson.current_source_dir() / 'graphs', check:true).stdout().strip().split('\n') +sets_glob = run_command(glob_cmd, meson.current_source_dir() / 'sets', check:true).stdout().strip().split('\n') + +# Chunk the glob lists to avoid command line length limits, and for parallelization chunk_size = 64 -chunks = [] -chunk = [] -foreach item : glob - if chunk.length() >= chunk_size +foreach glob_name : ['fonts_glob', 'subset_fonts_glob', 'graphs_glob', 'sets_glob'] + glob = get_variable(glob_name) + chunks = [] + chunk = [] + foreach item : glob + if chunk.length() >= chunk_size + chunks += [chunk] + chunk = [] + endif + chunk += [item] + endforeach + if chunk.length() > 0 chunks += [chunk] - chunk = [] endif - chunk += [item] + set_variable('@0@_chunks'.format(glob_name), chunks) endforeach -if chunk.length() > 0 - chunks += [chunk] -endif + +# Run fuzzers i = 0 -foreach chunk : chunks +foreach chunk : fonts_glob_chunks + test('shape-fuzzer-chunk-@0@'.format(i), + hb_shape_fuzzer_exe, + args: chunk, + workdir: meson.current_build_dir() / '..' / '..', + protocol: 'tap', + suite: ['fuzzing'], + ) + i += 1 +endforeach + +i = 0 +foreach chunk : fonts_glob_chunks + test('draw-fuzzer-chunk-@0@'.format(i), + hb_draw_fuzzer_exe, + args: chunk, + workdir: meson.current_build_dir() / '..' / '..', + protocol: 'tap', + suite: ['fuzzing'], + ) + i += 1 +endforeach + +i = 0 +foreach chunk : fonts_glob_chunks + subset_fonts_glob_chunks test('subset-fuzzer-chunk-@0@'.format(i), hb_subset_fuzzer_exe, args: chunk, @@ -99,3 +98,27 @@ foreach chunk : chunks ) i += 1 endforeach + +i = 0 +foreach chunk : graphs_glob_chunks + test('repacker-fuzzer-chunk-@0@'.format(i), + hb_repacker_fuzzer_exe, + args: chunk, + workdir: meson.current_build_dir() / '..' / '..', + protocol: 'tap', + suite: ['fuzzing'], + ) + i += 1 +endforeach + +i = 0 +foreach chunk : sets_glob_chunks + test('set-fuzzer-chunk-@0@'.format(i), + hb_set_fuzzer_exe, + args: chunk, + workdir: meson.current_build_dir() / '..' / '..', + protocol: 'tap', + suite: ['fuzzing'], + ) + i += 1 +endforeach diff --git a/test/fuzzing/run-fuzzer-tests.py b/test/fuzzing/run-fuzzer-tests.py deleted file mode 100755 index 04b0612a3..000000000 --- a/test/fuzzing/run-fuzzer-tests.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 - -import pathlib -import subprocess -import os -import sys -import tempfile - -EXE_WRAPPER = os.environ.get("MESON_EXE_WRAPPER") - - -def run_command(command): - """ - Run a command, capturing potentially large output in a temp file. - Returns (output_string, exit_code). - """ - global EXE_WRAPPER - if EXE_WRAPPER: - command = [EXE_WRAPPER] + command - - with tempfile.TemporaryFile() as tempf: - p = subprocess.Popen(command, stdout=tempf, stderr=tempf) - p.wait() - tempf.seek(0) - output = tempf.read().decode("utf-8", errors="replace").strip() - return output, p.returncode - - -def chunkify(lst, chunk_size=64): - """ - Yield successive chunk_size-sized slices from lst. - """ - for i in range(0, len(lst), chunk_size): - yield lst[i : i + chunk_size] - - -def main(): - assert len(sys.argv) > 2, "Please provide fuzzer binary and fonts directory paths." - - fuzzer = pathlib.Path(sys.argv[1]) - assert fuzzer.is_file(), f"Fuzzer binary not found: {fuzzer}" - print("Using fuzzer:", fuzzer) - - # Gather all test files - files_to_test = [] - for fonts_dir in sys.argv[2:]: - fonts_dir = pathlib.Path(fonts_dir) - assert fonts_dir.is_dir(), f"Fonts directory not found: {fonts_dir}" - test_files = [str(f) for f in fonts_dir.iterdir() if f.is_file()] - assert test_files, f"No files found in {fonts_dir}" - files_to_test += test_files - - if not files_to_test: - print("No test files found") - sys.exit(0) - - fails = 0 - batch_index = 0 - - for chunk in chunkify(files_to_test): - batch_index += 1 - cmd_line = [fuzzer] + chunk - output, returncode = run_command(cmd_line) - - if output: - print(output) - - if returncode != 0: - print(f"Failure in batch #{batch_index}") - fails += 1 - - if fails > 0: - sys.exit(f"{fails} fuzzer batch(es) failed.") - - print("All fuzzer tests passed successfully.") - - -if __name__ == "__main__": - main()