| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Utilities for test code.""" |
| |
| from compiler.util import ir_pb2 |
| |
| |
| def proto_is_superset(proto, expected_values, path=""): |
| """Returns true if every value in expected_values is set in proto. |
| |
| This is intended to be used in assertTrue in a unit test, like so: |
| |
| self.assertTrue(*proto_is_superset(proto, expected)) |
| |
| Arguments: |
| proto: The proto to check. |
| expected_values: The reference proto. |
| path: The path to the elements being compared. Clients can generally leave |
| this at default. |
| |
| Returns: |
| A tuple; the first element is True if the fields set in proto are a strict |
| superset of the fields set in expected_values. The second element is an |
| informational string specifying the path of a value found in expected_values |
| but not in proto. |
| |
| Every atomic field that is set in expected_values must be set to the same |
| value in proto; every message field set in expected_values must have a |
| matching field in proto, such that proto_is_superset(proto.field, |
| expected_values.field) is true. |
| |
| For repeated fields in expected_values, each element in the expected_values |
| proto must have a corresponding element at the same index in proto; proto |
| may have additional elements. |
| """ |
| if path: |
| path += "." |
| for name, expected_value in expected_values.raw_fields.items(): |
| field_path = "{}{}".format(path, name) |
| value = getattr(proto, name) |
| if issubclass(proto.field_specs[name].type, ir_pb2.Message): |
| if isinstance(proto.field_specs[name], ir_pb2.Repeated): |
| if len(expected_value) > len(value): |
| return False, "{}[{}] missing".format(field_path, |
| len(getattr(proto, name))) |
| for i in range(len(expected_value)): |
| result = proto_is_superset(value[i], expected_value[i], |
| "{}[{}]".format(field_path, i)) |
| if not result[0]: |
| return result |
| else: |
| if (expected_values.HasField(name) and |
| not proto.HasField(name)): |
| return False, "{} missing".format(field_path) |
| result = proto_is_superset(value, expected_value, field_path) |
| if not result[0]: |
| return result |
| else: |
| # Zero-length repeated fields and not-there repeated fields are "the |
| # same." |
| if (expected_value != value and |
| (isinstance(proto.field_specs[name], ir_pb2.Optional) or |
| len(expected_value))): |
| if isinstance(proto.field_specs[name], ir_pb2.Repeated): |
| return False, "{} differs: found {}, expected {}".format( |
| field_path, list(value), list(expected_value)) |
| else: |
| return False, "{} differs: found {}, expected {}".format( |
| field_path, value, expected_value) |
| return True, "" |
| |
| |
| def dict_file_reader(file_dict): |
| """Returns a callable that retrieves entries from file_dict as files. |
| |
| This can be used to call glue.parse_emboss_file with file text declared |
| inline. |
| |
| Arguments: |
| file_dict: A dictionary from "file names" to "contents." |
| |
| Returns: |
| A callable that can be passed to glue.parse_emboss_file in place of the |
| "read" builtin. |
| """ |
| |
| def read(file_name): |
| try: |
| return file_dict[file_name], None |
| except KeyError: |
| return None, ["File '{}' not found.".format(file_name)] |
| |
| return read |