mirror of
https://github.com/nlohmann/json.git
synced 2026-10-09 16:07:14 +00:00
Two changes roughly halve the CPU time needed to build the unit tests (clang 21: ~300 s -> ~150 s, GCC 16: ~750-820 s -> ~395-420 s, Debug): - Move the C++14/17/20-dependent tests into separate unit-<name>-cpp<N>.cpp files. A test file is built for every standard whose JSON_HAS_CPP_<N> macro it mentions, so far whole large files were rebuilt for C++14/17/20 because of a few #ifdef sections. The main files are now built for C++11 only; the ci_test_*_cxx<N> jobs still build every file for every standard. - Add the CMake option JSON_TestUnityBuild (ON by default, OFF with MinGW): compatible test files are compiled in batches as one translation unit so they share the template instantiations of the library. Each file keeps its own CTest test, which runs the batch executable filtered to that file's test cases. The binary-format tests form an explicit group; the rest is batched by JSON_TestUnityBatchSize. Fix the name clashes that merging files exposed, document the rules for test files in tests/README.md, and point CONTRIBUTING.md to it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
147 lines
6.2 KiB
C++
147 lines
6.2 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
|
// source file mentions the corresponding version macro. This whole file tests
|
|
// std::format (C++20), so it cannot be split: it is built for C++20 as well:
|
|
// JSON_HAS_CPP_20 (do not remove)
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using json = nlohmann::json;
|
|
using ordered_json = nlohmann::ordered_json;
|
|
|
|
#if JSON_HAS_STD_FORMAT
|
|
|
|
#include <iterator>
|
|
#include <string>
|
|
|
|
TEST_CASE("std::formatter<nlohmann::json>")
|
|
{
|
|
SECTION("compact formatting matches dump()")
|
|
{
|
|
CHECK(std::format("{}", json(nullptr)) == json(nullptr).dump());
|
|
CHECK(std::format("{}", json(true)) == json(true).dump());
|
|
CHECK(std::format("{}", json(42)) == json(42).dump());
|
|
CHECK(std::format("{}", json(42.23)) == json(42.23).dump());
|
|
CHECK(std::format("{}", json("foo")) == json("foo").dump());
|
|
CHECK(std::format("{}", json::array({1, 2, 3})) == json::array({1, 2, 3}).dump());
|
|
|
|
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
CHECK(std::format("{}", j) == j.dump());
|
|
}
|
|
|
|
SECTION("'#' triggers pretty-printing with an indent of 4, like dump(4)")
|
|
{
|
|
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
CHECK(std::format("{:#}", j) == j.dump(4));
|
|
CHECK(std::format("{:#}", json::array()) == json::array().dump(4));
|
|
}
|
|
|
|
SECTION("a width sets the indent, like dump(width), with or without '#'")
|
|
{
|
|
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
CHECK(std::format("{:2}", j) == j.dump(2));
|
|
CHECK(std::format("{:#2}", j) == j.dump(2));
|
|
CHECK(std::format("{:8}", j) == j.dump(8));
|
|
// multi-digit widths must accumulate every digit, not just the first
|
|
CHECK(std::format("{:12}", j) == j.dump(12));
|
|
CHECK(std::format("{:#12}", j) == j.dump(12));
|
|
CHECK(std::format("{:10}", j) == j.dump(10));
|
|
}
|
|
|
|
SECTION("bare alignment with no fill character defaults to a space indent character")
|
|
{
|
|
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
// without a preceding fill character, the alignment character itself must not
|
|
// be mistaken for the indent character -- the default space is kept
|
|
CHECK(std::format("{:<}", j) == j.dump());
|
|
CHECK(std::format("{:>}", j) == j.dump());
|
|
CHECK(std::format("{:^}", j) == j.dump());
|
|
CHECK(std::format("{:<3}", j) == j.dump(3, ' '));
|
|
CHECK(std::format("{:>3}", j) == j.dump(3, ' '));
|
|
CHECK(std::format("{:^3}", j) == j.dump(3, ' '));
|
|
}
|
|
|
|
SECTION("fill-and-align sets the indent character, like dump(indent, indent_char)")
|
|
{
|
|
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
CHECK(std::format("{:.>#}", j) == j.dump(4, '.'));
|
|
CHECK(std::format("{:.>#3}", j) == j.dump(3, '.'));
|
|
CHECK(std::format("{:.>3}", j) == j.dump(3, '.'));
|
|
// the alignment direction itself ('<', '>', '^') has no separate meaning for
|
|
// JSON values -- only the fill character before it is used as the indent character
|
|
CHECK(std::format("{:.<3}", j) == j.dump(3, '.'));
|
|
CHECK(std::format("{:.^3}", j) == j.dump(3, '.'));
|
|
}
|
|
|
|
SECTION("format args with no meaning for JSON values are rejected")
|
|
{
|
|
// std::vformat parses the format string at runtime (unlike std::format, whose
|
|
// format_string type is checked at compile time), so it lets us verify that an
|
|
// invalid spec throws std::format_error without needing a compile-time-illegal
|
|
// format string.
|
|
const json j = 42;
|
|
CHECK_THROWS_AS(std::vformat("{:x}", std::make_format_args(j)), std::format_error);
|
|
CHECK_THROWS_AS(std::vformat("{:+}", std::make_format_args(j)), std::format_error); // sign
|
|
CHECK_THROWS_AS(std::vformat("{:-}", std::make_format_args(j)), std::format_error); // sign
|
|
CHECK_THROWS_AS(std::vformat("{: }", std::make_format_args(j)), std::format_error); // sign
|
|
CHECK_THROWS_AS(std::vformat("{:04}", std::make_format_args(j)), std::format_error); // '0' flag
|
|
CHECK_THROWS_AS(std::vformat("{:.2}", std::make_format_args(j)), std::format_error); // precision
|
|
CHECK_THROWS_AS(std::vformat("{:L}", std::make_format_args(j)), std::format_error); // locale
|
|
const int dynamic_width = 4;
|
|
CHECK_THROWS_AS(std::vformat("{:{}}", std::make_format_args(j, dynamic_width)), std::format_error); // dynamic width
|
|
}
|
|
|
|
SECTION("a format spec may run to the end of the parse context")
|
|
{
|
|
// std::format always hands parse() a range that still holds the closing
|
|
// '}', but a parse context may also end right after the spec
|
|
const auto parse = [](const char* spec)
|
|
{
|
|
std::format_parse_context ctx(spec);
|
|
std::formatter<json> f;
|
|
CHECK(f.parse(ctx) == ctx.end());
|
|
return f;
|
|
};
|
|
|
|
CHECK(parse("").indent == -1);
|
|
CHECK(parse(">").indent == -1);
|
|
CHECK(parse("#").indent == 4);
|
|
CHECK(parse("3").indent == 3);
|
|
CHECK(parse("#12").indent == 12);
|
|
|
|
const auto f = parse(".>");
|
|
CHECK(f.indent == -1);
|
|
CHECK(f.indent_char == '.');
|
|
}
|
|
|
|
SECTION("std::format_to writes through an arbitrary output iterator")
|
|
{
|
|
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
std::string out;
|
|
std::format_to(std::back_inserter(out), "{}", j);
|
|
CHECK(out == j.dump());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("std::formatter<nlohmann::ordered_json>")
|
|
{
|
|
// spot-check a non-default basic_json instantiation, since the formatter
|
|
// is written against the generic NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
|
// template and must actually instantiate (and behave correctly) for
|
|
// template arguments other than nlohmann::json
|
|
const ordered_json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
|
CHECK(std::format("{}", j) == j.dump());
|
|
CHECK(std::format("{:#}", j) == j.dump(4));
|
|
CHECK(std::format("{:2}", j) == j.dump(2));
|
|
}
|
|
|
|
#endif
|