用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1479 lines
40 KiB

/* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <sys/types.h>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include "m_ctype.h"
#include "sql/json_dom.h"
#include "sql/json_path.h"
#include "sql_string.h"
#include "unittest/gunit/test_utils.h"
/**
Test json path abstraction.
*/
namespace json_path_unittest {
class JsonPathTest : public ::testing::Test {
protected:
virtual void SetUp() { initializer.SetUp(); }
virtual void TearDown() { initializer.TearDown(); }
THD *thd() const { return initializer.thd(); }
my_testing::Server_initializer initializer;
void vet_wrapper_seek(Json_wrapper *wrapper, const Json_path &path,
const std::string &expected, bool expected_null) const;
void vet_wrapper_seek(const char *json_text, const char *path_text,
const std::string &expected, bool expected_null) const;
};
/**
Struct that defines input and expected result for negative testing
of path parsing.
*/
struct Bad_path {
const char *m_path_expression; ///< the path to parse
const size_t m_expected_index; ///< the offset of the syntax error
};
/**
Class that contains parameterized test cases for bad paths.
*/
class JsonBadPathTestP : public ::testing::TestWithParam<Bad_path> {};
/**
Struct that defines input and expected result for positive testing
of path parsing.
*/
struct Good_path {
const char *m_path_expression; ///< the path to parse
const char *m_expected_path; ///< expected canonical path
};
/**
Struct that defines input and expected result for testing
Json_dom.get_location().
*/
struct Location_tuple {
const char *m_json_text; // the document text
const char *m_path_expression; // the path to parse
};
/**
Struct that defines input and expected result for testing
the only_needs_one argument of Json_wrapper.seek().
*/
struct Ono_tuple {
const char *m_json_text; // the document text
const char *m_path_expression; // the path to parse
const uint m_expected_hits; // total number of matches
};
/**
Struct that defines input for cloning test cases.
*/
struct Clone_tuple {
const char *m_path_expression_1; // the first path to parse
const char *m_path_expression_2; // the second path to parse
};
/**
Class that contains parameterized test cases for good paths.
*/
class JsonGoodPathTestP : public ::testing::TestWithParam<Good_path> {};
/**
Class that contains parameterized test cases for dom locations.
*/
class JsonGoodLocationTestP : public ::testing::TestWithParam<Location_tuple> {
};
/**
Class that contains parameterized test cases for the only_needs_one
arg of Json_wrapper.seek().
*/
class JsonGoodOnoTestP : public ::testing::TestWithParam<Ono_tuple> {
virtual void SetUp() { initializer.SetUp(); }
virtual void TearDown() { initializer.TearDown(); }
my_testing::Server_initializer initializer;
protected:
THD *thd() const { return initializer.thd(); }
};
/**
Class that contains parameterized test cases for cloning tests.
*/
class JsonGoodCloneTestP : public ::testing::TestWithParam<Clone_tuple> {
virtual void SetUp() { initializer.SetUp(); }
virtual void TearDown() { initializer.TearDown(); }
my_testing::Server_initializer initializer;
};
/*
Helper functions.
*/
/* Concatenate the left and right strings and write the result into dest */
char *concat(char *dest, const char *left, const char *right) {
dest[0] = 0;
std::strcat(dest, left);
std::strcat(dest, right);
return dest;
}
/** Code common to good_path() and good_leg_types() */
void good_path_common(const char *path_expression, Json_path *json_path) {
size_t bad_idx = 0;
EXPECT_FALSE(parse_path(strlen(path_expression), path_expression, json_path,
&bad_idx));
EXPECT_EQ(0U, bad_idx) << "bad_idx != 0 for " << path_expression;
}
/** Verify that a good path parses correctly */
void good_path(bool check_path, const char *path_expression,
std::string expected_path) {
Json_path json_path;
good_path_common(path_expression, &json_path);
if (check_path) {
String str;
EXPECT_FALSE(json_path.to_string(&str));
EXPECT_EQ(expected_path, std::string(str.ptr(), str.length()));
// Move-construct a new path and verify that it's the same.
Json_path path2{std::move(json_path)};
str.length(0);
EXPECT_FALSE(path2.to_string(&str));
EXPECT_EQ(expected_path, std::string(str.ptr(), str.length()));
// Move-assign to a new path and verify that it's the same.
Json_path path3;
path3 = std::move(path2);
str.length(0);
EXPECT_FALSE(path3.to_string(&str));
EXPECT_EQ(expected_path, std::string(str.ptr(), str.length()));
}
}
void good_path(const char *path_expression, std::string expected_path) {
good_path(true, path_expression, expected_path);
}
/** Shorter form of good_path() */
void good_path(const char *path_expression) {
good_path(false, path_expression, "");
}
/** Verify whether the path contains a wildcard, ellipsis or range token. */
void contains_wildcard(const char *path_expression, bool expected_answer) {
Json_path json_path;
good_path_common(path_expression, &json_path);
EXPECT_EQ(expected_answer, json_path.can_match_many());
}
/** Verify that the leg at the given offset looks good */
void good_leg_at(const char *path_expression, int leg_index,
const std::string &expected_leg,
enum_json_path_leg_type expected_leg_type) {
Json_path json_path;
good_path_common(path_expression, &json_path);
const Json_path_leg *actual_leg = *(json_path.begin() + leg_index);
String str;
EXPECT_FALSE(actual_leg->to_string(&str));
EXPECT_EQ(expected_leg, std::string(str.ptr(), str.length()));
EXPECT_EQ(expected_leg_type, actual_leg->get_type());
}
/* compare two path legs */
void compare_legs(const Json_path_leg *left, const Json_path_leg *right) {
String left_str;
String right_str;
EXPECT_EQ(0, left->to_string(&left_str));
EXPECT_EQ(0, right->to_string(&right_str));
EXPECT_EQ(std::string(left_str.ptr(), left_str.length()),
std::string(right_str.ptr(), right_str.length()));
}
/** Compare two paths */
void compare_paths(Json_path &left, Json_path_clone &right) {
EXPECT_EQ(left.leg_count(), right.leg_count());
Json_path_iterator right_it = right.begin();
for (const Json_path_leg *left_leg : left) {
compare_legs(left_leg, *right_it++);
}
}
/** Verify that clones look alike */
void verify_clone(const char *path_expression_1,
const char *path_expression_2) {
Json_path real_path1;
good_path_common(path_expression_1, &real_path1);
Json_path_clone cloned_path;
for (const Json_path_leg *leg : real_path1) cloned_path.append(leg);
compare_paths(real_path1, cloned_path);
Json_path real_path2;
good_path_common(path_expression_2, &real_path2);
cloned_path.clear();
for (const Json_path_leg *leg : real_path2) cloned_path.append(leg);
compare_paths(real_path2, cloned_path);
}
/**
Verify that a good path has the expected sequence of leg types.
*/
void good_leg_types(const char *path_expression,
enum_json_path_leg_type *expected_leg_types,
size_t length) {
Json_path json_path;
good_path_common(path_expression, &json_path);
EXPECT_EQ(length, json_path.leg_count());
Json_path_iterator it = json_path.begin();
for (size_t idx = 0; idx < length; idx++) {
EXPECT_EQ(expected_leg_types[idx], (*it++)->get_type());
}
}
/** Verify that a bad path fails as expected */
void bad_path(const char *path_expression, size_t expected_index) {
size_t actual_index = 0;
Json_path json_path;
EXPECT_TRUE(parse_path(strlen(path_expression), path_expression, &json_path,
&actual_index))
<< "Unexpectedly parsed " << path_expression;
EXPECT_EQ(expected_index, actual_index)
<< "Unexpected index for " << path_expression;
}
/** Bad identifiers are ok as member names if they are double-quoted */
void bad_identifier(const char *identifier, size_t expected_index) {
char dummy1[30];
char dummy2[30];
char *path_expression;
path_expression = concat(dummy1, "$.", identifier);
bad_path(path_expression, expected_index);
path_expression = concat(dummy1, "$.\"", identifier);
path_expression = concat(dummy2, path_expression, "\"");
good_path(path_expression);
}
/*
Helper functions for Json_wrapper tests.
*/
void JsonPathTest::vet_wrapper_seek(Json_wrapper *wrapper,
const Json_path &path,
const std::string &expected,
bool expected_null) const {
Json_wrapper_vector hits(PSI_NOT_INSTRUMENTED);
wrapper->seek(path, path.leg_count(), &hits, true, false);
String result_buffer;
if (hits.size() == 1) {
EXPECT_FALSE(hits[0].to_string(&result_buffer, true, "test"));
} else {
Json_array *a = new (std::nothrow) Json_array();
for (uint i = 0; i < hits.size(); ++i) {
a->append_clone(hits[i].to_dom(thd()));
}
Json_wrapper w(a);
EXPECT_FALSE(w.to_string(&result_buffer, true, "test"));
}
std::string actual = std::string(result_buffer.ptr(), result_buffer.length());
if (expected_null) {
const char *source_output = "";
const char *result_output = "";
if (hits.size() > 0) {
String source_buffer;
EXPECT_FALSE(wrapper->to_string(&source_buffer, true, "test"));
source_output = source_buffer.ptr();
result_output = actual.c_str();
}
EXPECT_TRUE(hits.size() == 0)
<< "Unexpected result wrapper for " << source_output
<< ". The output is " << result_output << "\n";
} else {
EXPECT_EQ(expected, actual);
}
}
void JsonPathTest::vet_wrapper_seek(const char *json_text,
const char *path_text,
const std::string &expected,
bool expected_null) const {
Json_dom_ptr dom =
Json_dom::parse(json_text, std::strlen(json_text), nullptr, nullptr);
String serialized_form;
EXPECT_FALSE(json_binary::serialize(thd(), dom.get(), &serialized_form));
json_binary::Value binary = json_binary::parse_binary(
serialized_form.ptr(), serialized_form.length());
Json_wrapper dom_wrapper(std::move(dom));
Json_wrapper binary_wrapper(binary);
Json_path path;
good_path_common(path_text, &path);
vet_wrapper_seek(&dom_wrapper, path, expected, expected_null);
vet_wrapper_seek(&binary_wrapper, path, expected, expected_null);
}
void vet_dom_location(const char *json_text, const char *path_text) {
Json_dom_ptr dom =
Json_dom::parse(json_text, std::strlen(json_text), nullptr, nullptr);
Json_path path;
good_path_common(path_text, &path);
Json_dom_vector hits(PSI_NOT_INSTRUMENTED);
dom->seek(path, path.leg_count(), &hits, true, false);
EXPECT_EQ(1U, hits.size());
if (hits.size() > 0) {
Json_dom *child = hits[0];
Json_path location = child->get_location();
String str;
EXPECT_EQ(0, location.to_string(&str));
EXPECT_EQ(path_text, std::string(str.ptr(), str.length()));
}
}
/**
Vet the short-circuiting effects of the only_needs_one argument
of Json_wrapper.seek().
@param[in] wrapper A wrapped JSON document.
@param[in] path A path to search for.
@param[in] expected_hits Total number of expected matches.
*/
void vet_only_needs_one(Json_wrapper &wrapper, const Json_path &path,
uint expected_hits) {
Json_wrapper_vector all_hits(PSI_NOT_INSTRUMENTED);
wrapper.seek(path, path.leg_count(), &all_hits, true, false);
EXPECT_EQ(expected_hits, all_hits.size());
Json_wrapper_vector only_needs_one_hits(PSI_NOT_INSTRUMENTED);
wrapper.seek(path, path.leg_count(), &only_needs_one_hits, true, true);
uint expected_onoh_hits = (expected_hits == 0) ? 0 : 1;
EXPECT_EQ(expected_onoh_hits, only_needs_one_hits.size());
}
/**
Vet the short-circuiting effects of the only_needs_one argument
of Json_wrapper.seek().
@param[in] json_text Text of the json document to search.
@param[in] path_text Text of the path expression to use.
@param[in] expected_hits Total number of expected matches.
@param[in] thd THD handle
*/
void vet_only_needs_one(const char *json_text, const char *path_text,
uint expected_hits, const THD *thd) {
Json_dom_ptr dom =
Json_dom::parse(json_text, std::strlen(json_text), nullptr, nullptr);
String serialized_form;
EXPECT_FALSE(json_binary::serialize(thd, dom.get(), &serialized_form));
json_binary::Value binary = json_binary::parse_binary(
serialized_form.ptr(), serialized_form.length());
Json_wrapper dom_wrapper(std::move(dom));
Json_wrapper binary_wrapper(binary);
Json_path path;
good_path_common(path_text, &path);
vet_only_needs_one(dom_wrapper, path, expected_hits);
vet_only_needs_one(binary_wrapper, path, expected_hits);
}
/*
Helper functions for testing Json_object.remove()
and Json_array.remove().
*/
/**
Format a Json_dom object to JSON text using Json_wrapper's
to_string functionality.
@param d The DOM object to be formatted
*/
std::string format(Json_dom *dom) {
String buffer;
Json_wrapper wrapper(dom->clone());
EXPECT_FALSE(wrapper.to_string(&buffer, true, "format"));
return std::string(buffer.ptr(), buffer.length());
}
/*
Tests
*/
// Good paths with no column scope.
static const Good_path good_paths_no_column_scope[] = {
{"$", "$"},
{" $", "$"},
{"$ ", "$"},
{" $ ", "$"},
{"$[5]", "$[5]"},
{"$[ 5 ]", "$[5]"},
{" $[ 5 ] ", "$[5]"},
{" $ [ 5 ] ", "$[5]"},
{"$[456]", "$[456]"},
{"$[ 456 ]", "$[456]"},
{" $[ 456 ] ", "$[456]"},
{" $ [ 456 ] ", "$[456]"},
{"$[last]", "$[last]"},
{"$[ last]", "$[last]"},
{"$[last ]", "$[last]"},
{"$[last-1]", "$[last-1]"},
{"$[last -1]", "$[last-1]"},
{"$[last- 1]", "$[last-1]"},
{"$[4294967295]", "$[4294967295]"},
{"$[last-4294967295]", "$[last-4294967295]"},
{"$.a", "$.a"},
{"$ .a", "$.a"},
{"$. a", "$.a"},
{" $ . a ", "$.a"},
{" $. abc", "$.abc"},
{" $ . abc", "$.abc"},
{" $ . abc ", "$.abc"},
{" $ . abc ", "$.abc"},
{"$.a[7]", "$.a[7]"},
{" $ . a [ 7 ] ", "$.a[7]"},
{"$[7].a", "$[7].a"},
{" $ [ 7 ] . a ", "$[7].a"},
{"$.*", "$.*"},
{" $ . * ", "$.*"},
{"$.*.b", "$.*.b"},
{" $ . * . b ", "$.*.b"},
{"$.*[4]", "$.*[4]"},
{" $ . * [ 4 ] ", "$.*[4]"},
{"$[*]", "$[*]"},
{" $ [ * ] ", "$[*]"},
{"$[*].a", "$[*].a"},
{" $ [ * ] . a ", "$[*].a"},
{"$[*][31]", "$[*][31]"},
{" $ [ * ] [ 31 ] ", "$[*][31]"},
{"$**.abc", "$**.abc"},
{" $ ** . abc ", "$**.abc"},
{"$**[0]", "$**[0]"},
{" $ ** [ 0 ] ", "$**[0]"},
{"$**.a", "$**.a"},
{" $ ** . a ", "$**.a"},
// backslash in front of a quote
{"$.\"\\\\\"", "$.\"\\\\\""},
// 0-length member names must be quoted
{"$.\"\"", "$.\"\""},
{"$.\"\".\"\"", "$.\"\".\"\""},
{"$.\"\".a.\"\"", "$.\"\".a.\"\""},
{"$.abc.\"\"", "$.abc.\"\""},
{"$.abc.\"\".def", "$.abc.\"\".def"},
{"$.\"abc\".\"\".def", "$.abc.\"\".def"},
{"$[0 to 0]", "$[0 to 0]"},
{"$[1 to 1]", "$[1 to 1]"},
{"$[1 to 3]", "$[1 to 3]"},
{"$[ 1 to 3 ]", "$[1 to 3]"},
{"$[0 to 4294967295]", "$[0 to 4294967295]"},
{"$[last to last]", "$[last to last]"},
{"$[last-0 to last - 0]", "$[last to last]"},
{"$[last-1 to last-1]", "$[last-1 to last-1]"},
{"$[last to 1]", "$[last to 1]"},
{"$[1 to last]", "$[1 to last]"},
};
/** Test good paths without column scope */
TEST_P(JsonGoodPathTestP, GoodPaths) {
Good_path param = GetParam();
good_path(param.m_path_expression, param.m_expected_path);
}
INSTANTIATE_TEST_CASE_P(PositiveNoColumnScope, JsonGoodPathTestP,
::testing::ValuesIn(good_paths_no_column_scope));
/** Test that path leg types look correct. */
TEST_F(JsonPathTest, LegTypes) {
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types1[] = {jpl_member};
good_leg_types("$.a", leg_types1, 1);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types2[] = {jpl_array_cell};
good_leg_types("$[3456]", leg_types2, 1);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types3[] = {jpl_member_wildcard};
good_leg_types("$.*", leg_types3, 1);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types4[] = {jpl_array_cell_wildcard};
good_leg_types("$[*]", leg_types4, 1);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types5[] = {jpl_member, jpl_member};
good_leg_types("$.foo.bar", leg_types5, 2);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types6[] = {jpl_member, jpl_array_cell};
good_leg_types("$.foo[987654321]", leg_types6, 2);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types7[] = {jpl_member, jpl_member_wildcard};
good_leg_types("$.foo.*", leg_types7, 2);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types8[] = {jpl_member,
jpl_array_cell_wildcard};
good_leg_types("$.foo[*]", leg_types8, 2);
}
{
SCOPED_TRACE("");
enum_json_path_leg_type leg_types9[] = {jpl_ellipsis, jpl_member};
good_leg_types("$**.foo", leg_types9, 2);
}
{
SCOPED_TRACE("");
good_leg_types(" $ ", nullptr, 0);
}
}
/** Test accessors. */
TEST_F(JsonPathTest, Accessors) {
{
SCOPED_TRACE("");
good_leg_at("$[*][31]", 0, "[*]", jpl_array_cell_wildcard);
}
{
SCOPED_TRACE("");
good_leg_at("$.abc[ 3 ].def", 2, ".def", jpl_member);
}
{
SCOPED_TRACE("");
good_leg_at("$.abc**.def", 1, "**", jpl_ellipsis);
}
}
/** Test detection of wildcard/ellipsis tokens. */
TEST_F(JsonPathTest, WildcardDetection) {
{
SCOPED_TRACE("");
contains_wildcard("$", false);
}
{
SCOPED_TRACE("");
contains_wildcard("$.foo", false);
}
{
SCOPED_TRACE("");
contains_wildcard("$[3]", false);
}
{
SCOPED_TRACE("");
contains_wildcard("$.foo.bar", false);
}
{
SCOPED_TRACE("");
contains_wildcard("$[3].foo", false);
}
{
SCOPED_TRACE("");
contains_wildcard("$[3][5]", false);
}
{
SCOPED_TRACE("");
contains_wildcard("$.*", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$[*]", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$.*.bar", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$**.bar", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$[*].foo", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$**.foo", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$[3].*", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$[*][5]", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$**[5]", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$[1 to 2]", true);
}
{
SCOPED_TRACE("");
contains_wildcard("$.a[1 to 2].b", true);
}
}
TEST_P(JsonBadPathTestP, BadPaths) {
Bad_path param = GetParam();
bad_path(param.m_path_expression, param.m_expected_index);
}
// Bad paths with no column scope.
static const Bad_path bad_paths_no_column_scope[] = {
// no leading $
{"foo", 1},
{"[5]", 1},
// no period before key name
{"$foo", 1},
{"$[5]foo", 4},
// array index not a number or a valid range
{"$[a]", 2},
{"$[5].foo[b]", 9},
{"$[]", 2},
{"$[1.2]", 4},
{"$[1,2]", 4},
{"$[1,]", 4},
{"$[1 TO 3]", 5},
{"$[1 tO 3]", 5},
{"$[1 To 3]", 5},
{"$[1 to]", 5},
{"$[1to]", 4},
{"$[1to 2]", 4},
{"$[1 to2]", 5},
{"$[1 ti 2]", 5},
{"$[1 t", 5},
{"$[1 to ", 5},
{"$[1 to 2,]", 9},
{"$[4 to 3]", 8},
{"$[0 tolast]", 5},
{"$[lastto 2]", 7},
{"$[lastto to 2]", 7},
{"$[last+0]", 7},
{"$[last+1]", 7},
{"$[LAST]", 2},
// absurdly large array index, largest supported array index is 2^32-1
{"$[9999999999999999999999999999999999999999"
"999999999999999999999999999]",
2},
{"$[4294967296]", 2},
{"$[18446744073709551616]", 2},
{"$[9223372036854775808]", 2},
{"$[4294967296 to 2]", 2},
{"$[0 to 4294967296]", 7},
{"$[0 to 4294967297]", 7},
{"$[last-4294967296]", 7},
// period not followed by member name
{"$.", 2},
{"$.foo.", 6},
{"$[3].", 5},
{"$.[3]", 2},
{"$.foo[4].", 9},
// array index not terminated by ]
{"$[4", 3},
{"$[4a]", 4},
{"$[4abc]", 4},
// ends in ellipsis
{"$**", 3},
{"$.foo**", 7},
// paths shouldn't have column scopes if the caller says
// they don't
{"a.b.c$", 1},
{"b.c$", 1},
{"c$", 1},
{"a.b.c$.e", 1},
{"b.c$.e", 1},
{"c$.e", 1},
// unterminated double-quoted name
{"$.\"bar", 6},
// 0-length member names must be quoted
{"$..ab", 2},
{"$.", 2},
{"$. ", 3},
{"$.abc.", 6},
{"$.abc..def", 6},
{"$.\"abc\"..def", 8},
// backslash in front of a quote, and no end quote
{"$.\"\\\"", 5},
// reject plus in front of array index
{"$[+1]", 2},
// negative array indexes are rejected
{"$[-0]", 2},
{"$[-1]", 2},
{"$[0 to -1]", 7},
{"$[-1 to 0]", 2},
{"$[- 1]", 2},
{"$[-]", 2},
};
INSTANTIATE_TEST_CASE_P(NegativeNoColumnScope, JsonBadPathTestP,
::testing::ValuesIn(bad_paths_no_column_scope));
/** Good paths with column scope not supported yet */
TEST_F(JsonPathTest, PositiveColumnScope) {
//
// Test good path syntax
//
bad_path("a.b.c$", 1);
}
/** Test good quoted key names */
static const Good_path good_quoted_key_names[] = {
{"$.\"a\"", "$.a"},
{"$ .\"a\"", "$.a"},
{"$. \"a\"", "$.a"},
{" $ . \"a\" ", "$.a"},
{" $. \"abc\"", "$.abc"},
{" $ . \"abc\"", "$.abc"},
{" $ . \"abc\" ", "$.abc"},
{" $ . \"abc\" ", "$.abc"},
{"$.\"a\"[7]", "$.a[7]"},
{" $ . \"a\" [ 7 ] ", "$.a[7]"},
{"$[7].\"a\"", "$[7].a"},
{" $ [ 7 ] . \"a\" ", "$[7].a"},
{"$.*.\"b\"", "$.*.b"},
{" $ . * . \"b\" ", "$.*.b"},
{"$[*].\"a\"", "$[*].a"},
{" $ [ * ] . \"a\" ", "$[*].a"},
{"$**.\"abc\"", "$**.abc"},
{" $ ** . \"abc\" ", "$**.abc"},
{"$**.\"a\"", "$**.a"},
{" $ ** . \"a\" ", "$**.a"},
// embedded spaces
{"$.\" c d \"", "$.\" c d \""},
{"$.\" c d \".\"a b\"", "$.\" c d \".\"a b\""},
{"$.\"a b\".\" c d \"", "$.\"a b\".\" c d \""},
};
INSTANTIATE_TEST_CASE_P(QuotedKeyNamesPositive, JsonGoodPathTestP,
::testing::ValuesIn(good_quoted_key_names));
/** Test bad quoted key names */
static const Bad_path bad_quoted_key_names[] = {
// no closing quote
{"$.a.\"bcd", 8},
{"$.a.\"", 5},
{"$.\"a\".\"bcd", 10},
// not followed by a member or array cell
{"$.abc.\"def\"ghi", 11},
{"$.abc.\"def\"5", 11},
// unrecognized escape character
{"$.abc.\"def\\aghi\"", 16},
// unrecognized unicode escape
{"$.abcd.\"ef\\u01kfmno\"", 20},
// not preceded by a period
{"$\"abcd\"", 1},
//{ false, "$.ghi\"abcd\"", 5 },
};
INSTANTIATE_TEST_CASE_P(QuotedKeyNamesNegative, JsonBadPathTestP,
::testing::ValuesIn(bad_quoted_key_names));
/* Test that unquoted key names may not be ECMAScript identifiers */
static const Good_path good_ecmascript_identifiers[] = {
// keywords, however, are allowed
{"$.if.break.return", "$.if.break.return"},
// member name can start with $ and _
{"$.$abc", "$.$abc"},
{"$.$abc", "$.$abc"},
// internal digits are ok
{"$.a1_$bc", "$.a1_$bc"},
// and so are internal <ZWNJ> and <ZWJ> characters
{"$.a\\u200Cbc",
"$.a\xE2\x80\x8C"
"bc"},
{"$.a\\u200Dbc",
"$.a\xE2\x80\x8D"
"bc"},
// and so are internal unicode combining marks
{"$.a\\u0300bc",
"$.a\xCC\x80"
"bc"},
{"$.a\\u030Fbc",
"$.a\xCC\x8F"
"bc"},
{"$.a\\u036Fbc",
"$.a\xCD\xAF"
"bc"},
// and so are internal unicode connector punctuation codepoints
{"$.a\\uFE33bc",
"$.a\xEF\xB8\xB3"
"bc"},
};
INSTANTIATE_TEST_CASE_P(GoodECMAScriptIdentifiers, JsonGoodPathTestP,
::testing::ValuesIn(good_ecmascript_identifiers));
TEST_F(JsonPathTest, BadECMAScriptIdentifiers) {
// key names may not contain embedded quotes
{
SCOPED_TRACE("");
bad_path("$.a\"bc", 6);
}
// key names may not start with a digit or punctuation
{
SCOPED_TRACE("");
bad_identifier("1abc", 6);
}
{
SCOPED_TRACE("");
bad_identifier(";abc", 6);
}
// and not with the <ZWNJ> and <ZWJ> characters
{
SCOPED_TRACE("");
bad_identifier("\\u200Cabc", 11);
}
// and not with a unicode combining mark
{
SCOPED_TRACE("");
bad_identifier("\\u0300abc", 11);
}
{
SCOPED_TRACE("");
bad_identifier("\\u030Fabc", 11);
}
{
SCOPED_TRACE("");
bad_identifier("\\u036Fabc", 11);
}
// and not with unicode connector punctuation
{
SCOPED_TRACE("");
bad_identifier("\\uFE33abc", 11);
}
}
TEST_F(JsonPathTest, WrapperSeekTest) {
// vacuous path
{
SCOPED_TRACE("");
vet_wrapper_seek("false", "$", "false", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek("[ false, true, 1 ]", "$", "[false, true, 1]", false);
}
// no match
{
SCOPED_TRACE("");
vet_wrapper_seek("false", "$.a", "", true);
}
{
SCOPED_TRACE("");
vet_wrapper_seek("[ false, true, 1 ]", "$[3]", "", true);
}
// first level retrieval
{
SCOPED_TRACE("");
vet_wrapper_seek("[ false, true, 1 ]", "$[2]", "1", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\" : 1, \"b\" : { \"c\" : [ 1, 2, 3 ] }, "
"\"d\" : 4 }",
"$.b", "{\"c\": [1, 2, 3]}", false);
}
// second level retrieval
{
SCOPED_TRACE("");
vet_wrapper_seek("[ false, true, [ 1, null, 200, 300 ], 400 ]", "$[2][3]",
"300", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\" : 1, \"b\" : { \"c\" : [ 1, 2, 3 ] }, "
"\"d\" : 4 }",
"$.b.c", "[1, 2, 3]", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"[ false, {\"abc\": 500}, "
"[ 1, null, 200, 300 ], 400 ]",
"$[1].abc", "500", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\" : 1, \"b\" : [ 100, 200, 300 ], "
"\"d\" : 4 }",
"$.b[2]", "300", false);
}
// wildcards
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\" : 1, \"b\" : [ 100, 200, 300 ], "
"\"d\" : 4 }",
"$.*", "[1, [100, 200, 300], 4]", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"[ false, {\"a\": true}, {\"b\": 200}, "
"{\"a\": 300} ]",
"$[*].a", "[true, 300]", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"b\": {\"c\": 100}, \"d\": {\"a\": 200}, "
"\"e\": {\"a\": 300}}",
"$.*.a", "[200, 300]", false);
}
//
// ellipsis
//
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"b\": {\"c\": 100}, \"d\": {\"a\": 200}, "
"\"e\": {\"a\": 300}, \"f\": {\"g\": {\"a\": 500} } }",
"$**.a", "[200, 300, 500]", false);
}
// ellipsis with array recursing into object
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\": 100, "
"\"d\": [ {\"a\": 200}, "
"{ \"e\": {\"a\": 300, \"f\": 500} }, "
" { \"g\" : true, \"a\": 600 } ] }",
"$.d**.a", "[200, 300, 600]", false);
}
// ellipsis with object recursing into arrays
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\": true, "
" \"b\": { "
" \"a\": 100,"
" \"c\": [ "
"200, { \"a\": 300 }, "
"{ \"d\": { \"e\": { \"a\": 400 } }, \"f\": true }, "
"500, [ { \"a\": 600 } ]"
"]"
"}, "
" \"g\": { \"a\": 700 } }",
"$.b**.a", "[100, 300, 400, 600]", false);
}
// daisy-chained ellipses
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ \"a\": { \"x\" : { \"b\": { \"y\": { \"b\": "
"{ \"z\": { \"c\": 100 } } } } } } }",
"$.a**.b**.c", "100", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{ "
" \"c\": true"
", \"a\": { "
" \"d\": [ "
" { "
" \"b\" : { "
" \"e\": ["
"{ \"c\": 100 "
", \"f\": { \"a\": 200, \"b\": { \"g\" : { \"h\": "
"{ \"c\": 300 } } } }"
" }"
" ]"
" }"
" }"
" ]"
" }"
", \"b\": true"
" }",
"$.a**.b**.c", "[100, 300]", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek(
"["
" 100,"
" ["
" true,"
" false,"
" true,"
" false,"
" { \"a\": ["
" 300,"
" 400,"
" ["
" 1, 2, 3, 4, 5,"
" {"
" \"b\": [ 500, 600, 700, 800, 900 ]"
" }"
" ]"
" ]"
" }"
" ],"
" 200"
"]",
"$[1]**[2]**[3]", "[4, 800]", false);
}
// $[1][2][3].b[3] is a match for $[1]**[2]**[3]
{
SCOPED_TRACE("");
vet_wrapper_seek(
"["
" 100,"
" ["
" 300,"
" 400,"
" ["
" 1, 2, 3, 4, 5,"
" {"
" \"b\": [ 500, 600, 700, 800, 900 ]"
" }"
" ]"
" ],"
" 200"
"]",
"$[1]**[2]**[3]", "[4, 800]", false);
}
/*
$**[2]**.c matches
$.a[ 2 ][ 1 ].c
$.c.d[2][5].c
$.d[2][4].d.c
but not
$.b[ 1 ][ 1 ].c
$.e[2].c
*/
{
SCOPED_TRACE("");
vet_wrapper_seek(
"{"
" \"a\": [ 0, 1, [ 0, { \"c\": 100 } ] ],"
" \"b\": [ 0, [ 0, { \"c\": 200 } ] ],"
" \"c\": { \"d\": [ 0, 1, [ 0, 1, 2, 3, 4, "
"{ \"c\": 300 } ] ] },"
" \"d\": [ 0, 1, [ 0, 1, 2, 3, { \"d\": "
"{ \"c\": 400 } } ] ],"
" \"e\": [ 0, 1, { \"c\": 500 } ]"
"}",
"$**[2]**.c", "[100, 300, 400, 500]", false);
}
// auto-wrapping
{
SCOPED_TRACE("");
vet_wrapper_seek("{ \"a\": 100 }", "$.a[ 0 ]", "100", false);
}
{
SCOPED_TRACE("");
vet_wrapper_seek("[ [ 100, 200, 300 ], 400, { \"c\": 500 } ]", "$[*][ 0 ]",
"[100, 400, {\"c\": 500}]", false);
}
// auto-wrapping only works for the 0th index
{
SCOPED_TRACE("");
vet_wrapper_seek("[ [ 100, 200, 300 ], 400, { \"c\": 500 } ]", "$[*][ 1 ]",
"200", false);
}
// verify more ellipsis and autowrapping cases.
// these two should have the same result.
{
SCOPED_TRACE("");
vet_wrapper_seek("[1]", "$[0][0]", "1", false);
SCOPED_TRACE("");
}
{
SCOPED_TRACE("");
vet_wrapper_seek("[1]", "$**[0]", "1", false);
}
// these two should have the same result.
{
SCOPED_TRACE("");
vet_wrapper_seek("{ \"a\": 1 }", "$.a[0]", "1", false);
SCOPED_TRACE("");
}
{
SCOPED_TRACE("");
vet_wrapper_seek("{ \"a\": 1 }", "$**[0]", "[{\"a\": 1}, 1]", false);
}
// these two should have the same result.
{
SCOPED_TRACE("");
vet_wrapper_seek("{ \"a\": 1 }", "$[0].a", "1", false);
SCOPED_TRACE("");
}
{
SCOPED_TRACE("");
vet_wrapper_seek("{ \"a\": 1 }", "$**.a", "1", false);
}
}
TEST_F(JsonPathTest, RemoveDomTest) {
{
SCOPED_TRACE("");
std::string json_text = "[100, 200, 300]";
Json_dom_ptr dom =
Json_dom::parse(json_text.data(), json_text.length(), nullptr, nullptr);
auto array = static_cast<Json_array *>(dom.get());
EXPECT_TRUE(array->remove(1));
EXPECT_EQ("[100, 300]", format(array));
EXPECT_FALSE(array->remove(2));
EXPECT_EQ("[100, 300]", format(array));
json_text = "{\"a\": 100, \"b\": 200, \"c\": 300}";
dom =
Json_dom::parse(json_text.data(), json_text.length(), nullptr, nullptr);
auto object = static_cast<Json_object *>(dom.get());
EXPECT_TRUE(object->remove("b"));
EXPECT_EQ("{\"a\": 100, \"c\": 300}", format(object));
EXPECT_FALSE(object->remove("d"));
EXPECT_EQ("{\"a\": 100, \"c\": 300}", format(object));
}
/*
test the adding of parent pointers
*/
// Json_dom.add_alias()
Json_object object1;
Json_boolean true_literal1(true);
Json_boolean false_literal1(false);
Json_null *null_literal1 = new (std::nothrow) Json_null();
EXPECT_EQ(nullptr, null_literal1->parent());
object1.add_clone(std::string("a"), &true_literal1);
object1.add_clone(std::string("b"), &false_literal1);
object1.add_alias(std::string("c"), null_literal1);
EXPECT_EQ(&object1, null_literal1->parent());
EXPECT_EQ("{\"a\": true, \"b\": false, \"c\": null}", format(&object1));
SCOPED_TRACE("");
EXPECT_TRUE(object1.remove("c"));
EXPECT_EQ("{\"a\": true, \"b\": false}", format(&object1));
EXPECT_FALSE(object1.remove("c"));
EXPECT_EQ("{\"a\": true, \"b\": false}", format(&object1));
// Json_dom.add_clone()
Json_null null_literal2;
EXPECT_EQ(nullptr, null_literal2.parent());
std::string key("d");
object1.add_clone(key, &null_literal2);
Json_dom *clone = object1.get(key);
EXPECT_EQ(&object1, clone->parent());
// Json_array.append_clone()
Json_array array;
Json_boolean true_literal2(true);
Json_boolean false_literal2(false);
Json_null null_literal3;
array.append_clone(&true_literal2);
array.append_clone(&false_literal2);
array.append_clone(&null_literal3);
EXPECT_EQ("[true, false, null]", format(&array));
Json_dom *cell = array[2];
EXPECT_EQ(&array, cell->parent());
// Json_array.append_alias()
Json_boolean *true_literal3 = new (std::nothrow) Json_boolean(true);
array.append_alias(true_literal3);
EXPECT_EQ("[true, false, null, true]", format(&array));
EXPECT_EQ(&array, true_literal3->parent());
EXPECT_TRUE(array.remove(3));
EXPECT_EQ("[true, false, null]", format(&array));
EXPECT_FALSE(array.remove(3));
EXPECT_EQ("[true, false, null]", format(&array));
// Json_array.insert_clone()
Json_boolean true_literal4(true);
array.insert_clone(2, &true_literal4);
EXPECT_EQ("[true, false, true, null]", format(&array));
cell = array[2];
EXPECT_EQ(&array, cell->parent());
// Json_array.insert_alias()
Json_boolean *false_literal3 = new (std::nothrow) Json_boolean(false);
array.insert_alias(3, Json_dom_ptr(false_literal3));
EXPECT_EQ("[true, false, true, false, null]", format(&array));
EXPECT_EQ(&array, false_literal3->parent());
EXPECT_TRUE(array.remove(3));
EXPECT_EQ("[true, false, true, null]", format(&array));
EXPECT_FALSE(array.remove(4));
EXPECT_EQ("[true, false, true, null]", format(&array));
// Json_array.insert_clone()
Json_boolean true_literal5(true);
array.insert_clone(5, &true_literal5);
EXPECT_EQ("[true, false, true, null, true]", format(&array));
EXPECT_EQ(&array, array[4]->parent());
// Json_array.insert_alias()
Json_boolean *false_literal4 = new (std::nothrow) Json_boolean(false);
array.insert_alias(7, Json_dom_ptr(false_literal4));
EXPECT_EQ("[true, false, true, null, true, false]", format(&array));
EXPECT_EQ(&array, false_literal4->parent());
EXPECT_EQ(&array, array[5]->parent());
EXPECT_TRUE(array.remove(5));
EXPECT_EQ("[true, false, true, null, true]", format(&array));
EXPECT_FALSE(array.remove(5));
EXPECT_EQ("[true, false, true, null, true]", format(&array));
}
// Tuples for the test of Json_dom.get_location()
static const Location_tuple location_tuples[] = {
{"true", "$"},
{"[true, false, null]", "$"},
{"[true, false, null]", "$[1]"},
{"{ \"a\": true}", "$"},
{"{ \"a\": true}", "$.a"},
{"{ \"a\": true, \"b\": [1, 2, 3] }", "$.b[2]"},
{"[ 0, 1, { \"a\": true, \"b\": [1, 2, 3] } ]", "$[2].b[0]"},
};
/** Test good paths without column scope */
TEST_P(JsonGoodLocationTestP, GoodLocations) {
Location_tuple param = GetParam();
vet_dom_location(param.m_json_text, param.m_path_expression);
}
INSTANTIATE_TEST_CASE_P(LocationTesting, JsonGoodLocationTestP,
::testing::ValuesIn(location_tuples));
// Tuples for the test of the only_needs_one arg of Json_wrapper.seek()
static const Ono_tuple ono_tuples[] = {
{"[ { \"a\": 1 }, { \"a\": 2 } ]", "$[*].a", 2},
{"[ { \"a\": 1 }, { \"a\": 2 } ]", "$**.a", 2},
{"{ \"a\": { \"x\" : { \"b\": { \"y\": { \"b\": "
"{ \"z\": { \"c\": 100 }, \"c\": 200 } } } } } }",
"$.a**.b**.c", 2},
};
/** Test good paths without column scope */
TEST_P(JsonGoodOnoTestP, GoodOno) {
Ono_tuple param = GetParam();
vet_only_needs_one(param.m_json_text, param.m_path_expression,
param.m_expected_hits, thd());
}
INSTANTIATE_TEST_CASE_P(OnoTesting, JsonGoodOnoTestP,
::testing::ValuesIn(ono_tuples));
// Tuples for tests of cloning
static const Clone_tuple clone_tuples[] = {
{"$", "$[33]"},
{"$[*].a", "$.a.b.c.d.e"},
{"$.a.b.c[73]", "$**.abc.d.e.f.g"},
};
/** Test cloning. */
TEST_P(JsonGoodCloneTestP, GoodClone) {
Clone_tuple param = GetParam();
verify_clone(param.m_path_expression_1, param.m_path_expression_2);
}
INSTANTIATE_TEST_CASE_P(CloneTesting, JsonGoodCloneTestP,
::testing::ValuesIn(clone_tuples));
/**
A class used for parameterized test cases for the
Json_path_leg::is_autowrap() function.
*/
class JsonPathLegAutowrapP
: public ::testing::TestWithParam<std::pair<std::string, bool>> {};
TEST_P(JsonPathLegAutowrapP, Autowrap) {
const auto param = GetParam();
const std::string path_text = "$" + param.first + ".a";
const bool expected_result = param.second;
Json_path path;
size_t idx = 0;
EXPECT_FALSE(parse_path(path_text.length(), path_text.data(), &path, &idx));
EXPECT_EQ(0U, idx);
EXPECT_EQ(2U, path.leg_count());
EXPECT_EQ(expected_result, (*path.begin())->is_autowrap());
}
static const std::pair<std::string, bool> autowrap_tuples[] = {
// These should match non-arrays due to auto-wrapping.
{"[0]", true},
{"[last]", true},
{"[last-0]", true},
{"[0 to last]", true},
{"[0 to last-0]", true},
{"[0 to 0]", true},
{"[0 to 1]", true},
{"[0 to 100]", true},
{"[last to 0]", true},
{"[last to 1]", true},
{"[last-0 to 1]", true},
{"[last to 100]", true},
{"[last to last]", true},
{"[last-1 to last]", true},
{"[last-100 to last]", true},
{"[last-1 to 0]", true},
{"[last-1 to 1]", true},
// These should not match non-arrays.
{"[*]", false},
{".*", false},
{"**", false},
{".name", false},
{".\"0\"", false},
{"[1]", false},
{"[100]", false},
{"[last-1]", false},
{"[last-100]", false},
{"[0 to last-1]", false},
{"[1 to last]", false},
{"[last-2 to last-1]", false},
};
INSTANTIATE_TEST_CASE_P(AutowrapTesting, JsonPathLegAutowrapP,
::testing::ValuesIn(autowrap_tuples));
} // end namespace json_path_unittest