Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions test/e2e/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ target_link_libraries(jsonbinpack_e2e_test_runner PRIVATE sourcemeta::core::json
target_link_libraries(jsonbinpack_e2e_test_runner PRIVATE sourcemeta::jsonbinpack::compiler)
target_link_libraries(jsonbinpack_e2e_test_runner PRIVATE sourcemeta::jsonbinpack::runtime)
target_link_libraries(jsonbinpack_e2e_test_runner PRIVATE sourcemeta::core::jsonschema)
target_link_libraries(jsonbinpack_e2e_test_runner PRIVATE sourcemeta::core::io)
target_link_libraries(jsonbinpack_e2e_test_runner PRIVATE sourcemeta::core::options)
set_target_properties(jsonbinpack_e2e_test_runner
PROPERTIES FOLDER "JSON BinPack/E2E")

Expand Down
209 changes: 167 additions & 42 deletions test/e2e/runner.cc
Original file line number Diff line number Diff line change
@@ -1,31 +1,152 @@
#include <sourcemeta/jsonbinpack/compiler.h>
#include <sourcemeta/jsonbinpack/runtime.h>

#include <sourcemeta/core/io.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonschema.h>
#include <sourcemeta/core/options.h>

#include <cassert> // assert
#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
#include <filesystem> // std::filesystem
#include <fstream> // std::ifstream, std::ofstream
#include <ios> // std::ios_base, std::ios::binary
#include <iostream> // std::cerr
#include <string> // std::to_string
#include <algorithm> // std::min
#include <cassert> // assert
#include <charconv> // std::from_chars
#include <cstdint> // std::int64_t
#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
#include <filesystem> // std::filesystem
#include <iostream> // std::cerr
#include <optional> // std::optional, std::nullopt
#include <sstream> // std::istringstream, std::ostringstream
#include <string> // std::string, std::to_string
#include <string_view> // std::string_view
#include <system_error> // std::errc

constexpr auto DEFAULT_METASCHEMA =
"https://json-schema.org/draft/2020-12/schema";

namespace {

auto prettify_to_string(const sourcemeta::core::JSON &document) -> std::string {
std::ostringstream stream;
sourcemeta::core::prettify(document, stream);
stream << "\n";
return stream.str();
}

auto report_missing_artifact(const std::filesystem::path &path) -> void {
std::cerr << "The artifact does not exist: " << path.string() << "\n";
}

auto report_artifact_mismatch(const std::filesystem::path &path) -> void {
std::cerr << "The artifact does not match: " << path.string() << "\n";
}

auto first_difference(const std::string_view left, const std::string_view right)
-> std::string_view::size_type {
const auto bound{std::min(left.size(), right.size())};
for (std::string_view::size_type index{0}; index < bound; index++) {
if (left[index] != right[index]) {
return index;
}
}

return bound;
}

auto check_text_artifact(const std::filesystem::path &path,
const std::string_view actual) -> bool {
if (!std::filesystem::is_regular_file(path)) {
report_missing_artifact(path);
return false;
}

const auto expected{sourcemeta::core::read_file_to_string(path)};
if (expected == actual) {
return true;
}

report_artifact_mismatch(path);
std::cerr << "GOT:\n" << actual << "EXPECTED:\n" << expected;
return false;
}

auto check_binary_artifact(const std::filesystem::path &path,
const std::string_view actual) -> bool {
if (!std::filesystem::is_regular_file(path)) {
report_missing_artifact(path);
return false;
}

const auto expected{sourcemeta::core::read_file_to_string(path)};
if (expected == actual) {
return true;
}

report_artifact_mismatch(path);
std::cerr << "GOT: " << actual.size() << " bytes\n"
<< "EXPECTED: " << expected.size() << " bytes\n"
<< "FIRST DIFFERENCE AT BYTE: "
<< first_difference(actual, expected) << "\n";
return false;
}

auto parse_size_artifact(const std::string_view contents)
-> std::optional<std::string::size_type> {
std::string::size_type result{0};
const auto *const end{contents.data() + contents.size()};
const auto outcome{std::from_chars(contents.data(), end, result)};
if (outcome.ec != std::errc{} || std::string_view{outcome.ptr, end} != "\n") {
return std::nullopt;
}

return result;
}

auto check_size_artifact(const std::filesystem::path &path,
const std::string::size_type actual) -> bool {
if (!std::filesystem::is_regular_file(path)) {
report_missing_artifact(path);
return false;
}

const auto contents{std::to_string(actual) + "\n"};
const auto expected{sourcemeta::core::read_file_to_string(path)};
if (expected == contents) {
return true;
}

report_artifact_mismatch(path);
const auto previous{parse_size_artifact(expected)};
if (previous.has_value()) {
const auto difference{static_cast<std::int64_t>(actual) -
static_cast<std::int64_t>(previous.value())};
std::cerr << "GOT: " << actual << " bytes\n"
<< "EXPECTED: " << previous.value() << " bytes\n"
<< "DIFFERENCE: " << (difference > 0 ? "+" : "") << difference
<< " bytes\n";
} else {
std::cerr << "GOT:\n" << contents << "EXPECTED:\n" << expected;
}

return false;
}

} // namespace

auto main(int argc, char *argv[]) -> int {
if (argc <= 2) {
std::cerr << "Usage: " << argv[0] << " <instance.json> <directory>\n";
sourcemeta::core::Options application;
application.flag("write", {"w"});
application.parse(argc, argv);

if (application.positional().size() < 2) {
std::cerr << "Usage: " << argv[0]
<< " <instance.json> <directory> [--write]\n";
return EXIT_FAILURE;
}

const std::filesystem::path instance_path{argv[1]};
const std::filesystem::path instance_path{application.positional().at(0)};
assert(std::filesystem::is_regular_file(instance_path));
const sourcemeta::core::JSON instance =
sourcemeta::core::read_json(instance_path);
const std::filesystem::path directory{argv[2]};
const std::filesystem::path directory{application.positional().at(1)};
assert(std::filesystem::is_directory(directory));

// Schema
Expand All @@ -38,64 +159,68 @@ auto main(int argc, char *argv[]) -> int {
sourcemeta::core::schema_resolver,
DEFAULT_METASCHEMA);

std::ofstream canonical_output_stream(directory / "canonical.json",
std::ios::binary);
canonical_output_stream.exceptions(std::ios_base::badbit);

const sourcemeta::core::SchemaFrame frame{
sourcemeta::core::SchemaFrame::Mode::Locations, schema,
sourcemeta::core::schema_walker, sourcemeta::core::schema_resolver,
DEFAULT_METASCHEMA};
sourcemeta::core::schema_format(schema, frame);
sourcemeta::core::prettify(schema, canonical_output_stream);
canonical_output_stream << "\n";
canonical_output_stream.flush();
canonical_output_stream.close();
const auto canonical{prettify_to_string(schema)};

// Compile
sourcemeta::jsonbinpack::compile(schema, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
DEFAULT_METASCHEMA);

std::ofstream encoding_output_stream(directory / "encoding.json",
std::ios::binary);
encoding_output_stream.exceptions(std::ios_base::badbit);
sourcemeta::core::prettify(schema, encoding_output_stream);
encoding_output_stream << "\n";
encoding_output_stream.flush();
encoding_output_stream.close();
const auto encoding_document{prettify_to_string(schema)};

// Encoder
const sourcemeta::jsonbinpack::Encoding encoding{
sourcemeta::jsonbinpack::load(schema)};
std::ofstream output_stream(directory / "output.bin", std::ios::binary);
output_stream.exceptions(std::ios_base::badbit);
std::ostringstream output_stream;
sourcemeta::jsonbinpack::Encoder encoder{output_stream};
encoder.write(instance, encoding);
output_stream.flush();
const auto size{output_stream.tellp()};
output_stream.close();
std::ofstream size_stream(directory / "size.txt", std::ios::binary);
size_stream.exceptions(std::ios_base::badbit);
size_stream << std::to_string(size) << "\n";
size_stream.flush();
size_stream.close();
const auto output{output_stream.str()};

// Decoder
std::ifstream data_stream{directory / "output.bin", std::ios::binary};
data_stream.exceptions(std::ios_base::badbit);
std::istringstream data_stream{output};
sourcemeta::jsonbinpack::Decoder decoder{data_stream};
const sourcemeta::core::JSON result = decoder.read(encoding);

// Report results
if (result == instance) {
return EXIT_SUCCESS;
} else {
if (result != instance) {
std::cerr << "GOT:\n";
sourcemeta::core::prettify(result, std::cerr);
std::cerr << "\nEXPECTED:\n";
sourcemeta::core::prettify(instance, std::cerr);
std::cerr << "\n";
return EXIT_FAILURE;
}

const std::filesystem::path canonical_path{directory / "canonical.json"};
const std::filesystem::path encoding_path{directory / "encoding.json"};
const std::filesystem::path output_path{directory / "output.bin"};
const std::filesystem::path size_path{directory / "size.txt"};

if (application.contains("write")) {
sourcemeta::core::write_file(canonical_path, canonical);
sourcemeta::core::write_file(encoding_path, encoding_document);
sourcemeta::core::write_file(output_path, output);
sourcemeta::core::write_file(size_path,
std::to_string(output.size()) + "\n");
return EXIT_SUCCESS;
}

auto matches{check_text_artifact(canonical_path, canonical)};
matches = check_text_artifact(encoding_path, encoding_document) && matches;
matches = check_binary_artifact(output_path, output) && matches;
matches = check_size_artifact(size_path, output.size()) && matches;

if (!matches) {
std::cerr << "\nIf these changes are expected, re-run as follows to "
"update the artifacts:\n "
<< argv[0] << " " << instance_path.string() << " "
<< directory.string() << " --write\n";
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}
Loading