blob: df4d3fcd4008f8d4eaaefc39378a0b605369e7fb [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/values_test_util.h"
#include <optional>
#include <ostream>
#include <string_view>
#include <utility>
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::test {
namespace {
std::string FormatAsJSON(ValueView value) {
return WriteJson(value).value_or("");
}
// Attempts to parse `json` as JSON. Returns resulting Value on success, has an
// EXPECT failure and returns nullopt on failure. If `expected_type` is
// provided, treats `json` parsing as a Value of a different type as a failure.
//
std::optional<Value> ParseJsonHelper(std::string_view json,
std::optional<Value::Type> expected_type,
int options) {
auto result = JSONReader::ReadAndReturnValueWithError(json, options);
if (!result.has_value()) {
ADD_FAILURE() << "Failed to parse \"" << json
<< "\": " << result.error().message;
return std::nullopt;
}
if (expected_type && result->type() != *expected_type) {
ADD_FAILURE() << "JSON is of wrong type: " << json;
return std::nullopt;
}
return std::move(*result);
}
bool CheckValue(const Value::Dict& dict,
std::string_view template_key,
const Value& template_value,
testing::MatchResultListener* listener) {
const Value* sub_value = dict.Find(template_key);
if (!sub_value) {
*listener << "\nDictionary does not have key '" << template_key << "'";
return false;
}
if (*sub_value != template_value) {
*listener << "\nDictionary value under key '" << template_key << "' is '"
<< FormatAsJSON(*sub_value) << "', expected '"
<< FormatAsJSON(template_value) << "'";
return false;
}
return true;
}
} // namespace
namespace internal {
DictionaryHasValueMatcher::DictionaryHasValueMatcher(
std::string key,
const Value& expected_value)
: key_(std::move(key)), expected_value_(expected_value.Clone()) {}
DictionaryHasValueMatcher::DictionaryHasValueMatcher(std::string key,
Value&& expected_value)
: key_(std::move(key)), expected_value_(std::move(expected_value)) {}
DictionaryHasValueMatcher::DictionaryHasValueMatcher(
const DictionaryHasValueMatcher& other)
: key_(other.key_), expected_value_(other.expected_value_.Clone()) {}
DictionaryHasValueMatcher& DictionaryHasValueMatcher::operator=(
const DictionaryHasValueMatcher& other) {
expected_value_ = other.expected_value_.Clone();
return *this;
}
DictionaryHasValueMatcher::~DictionaryHasValueMatcher() = default;
bool DictionaryHasValueMatcher::MatchAndExplain(
const Value& value,
testing::MatchResultListener* listener) const {
if (!value.is_dict()) {
*listener << "Value is not a dictionary: " << FormatAsJSON(value);
return false;
}
return MatchAndExplain(value.GetDict(), listener);
}
bool DictionaryHasValueMatcher::MatchAndExplain(
const Value::Dict& dict,
testing::MatchResultListener* listener) const {
return CheckValue(dict, key_, expected_value_, listener);
}
void DictionaryHasValueMatcher::DescribeTo(std::ostream* os) const {
*os << "has key '" << key_ << "' with value '"
<< FormatAsJSON(expected_value_) << "'";
}
void DictionaryHasValueMatcher::DescribeNegationTo(std::ostream* os) const {
*os << "does not have key '" << key_ << "' with value '"
<< FormatAsJSON(expected_value_) << "'";
}
DictionaryHasValuesMatcher::DictionaryHasValuesMatcher(
const Value::Dict& template_value)
: template_value_(template_value.Clone()) {}
DictionaryHasValuesMatcher::DictionaryHasValuesMatcher(
Value::Dict&& template_value)
: template_value_(std::move(template_value)) {}
DictionaryHasValuesMatcher::DictionaryHasValuesMatcher(
const DictionaryHasValuesMatcher& other)
: template_value_(other.template_value_.Clone()) {}
DictionaryHasValuesMatcher& DictionaryHasValuesMatcher::operator=(
const DictionaryHasValuesMatcher& other) {
template_value_ = other.template_value_.Clone();
return *this;
}
DictionaryHasValuesMatcher::~DictionaryHasValuesMatcher() = default;
bool DictionaryHasValuesMatcher::MatchAndExplain(
const Value& value,
testing::MatchResultListener* listener) const {
if (!value.is_dict()) {
*listener << "Value is not a dictionary: " << FormatAsJSON(value);
return false;
}
return MatchAndExplain(value.GetDict(), listener);
}
bool DictionaryHasValuesMatcher::MatchAndExplain(
const Value::Dict& dict,
testing::MatchResultListener* listener) const {
bool ok = true;
for (auto [template_key, template_value] : template_value_) {
ok &= CheckValue(dict, template_key, template_value, listener);
}
return ok;
}
void DictionaryHasValuesMatcher::DescribeTo(std::ostream* os) const {
*os << "contains all key-values from '" << FormatAsJSON(template_value_)
<< "'";
}
void DictionaryHasValuesMatcher::DescribeNegationTo(std::ostream* os) const {
*os << "does not contain key-values from '" << FormatAsJSON(template_value_)
<< "'";
}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(const Value& template_value)
: template_value_(template_value.Clone()) {}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(
const Value::Dict& template_value)
: template_value_(template_value.Clone()) {}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(
const Value::List& template_value)
: template_value_(template_value.Clone()) {}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(Value&& template_value)
: template_value_(std::move(template_value)) {}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(Value::Dict&& template_value)
: template_value_(std::move(template_value)) {}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(Value::List&& template_value)
: template_value_(std::move(template_value)) {}
IsSupersetOfValueMatcher::IsSupersetOfValueMatcher(
const IsSupersetOfValueMatcher& other)
: template_value_(other.template_value_.Clone()) {}
IsSupersetOfValueMatcher& IsSupersetOfValueMatcher::operator=(
const IsSupersetOfValueMatcher& other) {
template_value_ = other.template_value_.Clone();
return *this;
}
IsSupersetOfValueMatcher::~IsSupersetOfValueMatcher() = default;
bool IsSupersetOfValueMatcher::MatchAndExplain(
const Value& value,
testing::MatchResultListener* listener) const {
if (value.type() != template_value_.type()) {
return testing::ExplainMatchResult(
testing::Eq(Value::GetTypeName(template_value_.type())),
Value::GetTypeName(value.type()), listener);
}
switch (value.type()) {
case Value::Type::NONE:
case Value::Type::BOOLEAN:
case Value::Type::INTEGER:
case Value::Type::STRING:
case Value::Type::BINARY:
return testing::ExplainMatchResult(
testing::Eq(std::cref(template_value_)), value, listener);
case Value::Type::DOUBLE:
return testing::ExplainMatchResult(
testing::DoubleEq(template_value_.GetDouble()), value.GetDouble(),
listener);
case Value::Type::DICT:
return MatchAndExplain(value.GetDict(), listener);
case Value::Type::LIST:
return MatchAndExplain(value.GetList(), listener);
}
}
bool IsSupersetOfValueMatcher::MatchAndExplain(
const Value::Dict& dict,
testing::MatchResultListener* listener) const {
if (template_value_.type() != Value::Type::DICT) {
return testing::ExplainMatchResult(
testing::Eq(Value::GetTypeName(template_value_.type())),
Value::GetTypeName(Value::Type::DICT), listener);
}
std::vector<testing::Matcher<const Value::Dict&>> matchers;
for (auto [field_name, field_value] : template_value_.GetDict()) {
matchers.push_back(testing::ResultOf(
StrCat({"field '", field_name, "'"}),
[field_name](const Value::Dict& dict) { return dict.Find(field_name); },
testing::Pointee(IsSupersetOfValue(field_value))));
}
return testing::ExplainMatchResult(testing::AllOfArray(matchers), dict,
listener);
}
bool IsSupersetOfValueMatcher::MatchAndExplain(
const Value::List& list,
testing::MatchResultListener* listener) const {
if (template_value_.type() != Value::Type::LIST) {
return testing::ExplainMatchResult(
testing::Eq(Value::GetTypeName(template_value_.type())),
Value::GetTypeName(Value::Type::LIST), listener);
}
std::vector<testing::Matcher<const Value&>> matchers;
for (const auto& e : template_value_.GetList()) {
matchers.push_back(IsSupersetOfValue(e));
}
return testing::ExplainMatchResult(testing::IsSupersetOf(matchers), list,
listener);
}
void IsSupersetOfValueMatcher::DescribeTo(std::ostream* os) const {
switch (template_value_.type()) {
case Value::Type::NONE:
case Value::Type::BOOLEAN:
case Value::Type::INTEGER:
case Value::Type::DOUBLE:
case Value::Type::STRING:
case Value::Type::BINARY:
*os << "equals '" << FormatAsJSON(template_value_) << "'";
return;
case Value::Type::DICT:
case Value::Type::LIST:
*os << "is a superset of '" << FormatAsJSON(template_value_) << "'";
return;
}
NOTREACHED();
}
void IsSupersetOfValueMatcher::DescribeNegationTo(std::ostream* os) const {
switch (template_value_.type()) {
case Value::Type::NONE:
case Value::Type::BOOLEAN:
case Value::Type::INTEGER:
case Value::Type::DOUBLE:
case Value::Type::STRING:
case Value::Type::BINARY:
*os << "does not equal '" << FormatAsJSON(template_value_) << "'";
return;
case Value::Type::DICT:
case Value::Type::LIST:
*os << "is not a superset of '" << FormatAsJSON(template_value_) << "'";
return;
}
NOTREACHED();
}
IsJsonMatcher::IsJsonMatcher(std::string_view json)
: expected_value_(test::ParseJson(json)) {}
IsJsonMatcher::IsJsonMatcher(const Value& value)
: expected_value_(value.Clone()) {}
IsJsonMatcher::IsJsonMatcher(const Value::Dict& value)
: expected_value_(Value(value.Clone())) {}
IsJsonMatcher::IsJsonMatcher(const Value::List& value)
: expected_value_(Value(value.Clone())) {}
IsJsonMatcher::IsJsonMatcher(Value&& value)
: expected_value_(std::move(value)) {}
IsJsonMatcher::IsJsonMatcher(Value::Dict&& value)
: expected_value_(Value(std::move(value))) {}
IsJsonMatcher::IsJsonMatcher(Value::List&& value)
: expected_value_(Value(std::move(value))) {}
IsJsonMatcher::IsJsonMatcher(const IsJsonMatcher& other)
: expected_value_(other.expected_value_.Clone()) {}
IsJsonMatcher& IsJsonMatcher::operator=(const IsJsonMatcher& other) {
expected_value_ = other.expected_value_.Clone();
return *this;
}
IsJsonMatcher::~IsJsonMatcher() = default;
bool IsJsonMatcher::MatchAndExplain(
std::string_view json,
testing::MatchResultListener* listener) const {
// This is almost the same logic as ParseJson, but the parser uses stricter
// options for JSON data that is assumed to be generated by the code under
// test rather than written by hand as part of a unit test.
auto ret = JSONReader::ReadAndReturnValueWithError(json, JSON_PARSE_RFC);
if (!ret.has_value()) {
*listener << "Failed to parse \"" << json << "\": " << ret.error().message;
return false;
}
return MatchAndExplain(*ret, listener);
}
bool IsJsonMatcher::MatchAndExplain(
const Value& value,
testing::MatchResultListener* /* listener */) const {
return expected_value_ == value;
}
bool IsJsonMatcher::MatchAndExplain(
const Value::Dict& dict,
testing::MatchResultListener* /* listener */) const {
return expected_value_.is_dict() && expected_value_.GetDict() == dict;
}
bool IsJsonMatcher::MatchAndExplain(
const Value::List& list,
testing::MatchResultListener* /* listener */) const {
return expected_value_.is_list() && expected_value_.GetList() == list;
}
void IsJsonMatcher::DescribeTo(std::ostream* os) const {
*os << "is the JSON value " << expected_value_;
}
void IsJsonMatcher::DescribeNegationTo(std::ostream* os) const {
*os << "is not the JSON value " << expected_value_;
}
} // namespace internal
Value ParseJson(std::string_view json, int options) {
std::optional<Value> result =
ParseJsonHelper(json, /*expected_type=*/std::nullopt, options);
return result.has_value() ? std::move(*result) : Value();
}
Value::Dict ParseJsonDict(std::string_view json, int options) {
std::optional<Value> result =
ParseJsonHelper(json, /*expected_type=*/Value::Type::DICT, options);
return result.has_value() ? std::move(*result).TakeDict() : Value::Dict();
}
Value::List ParseJsonList(std::string_view json, int options) {
std::optional<Value> result =
ParseJsonHelper(json, /*expected_type=*/Value::Type::LIST, options);
return result.has_value() ? std::move(*result).TakeList() : Value::List();
}
Value::Dict ParseJsonDictFromFile(const FilePath& json_file_path) {
std::string json;
if (!ReadFileToString(json_file_path, &json)) {
ADD_FAILURE() << "Failed to load json file for parsing. path="
<< json_file_path;
return {};
}
return ParseJsonDict(json);
}
expected<void, WriteJsonError> WriteJsonFile(const FilePath& json_file_path,
ValueView root) {
std::optional<std::string> json = WriteJson(root);
if (!json.has_value()) {
return unexpected(WriteJsonError::kGenerateJsonFailure);
}
if (!WriteFile(json_file_path, json.value())) {
return unexpected(WriteJsonError::kWriteFileFailure);
}
return {};
}
} // namespace base::test