| import os |
| import platform |
| import tempfile |
| import unittest |
| |
| from scripts.test import shared |
| |
| from . import utils |
| |
| |
| def parse_stats(content): |
| stats = { |
| 'modules': None, |
| 'functions': None, |
| 'patterns': {}, |
| } |
| section = None |
| for raw_line in content.strip().splitlines(): |
| line = raw_line.strip() |
| if not line or line.startswith('#'): |
| continue |
| if line == 'Patterns:': |
| section = 'patterns' |
| continue |
| if line == 'Events:': |
| section = 'events' |
| continue |
| if section == 'patterns': |
| parts = line.split() |
| name = parts[0] |
| stats['patterns'][name] = { |
| 'occurrences': int(parts[1]), |
| 'modules': int(parts[4]), |
| 'functions': int(parts[6]), |
| } |
| continue |
| if line.startswith('Modules:'): |
| stats['modules'] = int(line.split(':')[1].strip()) |
| elif line.startswith('Functions:'): |
| stats['functions'] = int(line.split(':')[1].strip()) |
| return stats |
| |
| |
| class FuzzStatsTest(utils.BinaryenTestCase): |
| def test_stats_file_created(self): |
| with tempfile.TemporaryDirectory() as temp_dir: |
| stats_path = os.path.join(temp_dir, 'stats.txt') |
| random_data = self.input_path('random_data.txt') |
| env = dict(os.environ, BINARYEN_FUZZ_STATS=stats_path) |
| |
| shared.run_process( |
| shared.WASM_OPT + [ |
| '-ttf', random_data, '-q', '-o', os.devnull, |
| ], |
| env=env, |
| ) |
| |
| self.assertTrue(os.path.exists(stats_path)) |
| with open(stats_path) as f: |
| content = f.read() |
| |
| self.assertIn('# Binaryen Fuzzing Statistics', content) |
| stats = parse_stats(content) |
| self.assertEqual(stats['modules'], 1) |
| self.assertIsNotNone(stats['functions']) |
| |
| @unittest.skipIf(platform.system() == 'Windows', |
| 'Windows line endings affect random data PRNG seed') |
| def test_stats_file_updated_across_invocations(self): |
| with tempfile.TemporaryDirectory() as temp_dir: |
| stats_path = os.path.join(temp_dir, 'stats.txt') |
| wat_path = os.path.join(temp_dir, 'test.wat') |
| with open(wat_path, 'w') as f: |
| f.write( |
| """(module |
| (func $foo (param $x (ref null any)) (result (ref null any)) |
| (drop (ref.test (ref null any) (local.get $x))) |
| (drop (ref.cast (ref null any) (local.get $x))) |
| (local.get $x) |
| ) |
| )""", |
| ) |
| |
| random_data = self.input_path('random_data.txt') |
| env = dict(os.environ, BINARYEN_FUZZ_STATS=stats_path) |
| cmd = shared.WASM_OPT + [ |
| '-ttf', |
| random_data, |
| f'--initial-fuzz={wat_path}', |
| '--all-features', |
| '-q', |
| '-o', |
| os.devnull, |
| ] |
| |
| # First invocation |
| shared.run_process(cmd, env=env) |
| with open(stats_path) as f: |
| stats1 = parse_stats(f.read()) |
| |
| self.assertEqual(stats1['modules'], 1) |
| self.assertEqual(stats1['functions'], 1) |
| self.assertIn('ref_cast', stats1['patterns']) |
| self.assertIn('ref_test', stats1['patterns']) |
| self.assertEqual(stats1['patterns']['ref_cast']['occurrences'], 1) |
| self.assertEqual(stats1['patterns']['ref_cast']['modules'], 1) |
| self.assertEqual(stats1['patterns']['ref_test']['occurrences'], 1) |
| self.assertEqual(stats1['patterns']['ref_test']['modules'], 1) |
| |
| # Second invocation |
| shared.run_process(cmd, env=env) |
| with open(stats_path) as f: |
| stats2 = parse_stats(f.read()) |
| |
| self.assertEqual(stats2['modules'], 2) |
| self.assertEqual(stats2['functions'], 2) |
| self.assertEqual(stats2['patterns']['ref_cast']['occurrences'], 2) |
| self.assertEqual(stats2['patterns']['ref_cast']['modules'], 2) |
| self.assertEqual(stats2['patterns']['ref_test']['occurrences'], 2) |
| self.assertEqual(stats2['patterns']['ref_test']['modules'], 2) |
| |
| # Third invocation without initial-fuzz (empty function count) |
| empty_cmd = shared.WASM_OPT + [ |
| '-ttf', |
| random_data, |
| '-q', |
| '-o', |
| os.devnull, |
| ] |
| shared.run_process(empty_cmd, env=env) |
| with open(stats_path) as f: |
| stats3 = parse_stats(f.read()) |
| |
| self.assertEqual(stats3['modules'], 3) |
| self.assertEqual(stats3['functions'], 2) |
| # Pattern occurrences shouldn't change for module with no casts |
| self.assertEqual(stats3['patterns']['ref_cast']['occurrences'], 2) |
| self.assertEqual(stats3['patterns']['ref_cast']['modules'], 2) |
| |
| def test_stats_not_created_when_disabled(self): |
| with tempfile.TemporaryDirectory() as temp_dir: |
| stats_path = os.path.join(temp_dir, 'stats.txt') |
| random_data = self.input_path('random_data.txt') |
| env = { |
| k: v for k, v in os.environ.items() |
| if k != 'BINARYEN_FUZZ_STATS' |
| } |
| |
| shared.run_process( |
| shared.WASM_OPT + [ |
| '-ttf', random_data, '-q', '-o', os.devnull, |
| ], |
| env=env, |
| cwd=temp_dir, |
| ) |
| |
| self.assertFalse(os.path.exists(stats_path)) |
| default_stats = os.path.join(temp_dir, 'fuzz-stats.txt') |
| self.assertFalse(os.path.exists(default_stats)) |
| |
| def test_default_filename(self): |
| with tempfile.TemporaryDirectory() as temp_dir: |
| random_data = self.input_path('random_data.txt') |
| env = dict(os.environ, BINARYEN_FUZZ_STATS='1') |
| |
| shared.run_process( |
| shared.WASM_OPT + [ |
| '-ttf', random_data, '-q', '-o', os.devnull, |
| ], |
| env=env, |
| cwd=temp_dir, |
| ) |
| |
| default_stats = os.path.join(temp_dir, 'fuzz-stats.txt') |
| self.assertTrue(os.path.exists(default_stats)) |
| with open(default_stats) as f: |
| stats = parse_stats(f.read()) |
| self.assertEqual(stats['modules'], 1) |