|
| 1 | +/* |
| 2 | + * Copyright 2026 Redpanda Data, Inc. |
| 3 | + * |
| 4 | + * Use of this software is governed by the Business Source License |
| 5 | + * included in the file licenses/BSL.md |
| 6 | + * |
| 7 | + * As of the Change Date specified in that file, in accordance with |
| 8 | + * the Business Source License, use of this software will be governed |
| 9 | + * by the Apache License, Version 2.0 |
| 10 | + */ |
| 11 | + |
| 12 | +#include "serde/json/parser.h" |
| 13 | +#include "serde/json/tests/test_cases.h" |
| 14 | +#include "test_utils/runfiles.h" |
| 15 | +#include "test_utils/test.h" |
| 16 | +#include "utils/file_io.h" |
| 17 | + |
| 18 | +#include <gtest/gtest.h> |
| 19 | + |
| 20 | +#include <filesystem> |
| 21 | +#include <string> |
| 22 | + |
| 23 | +using namespace serde::json; |
| 24 | + |
| 25 | +/// Test suite from https://github.com/nst/JSONTestSuite. |
| 26 | +/// |
| 27 | +/// File-name prefixes encode the expected parser behavior per RFC 8259: |
| 28 | +/// - y_ must be accepted |
| 29 | +/// - n_ must be rejected |
| 30 | +/// - i_ parsers are free to accept or reject |
| 31 | +class json_test_suite_test |
| 32 | + : public seastar_test |
| 33 | + , public ::testing::WithParamInterface<std::string> {}; |
| 34 | + |
| 35 | +TEST_P_CORO(json_test_suite_test, test_parsing) { |
| 36 | + const auto& test_case_path = GetParam(); |
| 37 | + auto filename = std::filesystem::path(test_case_path).filename().string(); |
| 38 | + |
| 39 | + auto contents = co_await read_fully(test_case_path); |
| 40 | + auto parser = serde::json::parser(std::move(contents), parser_config{}); |
| 41 | + |
| 42 | + while (co_await parser.next()) { |
| 43 | + } |
| 44 | + |
| 45 | + auto final_token = parser.token(); |
| 46 | + ASSERT_TRUE_CORO(final_token == token::eof || final_token == token::error) |
| 47 | + << "parser::next() returned false but final token is neither eof nor " |
| 48 | + "error: " |
| 49 | + << final_token; |
| 50 | + bool accepted = final_token == token::eof; |
| 51 | + |
| 52 | + if (filename.starts_with("y_")) { |
| 53 | + EXPECT_TRUE(accepted) << filename << ": expected accept, got reject"; |
| 54 | + } else if (filename.starts_with("n_")) { |
| 55 | + EXPECT_FALSE(accepted) << filename << ": expected reject, got accept"; |
| 56 | + } else { |
| 57 | + vassert( |
| 58 | + filename.starts_with("i_"), |
| 59 | + "Unexpected test case name prefix: {}", |
| 60 | + filename); |
| 61 | + // Implementation-defined: either outcome is acceptable. The parser |
| 62 | + // must not crash. |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +INSTANTIATE_TEST_SUITE_P( |
| 67 | + json_test_suite, |
| 68 | + json_test_suite_test, |
| 69 | + ::testing::ValuesIn( |
| 70 | + serde::json::testing::collect_json_test_cases( |
| 71 | + test_utils::get_runfile_path("test_parsing", "nst_json_test_suite"))), |
| 72 | + [](const ::testing::TestParamInfo<std::string>& info) { |
| 73 | + // GTest requires parameter names to match [a-zA-Z0-9_] and to be |
| 74 | + // unique. Use the filename stem and hex-escape any non-alphanumeric |
| 75 | + // chars (using `_XX`) to preserve uniqueness across similar names. |
| 76 | + auto stem = std::filesystem::path(info.param).stem().string(); |
| 77 | + std::string out; |
| 78 | + out.reserve(stem.size()); |
| 79 | + for (auto c : stem) { |
| 80 | + auto uc = static_cast<unsigned char>(c); |
| 81 | + if (std::isalnum(uc) || uc == '_') { |
| 82 | + out.push_back(c); |
| 83 | + } else { |
| 84 | + fmt::format_to(std::back_inserter(out), "_{:02x}", uc); |
| 85 | + } |
| 86 | + } |
| 87 | + return out; |
| 88 | + }); |
0 commit comments