| #!/usr/bin/env python3.8 |
| # Copyright 2022 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 json |
| import sys |
| |
| |
| # Removes |prefix| from |line|. |
| # A removeprefix() function is not available until 3.9. |
| def removeprefix(line, prefix): |
| if line.startswith(prefix): |
| return line[len(prefix):] |
| return line |
| |
| |
| # Create new dict/lists containers that are hashable, but first remove any |
| # |prefixes| from nested strings. |
| def make_hashable(item, prefixes): |
| if isinstance(item, dict): |
| item = hashabledict(item, prefixes) |
| elif isinstance(item, list): |
| item = hashablelist(item, prefixes) |
| elif isinstance(item, str): |
| for prefix in prefixes: |
| item = removeprefix(item, prefix) |
| return item |
| |
| |
| # A list that is hashable so it can be added to a set. |
| class hashablelist(list): |
| |
| def __init__(self, list, prefixes): |
| for v in list: |
| self.append(make_hashable(v, prefixes)) |
| |
| def __hash__(self): |
| return hash(tuple(sorted(self))) |
| |
| def __eq__(self, other): |
| return sorted(self) == sorted(other) |
| |
| |
| # A dictionary that is hashable so it can be added to a set. |
| class hashabledict(dict): |
| |
| def __init__(self, dict, prefixes): |
| for k, v in dict.items(): |
| self[k] = make_hashable(v, prefixes) |
| |
| def __hash__(self): |
| return hash(tuple(sorted(self.items()))) |
| |
| |
| # Loads a set of items from a json file and optionally, uses |key| to get the list. |
| # Any |prefixes| will be stripped from strings in each item. |
| def load_set_from_json(log, key, prefixes): |
| items = json.load(log) |
| if key: |
| items = items.get(key, []) |
| return set(make_hashable(i, prefixes) for i in items) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description= |
| 'Compare two json lists generated by different assembly invocations' |
| 'and assert that they are functionally equivalent') |
| parser.add_argument( |
| '--reference', type=argparse.FileType('r'), required=True) |
| parser.add_argument( |
| '--comparison', type=argparse.FileType('r'), required=True) |
| parser.add_argument('--stamp', type=argparse.FileType('w'), required=True) |
| parser.add_argument('--strip-prefix', nargs='*') |
| parser.add_argument('--list-key', type=str) |
| args = parser.parse_args() |
| |
| reference = load_set_from_json( |
| args.reference, args.list_key, args.strip_prefix) |
| comparison = load_set_from_json( |
| args.comparison, args.list_key, args.strip_prefix) |
| |
| # Checks for missing commands. |
| missing = reference.difference(comparison) |
| extra = comparison.difference(reference) |
| if missing: |
| print('Items in the reference were missing from the comparison') |
| for cmd in missing: |
| print('Missing: ' + str(cmd)) |
| if extra: |
| print('Items in the comparison were not found in the reference') |
| for cmd in extra: |
| print('Extra: ' + str(cmd)) |
| if missing or extra: |
| return len(missing) + len(extra) |
| |
| # Writes an empty file because GN requires targets to have an output. |
| args.stamp.write("") |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |