blob: 1bbdd35cc8f6cdf84dc0eff023bd41df037f16a1 [file] [log] [blame]
Ben Olmsteadc0d77842019-07-31 17:34:05 -07001# 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 public import ir_pb2
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 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
85def 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