Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame^] | 1 | # Copyright 2019 Google LLC |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | """Utilities for test code.""" |
| 16 | |
| 17 | from public import ir_pb2 |
| 18 | |
| 19 | |
| 20 | def proto_is_superset(proto, expected_values, path=""): |
| 21 | """Returns true if every value in expected_values is set in proto. |
| 22 | |
| 23 | This is intended to be used in assertTrue in a unit test, like so: |
| 24 | |
| 25 | self.assertTrue(*proto_is_superset(proto, expected)) |
| 26 | |
| 27 | Arguments: |
| 28 | proto: The proto to check. |
| 29 | expected_values: The reference proto. |
| 30 | path: The path to the elements being compared. Clients can generally leave |
| 31 | this at default. |
| 32 | |
| 33 | Returns: |
| 34 | A tuple; the first element is True if the fields set in proto are a strict |
| 35 | superset of the fields set in expected_values. The second element is an |
| 36 | informational string specifying the path of a value found in expected_values |
| 37 | but not in proto. |
| 38 | |
| 39 | Every atomic field that is set in expected_values must be set to the same |
| 40 | value in proto; every message field set in expected_values must have a |
| 41 | matching field in proto, such that proto_is_superset(proto.field, |
| 42 | expected_values.field) is true. |
| 43 | |
| 44 | For repeated fields in expected_values, each element in the expected_values |
| 45 | proto must have a corresponding element at the same index in proto; proto |
| 46 | may have additional elements. |
| 47 | """ |
| 48 | if path: |
| 49 | path += "." |
| 50 | for name, expected_value in expected_values.raw_fields.items(): |
| 51 | field_path = "{}{}".format(path, name) |
| 52 | value = getattr(proto, name) |
| 53 | if issubclass(proto.field_specs[name].type, ir_pb2.Message): |
| 54 | if isinstance(proto.field_specs[name], ir_pb2.Repeated): |
| 55 | if len(expected_value) > len(value): |
| 56 | return False, "{}[{}] missing".format(field_path, |
| 57 | len(getattr(proto, name))) |
| 58 | for i in range(len(expected_value)): |
| 59 | result = proto_is_superset(value[i], expected_value[i], |
| 60 | "{}[{}]".format(field_path, i)) |
| 61 | if not result[0]: |
| 62 | return result |
| 63 | else: |
| 64 | if (expected_values.HasField(name) and |
| 65 | not proto.HasField(name)): |
| 66 | return False, "{} missing".format(field_path) |
| 67 | result = proto_is_superset(value, expected_value, field_path) |
| 68 | if not result[0]: |
| 69 | return result |
| 70 | else: |
| 71 | # Zero-length repeated fields and not-there repeated fields are "the |
| 72 | # same." |
| 73 | if (expected_value != value and |
| 74 | (isinstance(proto.field_specs[name], ir_pb2.Optional) or |
| 75 | len(expected_value))): |
| 76 | if isinstance(proto.field_specs[name], ir_pb2.Repeated): |
| 77 | return False, "{} differs: found {}, expected {}".format( |
| 78 | field_path, list(value), list(expected_value)) |
| 79 | else: |
| 80 | return False, "{} differs: found {}, expected {}".format( |
| 81 | field_path, value, expected_value) |
| 82 | return True, "" |
| 83 | |
| 84 | |
| 85 | def dict_file_reader(file_dict): |
| 86 | """Returns a callable that retrieves entries from file_dict as files. |
| 87 | |
| 88 | This can be used to call glue.parse_emboss_file with file text declared |
| 89 | inline. |
| 90 | |
| 91 | Arguments: |
| 92 | file_dict: A dictionary from "file names" to "contents." |
| 93 | |
| 94 | Returns: |
| 95 | A callable that can be passed to glue.parse_emboss_file in place of the |
| 96 | "read" builtin. |
| 97 | """ |
| 98 | |
| 99 | def read(file_name): |
| 100 | try: |
| 101 | return file_dict[file_name], None |
| 102 | except KeyError: |
| 103 | return None, ["File '{}' not found.".format(file_name)] |
| 104 | |
| 105 | return read |