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