[test/fuzzing] Port all to TAP

This commit is contained in:
Behdad Esfahbod 2025-04-06 13:54:47 -06:00
parent 58d7ab2d59
commit ba309a1826
2 changed files with 71 additions and 127 deletions

View file

@ -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

View file

@ -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()