tor-browser

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

diff_utils.py (5087B)


      1 # Copyright 2019 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import difflib
      6 import os
      7 import pathlib
      8 import sys
      9 
     10 from util import build_utils
     11 import action_helpers  # build_utils adds //build to sys.path.
     12 
     13 
     14 def _SkipOmitted(line):
     15  """
     16  Skip lines that are to be intentionally omitted from the expectations file.
     17 
     18  This is required when the file to be compared against expectations contains
     19  a line that changes from build to build because - for instance - it contains
     20  version information.
     21  """
     22  if line.rstrip().endswith('# OMIT FROM EXPECTATIONS'):
     23    return '# THIS LINE WAS OMITTED\n'
     24  return line
     25 
     26 
     27 def _GenerateDiffWithOnlyAdditons(expected_path, actual_data):
     28  """Generate a diff that only contains additions"""
     29  # Ignore blank lines when creating the diff to cut down on whitespace-only
     30  # lines in the diff. Also remove trailing whitespaces and add the new lines
     31  # manually (ndiff expects new lines but we don't care about trailing
     32  # whitespace).
     33  with open(expected_path) as expected:
     34    expected_lines = [l for l in expected.readlines() if l.strip()]
     35  actual_lines = [
     36      '{}\n'.format(l.rstrip()) for l in actual_data.splitlines() if l.strip()
     37  ]
     38 
     39  # This helps the diff to not over-anchor on comments or closing braces in
     40  # proguard configs.
     41  def is_junk_line(l):
     42    l = l.strip()
     43    if l.startswith('# File:'):
     44      return False
     45    return l == '' or l == '}' or l.startswith('#')
     46 
     47  diff = difflib.ndiff(expected_lines, actual_lines, linejunk=is_junk_line)
     48  filtered_diff = (l for l in diff if l.startswith('+'))
     49  return ''.join(filtered_diff)
     50 
     51 
     52 _REBASELINE_PROGUARD = os.environ.get('REBASELINE_PROGUARD', '0') != '0'
     53 
     54 def _DiffFileContents(expected_path, actual_data):
     55  """Check file contents for equality and return the diff or None."""
     56  # Remove all trailing whitespace and add it explicitly in the end.
     57  with open(expected_path) as f_expected:
     58    expected_lines = [l.rstrip() for l in f_expected.readlines()]
     59  actual_lines = [
     60      _SkipOmitted(line).rstrip() for line in actual_data.splitlines()
     61  ]
     62 
     63  if expected_lines == actual_lines:
     64    return None
     65 
     66  if _REBASELINE_PROGUARD:
     67    pathlib.Path(expected_path).write_text('\n'.join(actual_lines))
     68    print(f'Updated {expected_path}')
     69    return None
     70 
     71  expected_path = os.path.relpath(expected_path, build_utils.DIR_SOURCE_ROOT)
     72 
     73  diff = difflib.unified_diff(
     74      expected_lines,
     75      actual_lines,
     76      fromfile=os.path.join('before', expected_path),
     77      tofile=os.path.join('after', expected_path),
     78      n=0,
     79      lineterm='',
     80  )
     81 
     82  return '\n'.join(diff)
     83 
     84 
     85 def AddCommandLineFlags(parser):
     86  group = parser.add_argument_group('Expectations')
     87  group.add_argument(
     88      '--expected-file',
     89      help='Expected contents for the check. If --expected-file-base  is set, '
     90      'this is a diff of --actual-file and --expected-file-base.')
     91  group.add_argument(
     92      '--expected-file-base',
     93      help='File to diff against before comparing to --expected-file.')
     94  group.add_argument('--actual-file',
     95                     help='Path to write actual file (for reference).')
     96  group.add_argument('--failure-file',
     97                     help='Write to this file if expectations fail.')
     98  group.add_argument('--fail-on-expectations',
     99                     action="store_true",
    100                     help='Fail on expectation mismatches.')
    101  group.add_argument('--only-verify-expectations',
    102                     action='store_true',
    103                     help='Verify the expectation and exit.')
    104 
    105 def CheckExpectations(actual_data, options, custom_msg=''):
    106  if options.actual_file:
    107    with action_helpers.atomic_output(options.actual_file) as f:
    108      f.write(actual_data.encode('utf8'))
    109  if options.expected_file_base:
    110    actual_data = _GenerateDiffWithOnlyAdditons(options.expected_file_base,
    111                                                actual_data)
    112  diff_text = _DiffFileContents(options.expected_file, actual_data)
    113 
    114  if not diff_text:
    115    fail_msg = ''
    116  else:
    117    # The space before the `patch` command is intentional, as it causes the line
    118    # to not be saved in bash history for most configurations.
    119    fail_msg = """
    120 Expectations need updating:
    121 https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/expectations/README.md
    122 
    123 LogDog tip: Use "Raw log" or "Switch to lite mode" before copying:
    124 https://bugs.chromium.org/p/chromium/issues/detail?id=984616
    125 
    126 {}
    127 
    128 To update expectations, run:
    129 ########### START ###########
    130 patch -p1 <<'END_DIFF'
    131 {}
    132 END_DIFF
    133 ############ END ############
    134 
    135 If you are running this locally, you can `export REBASELINE_PROGUARD=1` to
    136 automatically apply this patch.
    137 """.format(custom_msg, diff_text)
    138 
    139    sys.stderr.write(fail_msg)
    140 
    141  if fail_msg and options.fail_on_expectations:
    142    # Don't write failure file when failing on expectations or else the target
    143    # will not be re-run on subsequent ninja invocations.
    144    sys.exit(1)
    145 
    146  if options.failure_file:
    147    with open(options.failure_file, 'w') as f:
    148      f.write(fail_msg)