blob: e186132609a3d7c6f5b100643ffdfa3a32862b43 [file] [log] [blame]
#!/usr/bin/env python3.8
# Copyright 2019 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import re
import sys
from typing import List, Tuple, Set, DefaultDict, Dict, Callable, Optional
from collections import defaultdict
from difl.changes import Change
from difl.ir import Libraries, Library, Declaration
from difl.library import library_changes
from difl.comparator import Comparator
# All the changes for a given line
LineChanges = Tuple[int, Set[str]]
# All the changes for a given source file
FileChanges = List[LineChanges]
# All the changes in to all the source files in a library
LibraryChanges = Dict[str, FileChanges]
# Regex to match expected changes in test source files
EXPECT_RE = re.compile(r'\s*//!(.+)')
def expected_file_changes(filename: str) -> FileChanges:
lines = [l.rstrip() for l in open(filename).readlines()]
expected_changes: FileChanges = []
changes: Set[str] = set()
for num, line in enumerate(lines):
match = EXPECT_RE.match(line)
if match:
changes.add(match.group(1))
elif len(changes):
expected_changes.append((num + 1, changes))
changes = set()
assert not len(changes)
return expected_changes
def expected_library_changes(library: Library,
source_base_dir: str) -> LibraryChanges:
'''Extract the list of expected changes from the source files for the library based on //! lines'''
return {
filename: expected_file_changes(
os.path.join(source_base_dir, filename))
for filename in library.filenames
}
# A function that returns either the before or after Declaration from a Change
DeclSelector = Callable[[Change], Optional[Declaration]]
def before_selector(change: Change) -> Optional[Declaration]:
return change.before
def after_selector(change: Change) -> Optional[Declaration]:
return change.after
def actual_file_changes(changes: List[Change], filename: str,
decl_selector: DeclSelector) -> FileChanges:
line_changes_dict: DefaultDict[int, Set[str]] = defaultdict(set)
for change in changes:
decl = decl_selector(change)
if decl is None: continue
if decl.filename != filename: continue
line_changes_dict[decl.line].add(change.name)
line_changes: List[Tuple[int, Set[str]]] = list(line_changes_dict.items())
line_changes.sort(key=lambda t: t[0])
return line_changes
def actual_library_changes(library: Library, changes: List[Change],
decl_selector: DeclSelector) -> LibraryChanges:
return {
filename: actual_file_changes(changes, filename, decl_selector)
for filename in library.filenames
}
def describe_file_differences(filename: str, expected: FileChanges,
actual: FileChanges):
# dictionaries mapping line numbers to change descriptions
expected_lines = {line: changes for line, changes in expected}
actual_lines = {line: changes for line, changes in actual}
# union of the line numbers from expected and actual
line_numbers: Set[int] = set(
[line for line, _ in expected] + [line for line, _ in actual])
for num in sorted(line_numbers):
expected_changes = expected_lines.get(num, set())
actual_changes = actual_lines.get(num, set())
if expected_changes == actual_changes: continue
print('%s:%d' % (filename, num))
for c in actual_changes - expected_changes:
print(' unexpected %s' % c)
for c in expected_changes - actual_changes:
print(' expected %s' % c)
print()
def describe_library_differences(expected: LibraryChanges,
actual: LibraryChanges):
# the union of the filenames in expected and actual
filenames = set(list(expected.keys()) + list(actual.keys()))
for filename in sorted(filenames):
describe_file_differences(filename, expected.get(filename, []),
actual.get(filename, []))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--build-dir', help='Fuchsia build directory', required=True)
parser.add_argument(
'--before',
help="Path to JSON IR for before version of the library",
required=True)
parser.add_argument(
'--after',
help="Path to JSON IR for after version of the library",
required=True)
parser.add_argument(
'--stamp', help="Path to stamp file to write after completion")
args = parser.parse_args()
fidl_json_dir = os.path.join(
args.build_dir, 'gen/garnet/public/lib/fidl/tools/difl_test_fidl')
before = Libraries().load(args.before)
after = Libraries().load(args.after)
before_expected_changes = expected_library_changes(before, args.build_dir)
after_expected_changes = expected_library_changes(after, args.build_dir)
comparator = Comparator()
changes: List[Change] = library_changes(before, after, comparator)
before_actual_changes = actual_library_changes(before, changes,
before_selector)
after_actual_changes = actual_library_changes(after, changes,
after_selector)
describe_library_differences(before_expected_changes,
before_actual_changes)
describe_library_differences(after_expected_changes, after_actual_changes)
if before_expected_changes == before_actual_changes and after_expected_changes == after_actual_changes:
if args.stamp:
with open(args.stamp, 'w') as stamp:
stamp.truncate()
else:
sys.exit(1)