| // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/lib/json_parser/json_parser.h" | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <functional> | |
| #include <string> | |
| #include <gtest/gtest.h> | |
| #include <rapidjson/document.h> | |
| #include <fbl/unique_fd.h> | |
| #include "lib/fit/function.h" | |
| #include "src/lib/files/file.h" | |
| #include "src/lib/files/path.h" | |
| #include "src/lib/files/scoped_temp_dir.h" | |
| #include "src/lib/fxl/strings/concatenate.h" | |
| #include "src/lib/fxl/strings/substitute.h" | |
| namespace json_parser { | |
| namespace { | |
| class JSONParserTest : public ::testing::Test { | |
| protected: | |
| // ExpectFailedParse() will replace '$0' with the JSON filename, if present. | |
| void ExpectFailedParse(JSONParser* parser, const std::string& json, std::string expected_error) { | |
| props_found_ = 0; | |
| const std::string json_file = NewJSONFile(json); | |
| std::string error; | |
| EXPECT_FALSE(ParseFromFile(parser, json_file, &error)); | |
| EXPECT_EQ(error, fxl::Substitute(expected_error, json_file)); | |
| EXPECT_EQ(0, props_found_); | |
| } | |
| bool ParseFromFile(JSONParser* parser, const std::string& file, std::string* error) { | |
| rapidjson::Document document = parser->ParseFromFile(file); | |
| if (!parser->HasError()) { | |
| InterpretDocument(parser, std::move(document)); | |
| } | |
| *error = parser->error_str(); | |
| return !parser->HasError(); | |
| } | |
| bool ParseFromFileAt(JSONParser* parser, int dirfd, const std::string& file, std::string* error) { | |
| rapidjson::Document document = parser->ParseFromFileAt(dirfd, file); | |
| if (!parser->HasError()) { | |
| InterpretDocument(parser, std::move(document)); | |
| } | |
| *error = parser->error_str(); | |
| return !parser->HasError(); | |
| } | |
| bool ParseFromDirectory(JSONParser* parser, const std::string& dir, std::string* error) { | |
| fit::function<void(rapidjson::Document)> cb = | |
| std::bind(&JSONParserTest::InterpretDocument, this, parser, std::placeholders::_1); | |
| parser->ParseFromDirectory(dir, std::move(cb)); | |
| *error = parser->error_str(); | |
| return !parser->HasError(); | |
| } | |
| bool ParseFromDirectoryAt(fbl::unique_fd& dir_fd, JSONParser* parser, | |
| const std::string& dir, std::string* error) { | |
| fit::function<void(rapidjson::Document)> cb = | |
| std::bind(&JSONParserTest::InterpretDocument, this, parser, std::placeholders::_1); | |
| parser->ParseFromDirectoryAt(dir_fd.get(), dir, std::move(cb)); | |
| *error = parser->error_str(); | |
| return !parser->HasError(); | |
| } | |
| std::string NewJSONFile(const std::string& json) { | |
| std::string json_file; | |
| if (!tmp_dir_.NewTempFileWithData(json, &json_file)) { | |
| return ""; | |
| } | |
| return json_file; | |
| } | |
| std::string NewJSONFileInDir(const std::string& dir, const std::string& json) { | |
| const std::string json_file = "json_file" + std::to_string(unique_id_++); | |
| if (!files::WriteFile(dir + "/" + json_file, json.data(), json.size())) { | |
| return ""; | |
| } | |
| return json_file; | |
| } | |
| void InterpretDocument(JSONParser* parser, rapidjson::Document document) { | |
| if (!document.IsObject()) { | |
| parser->ReportError("Document is not an object."); | |
| return; | |
| } | |
| auto prop1 = document.FindMember("prop1"); | |
| if (prop1 == document.MemberEnd()) { | |
| // Allow missing. | |
| } else if (!prop1->value.IsString()) { | |
| parser->ReportError("prop1 has wrong type"); | |
| } else { | |
| ++props_found_; | |
| } | |
| auto prop2 = document.FindMember("prop2"); | |
| if (prop2 == document.MemberEnd()) { | |
| // Allow missing. | |
| } else if (!prop2->value.IsInt()) { | |
| parser->ReportError("prop2 has wrong type"); | |
| } else { | |
| ++props_found_; | |
| } | |
| } | |
| files::ScopedTempDir tmp_dir_; | |
| int props_found_ = 0; | |
| private: | |
| int unique_id_ = 1; | |
| }; | |
| TEST_F(JSONParserTest, ReadInvalidFile) { | |
| const std::string invalid_path = fxl::Substitute("$0/does_not_exist", tmp_dir_.path()); | |
| std::string error; | |
| JSONParser parser; | |
| EXPECT_FALSE(ParseFromFile(&parser, invalid_path, &error)); | |
| EXPECT_EQ(error, fxl::Substitute("Failed to read file: $0", invalid_path)); | |
| } | |
| TEST_F(JSONParserTest, ParseWithErrors) { | |
| std::string json; | |
| // One error, in parsing. | |
| { | |
| const std::string json = R"JSON({ | |
| "prop1": "missing closing quote, | |
| "prop2": 42 | |
| })JSON"; | |
| JSONParser parser; | |
| ExpectFailedParse(&parser, json, "$0:2:35: Invalid encoding in string."); | |
| } | |
| // Multiple errors, after parsing. | |
| { | |
| const std::string json = R"JSON({ | |
| "prop1": 42, | |
| "prop2": "wrong_type" | |
| })JSON"; | |
| JSONParser parser; | |
| ExpectFailedParse(&parser, json, "$0: prop1 has wrong type\n$0: prop2 has wrong type"); | |
| } | |
| } | |
| TEST_F(JSONParserTest, ParseFromString) { | |
| const std::string json = R"JSON({ | |
| "prop1": "missing closing quote | |
| })JSON"; | |
| JSONParser parser; | |
| parser.ParseFromString(json, "test_file"); | |
| EXPECT_TRUE(parser.HasError()); | |
| EXPECT_EQ(parser.error_str(), "test_file:2:34: Invalid encoding in string."); | |
| } | |
| TEST_F(JSONParserTest, ParseTwice) { | |
| std::string json; | |
| JSONParser parser; | |
| // Two failed parses. Errors should accumulate. | |
| json = R"JSON({ | |
| "prop1": invalid_value, | |
| })JSON"; | |
| parser.ParseFromString(json, "test_file"); | |
| json = R"JSON({ | |
| "prop1": "missing closing quote | |
| })JSON"; | |
| parser.ParseFromString(json, "test_file"); | |
| EXPECT_TRUE(parser.HasError()); | |
| EXPECT_EQ(parser.error_str(), | |
| "test_file:2:12: Invalid value.\n" | |
| "test_file:2:34: Invalid encoding in string."); | |
| EXPECT_EQ(0, props_found_); | |
| } | |
| TEST_F(JSONParserTest, ParseValid) { | |
| const std::string json = R"JSON({ | |
| "prop1": "foo", | |
| "prop2": 42 | |
| })JSON"; | |
| const std::string file = NewJSONFile(json); | |
| std::string error; | |
| JSONParser parser; | |
| EXPECT_TRUE(ParseFromFile(&parser, file, &error)); | |
| EXPECT_EQ("", error); | |
| EXPECT_EQ(2, props_found_); | |
| } | |
| TEST_F(JSONParserTest, ParseFromFileAt) { | |
| const std::string json = R"JSON({ | |
| "prop1": "foo", | |
| "prop2": 42 | |
| })JSON"; | |
| const std::string file = NewJSONFile(json); | |
| const std::string basename = files::GetBaseName(file); | |
| const int dirfd = open(tmp_dir_.path().c_str(), O_RDONLY); | |
| ASSERT_GT(dirfd, 0); | |
| std::string error; | |
| JSONParser parser; | |
| EXPECT_TRUE(ParseFromFileAt(&parser, dirfd, basename, &error)); | |
| EXPECT_EQ("", error); | |
| EXPECT_EQ(2, props_found_); | |
| } | |
| TEST_F(JSONParserTest, ParseFromDirectory) { | |
| const std::string json1 = R"JSON({ | |
| "prop1": "foo" | |
| })JSON"; | |
| const std::string json2 = R"JSON({ | |
| "prop2": 42 | |
| })JSON"; | |
| std::string dir; | |
| ASSERT_TRUE(tmp_dir_.NewTempDir(&dir)); | |
| NewJSONFileInDir(dir, json1); | |
| NewJSONFileInDir(dir, json2); | |
| std::string error; | |
| JSONParser parser; | |
| EXPECT_TRUE(ParseFromDirectory(&parser, dir, &error)); | |
| EXPECT_EQ("", error); | |
| EXPECT_EQ(2, props_found_); | |
| } | |
| TEST_F(JSONParserTest, ParseFromDirectoryWithErrors) { | |
| const std::string json1 = R"JSON({,,,})JSON"; | |
| const std::string json2 = R"JSON({ | |
| "prop2": 42 | |
| })JSON"; | |
| std::string dir; | |
| ASSERT_TRUE(tmp_dir_.NewTempDir(&dir)); | |
| const std::string json_file1 = NewJSONFileInDir(dir, json1); | |
| NewJSONFileInDir(dir, json2); | |
| // Parsing should continue even when one file fails to parse. | |
| std::string error; | |
| JSONParser parser; | |
| EXPECT_FALSE(ParseFromDirectory(&parser, dir, &error)); | |
| EXPECT_EQ(error, fxl::Concatenate({json_file1, ":1:2: Missing a name for object member."})); | |
| EXPECT_EQ(1, props_found_); | |
| } | |
| TEST_F(JSONParserTest, ParseFromDirectoryDoesNotExist) { | |
| std::string dir = "do_not_exist"; | |
| JSONParser parser; | |
| std::string error; | |
| EXPECT_TRUE(ParseFromDirectory(&parser, dir, &error)); | |
| EXPECT_EQ("", error); | |
| EXPECT_EQ(0, props_found_); | |
| } | |
| TEST_F(JSONParserTest, ParseFromDirectoryAt) { | |
| const std::string json1 = R"JSON({ | |
| "prop1": "foo" | |
| })JSON"; | |
| const std::string json2 = R"JSON({ | |
| "prop2": 42 | |
| })JSON"; | |
| std::string dir; | |
| ASSERT_TRUE(tmp_dir_.NewTempDir(&dir)); | |
| NewJSONFileInDir(dir, json1); | |
| NewJSONFileInDir(dir, json2); | |
| fbl::unique_fd dirfd(open(dir.c_str(), O_RDONLY)); | |
| std::string error; | |
| JSONParser parser; | |
| EXPECT_TRUE(ParseFromDirectoryAt(dirfd, &parser, ".", &error)); | |
| EXPECT_EQ("", error); | |
| EXPECT_EQ(2, props_found_); | |
| } | |
| TEST_F(JSONParserTest, CopyArrayToVectorNonArray) { | |
| const std::string json = R"JSON({ | |
| "foo": 0 | |
| })JSON"; | |
| JSONParser parser; | |
| auto document = parser.ParseFromString(json, "test_file"); | |
| ASSERT_FALSE(parser.HasError()); | |
| std::vector<std::string> vec = {"foo"}; | |
| parser.CopyStringArray("foo", document["foo"], &vec); | |
| EXPECT_TRUE(parser.HasError()); | |
| EXPECT_EQ(parser.error_str(), "test_file: 'foo' is not an array."); | |
| EXPECT_EQ(vec.size(), 0u); | |
| } | |
| TEST_F(JSONParserTest, CopyArrayToVectorNonString) { | |
| const std::string json = R"JSON({ | |
| "bar": [ "1", 2, "3" ] | |
| })JSON"; | |
| JSONParser parser; | |
| auto document = parser.ParseFromString(json, "test_file"); | |
| ASSERT_FALSE(parser.HasError()); | |
| std::vector<std::string> vec = {"foo"}; | |
| parser.CopyStringArray("bar", document["bar"], &vec); | |
| EXPECT_TRUE(parser.HasError()); | |
| EXPECT_EQ(parser.error_str(), "test_file: 'bar' contains an item that's not a string"); | |
| EXPECT_EQ(vec.size(), 0u); | |
| } | |
| TEST_F(JSONParserTest, CopyArrayToVectorEmpty) { | |
| const std::string json = R"JSON({ | |
| "baz": [] | |
| })JSON"; | |
| JSONParser parser; | |
| auto document = parser.ParseFromString(json, "test_file"); | |
| ASSERT_FALSE(parser.HasError()); | |
| std::vector<std::string> vec = {"foo"}; | |
| parser.CopyStringArray("baz", document["baz"], &vec); | |
| EXPECT_FALSE(parser.HasError()); | |
| EXPECT_EQ(vec.size(), 0u); | |
| } | |
| TEST_F(JSONParserTest, CopyArrayToVector) { | |
| const std::string json = R"JSON({ | |
| "qux": [ "quux", "quuz" ] | |
| })JSON"; | |
| JSONParser parser; | |
| auto document = parser.ParseFromString(json, "test_file"); | |
| ASSERT_FALSE(parser.HasError()); | |
| std::vector<std::string> vec = {"foo"}; | |
| parser.CopyStringArray("qux", document["qux"], &vec); | |
| EXPECT_FALSE(parser.HasError()); | |
| EXPECT_EQ(vec.size(), 2u); | |
| EXPECT_EQ(vec[0], "quux"); | |
| EXPECT_EQ(vec[1], "quuz"); | |
| } | |
| } // namespace | |
| } // namespace json_parser |