Skip to content

Commit de348e1

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 <jv@jviotti.com>
1 parent d9a902a commit de348e1

File tree

11 files changed

+1696
-1
lines changed

11 files changed

+1696
-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: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
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+
static auto
57+
has_matching_annotations(const sourcemeta::blaze::SimpleOutput &output,
58+
const sourcemeta::core::WeakPointer &instance_location,
59+
const std::string &keyword) -> bool {
60+
std::cerr << "Looking for any annotation at instance location \""
61+
<< sourcemeta::core::to_string(instance_location)
62+
<< "\" from keyword \"" << keyword << "\"" << "\n";
63+
64+
for (const auto &annotation : output.annotations()) {
65+
std::cerr << "Checking against annotations for instance location \""
66+
<< sourcemeta::core::to_string(annotation.first.instance_location)
67+
<< "\" and evaluate path \""
68+
<< sourcemeta::core::to_string(annotation.first.evaluate_path)
69+
<< "\"" << "\n";
70+
71+
if (annotation.first.instance_location == instance_location &&
72+
!annotation.first.evaluate_path.empty() &&
73+
annotation.first.evaluate_path.back().is_property() &&
74+
annotation.first.evaluate_path.back().to_property() == keyword) {
75+
return true;
76+
}
77+
}
78+
79+
return false;
80+
}
81+
82+
class AnnotationTest : public testing::Test {
83+
public:
84+
explicit AnnotationTest(sourcemeta::blaze::Template test_schema,
85+
sourcemeta::core::JSON test_instance,
86+
sourcemeta::core::JSON::Array test_assertions)
87+
: schema{std::move(test_schema)}, instance{std::move(test_instance)},
88+
assertions{std::move(test_assertions)} {}
89+
90+
auto TestBody() -> void override {
91+
sourcemeta::blaze::SimpleOutput output{this->instance};
92+
const auto result{this->evaluator.validate(this->schema, this->instance,
93+
std::ref(output))};
94+
EXPECT_TRUE(result);
95+
96+
for (const auto &assertion : this->assertions) {
97+
assert(assertion.is_object());
98+
assert(assertion.defines("location"));
99+
assert(assertion.at("location").is_string());
100+
const auto instance_location{
101+
sourcemeta::core::to_pointer(assertion.at("location").to_string())};
102+
assert(assertion.defines("expected"));
103+
assert(assertion.at("expected").is_object());
104+
105+
if (assertion.at("expected").empty()) {
106+
assert(assertion.defines("keyword"));
107+
assert(assertion.at("keyword").is_string());
108+
EXPECT_FALSE(has_matching_annotations(
109+
output, sourcemeta::core::to_weak_pointer(instance_location),
110+
assertion.at("keyword").to_string()));
111+
} else {
112+
for (const auto &entry : assertion.at("expected").as_object()) {
113+
std::ostringstream schema_location;
114+
schema_location << entry.first;
115+
assert(assertion.defines("keyword"));
116+
assert(assertion.at("keyword").is_string());
117+
schema_location << "/" << assertion.at("keyword").to_string();
118+
119+
EXPECT_TRUE(has_annotation(
120+
output, sourcemeta::core::to_weak_pointer(instance_location),
121+
schema_location.str(), entry.second));
122+
}
123+
}
124+
}
125+
}
126+
127+
private:
128+
const sourcemeta::blaze::Template schema;
129+
const sourcemeta::core::JSON instance;
130+
const sourcemeta::core::JSON::Array assertions;
131+
sourcemeta::blaze::Evaluator evaluator;
132+
};
133+
134+
static auto register_tests(const std::string &suite_name,
135+
const std::string &default_dialect,
136+
const std::string &default_dialect_name,
137+
const std::unordered_set<std::string> &targets)
138+
-> void {
139+
auto suite_path{std::filesystem::path{ANNOTATION_SUITE_PATH} / suite_name};
140+
suite_path.replace_extension(".json");
141+
assert(std::filesystem::exists(suite_path));
142+
143+
std::cerr << "Registering suite: " << suite_path.string() << "\n";
144+
std::cerr << "-- Dialect: " << default_dialect << "\n";
145+
std::cerr << "-- Targets:";
146+
assert(!targets.empty());
147+
for (const auto &target : targets) {
148+
std::cerr << " " << target;
149+
}
150+
std::cerr << "\n";
151+
152+
const auto suite{sourcemeta::core::read_json(suite_path)};
153+
assert(suite.is_object());
154+
assert(suite.defines("suite"));
155+
assert(suite.at("suite").is_array());
156+
157+
for (const auto &entry : suite.at("suite").as_array()) {
158+
assert(entry.is_object());
159+
std::ostringstream category;
160+
category << "JSONSchemaAnnotationSuite_";
161+
slugify(default_dialect_name, category);
162+
category << "_" << suite_name << "_";
163+
assert(entry.defines("description"));
164+
assert(entry.at("description").is_string());
165+
slugify(entry.at("description").to_string(), category);
166+
167+
if (entry.defines("compatibility")) {
168+
assert(entry.at("compatibility").is_string());
169+
const auto &compatibility{entry.at("compatibility").to_string()};
170+
if (!targets.contains(compatibility)) {
171+
std::cerr << "-- Skipping " << category.str() << " for compatibility "
172+
<< compatibility << "\n";
173+
continue;
174+
}
175+
}
176+
177+
std::cerr << "-- Compiling " << category.str() << "\n";
178+
assert(entry.defines("schema"));
179+
const auto &schema{entry.at("schema")};
180+
assert(sourcemeta::core::is_schema(schema));
181+
const auto schema_template{sourcemeta::blaze::compile(
182+
schema, sourcemeta::core::schema_official_walker,
183+
sourcemeta::core::schema_official_resolver,
184+
sourcemeta::blaze::default_schema_compiler,
185+
sourcemeta::blaze::Mode::Exhaustive, default_dialect)};
186+
187+
assert(entry.defines("tests"));
188+
assert(entry.at("tests").is_array());
189+
for (std::size_t index = 0; index < entry.at("tests").size(); index++) {
190+
std::ostringstream title;
191+
title << index;
192+
const auto &test{entry.at("tests").at(index)};
193+
assert(test.is_object());
194+
assert(test.defines("instance"));
195+
const auto &instance{test.at("instance")};
196+
assert(test.defines("assertions"));
197+
assert(test.at("assertions").is_array());
198+
const auto &assertions{test.at("assertions").as_array()};
199+
200+
testing::RegisterTest(
201+
category.str().c_str(), title.str().c_str(), nullptr, nullptr,
202+
__FILE__, __LINE__, [=]() -> AnnotationTest * {
203+
return new AnnotationTest(schema_template, instance, assertions);
204+
});
205+
}
206+
}
207+
}
208+
209+
int main(int argc, char **argv) {
210+
testing::InitGoogleTest(&argc, argv);
211+
212+
try {
213+
// 2020-12
214+
register_tests("applicators",
215+
"https://json-schema.org/draft/2020-12/schema", "2020-12",
216+
{"3", "4", "6", "7", "2019", "2020"});
217+
register_tests("content", "https://json-schema.org/draft/2020-12/schema",
218+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
219+
register_tests("core", "https://json-schema.org/draft/2020-12/schema",
220+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
221+
register_tests("format", "https://json-schema.org/draft/2020-12/schema",
222+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
223+
register_tests("meta-data", "https://json-schema.org/draft/2020-12/schema",
224+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
225+
register_tests("unevaluated",
226+
"https://json-schema.org/draft/2020-12/schema", "2020-12",
227+
{"3", "4", "6", "7", "2019", "2020"});
228+
register_tests("unknown", "https://json-schema.org/draft/2020-12/schema",
229+
"2020-12", {"3", "4", "6", "7", "2019", "2020"});
230+
231+
// 2019-09
232+
register_tests("applicators",
233+
"https://json-schema.org/draft/2019-09/schema", "2019-09",
234+
{"3", "4", "6", "7", "2019"});
235+
register_tests("content", "https://json-schema.org/draft/2019-09/schema",
236+
"2019-09", {"3", "4", "6", "7", "2019"});
237+
register_tests("core", "https://json-schema.org/draft/2019-09/schema",
238+
"2019-09", {"3", "4", "6", "7", "2019"});
239+
register_tests("format", "https://json-schema.org/draft/2019-09/schema",
240+
"2019-09", {"3", "4", "6", "7", "2019"});
241+
register_tests("meta-data", "https://json-schema.org/draft/2019-09/schema",
242+
"2019-09", {"3", "4", "6", "7", "2019"});
243+
register_tests("unevaluated",
244+
"https://json-schema.org/draft/2019-09/schema", "2019-09",
245+
{"3", "4", "6", "7", "2019"});
246+
register_tests("unknown", "https://json-schema.org/draft/2019-09/schema",
247+
"2019-09", {"3", "4", "6", "7", "2019"});
248+
} catch (const std::exception &error) {
249+
std::cerr << "Error: " << error.what() << "\n";
250+
return EXIT_FAILURE;
251+
}
252+
253+
return RUN_ALL_TESTS();
254+
}

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)