xref: /aosp_15_r20/external/emboss/compiler/util/test_util.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
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