1 /**
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
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 * http://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
16 #ifndef PANDA_TOOLING_INSPECTOR_TEST_JSON_OBJECT_MATCHER_H
17 #define PANDA_TOOLING_INSPECTOR_TEST_JSON_OBJECT_MATCHER_H
18
19 #include <algorithm>
20 #include <iomanip>
21 #include <iostream>
22 #include <iterator>
23 #include <tuple>
24 #include <utility>
25 #include <vector>
26
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29
30 #include "macros.h"
31 #include "utils/json_parser.h"
32
33 // NOLINTBEGIN
34
35 namespace ark::tooling::inspector::test {
36 template <typename PropertyType>
37 constexpr const char *PropertyTypeName();
38
39 template <>
40 constexpr const char *PropertyTypeName<JsonObject::ArrayT>()
41 {
42 return "array";
43 }
44
45 template <>
46 constexpr const char *PropertyTypeName<JsonObject::BoolT>()
47 {
48 return "boolean";
49 }
50
51 template <>
52 constexpr const char *PropertyTypeName<JsonObject::JsonObjPointer>()
53 {
54 return "object";
55 }
56
57 template <>
58 constexpr const char *PropertyTypeName<JsonObject::NumT>()
59 {
60 return "number";
61 }
62
63 template <>
64 constexpr const char *PropertyTypeName<JsonObject::StringT>()
65 {
66 return "string";
67 }
68
69 template <typename PropertyType>
70 struct JsonProperty {
71 const char *key;
72 testing::Matcher<PropertyType> value_matcher;
73
74 template <typename OutputStream>
DescribeKeyToJsonProperty75 void DescribeKeyTo(OutputStream *os) const
76 {
77 *os << "property " << std::quoted(key) << " of type " << PropertyTypeName<PropertyType>();
78 }
79
DescribeToJsonProperty80 void DescribeTo(std::ostream *os) const
81 {
82 DescribeKeyTo(os);
83 *os << " which ";
84 value_matcher.DescribeTo(os);
85 }
86 };
87
88 template <typename PropertyType>
89 class HasJsonPropertyMatcher : public testing::MatcherInterface<const JsonObject &> {
90 public:
HasJsonPropertyMatcher(JsonProperty<PropertyType> property)91 explicit HasJsonPropertyMatcher(JsonProperty<PropertyType> property) : property_(std::move(property)) {}
92
HasJsonPropertyMatcher(const char * key,testing::Matcher<PropertyType> value_matcher)93 HasJsonPropertyMatcher(const char *key, testing::Matcher<PropertyType> value_matcher)
94 : property_ {key, value_matcher}
95 {
96 }
97
DescribeTo(std::ostream * os)98 void DescribeTo(std::ostream *os) const override
99 {
100 *os << "has ";
101 property_.DescribeTo(os);
102 }
103
DescribeNegationTo(std::ostream * os)104 void DescribeNegationTo(std::ostream *os) const override
105 {
106 *os << "does not have a ";
107 property_.DescribeTo(os);
108 }
109
MatchAndExplain(const JsonObject & object,testing::MatchResultListener * os)110 bool MatchAndExplain(const JsonObject &object, testing::MatchResultListener *os) const override
111 {
112 auto value_ptr = object.GetValue<PropertyType>(property_.key);
113
114 if (!value_ptr) {
115 *os << "no ";
116 property_.DescribeKeyTo(os);
117 return false;
118 }
119
120 return property_.value_matcher.MatchAndExplain(*value_ptr, os);
121 }
122
123 private:
124 JsonProperty<PropertyType> property_;
125 };
126
127 template <typename... PropertyType>
128 class JsonObjectMatcher : public testing::MatcherInterface<const JsonObject &> {
129 public:
JsonObjectMatcher(JsonProperty<PropertyType>...property)130 explicit JsonObjectMatcher(JsonProperty<PropertyType>... property) : properties_ {property...} {}
131
DescribeTo(std::ostream * os)132 void DescribeTo(std::ostream *os) const override
133 {
134 *os << "is ";
135 DescribeInternalTo(os);
136 }
137
DescribeNegationTo(std::ostream * os)138 void DescribeNegationTo(std::ostream *os) const override
139 {
140 *os << "isn't ";
141 DescribeInternalTo(os);
142 }
143
MatchAndExplain(const JsonObject & object,testing::MatchResultListener * os)144 bool MatchAndExplain(const JsonObject &object, testing::MatchResultListener *os) const override
145 {
146 if (object.GetSize() != sizeof...(PropertyType)) {
147 *os << "number of properties doesn't match";
148 return false;
149 }
150
151 return std::apply(
152 [&](JsonProperty<PropertyType>... property) {
153 return (HasJsonPropertyMatcher<PropertyType>(property).MatchAndExplain(object, os) && ...);
154 },
155 properties_);
156 }
157
158 private:
DescribeInternalTo(std::ostream * os)159 void DescribeInternalTo(std::ostream *os) const
160 {
161 if constexpr (sizeof...(PropertyType) == 0) {
162 *os << "an empty JsonObject";
163 } else {
164 *os << "a JsonObject with properties: ";
165
166 std::apply(
167 [os](auto first_property, auto... property) {
168 first_property.DescribeTo(os);
169 ((*os << ", ", property.DescribeTo(os)), ...);
170 },
171 properties_);
172 }
173 }
174
175 std::tuple<JsonProperty<PropertyType>...> properties_;
176 };
177
178 template <typename... PropertyType>
JsonProperties(JsonProperty<PropertyType>...property)179 auto JsonProperties(JsonProperty<PropertyType>... property)
180 {
181 return testing::Matcher<const JsonObject &>(new JsonObjectMatcher<PropertyType...>(property...));
182 }
183
184 template <typename... PropertyType>
JsonElements(testing::Matcher<PropertyType>...matcher)185 auto JsonElements(testing::Matcher<PropertyType>... matcher)
186 {
187 return testing::ElementsAre(
188 Property(PropertyTypeName<PropertyType>(), &JsonObject::Value::Get<PropertyType>, Pointee(matcher))...);
189 }
190
191 template <typename PropertyType, template <typename...> class Container, typename... Param>
JsonElementsAreArray(const Container<testing::Matcher<PropertyType>,Param...> & container)192 auto JsonElementsAreArray(const Container<testing::Matcher<PropertyType>, Param...> &container)
193 {
194 std::vector<testing::Matcher<const JsonObject::Value &>> elements;
195
196 std::transform(container.begin(), container.end(), std::back_inserter(elements), [](auto &matcher) {
197 return Property(PropertyTypeName<PropertyType>(), &JsonObject::Value::Get<PropertyType>, Pointee(matcher));
198 });
199
200 return ElementsAreArray(elements);
201 }
202 } // namespace ark::tooling::inspector::test
203
204 namespace ark {
205 std::ostream &operator<<(std::ostream &os, const JsonObject::Value &value);
206
207 inline std::ostream &operator<<(std::ostream &os, const JsonObject &object)
208 {
209 os << '{';
210
211 const char *delim = "";
212
213 for (auto &[key, value] : object.GetUnorderedMap()) {
214 os << delim << std::quoted(key) << ": " << value;
215 delim = ", ";
216 }
217
218 return os << '}';
219 }
220
221 inline std::ostream &operator<<(std::ostream &os, const JsonObject::ArrayT &array)
222 {
223 os << '[';
224
225 const char *delim = "";
226
227 for (auto &value : array) {
228 os << delim << value;
229 delim = ", ";
230 }
231
232 return os << ']';
233 }
234 } // namespace ark
235
236 // NOLINTEND
237
238 #endif // PANDA_TOOLING_INSPECTOR_TEST_JSON_OBJECT_MATCHER_H
239