Skip to content

Commit 8e3c264

Browse files
committed
[WIP] Integrate against the new official annotations test suite
See: json-schema-org/JSON-Schema-Test-Suite#775 Fixes: #442 Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 5e4971e commit 8e3c264

File tree

11 files changed

+1661
-1
lines changed

11 files changed

+1661
-1
lines changed

DEPENDENCIES

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4
22
core https://github.com/sourcemeta/core 375cdcacefe7f8bedb038c761fce1802e4954244
3-
jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite bc919bdb266a4949f8a2425f2f540371604d82b4
3+
jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite 9ad349be933f1e74810cb4fd3ad19780694dc77e

test/evaluator/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,15 @@ target_link_libraries(sourcemeta_blaze_evaluator_official_suite_unit PRIVATE sou
3333
target_link_libraries(sourcemeta_blaze_evaluator_official_suite_unit PRIVATE sourcemeta::core::jsonschema)
3434
target_link_libraries(sourcemeta_blaze_evaluator_official_suite_unit PRIVATE sourcemeta::blaze::compiler)
3535
target_link_libraries(sourcemeta_blaze_evaluator_official_suite_unit PRIVATE sourcemeta::blaze::evaluator)
36+
37+
# JSON Schema Test Suite (annotations)
38+
# See https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/annotations
39+
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT blaze NAME evaluator_annotation_suite
40+
FOLDER "Blaze/Evaluator" SOURCES annotationsuite.cc)
41+
target_compile_definitions(sourcemeta_blaze_evaluator_annotation_suite_unit
42+
PRIVATE ANNOTATION_SUITE_PATH="${PROJECT_SOURCE_DIR}/vendor/jsonschema-test-suite/annotations/tests")
43+
target_link_libraries(sourcemeta_blaze_evaluator_annotation_suite_unit PRIVATE sourcemeta::core::json)
44+
target_link_libraries(sourcemeta_blaze_evaluator_annotation_suite_unit PRIVATE sourcemeta::core::jsonschema)
45+
target_link_libraries(sourcemeta_blaze_evaluator_annotation_suite_unit PRIVATE sourcemeta::core::jsonpointer)
46+
target_link_libraries(sourcemeta_blaze_evaluator_annotation_suite_unit PRIVATE sourcemeta::blaze::compiler)
47+
target_link_libraries(sourcemeta_blaze_evaluator_annotation_suite_unit PRIVATE sourcemeta::blaze::evaluator)

test/evaluator/annotationsuite.cc

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <sourcemeta/blaze/compiler.h>
4+
#include <sourcemeta/blaze/evaluator.h>
5+
6+
#include <sourcemeta/core/json.h>
7+
#include <sourcemeta/core/jsonpointer.h>
8+
#include <sourcemeta/core/jsonschema.h>
9+
10+
#include <algorithm>
11+
#include <cassert>
12+
#include <filesystem>
13+
#include <functional>
14+
#include <iostream>
15+
#include <ostream>
16+
#include <sstream>
17+
#include <string>
18+
#include <unordered_set>
19+
#include <utility>
20+
21+
static auto slugify(const std::string &input, std::ostream &output) -> void {
22+
for (const auto character : input) {
23+
output << (std::isalnum(character) ? character : '_');
24+
}
25+
}
26+
27+
static auto
28+
has_annotation(const sourcemeta::blaze::SimpleOutput &output,
29+
const sourcemeta::core::WeakPointer &instance_location,
30+
const std::string &schema_location,
31+
const sourcemeta::core::JSON &value) -> bool {
32+
std::cerr << "Looking for ";
33+
sourcemeta::core::stringify(value, std::cerr);
34+
std::cerr << " at instance location \""
35+
<< sourcemeta::core::to_string(instance_location)
36+
<< "\" and schema location \"" << schema_location << "\"" << "\n";
37+
38+
for (const auto &annotation : output.annotations()) {
39+
std::cerr << "Checking against annotations for instance location \""
40+
<< sourcemeta::core::to_string(annotation.first.instance_location)
41+
<< "\" and schema location \"" << annotation.first.schema_location
42+
<< "\"" << "\n";
43+
44+
if (annotation.first.instance_location != instance_location ||
45+
annotation.first.schema_location != schema_location) {
46+
continue;
47+
} else if (std::find(annotation.second.cbegin(), annotation.second.cend(),
48+
value) != annotation.second.cend()) {
49+
return true;
50+
}
51+
}
52+
53+
return false;
54+
}
55+
56+
class AnnotationTest : public testing::Test {
57+
public:
58+
explicit AnnotationTest(sourcemeta::blaze::Template test_schema,
59+
sourcemeta::core::JSON test_instance,
60+
sourcemeta::core::JSON::Array test_assertions)
61+
: schema{std::move(test_schema)}, instance{std::move(test_instance)},
62+
assertions{std::move(test_assertions)} {}
63+
64+
auto TestBody() -> void override {
65+
sourcemeta::blaze::SimpleOutput output{this->instance};
66+
const auto result{this->evaluator.validate(this->schema, this->instance,
67+
std::ref(output))};
68+
EXPECT_TRUE(result);
69+
70+
for (const auto &assertion : this->assertions) {
71+
assert(assertion.is_object());
72+
assert(assertion.defines("location"));
73+
assert(assertion.at("location").is_string());
74+
const auto instance_location{
75+
sourcemeta::core::to_pointer(assertion.at("location").to_string())};
76+
assert(assertion.defines("expected"));
77+
assert(assertion.at("expected").is_object());
78+
for (const auto &entry : assertion.at("expected").as_object()) {
79+
std::ostringstream schema_location;
80+
schema_location << entry.first;
81+
assert(assertion.defines("keyword"));
82+
assert(assertion.at("keyword").is_string());
83+
schema_location << "/" << assertion.at("keyword").to_string();
84+
85+
EXPECT_TRUE(has_annotation(
86+
output, sourcemeta::core::to_weak_pointer(instance_location),
87+
schema_location.str(), entry.second));
88+
}
89+
}
90+
}
91+
92+
private:
93+
const sourcemeta::blaze::Template schema;
94+
const sourcemeta::core::JSON instance;
95+
const sourcemeta::core::JSON::Array assertions;
96+
sourcemeta::blaze::Evaluator evaluator;
97+
};
98+
99+
static auto register_tests(const std::string &suite_name,
100+
const std::string &default_dialect,
101+
const std::string &default_dialect_name,
102+
const std::unordered_set<std::string> &targets)
103+
-> void {
104+
auto suite_path{std::filesystem::path{ANNOTATION_SUITE_PATH} / suite_name};
105+
suite_path.replace_extension(".json");
106+
assert(std::filesystem::exists(suite_path));
107+
108+
std::cerr << "Registering suite: " << suite_path.string() << "\n";
109+
std::cerr << "-- Dialect: " << default_dialect << "\n";
110+
std::cerr << "-- Targets:";
111+
assert(!targets.empty());
112+
for (const auto &target : targets) {
113+
std::cerr << " " << target;
114+
}
115+
std::cerr << "\n";
116+
117+
const auto suite{sourcemeta::core::read_json(suite_path)};
118+
assert(suite.is_object());
119+
assert(suite.defines("suite"));
120+
assert(suite.at("suite").is_array());
121+
122+
for (const auto &entry : suite.at("suite").as_array()) {
123+
assert(entry.is_object());
124+
std::ostringstream category;
125+
category << "JSONSchemaAnnotationSuite_";
126+
slugify(default_dialect_name, category);
127+
category << "_" << suite_name << "_";
128+
assert(entry.defines("description"));
129+
assert(entry.at("description").is_string());
130+
slugify(entry.at("description").to_string(), category);
131+
132+
if (entry.defines("compatibility")) {
133+
assert(entry.at("compatibility").is_string());
134+
const auto &compatibility{entry.at("compatibility").to_string()};
135+
if (!targets.contains(compatibility)) {
136+
std::cerr << "-- Skipping " << category.str() << " for compatibility "
137+
<< compatibility << "\n";
138+
continue;
139+
}
140+
}
141+
142+
std::cerr << "-- Compiling " << category.str() << "\n";
143+
assert(entry.defines("schema"));
144+
const auto &schema{entry.at("schema")};
145+
assert(sourcemeta::core::is_schema(schema));
146+
const auto schema_template{sourcemeta::blaze::compile(
147+
schema, sourcemeta::core::schema_official_walker,
148+
sourcemeta::core::schema_official_resolver,
149+
sourcemeta::blaze::default_schema_compiler,
150+
sourcemeta::blaze::Mode::Exhaustive, default_dialect)};
151+
152+
assert(entry.defines("tests"));
153+
assert(entry.at("tests").is_array());
154+
for (std::size_t index = 0; index < entry.at("tests").size(); index++) {
155+
std::ostringstream title;
156+
title << index;
157+
const auto &test{entry.at("tests").at(index)};
158+
assert(test.is_object());
159+
assert(test.defines("instance"));
160+
const auto &instance{test.at("instance")};
161+
assert(test.defines("assertions"));
162+
assert(test.at("assertions").is_array());
163+
const auto &assertions{test.at("assertions").as_array()};
164+
165+
testing::RegisterTest(
166+
category.str().c_str(), title.str().c_str(), nullptr, nullptr,
167+
__FILE__, __LINE__, [=]() -> AnnotationTest * {
168+
return new AnnotationTest(schema_template, instance, assertions);
169+
});
170+
}
171+
}
172+
}
173+
174+
int main(int argc, char **argv) {
175+
testing::InitGoogleTest(&argc, argv);
176+
177+
try {
178+
// 2020-12
179+
register_tests("applicators",
180+
"https://json-schema.org/draft/2020-12/schema", "2020-12",
181+
{"3", "4", "6", "7", "2019", "2020"});
182+
register_tests("content", "https://json-schema.org/draft/2020-12/schema",
183+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
184+
register_tests("core", "https://json-schema.org/draft/2020-12/schema",
185+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
186+
register_tests("format", "https://json-schema.org/draft/2020-12/schema",
187+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
188+
register_tests("meta-data", "https://json-schema.org/draft/2020-12/schema",
189+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
190+
register_tests("unevaluated",
191+
"https://json-schema.org/draft/2020-12/schema", "2020-12",
192+
{"3", "4", "6", "7", "2019", "2020"});
193+
register_tests("unknown", "https://json-schema.org/draft/2020-12/schema",
194+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
195+
196+
// 2019-09
197+
register_tests("applicators",
198+
"https://json-schema.org/draft/2019-09/schema", "2019-09",
199+
{"3", "4", "6", "7", "2019"});
200+
register_tests("content", "https://json-schema.org/draft/2019-09/schema",
201+
"2019-09", {"3", "4", "6", "7", "2019"});
202+
register_tests("core", "https://json-schema.org/draft/2019-09/schema",
203+
"2019-09", {"3", "4", "6", "7", "2019"});
204+
register_tests("format", "https://json-schema.org/draft/2019-09/schema",
205+
"2019-09", {"3", "4", "6", "7", "2019"});
206+
register_tests("meta-data", "https://json-schema.org/draft/2019-09/schema",
207+
"2019-09", {"3", "4", "6", "7", "2019"});
208+
register_tests("unevaluated",
209+
"https://json-schema.org/draft/2019-09/schema", "2019-09",
210+
{"3", "4", "6", "7", "2019"});
211+
register_tests("unknown", "https://json-schema.org/draft/2019-09/schema",
212+
"2019-09", {"3", "4", "6", "7", "2019"});
213+
} catch (const std::exception &error) {
214+
std::cerr << "Error: " << error.what() << "\n";
215+
return EXIT_FAILURE;
216+
}
217+
218+
return RUN_ALL_TESTS();
219+
}

vendor/jsonschema-test-suite.mask

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)