tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

private_code_test.py (4517B)


      1 #!/usr/bin/env python3
      2 # Copyright 2023 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 """Tests that no linker inputs are from private paths."""
      6 
      7 import argparse
      8 import fnmatch
      9 import json
     10 import logging
     11 import os
     12 import pathlib
     13 import sys
     14 
     15 _DIR_SRC_ROOT = pathlib.Path(__file__).resolve().parents[2]
     16 
     17 
     18 def _print_paths(paths, limit):
     19  for path in paths[:limit]:
     20    print(path)
     21  if len(paths) > limit:
     22    print(f'... and {len(paths) - limit} more.')
     23  print()
     24 
     25 
     26 def _apply_allowlist(found, globs):
     27  ignored_paths = []
     28  new_found = []
     29  for path in found:
     30    for pattern in globs:
     31      if fnmatch.fnmatch(path, pattern):
     32        ignored_paths.append(path)
     33        break
     34    else:
     35      new_found.append(path)
     36  return new_found, ignored_paths
     37 
     38 
     39 def _find_private_paths(linker_inputs, private_paths, root_out_dir):
     40  seen = set()
     41  found = []
     42  for linker_input in linker_inputs:
     43    dirname = os.path.dirname(linker_input)
     44    if dirname in seen:
     45      continue
     46 
     47    to_check = dirname
     48    # Strip ../ prefix.
     49    if to_check.startswith('..'):
     50      to_check = os.path.relpath(to_check, _DIR_SRC_ROOT)
     51    else:
     52      if root_out_dir:
     53        # Strip secondary toolchain subdir
     54        to_check = to_check[len(root_out_dir) + 1:]
     55      # Strip top-level dir (e.g. "obj", "gen").
     56      parts = to_check.split(os.path.sep, 1)
     57      if len(parts) == 1:
     58        continue
     59      to_check = parts[1]
     60 
     61    if any(to_check.startswith(p) for p in private_paths):
     62      found.append(linker_input)
     63    else:
     64      seen.add(dirname)
     65  return found
     66 
     67 
     68 def _read_private_paths(path):
     69  text = pathlib.Path(path).read_text()
     70 
     71  # Check if .gclient_entries was not valid.  https://crbug.com/1427829
     72  if text.startswith('# ERROR: '):
     73    sys.stderr.write(text)
     74    sys.exit(1)
     75 
     76  # Remove src/ prefix from paths.
     77  # We care only about paths within src/ since GN cannot reference files
     78  # outside of // (and what would the obj/ path for them look like?).
     79  ret = [p[4:] for p in text.splitlines() if p.startswith('src/')]
     80  if not ret:
     81    sys.stderr.write(f'No src/ paths found in {path}\n')
     82    sys.stderr.write(f'This test should not be run on public bots.\n')
     83    sys.stderr.write(f'File contents:\n')
     84    sys.stderr.write(text)
     85    sys.exit(1)
     86 
     87  return ret
     88 
     89 
     90 def main():
     91  parser = argparse.ArgumentParser()
     92  parser.add_argument('--collect-sources-json',
     93                      required=True,
     94                      help='Path to ninja_parser.py output')
     95  parser.add_argument('--private-paths-file',
     96                      required=True,
     97                      help='Path to file containing list of paths that are '
     98                      'considered private, relative gclient root.')
     99  parser.add_argument('--root-out-dir',
    100                      required=True,
    101                      help='See --linker-inputs.')
    102  parser.add_argument('--allow-violation',
    103                      action='append',
    104                      help='globs of private paths to allow.')
    105  parser.add_argument('--expect-failure',
    106                      action='store_true',
    107                      help='Invert exit code.')
    108  args = parser.parse_args()
    109  logging.basicConfig(level=logging.INFO,
    110                      format='%(levelname).1s %(relativeCreated)6d %(message)s')
    111  with open(args.collect_sources_json) as f:
    112    collect_sources_json = json.load(f)
    113  if collect_sources_json['logs']:
    114    logging.info('Start logs from ninja_parser.py:')
    115    sys.stderr.write(collect_sources_json['logs'])
    116    logging.info('End logs from ninja_parser.py:')
    117  source_paths = collect_sources_json['source_paths']
    118 
    119  private_paths = _read_private_paths(args.private_paths_file)
    120 
    121  root_out_dir = args.root_out_dir
    122  if root_out_dir == '.':
    123    root_out_dir = ''
    124 
    125  found = _find_private_paths(source_paths, private_paths, root_out_dir)
    126 
    127  if args.allow_violation:
    128    found, ignored_paths = _apply_allowlist(found, args.allow_violation)
    129    if ignored_paths:
    130      print('Ignoring {len(ignored_paths)} allowlisted private paths:')
    131      _print_paths(sorted(ignored_paths), 10)
    132 
    133  if found:
    134    limit = 10 if args.expect_failure else 1000
    135    print(f'Found {len(found)} private paths being linked into public code:')
    136    _print_paths(found, limit)
    137  elif args.expect_failure:
    138    print('Expected to find a private path, but none were found.')
    139  else:
    140    print('No private paths found 👍.')
    141 
    142  sys.exit(0 if bool(found) == args.expect_failure else 1)
    143 
    144 
    145 if __name__ == '__main__':
    146  main()