1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/test/values_test_util.h"
6
7 #include <ostream>
8 #include <utility>
9
10 #include "base/files/file_util.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/types/optional_util.h"
16 #include "base/values.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "third_party/abseil-cpp/absl/types/optional.h"
19
20 namespace base {
21
ExpectDictBooleanValue(bool expected_value,const Value::Dict & dict,StringPiece path)22 void ExpectDictBooleanValue(bool expected_value,
23 const Value::Dict& dict,
24 StringPiece path) {
25 EXPECT_EQ(dict.FindBoolByDottedPath(path),
26 absl::make_optional(expected_value))
27 << path;
28 }
29
ExpectDictIntegerValue(int expected_value,const Value::Dict & dict,StringPiece path)30 void ExpectDictIntegerValue(int expected_value,
31 const Value::Dict& dict,
32 StringPiece path) {
33 EXPECT_EQ(dict.FindIntByDottedPath(path), absl::make_optional(expected_value))
34 << path;
35 }
36
ExpectDictStringValue(StringPiece expected_value,const Value::Dict & dict,StringPiece path)37 void ExpectDictStringValue(StringPiece expected_value,
38 const Value::Dict& dict,
39 StringPiece path) {
40 EXPECT_EQ(OptionalFromPtr(dict.FindStringByDottedPath(path)),
41 absl::make_optional(expected_value))
42 << path;
43 }
44
ExpectDictValue(const Value::Dict & expected_value,const Value::Dict & dict,StringPiece path)45 void ExpectDictValue(const Value::Dict& expected_value,
46 const Value::Dict& dict,
47 StringPiece path) {
48 const Value* found_value = dict.FindByDottedPath(path);
49 ASSERT_TRUE(found_value) << path;
50 EXPECT_EQ(*found_value, expected_value) << path;
51 }
52
ExpectDictValue(const Value & expected_value,const Value::Dict & dict,StringPiece path)53 void ExpectDictValue(const Value& expected_value,
54 const Value::Dict& dict,
55 StringPiece path) {
56 const Value* found_value = dict.FindByDottedPath(path);
57 ASSERT_TRUE(found_value) << path;
58 EXPECT_EQ(*found_value, expected_value) << path;
59 }
60
ExpectStringValue(const std::string & expected_str,const Value & actual)61 void ExpectStringValue(const std::string& expected_str, const Value& actual) {
62 const std::string* maybe_string = actual.GetIfString();
63 ASSERT_TRUE(maybe_string);
64 EXPECT_EQ(expected_str, *maybe_string);
65 }
66
67 namespace test {
68
69 namespace {
70
FormatAsJSON(ValueView value)71 std::string FormatAsJSON(ValueView value) {
72 std::string json;
73 JSONWriter::Write(value, &json);
74 return json;
75 }
76
77 class DictionaryHasValueMatcher
78 : public testing::MatcherInterface<const base::Value::Dict&> {
79 public:
DictionaryHasValueMatcher(const std::string & key,const base::Value & expected_value)80 DictionaryHasValueMatcher(const std::string& key,
81 const base::Value& expected_value)
82 : key_(key), expected_value_(expected_value.Clone()) {}
83
84 DictionaryHasValueMatcher(const DictionaryHasValueMatcher& other) = delete;
85 DictionaryHasValueMatcher& operator=(const DictionaryHasValueMatcher& other) =
86 delete;
87
MatchAndExplain(const base::Value::Dict & value,testing::MatchResultListener * listener) const88 bool MatchAndExplain(const base::Value::Dict& value,
89 testing::MatchResultListener* listener) const override {
90 const base::Value* sub_value = value.Find(key_);
91 if (!sub_value) {
92 *listener << "Dictionary '" << FormatAsJSON(value)
93 << "' does not have key '" << key_ << "'";
94 return false;
95 }
96 if (*sub_value != expected_value_) {
97 *listener << "Dictionary value under key '" << key_ << "' is '"
98 << FormatAsJSON(*sub_value) << "', expected '"
99 << FormatAsJSON(expected_value_) << "'";
100 return false;
101 }
102 return true;
103 }
104
DescribeTo(std::ostream * os) const105 void DescribeTo(std::ostream* os) const override {
106 *os << "has key '" << key_ << "' with value '"
107 << FormatAsJSON(expected_value_) << "'";
108 }
109
DescribeNegationTo(std::ostream * os) const110 void DescribeNegationTo(std::ostream* os) const override {
111 *os << "does not have key '" << key_ << "' with value '"
112 << FormatAsJSON(expected_value_) << "'";
113 }
114
115 private:
116 const std::string key_;
117 const base::Value expected_value_;
118 };
119
120 class DictionaryHasValuesMatcher
121 : public testing::MatcherInterface<const base::Value::Dict&> {
122 public:
DictionaryHasValuesMatcher(const base::Value::Dict & template_value)123 explicit DictionaryHasValuesMatcher(const base::Value::Dict& template_value)
124 : template_value_(template_value.Clone()) {}
125
126 DictionaryHasValuesMatcher(const DictionaryHasValuesMatcher& other) = delete;
127 DictionaryHasValuesMatcher& operator=(
128 const DictionaryHasValuesMatcher& other) = delete;
129
MatchAndExplain(const base::Value::Dict & value,testing::MatchResultListener * listener) const130 bool MatchAndExplain(const base::Value::Dict& value,
131 testing::MatchResultListener* listener) const override {
132 bool ok = true;
133 for (auto template_dict_item : template_value_) {
134 const base::Value* sub_value = value.Find(template_dict_item.first);
135 if (!sub_value) {
136 *listener << "\nDictionary does not have key '"
137 << template_dict_item.first << "'";
138 ok = false;
139 continue;
140 }
141 if (*sub_value != template_dict_item.second) {
142 *listener << "\nDictionary value under key '"
143 << template_dict_item.first << "' is '"
144 << FormatAsJSON(*sub_value) << "', expected '"
145 << FormatAsJSON(template_dict_item.second) << "'";
146 ok = false;
147 }
148 }
149 return ok;
150 }
151
DescribeTo(std::ostream * os) const152 void DescribeTo(std::ostream* os) const override {
153 *os << "contains all key-values from '" << FormatAsJSON(template_value_)
154 << "'";
155 }
156
DescribeNegationTo(std::ostream * os) const157 void DescribeNegationTo(std::ostream* os) const override {
158 *os << "does not contain key-values from '" << FormatAsJSON(template_value_)
159 << "'";
160 }
161
162 private:
163 const base::Value::Dict template_value_;
164 };
165
166 // Attempts to parse `json` as JSON. Returns resulting Value on success, has an
167 // EXPECT failure and returns nullopt on failure. If `expected_type` is
168 // provided, treats `json` parsing as a Value of a different type as a failure.
169 //
ParseJsonHelper(StringPiece json,absl::optional<Value::Type> expected_type)170 absl::optional<Value> ParseJsonHelper(
171 StringPiece json,
172 absl::optional<Value::Type> expected_type) {
173 auto result = JSONReader::ReadAndReturnValueWithError(
174 json, JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS);
175 if (!result.has_value()) {
176 ADD_FAILURE() << "Failed to parse \"" << json
177 << "\": " << result.error().message;
178 return absl::nullopt;
179 }
180 if (expected_type && result->type() != *expected_type) {
181 ADD_FAILURE() << "JSON is of wrong type: " << json;
182 return absl::nullopt;
183 }
184 return std::move(*result);
185 }
186
187 } // namespace
188
DictionaryHasValue(const std::string & key,const base::Value & expected_value)189 testing::Matcher<const base::Value::Dict&> DictionaryHasValue(
190 const std::string& key,
191 const base::Value& expected_value) {
192 return testing::MakeMatcher(
193 new DictionaryHasValueMatcher(key, expected_value));
194 }
195
DictionaryHasValues(const base::Value::Dict & template_value)196 testing::Matcher<const base::Value::Dict&> DictionaryHasValues(
197 const base::Value::Dict& template_value) {
198 return testing::MakeMatcher(new DictionaryHasValuesMatcher(template_value));
199 }
200
IsJsonMatcher(base::StringPiece json)201 IsJsonMatcher::IsJsonMatcher(base::StringPiece json)
202 : expected_value_(test::ParseJson(json)) {}
203
IsJsonMatcher(const base::Value & value)204 IsJsonMatcher::IsJsonMatcher(const base::Value& value)
205 : expected_value_(value.Clone()) {}
206
IsJsonMatcher(const base::Value::Dict & value)207 IsJsonMatcher::IsJsonMatcher(const base::Value::Dict& value)
208 : expected_value_(base::Value(value.Clone())) {}
209
IsJsonMatcher(const base::Value::List & value)210 IsJsonMatcher::IsJsonMatcher(const base::Value::List& value)
211 : expected_value_(base::Value(value.Clone())) {}
212
IsJsonMatcher(const IsJsonMatcher & other)213 IsJsonMatcher::IsJsonMatcher(const IsJsonMatcher& other)
214 : expected_value_(other.expected_value_.Clone()) {}
215
operator =(const IsJsonMatcher & other)216 IsJsonMatcher& IsJsonMatcher::operator=(const IsJsonMatcher& other) {
217 expected_value_ = other.expected_value_.Clone();
218 return *this;
219 }
220
221 IsJsonMatcher::~IsJsonMatcher() = default;
222
MatchAndExplain(base::StringPiece json,testing::MatchResultListener * listener) const223 bool IsJsonMatcher::MatchAndExplain(
224 base::StringPiece json,
225 testing::MatchResultListener* listener) const {
226 // This is almost the same logic as ParseJson, but the parser uses stricter
227 // options for JSON data that is assumed to be generated by the code under
228 // test rather than written by hand as part of a unit test.
229 auto ret = JSONReader::ReadAndReturnValueWithError(json, JSON_PARSE_RFC);
230 if (!ret.has_value()) {
231 *listener << "Failed to parse \"" << json << "\": " << ret.error().message;
232 return false;
233 }
234 return MatchAndExplain(*ret, listener);
235 }
236
MatchAndExplain(const base::Value & value,testing::MatchResultListener *) const237 bool IsJsonMatcher::MatchAndExplain(
238 const base::Value& value,
239 testing::MatchResultListener* /* listener */) const {
240 return expected_value_ == value;
241 }
242
MatchAndExplain(const base::Value::Dict & dict,testing::MatchResultListener *) const243 bool IsJsonMatcher::MatchAndExplain(
244 const base::Value::Dict& dict,
245 testing::MatchResultListener* /* listener */) const {
246 return expected_value_.is_dict() && expected_value_.GetDict() == dict;
247 }
248
MatchAndExplain(const base::Value::List & list,testing::MatchResultListener *) const249 bool IsJsonMatcher::MatchAndExplain(
250 const base::Value::List& list,
251 testing::MatchResultListener* /* listener */) const {
252 return expected_value_.is_list() && expected_value_.GetList() == list;
253 }
254
DescribeTo(std::ostream * os) const255 void IsJsonMatcher::DescribeTo(std::ostream* os) const {
256 *os << "is the JSON value " << expected_value_;
257 }
258
DescribeNegationTo(std::ostream * os) const259 void IsJsonMatcher::DescribeNegationTo(std::ostream* os) const {
260 *os << "is not the JSON value " << expected_value_;
261 }
262
ParseJson(StringPiece json)263 Value ParseJson(StringPiece json) {
264 absl::optional<Value> result =
265 ParseJsonHelper(json, /*expected_type=*/absl::nullopt);
266 return result.has_value() ? std::move(*result) : Value();
267 }
268
ParseJsonDict(StringPiece json)269 Value::Dict ParseJsonDict(StringPiece json) {
270 absl::optional<Value> result =
271 ParseJsonHelper(json, /*expected_type=*/Value::Type::DICT);
272 return result.has_value() ? std::move(*result).TakeDict() : Value::Dict();
273 }
274
ParseJsonList(StringPiece json)275 Value::List ParseJsonList(StringPiece json) {
276 absl::optional<Value> result =
277 ParseJsonHelper(json, /*expected_type=*/Value::Type::LIST);
278 return result.has_value() ? std::move(*result).TakeList() : Value::List();
279 }
280
ParseJsonDictFromFile(const FilePath & json_file_path)281 Value::Dict ParseJsonDictFromFile(const FilePath& json_file_path) {
282 std::string json;
283 if (!ReadFileToString(json_file_path, &json)) {
284 ADD_FAILURE() << "Failed to load json file for parsing. path="
285 << json_file_path;
286 return {};
287 }
288 return ParseJsonDict(json);
289 }
290
WriteJsonFile(const FilePath & json_file_path,ValueView root)291 expected<void, WriteJsonError> WriteJsonFile(const FilePath& json_file_path,
292 ValueView root) {
293 std::string json;
294 if (!JSONWriter::Write(root, &json)) {
295 return unexpected(WriteJsonError::kGenerateJsonFailure);
296 }
297 if (!WriteFile(json_file_path, json)) {
298 return unexpected(WriteJsonError::kWriteFileFailure);
299 }
300 return {};
301 }
302
303 } // namespace test
304 } // namespace base
305