• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2024 The Chromium Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""List the fuzzers within a fuzztest and confirm they match what we expect.
7
8Invoked by GN from fuzzer_test.gni.
9"""
10
11import argparse
12import os
13import re
14import subprocess
15import sys
16
17# //build imports.
18sys.path.append(
19    os.path.join(
20        os.path.dirname(os.path.abspath(__file__)),
21        os.pardir,
22        os.pardir,
23        'build',
24    ))
25import action_helpers
26
27
28def CreateArgumentParser():
29  """Creates an argparse.ArgumentParser instance."""
30  parser = argparse.ArgumentParser(description='Generate fuzztest fuzzers.')
31  parser.add_argument('--executable',
32                      help='Executable to interrogate for present fuzztests.')
33  parser.add_argument(
34      '--output',
35      help='Path to the output file (which will be intentionally blank).',
36  )
37  parser.add_argument('--fuzztests',
38                      nargs='+',
39                      help='List of fuzztests we expect to find.')
40  return parser
41
42
43def main():
44  parser = CreateArgumentParser()
45  args = parser.parse_args()
46
47  expected_tests = set(args.fuzztests)
48
49  env = os.environ
50  env['ASAN_OPTIONS'] = 'detect_odr_violation=0'
51  process_result = subprocess.run([args.executable, '--list_fuzz_tests=1'],
52                                  env=env,
53                                  stdout=subprocess.PIPE,
54                                  check=False)
55
56  if process_result.returncode == 0:
57    test_list = process_result.stdout.decode('utf-8')
58
59    actual_tests = re.findall('Fuzz test: (.*)', test_list)
60    # Gardeners may disable fuzztests by changing the names of the fuzztest
61    # in code to start with DISABLED_. We don't want gardeners to need to
62    # change gn files correspondingly, so strip such prefixes when checking
63    # that the test lists match.
64    actual_tests = {
65        test.replace('DISABLED_', '').replace('FLAKY_', '')
66        for test in actual_tests
67    }
68
69    if expected_tests != actual_tests:
70      print('Unexpected fuzztests found in this binary.\nFuzztest binary: ' +
71            args.executable + '\n' +
72            'Expected fuzztests (as declared by the gn "fuzztests" variable on'
73            ' this target): ' + str(expected_tests) +
74            '\nActual tests (as found in the binary): ' + str(actual_tests) +
75            '\nYou probably need to update the gn variable to match the tests'
76            ' actually in the binary.')
77      sys.exit(-1)
78
79  # If we couldn't run the fuzztest binary itself, we'll
80  # regard that as fine. This is a best-efforts check that the
81  # gn 'fuzztests' variable is correct, and sometimes fuzzers don't
82  # run on LUCI e.g. due to lack of dependencies.
83
84  with action_helpers.atomic_output(args.output) as output:
85    output.write(''.encode('utf-8'))
86
87
88if __name__ == '__main__':
89  main()
90