137 lines
4.5 KiB
Python
137 lines
4.5 KiB
Python
|
|
# Copyright 2019 The Chromium Authors
|
||
|
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
|
# found in the LICENSE file.
|
||
|
|
|
||
|
|
import difflib
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
from util import build_utils
|
||
|
|
import action_helpers # build_utils adds //build to sys.path.
|
||
|
|
|
||
|
|
|
||
|
|
def _SkipOmitted(line):
|
||
|
|
"""
|
||
|
|
Skip lines that are to be intentionally omitted from the expectations file.
|
||
|
|
|
||
|
|
This is required when the file to be compared against expectations contains
|
||
|
|
a line that changes from build to build because - for instance - it contains
|
||
|
|
version information.
|
||
|
|
"""
|
||
|
|
if line.rstrip().endswith('# OMIT FROM EXPECTATIONS'):
|
||
|
|
return '# THIS LINE WAS OMITTED\n'
|
||
|
|
return line
|
||
|
|
|
||
|
|
|
||
|
|
def _GenerateDiffWithOnlyAdditons(expected_path, actual_data):
|
||
|
|
"""Generate a diff that only contains additions"""
|
||
|
|
# Ignore blank lines when creating the diff to cut down on whitespace-only
|
||
|
|
# lines in the diff. Also remove trailing whitespaces and add the new lines
|
||
|
|
# manually (ndiff expects new lines but we don't care about trailing
|
||
|
|
# whitespace).
|
||
|
|
with open(expected_path) as expected:
|
||
|
|
expected_lines = [l for l in expected.readlines() if l.strip()]
|
||
|
|
actual_lines = [
|
||
|
|
'{}\n'.format(l.rstrip()) for l in actual_data.splitlines() if l.strip()
|
||
|
|
]
|
||
|
|
|
||
|
|
# This helps the diff to not over-anchor on comments or closing braces in
|
||
|
|
# proguard configs.
|
||
|
|
def is_junk_line(l):
|
||
|
|
l = l.strip()
|
||
|
|
if l.startswith('# File:'):
|
||
|
|
return False
|
||
|
|
return l == '' or l == '}' or l.startswith('#')
|
||
|
|
|
||
|
|
diff = difflib.ndiff(expected_lines, actual_lines, linejunk=is_junk_line)
|
||
|
|
filtered_diff = (l for l in diff if l.startswith('+'))
|
||
|
|
return ''.join(filtered_diff)
|
||
|
|
|
||
|
|
|
||
|
|
def _DiffFileContents(expected_path, actual_data):
|
||
|
|
"""Check file contents for equality and return the diff or None."""
|
||
|
|
# Remove all trailing whitespace and add it explicitly in the end.
|
||
|
|
with open(expected_path) as f_expected:
|
||
|
|
expected_lines = [l.rstrip() for l in f_expected.readlines()]
|
||
|
|
actual_lines = [
|
||
|
|
_SkipOmitted(line).rstrip() for line in actual_data.splitlines()
|
||
|
|
]
|
||
|
|
|
||
|
|
if expected_lines == actual_lines:
|
||
|
|
return None
|
||
|
|
|
||
|
|
expected_path = os.path.relpath(expected_path, build_utils.DIR_SOURCE_ROOT)
|
||
|
|
|
||
|
|
diff = difflib.unified_diff(
|
||
|
|
expected_lines,
|
||
|
|
actual_lines,
|
||
|
|
fromfile=os.path.join('before', expected_path),
|
||
|
|
tofile=os.path.join('after', expected_path),
|
||
|
|
n=0,
|
||
|
|
lineterm='',
|
||
|
|
)
|
||
|
|
|
||
|
|
return '\n'.join(diff)
|
||
|
|
|
||
|
|
|
||
|
|
def AddCommandLineFlags(parser):
|
||
|
|
group = parser.add_argument_group('Expectations')
|
||
|
|
group.add_argument(
|
||
|
|
'--expected-file',
|
||
|
|
help='Expected contents for the check. If --expected-file-base is set, '
|
||
|
|
'this is a diff of --actual-file and --expected-file-base.')
|
||
|
|
group.add_argument(
|
||
|
|
'--expected-file-base',
|
||
|
|
help='File to diff against before comparing to --expected-file.')
|
||
|
|
group.add_argument('--actual-file',
|
||
|
|
help='Path to write actual file (for reference).')
|
||
|
|
group.add_argument('--failure-file',
|
||
|
|
help='Write to this file if expectations fail.')
|
||
|
|
group.add_argument('--fail-on-expectations',
|
||
|
|
action="store_true",
|
||
|
|
help='Fail on expectation mismatches.')
|
||
|
|
group.add_argument('--only-verify-expectations',
|
||
|
|
action='store_true',
|
||
|
|
help='Verify the expectation and exit.')
|
||
|
|
|
||
|
|
|
||
|
|
def CheckExpectations(actual_data, options, custom_msg=''):
|
||
|
|
if options.actual_file:
|
||
|
|
with action_helpers.atomic_output(options.actual_file) as f:
|
||
|
|
f.write(actual_data.encode('utf8'))
|
||
|
|
if options.expected_file_base:
|
||
|
|
actual_data = _GenerateDiffWithOnlyAdditons(options.expected_file_base,
|
||
|
|
actual_data)
|
||
|
|
diff_text = _DiffFileContents(options.expected_file, actual_data)
|
||
|
|
|
||
|
|
if not diff_text:
|
||
|
|
fail_msg = ''
|
||
|
|
else:
|
||
|
|
fail_msg = """
|
||
|
|
Expectations need updating:
|
||
|
|
https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/expectations/README.md
|
||
|
|
|
||
|
|
LogDog tip: Use "Raw log" or "Switch to lite mode" before copying:
|
||
|
|
https://bugs.chromium.org/p/chromium/issues/detail?id=984616
|
||
|
|
|
||
|
|
{}
|
||
|
|
|
||
|
|
To update expectations, run:
|
||
|
|
########### START ###########
|
||
|
|
patch -p1 <<'END_DIFF'
|
||
|
|
{}
|
||
|
|
END_DIFF
|
||
|
|
############ END ############
|
||
|
|
""".format(custom_msg, diff_text)
|
||
|
|
|
||
|
|
sys.stderr.write(fail_msg)
|
||
|
|
|
||
|
|
if fail_msg and options.fail_on_expectations:
|
||
|
|
# Don't write failure file when failing on expectations or else the target
|
||
|
|
# will not be re-run on subsequent ninja invocations.
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
if options.failure_file:
|
||
|
|
with open(options.failure_file, 'w') as f:
|
||
|
|
f.write(fail_msg)
|