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 17from compiler.util import ir_data_utils 18 19 20def 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 spec, expected_value in ir_data_utils.get_set_fields(expected_values): 51 name = spec.name 52 field_path = "{}{}".format(path, name) 53 value = getattr(proto, name) 54 if spec.is_dataclass: 55 if spec.is_sequence: 56 if len(expected_value) > len(value): 57 return False, "{}[{}] missing".format(field_path, 58 len(getattr(proto, name))) 59 for i in range(len(expected_value)): 60 result = proto_is_superset(value[i], expected_value[i], 61 "{}[{}]".format(field_path, i)) 62 if not result[0]: 63 return result 64 else: 65 if (expected_values.HasField(name) and 66 not proto.HasField(name)): 67 return False, "{} missing".format(field_path) 68 result = proto_is_superset(value, expected_value, field_path) 69 if not result[0]: 70 return result 71 else: 72 # Zero-length repeated fields and not-there repeated fields are "the 73 # same." 74 if (expected_value != value and 75 (not spec.is_sequence or 76 len(expected_value))): 77 if spec.is_sequence: 78 return False, "{} differs: found {}, expected {}".format( 79 field_path, list(value), list(expected_value)) 80 else: 81 return False, "{} differs: found {}, expected {}".format( 82 field_path, value, expected_value) 83 return True, "" 84 85 86def dict_file_reader(file_dict): 87 """Returns a callable that retrieves entries from file_dict as files. 88 89 This can be used to call glue.parse_emboss_file with file text declared 90 inline. 91 92 Arguments: 93 file_dict: A dictionary from "file names" to "contents." 94 95 Returns: 96 A callable that can be passed to glue.parse_emboss_file in place of the 97 "read" builtin. 98 """ 99 100 def read(file_name): 101 try: 102 return file_dict[file_name], None 103 except KeyError: 104 return None, ["File '{}' not found.".format(file_name)] 105 106 return read 107