mirror of
https://github.com/nlohmann/json.git
synced 2026-10-09 16:07:14 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
993e37e7ec | ||
|
|
d33068da73 | ||
|
|
d8dfc0d0f9 |
No files matched your search
+10
-3
@@ -108,9 +108,16 @@ The tests are located in [`tests/src/unit-*.cpp`](https://github.com/nlohmann/js
|
|||||||
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
||||||
context which existing file needs to be extended, and only very few cases require creating new test files.
|
context which existing file needs to be extended, and only very few cases require creating new test files.
|
||||||
|
|
||||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue. Tests that need C++17 or
|
||||||
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
C++20 go into `unit-regression3-cpp17.cpp` or `unit-regression3-cpp20.cpp` instead, so the large file is not rebuilt
|
||||||
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
for every C++ standard. `unit-regression2.cpp` holds the older tests; the two files exist because a single one grew
|
||||||
|
large enough for the MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing
|
||||||
|
the larger one.
|
||||||
|
|
||||||
|
To keep compile times down, several test files are compiled together as one translation unit. This sets a few rules
|
||||||
|
for test files, such as giving file-scope helpers file-specific names. See the README in the
|
||||||
|
[`tests`](https://github.com/nlohmann/json/tree/develop/tests) folder for how the tests are built and what to keep in
|
||||||
|
mind when adding them.
|
||||||
|
|
||||||
#### Exceptions
|
#### Exceptions
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -871,11 +871,11 @@ add_custom_target(ci_icpx
|
|||||||
# The following tests are excluded as they trigger known nvc++ 25.5 defects (not
|
# The following tests are excluded as they trigger known nvc++ 25.5 defects (not
|
||||||
# library bugs); see https://github.com/nlohmann/json for tracking. Only the
|
# library bugs); see https://github.com/nlohmann/json for tracking. Only the
|
||||||
# affected language-standard variants are excluded so coverage is otherwise kept:
|
# affected language-standard variants are excluded so coverage is otherwise kept:
|
||||||
# - test-comparison_cpp20, test-comparison_legacy_cpp20
|
# - test-comparison-cpp20_cpp20, test-comparison_legacy-cpp20_cpp20
|
||||||
# miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
|
# miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
|
||||||
# - test-constructor1_cpp11
|
# - test-constructor1_cpp11
|
||||||
# std::initializer_list lifetime bug -> SIGSEGV
|
# std::initializer_list lifetime bug -> SIGSEGV
|
||||||
# - test-deserialization_cpp20
|
# - test-deserialization-cpp20_cpp20
|
||||||
# mangles the UTF-8 u8"" string literal in the char8_t (C++20) section
|
# mangles the UTF-8 u8"" string literal in the char8_t (C++20) section
|
||||||
add_custom_target(ci_nvhpc
|
add_custom_target(ci_nvhpc
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
@@ -887,7 +887,7 @@ add_custom_target(ci_nvhpc
|
|||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
||||||
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
||||||
# instead of treating them as shell pipe operators
|
# instead of treating them as shell pipe operators
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison-cpp20_cpp20\\|test-comparison_legacy-cpp20_cpp20\\|test-constructor1_cpp11\\|test-deserialization-cpp20_cpp20" --output-on-failure
|
||||||
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+352
-39
@@ -109,6 +109,55 @@ function(_json_test_apply_test_properties test_target properties_target)
|
|||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# for internal use by _json_test_add_test() and _json_test_add_unity_batch():
|
||||||
|
# registers the CTest test <test_name>_cpp<cxx_standard> (plus its Valgrind
|
||||||
|
# variant), which runs the executable target <test_target> with the arguments
|
||||||
|
# in ARGN, and applies the test properties of the test- and standard-specific
|
||||||
|
# interface targets
|
||||||
|
function(_json_test_register_test test_name test_target cxx_standard)
|
||||||
|
set(ctest_name ${test_name}_cpp${cxx_standard})
|
||||||
|
|
||||||
|
if (JSON_FastTests)
|
||||||
|
add_test(NAME ${ctest_name}
|
||||||
|
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} ${ARGN}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
add_test(NAME ${ctest_name}
|
||||||
|
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} ${ARGN} --no-skip
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
set_tests_properties(${ctest_name} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
|
||||||
|
|
||||||
|
# apply standard-specific test properties
|
||||||
|
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
||||||
|
_json_test_apply_test_properties(${ctest_name} _json_test_interface__cpp_${cxx_standard})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# apply test-specific test properties
|
||||||
|
if(TARGET _json_test_interface_${test_name})
|
||||||
|
_json_test_apply_test_properties(${ctest_name} _json_test_interface_${test_name})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# apply test- and standard-specific test properties
|
||||||
|
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
||||||
|
_json_test_apply_test_properties(${ctest_name}
|
||||||
|
_json_test_interface_${test_name}_cpp_${cxx_standard}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(JSON_Valgrind)
|
||||||
|
add_test(NAME ${ctest_name}_valgrind
|
||||||
|
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER} ${ARGN}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
set_tests_properties(${ctest_name}_valgrind PROPERTIES
|
||||||
|
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
# for internal use by json_test_add_test_for()
|
# for internal use by json_test_add_test_for()
|
||||||
function(_json_test_add_test test_name file main cxx_standard)
|
function(_json_test_add_test test_name file main cxx_standard)
|
||||||
set(test_target ${test_name}_cpp${cxx_standard})
|
set(test_target ${test_name}_cpp${cxx_standard})
|
||||||
@@ -143,45 +192,7 @@ function(_json_test_add_test test_name file main cxx_standard)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (JSON_FastTests)
|
_json_test_register_test(${test_name} ${test_target} ${cxx_standard})
|
||||||
add_test(NAME ${test_target}
|
|
||||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER}
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
add_test(NAME ${test_target}
|
|
||||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} --no-skip
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
set_tests_properties(${test_target} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
|
|
||||||
|
|
||||||
# apply standard-specific test properties
|
|
||||||
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
|
||||||
_json_test_apply_test_properties(${test_target} _json_test_interface__cpp_${cxx_standard})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# apply test-specific test properties
|
|
||||||
if(TARGET _json_test_interface_${test_name})
|
|
||||||
_json_test_apply_test_properties(${test_target} _json_test_interface_${test_name})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# apply test- and standard-specific test properties
|
|
||||||
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
|
||||||
_json_test_apply_test_properties(${test_target}
|
|
||||||
_json_test_interface_${test_name}_cpp_${cxx_standard}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(JSON_Valgrind)
|
|
||||||
add_test(NAME ${test_target}_valgrind
|
|
||||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
set_tests_properties(${test_target}_valgrind PROPERTIES
|
|
||||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -200,6 +211,14 @@ endfunction()
|
|||||||
# Use NAME <name> to override the filename-derived test name.
|
# Use NAME <name> to override the filename-derived test name.
|
||||||
# Use FORCE to create the test regardless of the file containing
|
# Use FORCE to create the test regardless of the file containing
|
||||||
# JSON_HAS_CPP_<version_number>.
|
# JSON_HAS_CPP_<version_number>.
|
||||||
|
#
|
||||||
|
# Tests that depend on the C++ standard (e.g., because they use the macros
|
||||||
|
# JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
|
# should not make the whole of a large unit-foo.cpp be rebuilt for every
|
||||||
|
# standard. Put them into a separate file unit-foo-cpp<NN>.cpp (see, e.g.,
|
||||||
|
# unit-items-cpp17.cpp) which wraps its content in #ifdef JSON_HAS_CPP_<NN>.
|
||||||
|
# Then, unit-foo.cpp itself contains no JSON_HAS_CPP_<NN> and is only built for
|
||||||
|
# C++11.
|
||||||
# Test targets are linked against <main>.
|
# Test targets are linked against <main>.
|
||||||
# CXX_STANDARDS defaults to "11".
|
# CXX_STANDARDS defaults to "11".
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -243,6 +262,300 @@ function(json_test_add_test_for file)
|
|||||||
endforeach()
|
endforeach()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# for internal use by json_test_add_unity_tests(): sets <result> to whether the
|
||||||
|
# (absolute) <file> is built for <cxx_standard>; same rule as in
|
||||||
|
# json_test_add_test_for(): C++11 always, others only if the file contains
|
||||||
|
# JSON_HAS_CPP_<cxx_standard> or <force> is set
|
||||||
|
function(_json_test_unity_applies file cxx_standard force result)
|
||||||
|
set(${result} TRUE PARENT_SCOPE)
|
||||||
|
if(NOT ("${cxx_standard}" STREQUAL 11 OR force))
|
||||||
|
file(READ ${file} file_content)
|
||||||
|
string(FIND "${file_content}" JSON_HAS_CPP_${cxx_standard} has_cpp_found)
|
||||||
|
if(${has_cpp_found} EQUAL -1)
|
||||||
|
set(${result} FALSE PARENT_SCOPE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# for internal use by json_test_add_unity_tests(): creates the executable
|
||||||
|
# test-unity-<batch_name>_cpp<cxx_standard> from the (absolute) source files
|
||||||
|
# in ARGN, which are #include-d by a generated source file, and registers one
|
||||||
|
# CTest test per source file that runs only the test cases of that file; if
|
||||||
|
# <private> is true, the generated file defines JSON_TESTS_PRIVATE first
|
||||||
|
function(_json_test_add_unity_batch batch_name cxx_standard main private)
|
||||||
|
set(batch_target test-unity-${batch_name}_cpp${cxx_standard})
|
||||||
|
set(batch_source ${PROJECT_BINARY_DIR}/tests/unity/${batch_target}.cpp)
|
||||||
|
|
||||||
|
set(batch_content "// generated by cmake/test.cmake; do not edit\n")
|
||||||
|
if(private)
|
||||||
|
string(APPEND batch_content "// at least one file of this batch needs access to private members of the library\n")
|
||||||
|
string(APPEND batch_content "#define JSON_TESTS_PRIVATE\n")
|
||||||
|
endif()
|
||||||
|
foreach(file ${ARGN})
|
||||||
|
string(APPEND batch_content "#include \"${file}\"\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# only touch the generated file if it changed to keep incremental builds incremental
|
||||||
|
set(old_content "")
|
||||||
|
if(EXISTS ${batch_source})
|
||||||
|
file(READ ${batch_source} old_content)
|
||||||
|
endif()
|
||||||
|
if(NOT "${old_content}" STREQUAL "${batch_content}")
|
||||||
|
file(WRITE ${batch_source} "${batch_content}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(${batch_target} ${batch_source})
|
||||||
|
target_link_libraries(${batch_target} PRIVATE ${main})
|
||||||
|
set_target_properties(${batch_target} PROPERTIES
|
||||||
|
CXX_STANDARD ${cxx_standard}
|
||||||
|
CXX_STANDARD_REQUIRED ON
|
||||||
|
)
|
||||||
|
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
||||||
|
target_link_libraries(${batch_target} PRIVATE _json_test_interface__cpp_${cxx_standard})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# rebuild the batch when one of its files changes, and show the files in IDEs;
|
||||||
|
# files that are also built standalone (VARIANT_FILES) are left out, as
|
||||||
|
# HEADER_FILE_ONLY is a per-file property and would also affect those targets
|
||||||
|
set_source_files_properties(${batch_source} PROPERTIES OBJECT_DEPENDS "${ARGN}")
|
||||||
|
foreach(file ${ARGN})
|
||||||
|
if(NOT file IN_LIST _json_test_unity_variant_files)
|
||||||
|
set_source_files_properties(${file} PROPERTIES HEADER_FILE_ONLY ON)
|
||||||
|
target_sources(${batch_target} PRIVATE ${file})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
foreach(file ${ARGN})
|
||||||
|
get_filename_component(file_basename ${file} NAME_WE)
|
||||||
|
string(REGEX REPLACE "unit-(.+)" "test-\\1" test_name ${file_basename})
|
||||||
|
|
||||||
|
# run only the test cases defined in this file (and in the shared
|
||||||
|
# make_test_data_available.hpp if the file uses it)
|
||||||
|
file(READ ${file} file_content)
|
||||||
|
set(source_filter "--source-file=*${file_basename}.cpp")
|
||||||
|
string(FIND "${file_content}" make_test_data_available.hpp uses_test_data)
|
||||||
|
if(NOT ${uses_test_data} EQUAL -1)
|
||||||
|
string(APPEND source_filter ",*make_test_data_available.hpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
_json_test_register_test(${test_name} ${batch_target} ${cxx_standard} "${source_filter}")
|
||||||
|
endforeach()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# json_test_add_unity_tests(
|
||||||
|
# FILES <files>...
|
||||||
|
# MAIN <main>
|
||||||
|
# [CXX_STANDARDS <version_number>...] [FORCE]
|
||||||
|
# [BATCH_SIZE <size>]
|
||||||
|
# [GROUPS <group>...]
|
||||||
|
# [VARIANT_FILES <files>...])
|
||||||
|
#
|
||||||
|
# Like calling json_test_add_test_for(<file> MAIN <main> ...) for each of the
|
||||||
|
# <files>, but compiles several files together to speed up the build: for each
|
||||||
|
# C++ standard, the files are split into batches of <size> files (default: 8)
|
||||||
|
# and each batch is built as a single executable
|
||||||
|
#
|
||||||
|
# test-unity-<pool><index>_cpp<version_number>
|
||||||
|
#
|
||||||
|
# whose generated source file #include-s the files of the batch (so they share
|
||||||
|
# the template instantiations of the library). The tests are still named
|
||||||
|
# test-foo_cpp<version_number>, one per file, but run the batch executable with
|
||||||
|
# a doctest filter that selects the test cases of that file only.
|
||||||
|
#
|
||||||
|
# Files are only batched with files that agree on the macros defined before
|
||||||
|
# the library is included: files that define at most JSON_TESTS_PRIVATE (or
|
||||||
|
# macros derived from global compile definitions) form the pools "plain" and
|
||||||
|
# "private". All other files are added with json_test_add_test_for() as usual,
|
||||||
|
# as are files with test-specific build settings (see
|
||||||
|
# json_test_set_test_options()) and the files listed in the explicit
|
||||||
|
# exclusion list below.
|
||||||
|
# Each <group> names a list variable json_test_unity_group_<group> of test file
|
||||||
|
# stems (file names without "unit-" and ".cpp"). The batchable files of a group
|
||||||
|
# are compiled together (regardless of BATCH_SIZE) as one executable
|
||||||
|
#
|
||||||
|
# test-unity-<group>_cpp<version_number>
|
||||||
|
#
|
||||||
|
# so that related tests, which instantiate the same templates, share one
|
||||||
|
# translation unit. A group may mix the pools "plain" and "private"; if any of
|
||||||
|
# its files needs JSON_TESTS_PRIVATE, the whole group is built with it. Files
|
||||||
|
# that cannot be batched stay standalone even if they are listed in a group.
|
||||||
|
# Files in no group are batched by BATCH_SIZE as described above.
|
||||||
|
# <files> in VARIANT_FILES are also built standalone with other settings, so
|
||||||
|
# they are not marked as header-only sources of the batch.
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
function(json_test_add_unity_tests)
|
||||||
|
cmake_parse_arguments(args "FORCE" "MAIN;BATCH_SIZE" "FILES;CXX_STANDARDS;VARIANT_FILES;GROUPS" ${ARGN})
|
||||||
|
|
||||||
|
if("${args_MAIN}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Required argument MAIN <main> missing.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("${args_BATCH_SIZE}" STREQUAL "")
|
||||||
|
set(args_BATCH_SIZE 8)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("${args_CXX_STANDARDS}" STREQUAL "")
|
||||||
|
set(args_CXX_STANDARDS 11)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(args_FORCE)
|
||||||
|
set(force FORCE)
|
||||||
|
else()
|
||||||
|
set(force "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(_json_test_unity_variant_files "")
|
||||||
|
foreach(file ${args_VARIANT_FILES})
|
||||||
|
get_filename_component(file ${file} ABSOLUTE)
|
||||||
|
list(APPEND _json_test_unity_variant_files ${file})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# files that must not be merged into a batch: unit-32bit.cpp is only built
|
||||||
|
# for 32bit targets, and unit-no-macro-leak.cpp checks that including the
|
||||||
|
# library defines no unprefixed macro, which any other file would disturb
|
||||||
|
set(standalone_files unit-32bit.cpp unit-no-macro-leak.cpp)
|
||||||
|
|
||||||
|
set(harmless_macros "^(DOCTEST_.*|SKIP_TESTS_FOR_.*|JSON_TEST_DEPRECATED_FUNCTIONS_DELETED|JSON_TEST_STRICT_NUL_HANDLING_ENABLED|JSON_TEST_STRINGIZE)$")
|
||||||
|
|
||||||
|
# classify the files
|
||||||
|
set(plain_files "")
|
||||||
|
set(private_files "")
|
||||||
|
set(standalone_abs_files "")
|
||||||
|
foreach(file ${args_FILES})
|
||||||
|
get_filename_component(file_name ${file} NAME)
|
||||||
|
get_filename_component(file_basename ${file} NAME_WE)
|
||||||
|
string(REGEX REPLACE "unit-(.+)" "test-\\1" test_name ${file_basename})
|
||||||
|
|
||||||
|
set(batchable TRUE)
|
||||||
|
if(file_name IN_LIST standalone_files)
|
||||||
|
set(batchable FALSE)
|
||||||
|
endif()
|
||||||
|
if(TARGET _json_test_interface_${test_name})
|
||||||
|
set(batchable FALSE)
|
||||||
|
endif()
|
||||||
|
foreach(cxx_standard ${args_CXX_STANDARDS})
|
||||||
|
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
||||||
|
set(batchable FALSE)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(pool plain)
|
||||||
|
if(batchable)
|
||||||
|
# collect the macros (un)defined before the library is included
|
||||||
|
file(READ ${file} file_content)
|
||||||
|
string(FIND "${file_content}" "#include <nlohmann/" include_position)
|
||||||
|
if(NOT ${include_position} EQUAL -1)
|
||||||
|
string(SUBSTRING "${file_content}" 0 ${include_position} file_content)
|
||||||
|
endif()
|
||||||
|
string(REGEX MATCHALL "(^|\n)[ \t]*#[ \t]*(define|undef)[ \t]+[A-Za-z_0-9]+" directives "${file_content}")
|
||||||
|
foreach(directive ${directives})
|
||||||
|
string(REGEX REPLACE "^.*[ \t]([A-Za-z_0-9]+)$" "\\1" macro "${directive}")
|
||||||
|
if(macro MATCHES "${harmless_macros}")
|
||||||
|
continue()
|
||||||
|
elseif("${macro}" STREQUAL JSON_TESTS_PRIVATE)
|
||||||
|
set(pool private)
|
||||||
|
else()
|
||||||
|
set(batchable FALSE)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_filename_component(file_abs ${file} ABSOLUTE)
|
||||||
|
if(NOT batchable)
|
||||||
|
list(APPEND standalone_abs_files ${file_abs})
|
||||||
|
json_test_add_test_for(${file} MAIN ${args_MAIN} CXX_STANDARDS ${args_CXX_STANDARDS} ${force})
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
get_filename_component(file ${file} ABSOLUTE)
|
||||||
|
list(APPEND ${pool}_files ${file})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# resolve the explicit groups: group_<name>_files are the (absolute) files
|
||||||
|
# of the group, group_<name>_private tells whether one of them is private
|
||||||
|
set(grouped_files "")
|
||||||
|
foreach(group ${args_GROUPS})
|
||||||
|
if(NOT DEFINED json_test_unity_group_${group})
|
||||||
|
message(FATAL_ERROR "Unity test group '${group}' is not defined (json_test_unity_group_${group}).")
|
||||||
|
endif()
|
||||||
|
set(group_${group}_files "")
|
||||||
|
set(group_${group}_private FALSE)
|
||||||
|
foreach(stem ${json_test_unity_group_${group}})
|
||||||
|
# check against the source directory, because FILES may be filtered (JSON_TestShard)
|
||||||
|
get_filename_component(file ${CMAKE_CURRENT_SOURCE_DIR}/src/unit-${stem}.cpp ABSOLUTE)
|
||||||
|
if(NOT EXISTS ${file})
|
||||||
|
message(FATAL_ERROR "Unity test group '${group}' lists '${stem}', but ${file} does not exist.")
|
||||||
|
endif()
|
||||||
|
if(file IN_LIST grouped_files)
|
||||||
|
message(FATAL_ERROR "Unity test file unit-${stem}.cpp is listed in more than one group (second: '${group}').")
|
||||||
|
endif()
|
||||||
|
list(APPEND grouped_files ${file})
|
||||||
|
|
||||||
|
if(file IN_LIST standalone_abs_files)
|
||||||
|
message(STATUS "Unity test group '${group}': unit-${stem}.cpp cannot be batched and stays standalone")
|
||||||
|
elseif(file IN_LIST plain_files)
|
||||||
|
list(APPEND group_${group}_files ${file})
|
||||||
|
list(REMOVE_ITEM plain_files ${file})
|
||||||
|
elseif(file IN_LIST private_files)
|
||||||
|
list(APPEND group_${group}_files ${file})
|
||||||
|
list(REMOVE_ITEM private_files ${file})
|
||||||
|
set(group_${group}_private TRUE)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
foreach(cxx_standard ${args_CXX_STANDARDS})
|
||||||
|
if(NOT compiler_supports_cpp_${cxx_standard})
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# explicit groups: one batch per group
|
||||||
|
foreach(group ${args_GROUPS})
|
||||||
|
set(batch_files "")
|
||||||
|
foreach(file ${group_${group}_files})
|
||||||
|
_json_test_unity_applies(${file} ${cxx_standard} "${force}" applies)
|
||||||
|
if(applies)
|
||||||
|
list(APPEND batch_files ${file})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
if(batch_files)
|
||||||
|
_json_test_add_unity_batch(${group} ${cxx_standard} ${args_MAIN} ${group_${group}_private} ${batch_files})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# remaining files: batches of BATCH_SIZE files per pool
|
||||||
|
foreach(pool plain private)
|
||||||
|
set(is_private FALSE)
|
||||||
|
if(pool STREQUAL private)
|
||||||
|
set(is_private TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(batch_files "")
|
||||||
|
set(batch_count 0)
|
||||||
|
set(batch_index 0)
|
||||||
|
foreach(file ${${pool}_files})
|
||||||
|
_json_test_unity_applies(${file} ${cxx_standard} "${force}" applies)
|
||||||
|
if(NOT applies)
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND batch_files ${file})
|
||||||
|
math(EXPR batch_count "${batch_count} + 1")
|
||||||
|
if(batch_count EQUAL args_BATCH_SIZE)
|
||||||
|
_json_test_add_unity_batch(${pool}${batch_index} ${cxx_standard} ${args_MAIN} ${is_private} ${batch_files})
|
||||||
|
set(batch_files "")
|
||||||
|
set(batch_count 0)
|
||||||
|
math(EXPR batch_index "${batch_index} + 1")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
if(batch_files)
|
||||||
|
_json_test_add_unity_batch(${pool}${batch_index} ${cxx_standard} ${args_MAIN} ${is_private} ${batch_files})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# json_test_should_build_32bit_test(
|
# json_test_should_build_32bit_test(
|
||||||
# <build_32bit_var> <build_32bit_only_var> <input>)
|
# <build_32bit_var> <build_32bit_only_var> <input>)
|
||||||
|
|||||||
@@ -36,13 +36,26 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
|
|||||||
## API stability
|
## API stability
|
||||||
|
|
||||||
Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code
|
Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code
|
||||||
that uses the public API. In particular, a 3.x release does not:
|
that uses the public API, unless that code opts in to a change with a macro as described [below](#version-40). In
|
||||||
|
particular, a 3.x release does not:
|
||||||
|
|
||||||
- change the signature of a function (its parameter types, return type, number of parameters, or the const-ness of a
|
- make breaking changes to the signature of a function: the types or order of its existing parameters, its return type,
|
||||||
member function);
|
its `noexcept` or `constexpr` specifier, or the const-ness of a member function. New parameters may be added if they
|
||||||
- remove or rename a function or class;
|
have a default value;
|
||||||
|
- remove or rename a function or class, or change the template parameters of a public class template;
|
||||||
- change which exceptions a function throws, or the [exception ids](../home/exceptions.md);
|
- change which exceptions a function throws, or the [exception ids](../home/exceptions.md);
|
||||||
- change access specifiers or default arguments.
|
- change access specifiers, or change or remove existing default arguments. New default arguments may be added;
|
||||||
|
- change the JSON type that a valid input parses to, or the text that `dump()` produces for a valid value;
|
||||||
|
- accept input that was rejected before, or reject input that was accepted before;
|
||||||
|
- change the order in which the keys of an object are iterated. The default type sorts keys, and
|
||||||
|
[`ordered_json`](../api/ordered_json.md) keeps insertion order;
|
||||||
|
- change when iterators, pointers, or references are invalidated, or the state of a moved-from `basic_json`;
|
||||||
|
- add or remove implicit conversions from `basic_json`;
|
||||||
|
- change how `to_json` and `from_json` functions are found, or the behavior of
|
||||||
|
[`adl_serializer`](../api/adl_serializer/index.md);
|
||||||
|
- add pure virtual functions to the [`json_sax`](../api/json_sax/index.md) interface;
|
||||||
|
- remove, rename, renumber, or add enumerators of `value_t`;
|
||||||
|
- remove or rename a documented macro, CMake option, CMake target, or header, or change what a documented macro does.
|
||||||
|
|
||||||
Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are
|
Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are
|
||||||
documented in the [release notes](../home/releases.md).
|
documented in the [release notes](../home/releases.md).
|
||||||
@@ -51,13 +64,14 @@ The following are **not** part of the public API and may change in any release,
|
|||||||
|
|
||||||
- The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors
|
- The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors
|
||||||
apart.
|
apart.
|
||||||
- The ABI, including `sizeof(basic_json)` and the memory layout of its values. Recompile your code when you upgrade the
|
- The ABI, including `sizeof(basic_json)` and the memory layout of its values. The
|
||||||
library. The [versioned inline namespace](../features/namespace.md) turns mixing versions into a link error.
|
[versioned inline namespace](../features/namespace.md) turns mixing versions into a link error.
|
||||||
|
- The hash values returned by `std::hash` for `basic_json`. Numbers that compare equal still hash equally.
|
||||||
- Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the
|
- Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the
|
||||||
[API reference](../api/basic_json/index.md).
|
[API reference](../api/basic_json/index.md).
|
||||||
|
|
||||||
Changes that would break the public API are only added behind a macro whose default keeps the 3.x behavior, see
|
Breaking changes are only added behind a macro whose default keeps the 3.x behavior. See [Version 4.0](#version-40) and
|
||||||
[Version 4.0](#version-40).
|
the [macro overview](../features/macros.md).
|
||||||
|
|
||||||
## Version 4.0
|
## Version 4.0
|
||||||
|
|
||||||
|
|||||||
@@ -19,25 +19,9 @@ int main()
|
|||||||
<< j.contains("/array/1"_json_pointer) << '\n'
|
<< j.contains("/array/1"_json_pointer) << '\n'
|
||||||
<< j.contains("/array/-"_json_pointer) << '\n'
|
<< j.contains("/array/-"_json_pointer) << '\n'
|
||||||
<< j.contains("/array/4"_json_pointer) << '\n'
|
<< j.contains("/array/4"_json_pointer) << '\n'
|
||||||
<< j.contains("/baz"_json_pointer) << std::endl;
|
<< j.contains("/baz"_json_pointer) << '\n'
|
||||||
|
// an array index with a leading '0' is not found
|
||||||
try
|
<< j.contains("/array/01"_json_pointer) << '\n'
|
||||||
{
|
// an array index that is not a number is not found
|
||||||
// try to use an array index with leading '0'
|
<< j.contains("/array/one"_json_pointer) << std::endl;
|
||||||
j.contains("/array/01"_json_pointer);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
std::cout << e.what() << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// try to use an array index that is not a number
|
|
||||||
j.contains("/array/one"_json_pointer);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
std::cout << e.what() << '\n';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,3 +5,5 @@ true
|
|||||||
false
|
false
|
||||||
false
|
false
|
||||||
false
|
false
|
||||||
|
false
|
||||||
|
false
|
||||||
@@ -8,7 +8,7 @@ int main()
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// parsing input with a syntax error
|
// parsing input with a syntax error
|
||||||
json::parse("[1,2,3,]");
|
json j = json::parse("[1,2,3,]");
|
||||||
}
|
}
|
||||||
catch (const json::parse_error& e)
|
catch (const json::parse_error& e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -235,6 +235,17 @@ Build the unit tests against the [simdutf](https://github.com/simdutf/simdutf) U
|
|||||||
its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on
|
its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on
|
||||||
`JSON_BuildTests`.
|
`JSON_BuildTests`.
|
||||||
|
|
||||||
|
### `JSON_TestUnityBuild`
|
||||||
|
|
||||||
|
Build the unit tests in batches of several test files per executable to speed up compilation: the files of a batch are
|
||||||
|
compiled as one translation unit, so the template instantiations of the library are shared. Every test file still gets
|
||||||
|
its own CTest test (e.g., `test-foo_cpp11`), which runs only the test cases of that file from the shared executable.
|
||||||
|
This option is `ON` by default (and `OFF` with MinGW). Set it to `OFF` to get one executable per test file, e.g., when
|
||||||
|
debugging a single file. The number of files per executable is set by the cache variable `JSON_TestUnityBatchSize`
|
||||||
|
(default: `8`). Related test files (e.g., all binary formats) are grouped explicitly in `tests/CMakeLists.txt`; each
|
||||||
|
group is compiled as one executable per C++ standard regardless of the batch size, and only the remaining files are
|
||||||
|
batched by size. Depends on `JSON_BuildTests`.
|
||||||
|
|
||||||
### `JSON_Valgrind`
|
### `JSON_Valgrind`
|
||||||
|
|
||||||
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||||
|
|||||||
+35
-1
@@ -2,12 +2,14 @@ cmake_minimum_required(VERSION 3.13...4.0)
|
|||||||
|
|
||||||
option(JSON_Valgrind "Execute test suite with Valgrind." OFF)
|
option(JSON_Valgrind "Execute test suite with Valgrind." OFF)
|
||||||
option(JSON_FastTests "Skip expensive/slow tests." OFF)
|
option(JSON_FastTests "Skip expensive/slow tests." OFF)
|
||||||
|
option(JSON_TestUnityBuild "Build the unit tests in batches of several files per executable to speed up compilation." ON)
|
||||||
option(JSON_TestSimdutf "Build the unit tests against the simdutf UTF-8 validation backend." OFF)
|
option(JSON_TestSimdutf "Build the unit tests against the simdutf UTF-8 validation backend." OFF)
|
||||||
|
|
||||||
set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_TestSimdutf.")
|
set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_TestSimdutf.")
|
||||||
|
|
||||||
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
||||||
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
||||||
|
set(JSON_TestUnityBatchSize 8 CACHE STRING "The number of unit test files compiled into one executable with JSON_TestUnityBuild.")
|
||||||
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
||||||
|
|
||||||
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
||||||
@@ -20,6 +22,11 @@ endif()
|
|||||||
|
|
||||||
include(test)
|
include(test)
|
||||||
|
|
||||||
|
# MinGW's GNU ld hits section and relocation limits with the large merged translation units
|
||||||
|
if(MINGW)
|
||||||
|
set(JSON_TestUnityBuild OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# override standard support
|
# override standard support
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -138,7 +145,8 @@ json_test_set_test_options(test-disabled_exceptions
|
|||||||
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
||||||
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
||||||
|
|
||||||
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742).
|
# Regression test for GCC's false -Warray-bounds error with JSON_DIAGNOSTICS (#5742, fixed in #5585). It only
|
||||||
|
# showed up when optimizing, so build this test with -O3 and the warning as an error.
|
||||||
# -O3 makes the optimizer-driven warnings of the ci_test_gcc flag set (-Winline,
|
# -O3 makes the optimizer-driven warnings of the ci_test_gcc flag set (-Winline,
|
||||||
# -Wsuggest-attribute=...) fire on the library's inline functions; they are not
|
# -Wsuggest-attribute=...) fire on the library's inline functions; they are not
|
||||||
# what this test checks, so turn them off for it.
|
# what this test checks, so turn them off for it.
|
||||||
@@ -321,9 +329,28 @@ if(NOT "${JSON_TestShard}" STREQUAL "")
|
|||||||
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Explicit groups of test files (file names without "unit-" and ".cpp") for
|
||||||
|
# JSON_TestUnityBuild: all files of a group are compiled into one translation
|
||||||
|
# unit per C++ standard, so that related tests share their template
|
||||||
|
# instantiations (all binary formats instantiate the same readers and
|
||||||
|
# writers). Only add a group where this was measured to pay off: grouping
|
||||||
|
# files that share little just creates one slow translation unit. Files in no
|
||||||
|
# group are batched alphabetically by JSON_TestUnityBatchSize.
|
||||||
|
set(json_test_unity_groups binary)
|
||||||
|
set(json_test_unity_group_binary binary_formats binary_utf8_error_handler binary_writer_sinks bjdata bon8 bon8-cpp17 bson byte_container_with_subtype cbor msgpack msgpack-cpp17 ubjson)
|
||||||
|
|
||||||
|
if(JSON_TestUnityBuild)
|
||||||
|
message(STATUS "Building the unit tests in batches of ${JSON_TestUnityBatchSize} files (JSON_TestUnityBuild)")
|
||||||
|
json_test_add_unity_tests(FILES ${files} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
BATCH_SIZE ${JSON_TestUnityBatchSize}
|
||||||
|
GROUPS ${json_test_unity_groups}
|
||||||
|
VARIANT_FILES src/unit-comparison.cpp src/unit-comparison-cpp20.cpp src/unit-class_parser.cpp src/unit-diagnostic-positions.cpp
|
||||||
|
)
|
||||||
|
else()
|
||||||
foreach(file ${files})
|
foreach(file ${files})
|
||||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
||||||
# so its test targets must be built after generate_hedley_undef_checks.
|
# so its test targets must be built after generate_hedley_undef_checks.
|
||||||
@@ -349,6 +376,13 @@ json_test_add_test_for(src/unit-comparison.cpp
|
|||||||
NAME test-comparison_legacy
|
NAME test-comparison_legacy
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
json_test_set_test_options(test-comparison_legacy-cpp20
|
||||||
|
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-comparison-cpp20.cpp
|
||||||
|
NAME test-comparison_legacy-cpp20
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
|
||||||
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
||||||
json_test_set_test_options(test-class_parser_diagnostic_positions
|
json_test_set_test_options(test-class_parser_diagnostic_positions
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Unit tests
|
||||||
|
|
||||||
|
The unit tests are in [`src/unit-*.cpp`](src) and use [doctest](https://github.com/doctest/doctest). Each file becomes
|
||||||
|
one CTest test per C++ standard it is built for, named after the file: `src/unit-foo.cpp` becomes `test-foo_cpp11`.
|
||||||
|
|
||||||
|
## Build and run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build -DJSON_BuildTests=ON
|
||||||
|
cmake --build build -j 10
|
||||||
|
ctest --test-dir build -j 10
|
||||||
|
```
|
||||||
|
|
||||||
|
The [CMake options](../docs/mkdocs/docs/integration/cmake.md) starting with `JSON_Test` (and `JSON_FastTests`,
|
||||||
|
`JSON_Valgrind`) control the test build. The most relevant ones:
|
||||||
|
|
||||||
|
- `JSON_TestStandards`: build every test file for the given standards, e.g., `-DJSON_TestStandards=17`. By default,
|
||||||
|
a file is built for C++11 and for each standard whose `JSON_HAS_CPP_<N>` macro it mentions (see below).
|
||||||
|
- `JSON_TestUnityBuild`: compile several test files together (see below). Set it to `OFF` to get one executable per
|
||||||
|
test file, e.g., to debug a single test in a debugger.
|
||||||
|
- `JSON_TestShard`: build only a part of the test files, e.g., `-DJSON_TestShard=0/2`.
|
||||||
|
|
||||||
|
To run the tests of one file only, run its CTest test, e.g., `ctest --test-dir build -R test-foo_cpp11`.
|
||||||
|
|
||||||
|
## How test files are built
|
||||||
|
|
||||||
|
Two mechanisms keep the compile time down. Both are automatic, but they set a few rules for test files.
|
||||||
|
|
||||||
|
### C++ standards
|
||||||
|
|
||||||
|
A test file is built for a C++ standard beyond C++11 if its text contains `JSON_HAS_CPP_<N>`, for instance in
|
||||||
|
`#ifdef JSON_HAS_CPP_17`, or in a comment `// JSON_HAS_CPP_17` next to code that needs a macro only defined for that
|
||||||
|
standard, such as `JSON_HAS_FILESYSTEM`. Then the *whole file* is compiled again for that standard.
|
||||||
|
|
||||||
|
So that this does not happen for the large files, tests that need C++14, C++17, or C++20 go into a separate file
|
||||||
|
`src/unit-<name>-cpp<N>.cpp` (e.g., [`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp)), whose body
|
||||||
|
is wrapped in `#ifdef JSON_HAS_CPP_<N>`. The main file `src/unit-<name>.cpp` must not mention `JSON_HAS_CPP_<N>` at
|
||||||
|
all, not even in a comment, so it is built for C++11 only. The CI jobs `ci_test_*_cxx<N>` still build every test file
|
||||||
|
for every standard.
|
||||||
|
|
||||||
|
### Unity build
|
||||||
|
|
||||||
|
With `JSON_TestUnityBuild` (`ON` by default, `OFF` with MinGW), CMake compiles several test files as one translation
|
||||||
|
unit, so they share the template instantiations of the library. CMake generates `build/tests/unity/test-unity-*.cpp`,
|
||||||
|
which `#include` the test files of a batch. Every test file still gets its own CTest test, which runs the batch
|
||||||
|
executable with `--source-file=*unit-foo.cpp`, so only the test cases of that file run.
|
||||||
|
|
||||||
|
CMake decides from the macros a file defines *before* including `<nlohmann/json.hpp>`:
|
||||||
|
|
||||||
|
| Macros defined before the include | Built as |
|
||||||
|
|-------------------------------------------------------------------------------------------|-----------------------------------|
|
||||||
|
| none (or only `SKIP_TESTS_FOR_*` and similar macros derived from global definitions) | batch with the other plain files |
|
||||||
|
| only `JSON_TESTS_PRIVATE` | batch with the other such files |
|
||||||
|
| any library configuration macro (`JSON_DIAGNOSTICS`, `JSON_NO_IO`, `JSON_ASSERT`, ...) | its own executable |
|
||||||
|
|
||||||
|
Files with test-specific build options (`json_test_set_test_options(test-foo ...)` in
|
||||||
|
[`CMakeLists.txt`](CMakeLists.txt)) also get their own executable. The batchable files are split alphabetically into
|
||||||
|
batches of `JSON_TestUnityBatchSize` files, except for explicit groups of related files: `json_test_unity_group_binary`
|
||||||
|
compiles all binary-format tests together, because they share the binary readers and writers. Only add a group if it
|
||||||
|
measurably pays off; files that share little just make one slow translation unit.
|
||||||
|
|
||||||
|
## Rules for test files
|
||||||
|
|
||||||
|
Because the files of a batch share one translation unit, a test file must not affect the files that follow it:
|
||||||
|
|
||||||
|
1. **Give file-scope helpers file-specific names.** An anonymous namespace does not help, as the batch is one
|
||||||
|
translation unit. Use a name such as `my_allocator_2982`, or wrap the helpers in a namespace named after the file
|
||||||
|
(e.g., `namespace unit_comparison_detail`). Clashes show up as compile errors.
|
||||||
|
2. **Do not `#undef` library macros**, such as `JSON_HAS_CPP_17`, at the end of a file: this silently disables the tests
|
||||||
|
that depend on them in the later files of the batch. `#undef` macros you define yourself after including the library.
|
||||||
|
3. **Put version-dependent tests into `unit-<name>-cpp<N>.cpp`** (see [C++ standards](#c-standards)).
|
||||||
|
4. **Define test cases in the `.cpp` file itself.** The batch executable selects the tests of a file by its name, so a
|
||||||
|
`TEST_CASE` in a shared header is not run. The only exception is the test case in
|
||||||
|
[`src/make_test_data_available.hpp`](src/make_test_data_available.hpp), which CMake handles explicitly.
|
||||||
|
|
||||||
|
To check a new file for clashes with all others at once, build with `-DJSON_TestUnityBatchSize=1000`, which puts all
|
||||||
|
batchable files of a kind into one translation unit.
|
||||||
|
|
||||||
|
## Where to add tests
|
||||||
|
|
||||||
|
Tests are structured along the features of the library. Usually, an existing file is the right place:
|
||||||
|
|
||||||
|
- For a bug fix, add a section referencing the issue to [`src/unit-regression3.cpp`](src/unit-regression3.cpp), or to
|
||||||
|
[`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp) /
|
||||||
|
[`src/unit-regression3-cpp20.cpp`](src/unit-regression3-cpp20.cpp) if the test needs C++17 / C++20.
|
||||||
|
[`src/unit-regression2.cpp`](src/unit-regression2.cpp) holds older tests and should not grow further.
|
||||||
|
- When testing exceptions, use `CHECK_THROWS_WITH_AS`, which also checks the `what()` message.
|
||||||
|
|
||||||
|
A new file `src/unit-<name>.cpp` is picked up automatically when CMake runs again; no change to `CMakeLists.txt` is
|
||||||
|
needed.
|
||||||
|
|
||||||
|
See also [`fuzzing.md`](fuzzing.md) for fuzz testing.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-allocator.cpp (std::string_view key
|
||||||
|
// lookup with a failing allocator). It is kept in a separate translation unit so the (much
|
||||||
|
// larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every C++
|
||||||
|
// standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool next_construct_fails_cpp17 = false;
|
||||||
|
bool next_destroy_fails_cpp17 = false;
|
||||||
|
bool next_deallocate_fails_cpp17 = false;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct my_allocator_cpp17 : std::allocator<T>
|
||||||
|
{
|
||||||
|
using std::allocator<T>::allocator;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
void construct(T* p, Args&& ... args)
|
||||||
|
{
|
||||||
|
if (next_construct_fails_cpp17)
|
||||||
|
{
|
||||||
|
next_construct_fails_cpp17 = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* p, std::size_t n)
|
||||||
|
{
|
||||||
|
if (next_deallocate_fails_cpp17)
|
||||||
|
{
|
||||||
|
next_deallocate_fails_cpp17 = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::allocator<T>::deallocate(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(T* p)
|
||||||
|
{
|
||||||
|
if (next_destroy_fails_cpp17)
|
||||||
|
{
|
||||||
|
next_destroy_fails_cpp17 = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static_cast<void>(p); // fix MSVC's C4100 warning
|
||||||
|
p->~T();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = my_allocator_cpp17<U>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||||
|
// next_construct_fails_cpp17 set for the next allocation outside a check
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
TEST_CASE("a failed allocation leaves the value unchanged (C++17)")
|
||||||
|
{
|
||||||
|
// create JSON type using the throwing allocator
|
||||||
|
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp17>;
|
||||||
|
|
||||||
|
SECTION("turning a null value into an array or object")
|
||||||
|
{
|
||||||
|
my_json j;
|
||||||
|
|
||||||
|
next_construct_fails_cpp17 = true;
|
||||||
|
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
next_construct_fails_cpp17 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-allocator.cpp (creating a value from a
|
||||||
|
// std::ranges view with a failing allocator). It is kept in a separate translation unit so
|
||||||
|
// the (much larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every
|
||||||
|
// C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#if JSON_HAS_RANGES
|
||||||
|
#include <ranges>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool next_construct_fails_cpp20 = false;
|
||||||
|
bool next_destroy_fails_cpp20 = false;
|
||||||
|
bool next_deallocate_fails_cpp20 = false;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct my_allocator_cpp20 : std::allocator<T>
|
||||||
|
{
|
||||||
|
using std::allocator<T>::allocator;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
void construct(T* p, Args&& ... args)
|
||||||
|
{
|
||||||
|
if (next_construct_fails_cpp20)
|
||||||
|
{
|
||||||
|
next_construct_fails_cpp20 = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* p, std::size_t n)
|
||||||
|
{
|
||||||
|
if (next_deallocate_fails_cpp20)
|
||||||
|
{
|
||||||
|
next_deallocate_fails_cpp20 = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::allocator<T>::deallocate(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(T* p)
|
||||||
|
{
|
||||||
|
if (next_destroy_fails_cpp20)
|
||||||
|
{
|
||||||
|
next_destroy_fails_cpp20 = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static_cast<void>(p); // fix MSVC's C4100 warning
|
||||||
|
p->~T();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = my_allocator_cpp20<U>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||||
|
// next_construct_fails_cpp20 set for the next allocation outside a check
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
TEST_CASE("a failed allocation leaves the value unchanged (C++20)")
|
||||||
|
{
|
||||||
|
// create JSON type using the throwing allocator
|
||||||
|
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp20>;
|
||||||
|
|
||||||
|
// With iterator debugging, VS 2015's containers construct a proxy with the
|
||||||
|
// allocator in constructors that cannot report its failure, so a failing
|
||||||
|
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
|
||||||
|
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||||
|
SECTION("converting into an existing value")
|
||||||
|
{
|
||||||
|
// to_json replaces the value it is given; the old one must survive a
|
||||||
|
// failed creation of the new one
|
||||||
|
my_json j = "old";
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
|
const std::vector<int> numbers = {1, 2};
|
||||||
|
next_construct_fails_cpp20 = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
})), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
next_construct_fails_cpp20 = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -13,9 +13,6 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
#if JSON_HAS_RANGES
|
|
||||||
#include <ranges>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -684,12 +681,6 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
||||||
CHECK(j.is_null());
|
CHECK(j.is_null());
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
next_construct_fails = true;
|
|
||||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
|
||||||
CHECK(j.is_null());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
next_construct_fails = true;
|
next_construct_fails = true;
|
||||||
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
||||||
CHECK(j.is_null());
|
CHECK(j.is_null());
|
||||||
@@ -787,16 +778,6 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
||||||
CHECK(j == "old");
|
CHECK(j == "old");
|
||||||
|
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
|
||||||
const std::vector<int> numbers = {1, 2};
|
|
||||||
next_construct_fails = true;
|
|
||||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
})), std::bad_alloc&);
|
|
||||||
CHECK(j == "old");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
next_construct_fails = false;
|
next_construct_fails = false;
|
||||||
nlohmann::to_json(j, std::vector<int> {1, 2});
|
nlohmann::to_json(j, std::vector<int> {1, 2});
|
||||||
CHECK(j == my_json({1, 2}));
|
CHECK(j == my_json({1, 2}));
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-bon8.cpp (BON8 with std::byte
|
||||||
|
// containers). It is kept in a separate translation unit so the (much larger) unit-
|
||||||
|
// bon8.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
TEST_CASE("BON8 with std::byte")
|
||||||
|
{
|
||||||
|
SECTION("vector roundtrip")
|
||||||
|
{
|
||||||
|
const json original =
|
||||||
|
{
|
||||||
|
{"name", "test"},
|
||||||
|
{"value", 42},
|
||||||
|
{"array", {1, 2, 3}}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||||
|
std::vector<std::byte> bon8_data(temp.size());
|
||||||
|
for (size_t i = 0; i < temp.size(); ++i)
|
||||||
|
{
|
||||||
|
bon8_data[i] = std::byte(temp[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
json from_bytes;
|
||||||
|
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||||
|
CHECK(from_bytes == original);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("empty vector")
|
||||||
|
{
|
||||||
|
const std::vector<std::byte> empty_data;
|
||||||
|
CHECK_THROWS_WITH_AS([&]()
|
||||||
|
{
|
||||||
|
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(),
|
||||||
|
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||||
|
json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1052,41 +1052,3 @@ TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
TEST_CASE("BON8 with std::byte")
|
|
||||||
{
|
|
||||||
SECTION("vector roundtrip")
|
|
||||||
{
|
|
||||||
const json original =
|
|
||||||
{
|
|
||||||
{"name", "test"},
|
|
||||||
{"value", 42},
|
|
||||||
{"array", {1, 2, 3}}
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
|
||||||
std::vector<std::byte> bon8_data(temp.size());
|
|
||||||
for (size_t i = 0; i < temp.size(); ++i)
|
|
||||||
{
|
|
||||||
bon8_data[i] = std::byte(temp[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
json from_bytes;
|
|
||||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
|
||||||
CHECK(from_bytes == original);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("empty vector")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> empty_data;
|
|
||||||
CHECK_THROWS_WITH_AS([&]()
|
|
||||||
{
|
|
||||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
(),
|
|
||||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-comparison.cpp (tests of operator<=> and
|
||||||
|
// other three-way comparison specific behavior). It is kept in a separate translation unit
|
||||||
|
// so the (much larger) unit-comparison.cpp is built for C++11 only and not rebuilt for
|
||||||
|
// every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
#include <cmath>
|
||||||
|
#include <compare>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// this can be replaced with the doctest stl extension header in version 2.5
|
||||||
|
namespace doctest
|
||||||
|
{
|
||||||
|
template<> struct StringMaker<std::partial_ordering>
|
||||||
|
{
|
||||||
|
static String convert(const std::partial_ordering& order)
|
||||||
|
{
|
||||||
|
if (order == std::partial_ordering::less)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::less";
|
||||||
|
}
|
||||||
|
if (order == std::partial_ordering::equivalent)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::equivalent";
|
||||||
|
}
|
||||||
|
if (order == std::partial_ordering::greater)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::greater";
|
||||||
|
}
|
||||||
|
if (order == std::partial_ordering::unordered)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::unordered";
|
||||||
|
}
|
||||||
|
return "{?}";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace doctest
|
||||||
|
|
||||||
|
TEST_CASE("lexicographical comparison operators (C++20)")
|
||||||
|
{
|
||||||
|
constexpr auto f_ = false;
|
||||||
|
constexpr auto _t = true;
|
||||||
|
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||||
|
constexpr auto lt = std::partial_ordering::less;
|
||||||
|
constexpr auto gt = std::partial_ordering::greater;
|
||||||
|
constexpr auto eq = std::partial_ordering::equivalent;
|
||||||
|
constexpr auto un = std::partial_ordering::unordered;
|
||||||
|
|
||||||
|
INFO("using 3-way comparison");
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
INFO("using legacy comparison");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("types")
|
||||||
|
{
|
||||||
|
std::vector<json::value_t> j_types =
|
||||||
|
{
|
||||||
|
json::value_t::null,
|
||||||
|
json::value_t::boolean,
|
||||||
|
json::value_t::number_integer,
|
||||||
|
json::value_t::number_unsigned,
|
||||||
|
json::value_t::number_float,
|
||||||
|
json::value_t::object,
|
||||||
|
json::value_t::array,
|
||||||
|
json::value_t::string,
|
||||||
|
json::value_t::binary,
|
||||||
|
json::value_t::discarded
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::vector<bool>> expected_lt =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9
|
||||||
|
{f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0
|
||||||
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1
|
||||||
|
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2
|
||||||
|
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3
|
||||||
|
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4
|
||||||
|
{f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("comparison: less")
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < j_types.size(); ++i)
|
||||||
|
{
|
||||||
|
for (size_t j = 0; j < j_types.size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
// check precomputed values
|
||||||
|
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparison: 3-way")
|
||||||
|
{
|
||||||
|
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
||||||
|
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
||||||
|
REQUIRE(std::isnan(nan));
|
||||||
|
|
||||||
|
std::vector<std::vector<std::partial_ordering>> expected =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9
|
||||||
|
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
||||||
|
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
||||||
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
||||||
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
||||||
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
||||||
|
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
||||||
|
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
||||||
|
{un, un, un, un, un, un, un, un, un, un}, // 9
|
||||||
|
};
|
||||||
|
|
||||||
|
// check expected partial_ordering against expected boolean
|
||||||
|
REQUIRE(expected.size() == expected_lt.size());
|
||||||
|
for (size_t i = 0; i < expected.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||||
|
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check 3-way comparison against expected partial_ordering
|
||||||
|
REQUIRE(expected.size() == j_types.size());
|
||||||
|
for (size_t i = 0; i < j_types.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == j_types.size());
|
||||||
|
for (size_t j = 0; j < j_types.size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values")
|
||||||
|
{
|
||||||
|
json j_values =
|
||||||
|
{
|
||||||
|
nullptr, nullptr, // 0 1
|
||||||
|
-17, 42, // 2 3
|
||||||
|
8u, 13u, // 4 5
|
||||||
|
3.14159, 23.42, // 6 7
|
||||||
|
nan, nan, // 8 9
|
||||||
|
"foo", "bar", // 10 11
|
||||||
|
true, false, // 12 13
|
||||||
|
{1, 2, 3}, {"one", "two", "three"}, // 14 15
|
||||||
|
{{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17
|
||||||
|
json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19
|
||||||
|
json(json::value_t::discarded), json(json::value_t::discarded) // 20 21
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::vector<bool>> expected_eq =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||||
|
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
|
||||||
|
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
|
||||||
|
{f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
|
||||||
|
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
|
||||||
|
{f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
|
||||||
|
{f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
|
||||||
|
{f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::vector<bool>> expected_lt =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||||
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0
|
||||||
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1
|
||||||
|
{f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3
|
||||||
|
{f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4
|
||||||
|
{f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5
|
||||||
|
{f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6
|
||||||
|
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
|
||||||
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
|
||||||
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
||||||
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("signed/unsigned mixed comparison above INT64_MAX")
|
||||||
|
{
|
||||||
|
const json above_int64_max = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL;
|
||||||
|
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||||
|
const json negative_one = -1;
|
||||||
|
const json one = 1;
|
||||||
|
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||||
|
|
||||||
|
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("integer/float mixed comparison is exact")
|
||||||
|
{
|
||||||
|
// Widening the integer to a double loses precision past the
|
||||||
|
// mantissa, so 2^63-2 and 2^63-1 both used to compare equal to the
|
||||||
|
// double 2^63 while differing from each other. That makes equality
|
||||||
|
// intransitive and the ordering not a strict weak ordering.
|
||||||
|
const json below_two_63 = static_cast<std::int64_t>(9223372036854775806LL);
|
||||||
|
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||||
|
const json two_63 = 9223372036854775808.0;
|
||||||
|
|
||||||
|
// the same past the unsigned range
|
||||||
|
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||||
|
const json two_64 = 18446744073709551616.0;
|
||||||
|
|
||||||
|
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparison: 3-way")
|
||||||
|
{
|
||||||
|
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
||||||
|
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
||||||
|
REQUIRE(std::isnan(nan));
|
||||||
|
|
||||||
|
std::vector<std::vector<std::partial_ordering>> expected =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||||
|
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
||||||
|
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
||||||
|
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
||||||
|
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
||||||
|
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
||||||
|
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
||||||
|
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
||||||
|
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
||||||
|
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
||||||
|
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
||||||
|
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
||||||
|
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
||||||
|
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
||||||
|
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
||||||
|
};
|
||||||
|
|
||||||
|
// check expected partial_ordering against expected booleans
|
||||||
|
REQUIRE(expected.size() == expected_eq.size());
|
||||||
|
REQUIRE(expected.size() == expected_lt.size());
|
||||||
|
for (size_t i = 0; i < expected.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == expected_eq[i].size());
|
||||||
|
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||||
|
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
||||||
|
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||||
|
if (std::is_gt(expected[i][j]))
|
||||||
|
{
|
||||||
|
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that two values compare according to their expected ordering
|
||||||
|
REQUIRE(expected.size() == j_values.size());
|
||||||
|
for (size_t i = 0; i < j_values.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == j_values.size());
|
||||||
|
for (size_t j = 0; j < j_values.size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
||||||
|
{
|
||||||
|
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
||||||
|
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
||||||
|
// Verify that heterogeneous comparisons now work.
|
||||||
|
|
||||||
|
SECTION("string vs json")
|
||||||
|
{
|
||||||
|
std::string s = "string";
|
||||||
|
json j = "string";
|
||||||
|
CHECK(s == j);
|
||||||
|
CHECK(j == s);
|
||||||
|
CHECK_FALSE(s != j);
|
||||||
|
CHECK_FALSE(j != s);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("other heterogeneous types")
|
||||||
|
{
|
||||||
|
int i = 42;
|
||||||
|
json j = 42;
|
||||||
|
CHECK(i == j);
|
||||||
|
CHECK(j == i);
|
||||||
|
CHECK_FALSE(i != j);
|
||||||
|
CHECK_FALSE(j != i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||||
|
{
|
||||||
|
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||||
|
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||||
|
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||||
|
// other three operand orders.
|
||||||
|
const json discarded(json::value_t::discarded);
|
||||||
|
const json one = 1;
|
||||||
|
|
||||||
|
CHECK(discarded <= 1);
|
||||||
|
CHECK(discarded >= 1);
|
||||||
|
CHECK(one <= discarded);
|
||||||
|
CHECK(one >= discarded);
|
||||||
|
CHECK(1 <= discarded);
|
||||||
|
CHECK(1 >= discarded);
|
||||||
|
CHECK(1.5 <= discarded);
|
||||||
|
CHECK(1.5 >= discarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("containers are compared element by element (C++20)")
|
||||||
|
{
|
||||||
|
// Containers nested deeper than a bound are compared without the call
|
||||||
|
// stack, by code of their own; every relation is checked both at the top
|
||||||
|
// level and below that bound.
|
||||||
|
const auto deep = [](const json & j, const std::size_t depth)
|
||||||
|
{
|
||||||
|
json result = j;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
result = json::array({std::move(result)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||||
|
{
|
||||||
|
CAPTURE(depth)
|
||||||
|
|
||||||
|
// objects with different keys
|
||||||
|
{
|
||||||
|
const json a = deep({{"a", 1}}, depth);
|
||||||
|
const json b = deep({{"b", 1}}, depth);
|
||||||
|
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
}
|
||||||
|
|
||||||
|
// a container that is a prefix of the other one
|
||||||
|
{
|
||||||
|
// the one that runs out of elements first is the smaller one
|
||||||
|
const json shorter = deep({1}, depth);
|
||||||
|
const json longer = deep({1, 2}, depth);
|
||||||
|
|
||||||
|
const json smaller_object = deep({{"a", 1}}, depth);
|
||||||
|
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
||||||
|
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
}
|
||||||
|
|
||||||
|
// elements that cannot be ordered
|
||||||
|
{
|
||||||
|
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
const json lhs = deep({nan, 1}, depth);
|
||||||
|
const json rhs = deep({nan, 2}, depth);
|
||||||
|
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||||
|
// does, and operator< is derived from it
|
||||||
|
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK_FALSE(lhs < rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||||
|
{
|
||||||
|
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||||
|
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||||
|
// pair of binary values with the same bytes but a different subtype is
|
||||||
|
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||||
|
// that a NaN has. Within the nesting bound, an array compares itself
|
||||||
|
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||||
|
// undecided and lets the next element decide, same as
|
||||||
|
// std::lexicographical_compare_three_way does. Past the bound,
|
||||||
|
// compare_iteratively<true>() takes over and must classify the pair the
|
||||||
|
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||||
|
// from it - depends on how deeply the values are nested.
|
||||||
|
const json a = json::array({json::binary({1}, 1), 1});
|
||||||
|
const json b = json::array({json::binary({1}, 2), 2});
|
||||||
|
|
||||||
|
// the root inconsistency: unequal, yet <=>-equivalent
|
||||||
|
CHECK_FALSE(a[0] == b[0]);
|
||||||
|
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
|
||||||
|
const auto deep = [](const json & j, const std::size_t depth)
|
||||||
|
{
|
||||||
|
json result = j;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
result = json::array({std::move(result)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||||
|
// and must still agree with the levels that do
|
||||||
|
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||||
|
{
|
||||||
|
CAPTURE(depth)
|
||||||
|
const json x = deep(a, depth);
|
||||||
|
const json y = deep(b, depth);
|
||||||
|
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK(x < y);
|
||||||
|
CHECK(y > x);
|
||||||
|
CHECK_FALSE(y < x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
#endif
|
||||||
+11
-305
@@ -6,12 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-comparison-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -28,37 +27,6 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// this can be replaced with the doctest stl extension header in version 2.5
|
|
||||||
namespace doctest
|
|
||||||
{
|
|
||||||
template<> struct StringMaker<std::partial_ordering>
|
|
||||||
{
|
|
||||||
static String convert(const std::partial_ordering& order)
|
|
||||||
{
|
|
||||||
if (order == std::partial_ordering::less)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::less";
|
|
||||||
}
|
|
||||||
if (order == std::partial_ordering::equivalent)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::equivalent";
|
|
||||||
}
|
|
||||||
if (order == std::partial_ordering::greater)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::greater";
|
|
||||||
}
|
|
||||||
if (order == std::partial_ordering::unordered)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::unordered";
|
|
||||||
}
|
|
||||||
return "{?}";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace doctest
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// helper function to check std::less<json::value_t>
|
// helper function to check std::less<json::value_t>
|
||||||
@@ -75,16 +43,6 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
constexpr auto f_ = false;
|
constexpr auto f_ = false;
|
||||||
constexpr auto _t = true;
|
constexpr auto _t = true;
|
||||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
constexpr auto lt = std::partial_ordering::less;
|
|
||||||
constexpr auto gt = std::partial_ordering::greater;
|
|
||||||
constexpr auto eq = std::partial_ordering::equivalent;
|
|
||||||
constexpr auto un = std::partial_ordering::unordered;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
INFO("using 3-way comparison");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
INFO("using legacy comparison");
|
INFO("using legacy comparison");
|
||||||
@@ -135,62 +93,13 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
CAPTURE(i)
|
CAPTURE(i)
|
||||||
CAPTURE(j)
|
CAPTURE(j)
|
||||||
// check precomputed values
|
// check precomputed values
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
#if !JSON_HAS_THREE_WAY_COMPARISON
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
|
||||||
#else
|
|
||||||
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||||
#endif
|
#endif
|
||||||
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
SECTION("comparison: 3-way")
|
|
||||||
{
|
|
||||||
std::vector<std::vector<std::partial_ordering>> expected =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9
|
|
||||||
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
|
||||||
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
|
||||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
|
||||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
|
||||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
|
||||||
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
|
||||||
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
|
||||||
{un, un, un, un, un, un, un, un, un, un}, // 9
|
|
||||||
};
|
|
||||||
|
|
||||||
// check expected partial_ordering against expected boolean
|
|
||||||
REQUIRE(expected.size() == expected_lt.size());
|
|
||||||
for (size_t i = 0; i < expected.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
|
||||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check 3-way comparison against expected partial_ordering
|
|
||||||
REQUIRE(expected.size() == j_types.size());
|
|
||||||
for (size_t i = 0; i < j_types.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == j_types.size());
|
|
||||||
for (size_t j = 0; j < j_types.size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("values")
|
SECTION("values")
|
||||||
@@ -319,18 +228,6 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
CHECK_FALSE(above_int64_max <= max_int64);
|
CHECK_FALSE(above_int64_max <= max_int64);
|
||||||
CHECK(above_int64_max > max_int64);
|
CHECK(above_int64_max > max_int64);
|
||||||
CHECK(above_int64_max >= max_int64);
|
CHECK(above_int64_max >= max_int64);
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("integer/float mixed comparison is exact")
|
SECTION("integer/float mixed comparison is exact")
|
||||||
@@ -381,16 +278,6 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
CHECK_FALSE(json(1) < json(nan));
|
CHECK_FALSE(json(1) < json(nan));
|
||||||
CHECK_FALSE(json(nan) < json(1));
|
CHECK_FALSE(json(nan) < json(1));
|
||||||
CHECK_FALSE(json(1u) == json(nan));
|
CHECK_FALSE(json(1u) == json(nan));
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("compares unordered")
|
SECTION("compares unordered")
|
||||||
@@ -617,72 +504,6 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
SECTION("comparison: 3-way")
|
|
||||||
{
|
|
||||||
std::vector<std::vector<std::partial_ordering>> expected =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
||||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
|
||||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
|
||||||
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
|
||||||
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
|
||||||
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
|
||||||
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
|
||||||
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
|
||||||
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
|
||||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
|
||||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
|
||||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
|
||||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
|
||||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
|
||||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
|
||||||
};
|
|
||||||
|
|
||||||
// check expected partial_ordering against expected booleans
|
|
||||||
REQUIRE(expected.size() == expected_eq.size());
|
|
||||||
REQUIRE(expected.size() == expected_lt.size());
|
|
||||||
for (size_t i = 0; i < expected.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == expected_eq[i].size());
|
|
||||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
|
||||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
|
||||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
|
||||||
if (std::is_gt(expected[i][j]))
|
|
||||||
{
|
|
||||||
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that two values compare according to their expected ordering
|
|
||||||
REQUIRE(expected.size() == j_values.size());
|
|
||||||
for (size_t i = 0; i < j_values.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == j_values.size());
|
|
||||||
for (size_t j = 0; j < j_values.size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
@@ -721,60 +542,7 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
namespace unit_comparison_detail
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
|
||||||
{
|
|
||||||
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
|
||||||
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
|
||||||
// Verify that heterogeneous comparisons now work.
|
|
||||||
|
|
||||||
SECTION("string vs json")
|
|
||||||
{
|
|
||||||
std::string s = "string";
|
|
||||||
json j = "string";
|
|
||||||
CHECK(s == j);
|
|
||||||
CHECK(j == s);
|
|
||||||
CHECK_FALSE(s != j);
|
|
||||||
CHECK_FALSE(j != s);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("other heterogeneous types")
|
|
||||||
{
|
|
||||||
int i = 42;
|
|
||||||
json j = 42;
|
|
||||||
CHECK(i == j);
|
|
||||||
CHECK(j == i);
|
|
||||||
CHECK_FALSE(i != j);
|
|
||||||
CHECK_FALSE(j != i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
||||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
|
||||||
{
|
|
||||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
|
||||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
|
||||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
|
||||||
// other three operand orders.
|
|
||||||
const json discarded(json::value_t::discarded);
|
|
||||||
const json one = 1;
|
|
||||||
|
|
||||||
CHECK(discarded <= 1);
|
|
||||||
CHECK(discarded >= 1);
|
|
||||||
CHECK(one <= discarded);
|
|
||||||
CHECK(one >= discarded);
|
|
||||||
CHECK(1 <= discarded);
|
|
||||||
CHECK(1 >= discarded);
|
|
||||||
CHECK(1.5 <= discarded);
|
|
||||||
CHECK(1.5 >= discarded);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
{
|
||||||
// orders keys ascending or descending, as chosen when a map is created
|
// orders keys ascending or descending, as chosen when a map is created
|
||||||
template<class Key>
|
template<class Key>
|
||||||
@@ -906,7 +674,9 @@ struct case_insensitive_less
|
|||||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||||
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
||||||
using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
|
using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
|
||||||
} // namespace
|
} // namespace unit_comparison_detail
|
||||||
|
|
||||||
|
using namespace unit_comparison_detail; // NOLINT(google-build-using-namespace)
|
||||||
|
|
||||||
TEST_CASE("equality of objects whose entries have no fixed order")
|
TEST_CASE("equality of objects whose entries have no fixed order")
|
||||||
{
|
{
|
||||||
@@ -1049,12 +819,6 @@ TEST_CASE("containers are compared element by element")
|
|||||||
CHECK(a < b);
|
CHECK(a < b);
|
||||||
CHECK(b > a);
|
CHECK(b > a);
|
||||||
CHECK_FALSE(b < a);
|
CHECK_FALSE(b < a);
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// a container that is a prefix of the other one
|
// a container that is a prefix of the other one
|
||||||
@@ -1072,11 +836,6 @@ TEST_CASE("containers are compared element by element")
|
|||||||
CHECK(smaller_object < larger_object);
|
CHECK(smaller_object < larger_object);
|
||||||
CHECK(larger_object > smaller_object);
|
CHECK(larger_object > smaller_object);
|
||||||
CHECK_FALSE(smaller_object == larger_object);
|
CHECK_FALSE(smaller_object == larger_object);
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// elements that cannot be ordered
|
// elements that cannot be ordered
|
||||||
@@ -1087,13 +846,7 @@ TEST_CASE("containers are compared element by element")
|
|||||||
|
|
||||||
CHECK_FALSE(lhs == lhs);
|
CHECK_FALSE(lhs == lhs);
|
||||||
CHECK_FALSE(rhs < lhs);
|
CHECK_FALSE(rhs < lhs);
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
#if !JSON_HAS_THREE_WAY_COMPARISON
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
|
||||||
// does, and operator< is derived from it
|
|
||||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK_FALSE(lhs < rhs);
|
|
||||||
#else
|
|
||||||
// operator< skips a pair of elements that cannot be ordered, as
|
// operator< skips a pair of elements that cannot be ordered, as
|
||||||
// std::lexicographical_compare does, and the next pair decides
|
// std::lexicographical_compare does, and the next pair decides
|
||||||
CHECK(lhs < rhs);
|
CHECK(lhs < rhs);
|
||||||
@@ -1102,50 +855,3 @@ TEST_CASE("containers are compared element by element")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
|
||||||
{
|
|
||||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
|
||||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
|
||||||
// pair of binary values with the same bytes but a different subtype is
|
|
||||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
|
||||||
// that a NaN has. Within the nesting bound, an array compares itself
|
|
||||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
|
||||||
// undecided and lets the next element decide, same as
|
|
||||||
// std::lexicographical_compare_three_way does. Past the bound,
|
|
||||||
// compare_iteratively<true>() takes over and must classify the pair the
|
|
||||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
|
||||||
// from it - depends on how deeply the values are nested.
|
|
||||||
const json a = json::array({json::binary({1}, 1), 1});
|
|
||||||
const json b = json::array({json::binary({1}, 2), 2});
|
|
||||||
|
|
||||||
// the root inconsistency: unequal, yet <=>-equivalent
|
|
||||||
CHECK_FALSE(a[0] == b[0]);
|
|
||||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
|
|
||||||
const auto deep = [](const json & j, const std::size_t depth)
|
|
||||||
{
|
|
||||||
json result = j;
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
result = json::array({std::move(result)});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
|
||||||
// and must still agree with the levels that do
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
|
||||||
{
|
|
||||||
CAPTURE(depth)
|
|
||||||
const json x = deep(a, depth);
|
|
||||||
const json y = deep(b, depth);
|
|
||||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK(x < y);
|
|
||||||
CHECK(y > x);
|
|
||||||
CHECK_FALSE(y < x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-conversions1.cpp (conversions to and from
|
||||||
|
// std::string_view). It is kept in a separate translation unit so the (much larger) unit-
|
||||||
|
// conversions1.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
||||||
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||||
|
#define JSON_HAS_CPP_17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
#include <experimental/optional>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("value conversion (C++17)")
|
||||||
|
{
|
||||||
|
SECTION("get a string (explicit)")
|
||||||
|
{
|
||||||
|
const json::string_t s_reference{"Hello world"};
|
||||||
|
json j(s_reference);
|
||||||
|
|
||||||
|
SECTION("std::string_view")
|
||||||
|
{
|
||||||
|
std::string_view const s = j.get<std::string_view>();
|
||||||
|
CHECK(json(s) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("exception in case of a non-string type using string_view")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("get a string (explicit, get_to)")
|
||||||
|
{
|
||||||
|
const json::string_t s_reference{"Hello world"};
|
||||||
|
json j(s_reference);
|
||||||
|
|
||||||
|
SECTION("std::string_view")
|
||||||
|
{
|
||||||
|
std::string const s = "previous value";
|
||||||
|
std::string_view sv = s;
|
||||||
|
j.get_to(sv);
|
||||||
|
CHECK(json(sv) == j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||||
|
SECTION("get a string (implicit)")
|
||||||
|
{
|
||||||
|
const json::string_t s_reference{"Hello world"};
|
||||||
|
json j(s_reference);
|
||||||
|
|
||||||
|
SECTION("std::string_view")
|
||||||
|
{
|
||||||
|
std::string_view const s = j.get<std::string_view>();
|
||||||
|
CHECK(json(s) == j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-conversions1-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -31,25 +30,6 @@ using nlohmann::json;
|
|||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
|
||||||
#define JSON_HAS_CPP_17
|
|
||||||
#define JSON_HAS_CPP_14
|
|
||||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
|
||||||
#define JSON_HAS_CPP_14
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
#include <experimental/optional>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(JSON_HAS_CPP_17)
|
|
||||||
#include <string_view>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("value conversion")
|
TEST_CASE("value conversion")
|
||||||
{
|
{
|
||||||
SECTION("get an object (explicit)")
|
SECTION("get an object (explicit)")
|
||||||
@@ -519,13 +499,6 @@ TEST_CASE("value conversion")
|
|||||||
const std::string s = j.get<std::string>();
|
const std::string s = j.get<std::string>();
|
||||||
CHECK(json(s) == j);
|
CHECK(json(s) == j);
|
||||||
}
|
}
|
||||||
#if defined(JSON_HAS_CPP_17)
|
|
||||||
SECTION("std::string_view")
|
|
||||||
{
|
|
||||||
std::string_view const s = j.get<std::string_view>();
|
|
||||||
CHECK(json(s) == j);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("exception in case of a non-string type")
|
SECTION("exception in case of a non-string type")
|
||||||
{
|
{
|
||||||
@@ -551,26 +524,6 @@ TEST_CASE("value conversion")
|
|||||||
json(json::value_t::number_float).get<json::string_t>(),
|
json(json::value_t::number_float).get<json::string_t>(),
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(JSON_HAS_CPP_17)
|
|
||||||
SECTION("exception in case of a non-string type using string_view")
|
|
||||||
{
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get a string (explicit, get_to)")
|
SECTION("get a string (explicit, get_to)")
|
||||||
@@ -591,15 +544,6 @@ TEST_CASE("value conversion")
|
|||||||
j.get_to(s);
|
j.get_to(s);
|
||||||
CHECK(json(s) == j);
|
CHECK(json(s) == j);
|
||||||
}
|
}
|
||||||
#if defined(JSON_HAS_CPP_17)
|
|
||||||
SECTION("std::string_view")
|
|
||||||
{
|
|
||||||
std::string const s = "previous value";
|
|
||||||
std::string_view sv = s;
|
|
||||||
j.get_to(sv);
|
|
||||||
CHECK(json(sv) == j);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get null (explicit)")
|
SECTION("get null (explicit)")
|
||||||
@@ -638,14 +582,6 @@ TEST_CASE("value conversion")
|
|||||||
CHECK(json(s) == j);
|
CHECK(json(s) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(JSON_HAS_CPP_17)
|
|
||||||
SECTION("std::string_view")
|
|
||||||
{
|
|
||||||
std::string_view const s = j.get<std::string_view>();
|
|
||||||
CHECK(json(s) == j);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("std::string")
|
SECTION("std::string")
|
||||||
{
|
{
|
||||||
const std::string s = j;
|
const std::string s = j;
|
||||||
@@ -1256,11 +1192,4 @@ TEST_CASE("value conversion")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#undef JSON_HAS_CPP_17
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_14
|
|
||||||
#undef JSON_HAS_CPP_14
|
|
||||||
#endif
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-conversions2.cpp (conversions of
|
||||||
|
// std::filesystem::path, std::u8string and std::optional). It is kept in a separate
|
||||||
|
// translation unit so the (much larger) unit-conversions2.cpp is built for C++11 only and
|
||||||
|
// not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
||||||
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||||
|
#define JSON_HAS_CPP_17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <map>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
#include <experimental/optional>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||||
|
TEST_CASE("std::filesystem::path")
|
||||||
|
{
|
||||||
|
SECTION("ascii")
|
||||||
|
{
|
||||||
|
json const j_string = "Path";
|
||||||
|
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||||
|
json const j_path = p;
|
||||||
|
|
||||||
|
CHECK(j_path.template get<std::string>() ==
|
||||||
|
j_string.template get<std::string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("utf-8")
|
||||||
|
{
|
||||||
|
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||||
|
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||||
|
json const j_path = p;
|
||||||
|
|
||||||
|
CHECK(j_path.template get<std::string>() ==
|
||||||
|
j_string.template get<std::string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||||
|
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||||
|
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||||
|
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||||
|
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||||
|
#if defined(__cpp_lib_char8_t)
|
||||||
|
TEST_CASE("std::u8string")
|
||||||
|
{
|
||||||
|
SECTION("ascii")
|
||||||
|
{
|
||||||
|
const std::u8string s = u8"Path";
|
||||||
|
json const j = s;
|
||||||
|
|
||||||
|
CHECK(j.template get<std::string>() == "Path");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("utf-8")
|
||||||
|
{
|
||||||
|
// use \u universal-character-names (rather than raw \x byte escapes
|
||||||
|
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||||
|
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||||
|
// literal as a nonstandard extension (warning C5321), which some of
|
||||||
|
// our CI configs promote to an error; \u is portable and produces
|
||||||
|
// the exact same encoded bytes without depending on the source
|
||||||
|
// file's encoding
|
||||||
|
const std::u8string s = u8"P\u011B\u0161ina";
|
||||||
|
json const j = s;
|
||||||
|
|
||||||
|
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a type whose to_json reports an error by throwing, used below to check that
|
||||||
|
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||||
|
// converting its contained value instead of calling std::terminate (#5642)
|
||||||
|
struct throwing_to_json_type {};
|
||||||
|
|
||||||
|
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("std::optional")
|
||||||
|
{
|
||||||
|
SECTION("null")
|
||||||
|
{
|
||||||
|
const json j_null;
|
||||||
|
const std::optional<std::string> opt_null;
|
||||||
|
|
||||||
|
CHECK(json(opt_null) == j_null);
|
||||||
|
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||||
|
|
||||||
|
// Constructing std::optional<T> directly from JSON null throws because
|
||||||
|
// std::optional's own converting constructor is chosen over basic_json's
|
||||||
|
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||||
|
// constructible from T, and T is constructible from basic_json via the
|
||||||
|
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||||
|
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||||
|
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||||
|
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||||
|
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||||
|
|
||||||
|
// Assignment goes through the same overload resolution as direct
|
||||||
|
// construction, so it throws for the same reason. This relies on
|
||||||
|
// basic_json's implicit conversion operator, so it only applies
|
||||||
|
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||||
|
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||||
|
std::optional<std::string> opt_assign;
|
||||||
|
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||||
|
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||||
|
std::optional<std::string> opt_get_to = "placeholder";
|
||||||
|
j_null.get_to(opt_get_to);
|
||||||
|
CHECK(opt_get_to == std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("string")
|
||||||
|
{
|
||||||
|
json j_string = "string";
|
||||||
|
std::optional<std::string> opt_string = "string";
|
||||||
|
|
||||||
|
CHECK(json(opt_string) == j_string);
|
||||||
|
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||||
|
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||||
|
// @infer-ignore USE_AFTER_DELETE
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("bool")
|
||||||
|
{
|
||||||
|
json j_bool = true;
|
||||||
|
std::optional<bool> opt_bool = true;
|
||||||
|
|
||||||
|
CHECK(json(opt_bool) == j_bool);
|
||||||
|
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("number")
|
||||||
|
{
|
||||||
|
json j_number = 1;
|
||||||
|
std::optional<int> opt_int = 1;
|
||||||
|
|
||||||
|
CHECK(json(opt_int) == j_number);
|
||||||
|
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("array")
|
||||||
|
{
|
||||||
|
json j_array = {1, 2, nullptr};
|
||||||
|
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||||
|
|
||||||
|
CHECK(json(opt_array) == j_array);
|
||||||
|
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object")
|
||||||
|
{
|
||||||
|
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||||
|
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||||
|
|
||||||
|
CHECK(json(opt_object) == j_object);
|
||||||
|
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||||
|
{
|
||||||
|
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||||
|
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||||
|
// to_json that reports an error, or std::bad_alloc for T =
|
||||||
|
// std::string/vector/json). Before the fix, this called
|
||||||
|
// std::terminate() instead of letting the exception propagate.
|
||||||
|
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||||
|
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||||
|
|
||||||
|
// the conversion is noexcept exactly when converting the contained value is
|
||||||
|
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
||||||
|
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
||||||
|
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||||
|
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
|
#endif
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-conversions2-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -36,25 +35,6 @@ using nlohmann::json;
|
|||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
|
||||||
#define JSON_HAS_CPP_17
|
|
||||||
#define JSON_HAS_CPP_14
|
|
||||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
|
||||||
#define JSON_HAS_CPP_14
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
#include <experimental/optional>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(JSON_HAS_CPP_17)
|
|
||||||
#include <string_view>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("value conversion")
|
TEST_CASE("value conversion")
|
||||||
{
|
{
|
||||||
SECTION("get a binary value (explicit)")
|
SECTION("get a binary value (explicit)")
|
||||||
@@ -680,194 +660,4 @@ TEST_CASE("Strict JSON to enum mapping")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
|
||||||
TEST_CASE("std::filesystem::path")
|
|
||||||
{
|
|
||||||
SECTION("ascii")
|
|
||||||
{
|
|
||||||
json const j_string = "Path";
|
|
||||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
|
||||||
json const j_path = p;
|
|
||||||
|
|
||||||
CHECK(j_path.template get<std::string>() ==
|
|
||||||
j_string.template get<std::string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("utf-8")
|
|
||||||
{
|
|
||||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
|
||||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
|
||||||
json const j_path = p;
|
|
||||||
|
|
||||||
CHECK(j_path.template get<std::string>() ==
|
|
||||||
j_string.template get<std::string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
|
||||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
|
||||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
|
||||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
|
||||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
|
||||||
#if defined(__cpp_lib_char8_t)
|
|
||||||
TEST_CASE("std::u8string")
|
|
||||||
{
|
|
||||||
SECTION("ascii")
|
|
||||||
{
|
|
||||||
const std::u8string s = u8"Path";
|
|
||||||
json const j = s;
|
|
||||||
|
|
||||||
CHECK(j.template get<std::string>() == "Path");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("utf-8")
|
|
||||||
{
|
|
||||||
// use \u universal-character-names (rather than raw \x byte escapes
|
|
||||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
|
||||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
|
||||||
// literal as a nonstandard extension (warning C5321), which some of
|
|
||||||
// our CI configs promote to an error; \u is portable and produces
|
|
||||||
// the exact same encoded bytes without depending on the source
|
|
||||||
// file's encoding
|
|
||||||
const std::u8string s = u8"P\u011B\u0161ina";
|
|
||||||
json const j = s;
|
|
||||||
|
|
||||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a type whose to_json reports an error by throwing, used below to check that
|
|
||||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
|
||||||
// converting its contained value instead of calling std::terminate (#5642)
|
|
||||||
struct throwing_to_json_type {};
|
|
||||||
|
|
||||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("std::optional")
|
|
||||||
{
|
|
||||||
SECTION("null")
|
|
||||||
{
|
|
||||||
const json j_null;
|
|
||||||
const std::optional<std::string> opt_null;
|
|
||||||
|
|
||||||
CHECK(json(opt_null) == j_null);
|
|
||||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
|
||||||
|
|
||||||
// Constructing std::optional<T> directly from JSON null throws because
|
|
||||||
// std::optional's own converting constructor is chosen over basic_json's
|
|
||||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
|
||||||
// constructible from T, and T is constructible from basic_json via the
|
|
||||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
|
||||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
|
||||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
|
||||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
|
||||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
|
||||||
|
|
||||||
// Assignment goes through the same overload resolution as direct
|
|
||||||
// construction, so it throws for the same reason. This relies on
|
|
||||||
// basic_json's implicit conversion operator, so it only applies
|
|
||||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
|
||||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
|
||||||
std::optional<std::string> opt_assign;
|
|
||||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
|
||||||
std::optional<std::string> opt_get_to = "placeholder";
|
|
||||||
j_null.get_to(opt_get_to);
|
|
||||||
CHECK(opt_get_to == std::nullopt);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("string")
|
|
||||||
{
|
|
||||||
json j_string = "string";
|
|
||||||
std::optional<std::string> opt_string = "string";
|
|
||||||
|
|
||||||
CHECK(json(opt_string) == j_string);
|
|
||||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
|
||||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
|
||||||
// @infer-ignore USE_AFTER_DELETE
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("bool")
|
|
||||||
{
|
|
||||||
json j_bool = true;
|
|
||||||
std::optional<bool> opt_bool = true;
|
|
||||||
|
|
||||||
CHECK(json(opt_bool) == j_bool);
|
|
||||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("number")
|
|
||||||
{
|
|
||||||
json j_number = 1;
|
|
||||||
std::optional<int> opt_int = 1;
|
|
||||||
|
|
||||||
CHECK(json(opt_int) == j_number);
|
|
||||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("array")
|
|
||||||
{
|
|
||||||
json j_array = {1, 2, nullptr};
|
|
||||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
|
||||||
|
|
||||||
CHECK(json(opt_array) == j_array);
|
|
||||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("object")
|
|
||||||
{
|
|
||||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
|
||||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
|
||||||
|
|
||||||
CHECK(json(opt_object) == j_object);
|
|
||||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
|
||||||
{
|
|
||||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
|
||||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
|
||||||
// to_json that reports an error, or std::bad_alloc for T =
|
|
||||||
// std::string/vector/json). Before the fix, this called
|
|
||||||
// std::terminate() instead of letting the exception propagate.
|
|
||||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
|
||||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
|
||||||
|
|
||||||
// the conversion is noexcept exactly when converting the contained value is
|
|
||||||
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
|
||||||
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
|
||||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
|
||||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#undef JSON_HAS_CPP_17
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_14
|
|
||||||
#undef JSON_HAS_CPP_14
|
|
||||||
#endif
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-custom-binary-type.cpp (binary types
|
||||||
|
// whose value type is std::byte). It is kept in a separate translation unit so the (much
|
||||||
|
// larger) unit-custom-binary-type.cpp is built for C++11 only and not rebuilt for every
|
||||||
|
// C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// a BinaryType whose value type is not an integer type at all
|
||||||
|
using byte_binary_json = nlohmann::basic_json <
|
||||||
|
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||||
|
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
|
||||||
|
|
||||||
|
TEST_CASE("binary type whose value type is not std::uint8_t (C++17)")
|
||||||
|
{
|
||||||
|
SECTION("dumping a value type that is not an integer")
|
||||||
|
{
|
||||||
|
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||||
|
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||||
|
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||||
|
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("hashing and the binary formats")
|
||||||
|
{
|
||||||
|
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||||
|
const auto j = byte_binary_json::binary(bytes);
|
||||||
|
|
||||||
|
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
||||||
|
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
||||||
|
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
||||||
|
|
||||||
|
// UBJSON has no binary type, so binary values are written as an array
|
||||||
|
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||||
|
// the same holds for BON8
|
||||||
|
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -17,10 +17,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <cstddef>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -30,13 +26,6 @@ using char_binary_json = nlohmann::basic_json <
|
|||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||||
double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >;
|
double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >;
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
// a BinaryType whose value type is not an integer type at all
|
|
||||||
using byte_binary_json = nlohmann::basic_json <
|
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("binary type whose value type is not std::uint8_t")
|
TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||||
@@ -63,29 +52,4 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
|||||||
{
|
{
|
||||||
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
SECTION("dumping a value type that is not an integer")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
|
||||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
|
||||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
|
||||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("hashing and the binary formats")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
|
||||||
const auto j = byte_binary_json::binary(bytes);
|
|
||||||
|
|
||||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
|
||||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
|
||||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
|
||||||
|
|
||||||
// UBJSON has no binary type, so binary values are written as an array
|
|
||||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
|
||||||
// the same holds for BON8
|
|
||||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-deserialization.cpp (char8_t support:
|
||||||
|
// _json with char8_t literals and char8_t input). It is kept in a separate translation
|
||||||
|
// unit so the (much larger) unit-deserialization.cpp is built for C++11 only and not
|
||||||
|
// rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||||
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// copy of SaxEventLogger from unit-deserialization.cpp (renamed to avoid clashes in unity builds)
|
||||||
|
struct SaxEventLoggerChar8 : public nlohmann::json_sax<json>
|
||||||
|
{
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
events.emplace_back("null()");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool val) override
|
||||||
|
{
|
||||||
|
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(json::number_integer_t val) override
|
||||||
|
{
|
||||||
|
events.push_back("number_integer(" + std::to_string(val) + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(json::number_unsigned_t val) override
|
||||||
|
{
|
||||||
|
events.push_back("number_unsigned(" + std::to_string(val) + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(json::number_float_t /*val*/, const std::string& s) override
|
||||||
|
{
|
||||||
|
events.push_back("number_float(" + s + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(std::string& val) override
|
||||||
|
{
|
||||||
|
events.push_back("string(" + val + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(json::binary_t& val) override
|
||||||
|
{
|
||||||
|
std::string binary_contents = "binary(";
|
||||||
|
std::string comma_space;
|
||||||
|
for (auto b : val)
|
||||||
|
{
|
||||||
|
binary_contents.append(comma_space);
|
||||||
|
binary_contents.append(std::to_string(static_cast<int>(b)));
|
||||||
|
comma_space = ", ";
|
||||||
|
}
|
||||||
|
binary_contents.append(")");
|
||||||
|
events.push_back(binary_contents);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t elements) override
|
||||||
|
{
|
||||||
|
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||||
|
{
|
||||||
|
events.emplace_back("start_object()");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(std::string& val) override
|
||||||
|
{
|
||||||
|
events.push_back("key(" + val + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
events.emplace_back("end_object()");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t elements) override
|
||||||
|
{
|
||||||
|
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||||
|
{
|
||||||
|
events.emplace_back("start_array()");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
events.emplace_back("end_array()");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||||
|
{
|
||||||
|
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII) (C++20)", T, char8_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
|
{
|
||||||
|
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
||||||
|
CHECK(json::parse(v) == json(true));
|
||||||
|
CHECK(json::accept(v));
|
||||||
|
|
||||||
|
SaxEventLoggerChar8 l;
|
||||||
|
CHECK(json::sax_parse(v, &l));
|
||||||
|
CHECK(l.events.size() == 1);
|
||||||
|
CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("deserialization (C++20)")
|
||||||
|
{
|
||||||
|
#if defined(__cpp_char8_t)
|
||||||
|
SECTION("Using _json with char8_t literals #4945")
|
||||||
|
{
|
||||||
|
// Regular narrow string literal
|
||||||
|
const auto j1 = R"({"key": "value", "num": 42})"_json;
|
||||||
|
CHECK(j1["key"] == "value");
|
||||||
|
CHECK(j1["num"] == 42);
|
||||||
|
|
||||||
|
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
|
||||||
|
// \U escape rather than a raw multibyte character so this does not
|
||||||
|
// depend on the compiler's source-file encoding (e.g., MSVC without
|
||||||
|
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
||||||
|
// string literals as UTF-8 - compare against a \x-escaped expectation
|
||||||
|
// for the same reason)
|
||||||
|
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
||||||
|
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
||||||
|
CHECK(j2["msg"] == "hello");
|
||||||
|
|
||||||
|
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
|
||||||
|
CHECK(j3["key"] == "value");
|
||||||
|
CHECK(j3["num"] == 42);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1306,44 +1306,9 @@ TEST_CASE("deserialization")
|
|||||||
CHECK(line == "foo");
|
CHECK(line == "foo");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build with C++20
|
|
||||||
// JSON_HAS_CPP_20
|
|
||||||
#if defined(__cpp_char8_t)
|
|
||||||
SECTION("Using _json with char8_t literals #4945")
|
|
||||||
{
|
|
||||||
// Regular narrow string literal
|
|
||||||
const auto j1 = R"({"key": "value", "num": 42})"_json;
|
|
||||||
CHECK(j1["key"] == "value");
|
|
||||||
CHECK(j1["num"] == 42);
|
|
||||||
|
|
||||||
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
|
|
||||||
// \U escape rather than a raw multibyte character so this does not
|
|
||||||
// depend on the compiler's source-file encoding (e.g., MSVC without
|
|
||||||
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
|
||||||
// string literals as UTF-8 - compare against a \x-escaped expectation
|
|
||||||
// for the same reason)
|
|
||||||
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
|
||||||
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
|
||||||
CHECK(j2["msg"] == "hello");
|
|
||||||
|
|
||||||
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
|
|
||||||
CHECK(j3["key"] == "value");
|
|
||||||
CHECK(j3["num"] == 42);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// select the types to test - char8_t is only available since C++20 if and only
|
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, char, wchar_t, char16_t, char32_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
// if __cpp_char8_t is defined.
|
|
||||||
#define TYPE_LIST(...) __VA_ARGS__
|
|
||||||
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
|
||||||
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t, char8_t)
|
|
||||||
#else
|
|
||||||
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, ASCII_TYPES) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
|
||||||
{
|
{
|
||||||
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
||||||
CHECK(json::parse(v) == json(true));
|
CHECK(json::parse(v) == json(true));
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -27,6 +27,9 @@ using nlohmann::json;
|
|||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
// the types live in a namespace, because the file may be compiled together with other test files
|
||||||
|
namespace enum_keyed_maps_default
|
||||||
|
{
|
||||||
enum class cards {kreuz, pik, herz, karo};
|
enum class cards {kreuz, pik, herz, karo};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
||||||
@@ -65,6 +68,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
|||||||
{strict_cards::herz, "herz"},
|
{strict_cards::herz, "herz"},
|
||||||
{strict_cards::karo, "karo"}
|
{strict_cards::karo, "karo"}
|
||||||
})
|
})
|
||||||
|
} // namespace enum_keyed_maps_default
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -82,6 +86,15 @@ struct enum_hash
|
|||||||
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
|
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
|
||||||
TEST_CASE("maps with enum keys")
|
TEST_CASE("maps with enum keys")
|
||||||
{
|
{
|
||||||
|
// block-scope using-declarations take precedence over same-named types of other files in the same translation unit
|
||||||
|
using enum_keyed_maps_default::cards;
|
||||||
|
using enum_keyed_maps_default::strict_cards;
|
||||||
|
using enum_keyed_maps_default::TaskState;
|
||||||
|
using enum_keyed_maps_default::TS_COMPLETED;
|
||||||
|
using enum_keyed_maps_default::TS_INVALID;
|
||||||
|
using enum_keyed_maps_default::TS_RUNNING;
|
||||||
|
using enum_keyed_maps_default::TS_STOPPED;
|
||||||
|
|
||||||
using task_map = std::map<TaskState, std::string>;
|
using task_map = std::map<TaskState, std::string>;
|
||||||
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
|
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
|
||||||
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
|
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. The regression below only
|
||||||
// The regression below only showed on C++17, so build this file for every
|
// showed on C++17, and the whole file is just this explicit instantiation, so it
|
||||||
// standard like the other regression tests:
|
// cannot be split: build this file for every standard like the other regression tests:
|
||||||
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove)
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-iterators2.cpp (iterators and
|
||||||
|
// std::ranges). It is kept in a separate translation unit so the (much larger) unit-
|
||||||
|
// iterators2.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#include <iterator>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGES
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ranges>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("iterators 2 (C++20)")
|
||||||
|
{
|
||||||
|
#if JSON_HAS_RANGES
|
||||||
|
SECTION("ranges")
|
||||||
|
{
|
||||||
|
SECTION("concepts")
|
||||||
|
{
|
||||||
|
using nlohmann::detail::iteration_proxy_value;
|
||||||
|
CHECK(std::bidirectional_iterator<json::iterator>);
|
||||||
|
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
||||||
|
CHECK(std::ranges::bidirectional_range<json>);
|
||||||
|
|
||||||
|
using nlohmann::detail::iteration_proxy;
|
||||||
|
using items_type = decltype(std::declval<json&>().items());
|
||||||
|
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
||||||
|
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
||||||
|
CHECK(std::ranges::input_range<items_type>);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("algorithms")
|
||||||
|
{
|
||||||
|
SECTION("copy")
|
||||||
|
{
|
||||||
|
json j{"foo", "bar"};
|
||||||
|
auto j_copied = json::array();
|
||||||
|
|
||||||
|
std::ranges::copy(j, std::back_inserter(j_copied));
|
||||||
|
|
||||||
|
CHECK(j == j_copied);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("find_if")
|
||||||
|
{
|
||||||
|
json j{1, 3, 2, 4};
|
||||||
|
auto j_even = json::array();
|
||||||
|
|
||||||
|
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||||
|
auto it = std::ranges::find_if(j, [](int v) noexcept
|
||||||
|
{
|
||||||
|
return (v % 2) == 0;
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
||||||
|
{
|
||||||
|
int v;
|
||||||
|
j.get_to(v);
|
||||||
|
return (v % 2) == 0;
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CHECK(*it == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("views")
|
||||||
|
{
|
||||||
|
SECTION("reverse")
|
||||||
|
{
|
||||||
|
json j{1, 2, 3, 4, 5};
|
||||||
|
json j_expected{5, 4, 3, 2, 1};
|
||||||
|
|
||||||
|
auto reversed = j | std::views::reverse;
|
||||||
|
CHECK(reversed == j_expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("transform")
|
||||||
|
{
|
||||||
|
json j
|
||||||
|
{
|
||||||
|
{ "a_key", "a_value"},
|
||||||
|
{ "b_key", "b_value"},
|
||||||
|
{ "c_key", "c_value"},
|
||||||
|
};
|
||||||
|
json j_expected{"a_key", "b_key", "c_key"};
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(fuchsia-trailing-return)
|
||||||
|
auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
|
||||||
|
{
|
||||||
|
return item.key();
|
||||||
|
});
|
||||||
|
auto j_transformed = json::array();
|
||||||
|
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
||||||
|
|
||||||
|
CHECK(j_transformed == j_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -6,23 +6,17 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-iterators2-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#if JSON_HAS_RANGES
|
|
||||||
#include <algorithm>
|
|
||||||
#include <ranges>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("iterators 2")
|
TEST_CASE("iterators 2")
|
||||||
{
|
{
|
||||||
SECTION("iterator comparisons")
|
SECTION("iterator comparisons")
|
||||||
@@ -872,94 +866,4 @@ TEST_CASE("iterators 2")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_HAS_RANGES
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
SECTION("ranges")
|
|
||||||
{
|
|
||||||
SECTION("concepts")
|
|
||||||
{
|
|
||||||
using nlohmann::detail::iteration_proxy_value;
|
|
||||||
CHECK(std::bidirectional_iterator<json::iterator>);
|
|
||||||
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
|
||||||
|
|
||||||
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
|
||||||
CHECK(std::ranges::bidirectional_range<json>);
|
|
||||||
|
|
||||||
using nlohmann::detail::iteration_proxy;
|
|
||||||
using items_type = decltype(std::declval<json&>().items());
|
|
||||||
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
|
||||||
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
|
||||||
CHECK(std::ranges::input_range<items_type>);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("algorithms")
|
|
||||||
{
|
|
||||||
SECTION("copy")
|
|
||||||
{
|
|
||||||
json j{"foo", "bar"};
|
|
||||||
auto j_copied = json::array();
|
|
||||||
|
|
||||||
std::ranges::copy(j, std::back_inserter(j_copied));
|
|
||||||
|
|
||||||
CHECK(j == j_copied);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("find_if")
|
|
||||||
{
|
|
||||||
json j{1, 3, 2, 4};
|
|
||||||
auto j_even = json::array();
|
|
||||||
|
|
||||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
|
||||||
auto it = std::ranges::find_if(j, [](int v) noexcept
|
|
||||||
{
|
|
||||||
return (v % 2) == 0;
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
|
||||||
{
|
|
||||||
int v;
|
|
||||||
j.get_to(v);
|
|
||||||
return (v % 2) == 0;
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CHECK(*it == 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("views")
|
|
||||||
{
|
|
||||||
SECTION("reverse")
|
|
||||||
{
|
|
||||||
json j{1, 2, 3, 4, 5};
|
|
||||||
json j_expected{5, 4, 3, 2, 1};
|
|
||||||
|
|
||||||
auto reversed = j | std::views::reverse;
|
|
||||||
CHECK(reversed == j_expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("transform")
|
|
||||||
{
|
|
||||||
json j
|
|
||||||
{
|
|
||||||
{ "a_key", "a_value"},
|
|
||||||
{ "b_key", "b_value"},
|
|
||||||
{ "c_key", "c_value"},
|
|
||||||
};
|
|
||||||
json j_expected{"a_key", "b_key", "c_key"};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(fuchsia-trailing-return)
|
|
||||||
auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
|
|
||||||
{
|
|
||||||
return item.key();
|
|
||||||
});
|
|
||||||
auto j_transformed = json::array();
|
|
||||||
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
|
||||||
|
|
||||||
CHECK(j_transformed == j_expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-json_pointer.cpp (JSON pointer comparison
|
||||||
|
// with C++20 rewritten candidates and operator<=>, and char8_t literals). It is kept in a
|
||||||
|
// separate translation unit so the (much larger) unit-json_pointer.cpp is built for C++11
|
||||||
|
// only and not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
||||||
|
// line *before* including json.hpp, since the library #undefs it once the header
|
||||||
|
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
||||||
|
// tests of deprecated functions are skipped if these functions are deleted
|
||||||
|
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
||||||
|
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||||
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#include <compare>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
TEST_CASE("JSON pointers (C++20)")
|
||||||
|
{
|
||||||
|
SECTION("equality comparison")
|
||||||
|
{
|
||||||
|
std::string ptr_string{"/foo/bar"};
|
||||||
|
auto ptr1 = json::json_pointer(ptr_string);
|
||||||
|
auto ptr2 = json::json_pointer(ptr_string);
|
||||||
|
|
||||||
|
CHECK(ptr1 == ptr2);
|
||||||
|
|
||||||
|
CHECK_FALSE(ptr1 != ptr2);
|
||||||
|
|
||||||
|
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||||
|
const char* ptr_cpstring = "/foo/bar";
|
||||||
|
const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
|
||||||
|
|
||||||
|
CHECK(ptr1 == "/foo/bar");
|
||||||
|
CHECK(ptr1 == ptr_cpstring);
|
||||||
|
CHECK(ptr1 == ptr_castring);
|
||||||
|
CHECK(ptr1 == ptr_string);
|
||||||
|
|
||||||
|
CHECK("/foo/bar" == ptr1);
|
||||||
|
CHECK(ptr_cpstring == ptr1);
|
||||||
|
CHECK(ptr_castring == ptr1);
|
||||||
|
CHECK(ptr_string == ptr1);
|
||||||
|
|
||||||
|
CHECK_FALSE(ptr1 != "/foo/bar");
|
||||||
|
CHECK_FALSE(ptr1 != ptr_cpstring);
|
||||||
|
CHECK_FALSE(ptr1 != ptr_castring);
|
||||||
|
CHECK_FALSE(ptr1 != ptr_string);
|
||||||
|
|
||||||
|
CHECK_FALSE("/foo/bar" != ptr1);
|
||||||
|
CHECK_FALSE(ptr_cpstring != ptr1);
|
||||||
|
CHECK_FALSE(ptr_castring != ptr1);
|
||||||
|
CHECK_FALSE(ptr_string != ptr1);
|
||||||
|
|
||||||
|
SECTION("exceptions")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_WITH_AS(ptr1 == "foo",
|
||||||
|
"[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS("foo" == ptr1,
|
||||||
|
"[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(ptr1 == "/~~",
|
||||||
|
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS("/~~" == ptr1,
|
||||||
|
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("less-than comparison")
|
||||||
|
{
|
||||||
|
auto ptr1 = json::json_pointer("/foo/a");
|
||||||
|
auto ptr2 = json::json_pointer("/foo/b");
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
|
||||||
|
CHECK(ptr2 > ptr1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("backwards compatibility and mixing")
|
||||||
|
{
|
||||||
|
json j = R"(
|
||||||
|
{
|
||||||
|
"foo": ["bar", "baz"]
|
||||||
|
}
|
||||||
|
)"_json;
|
||||||
|
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
using json_ptr_str = nlohmann::json_pointer<std::string>;
|
||||||
|
using json_ptr_j = nlohmann::json_pointer<json>;
|
||||||
|
using json_ptr_oj = nlohmann::json_pointer<ordered_json>;
|
||||||
|
|
||||||
|
std::string const ptr_string{"/foo/0"};
|
||||||
|
json_ptr_str ptr{ptr_string};
|
||||||
|
json_ptr_j ptr_j{ptr_string};
|
||||||
|
json_ptr_oj ptr_oj{ptr_string};
|
||||||
|
|
||||||
|
SECTION("equality comparison")
|
||||||
|
{
|
||||||
|
CHECK(ptr == ptr_j);
|
||||||
|
CHECK(ptr == ptr_oj);
|
||||||
|
CHECK(ptr_j == ptr);
|
||||||
|
CHECK(ptr_j == ptr_oj);
|
||||||
|
CHECK(ptr_oj == ptr_j);
|
||||||
|
CHECK(ptr_oj == ptr);
|
||||||
|
|
||||||
|
CHECK_FALSE(ptr != ptr_j);
|
||||||
|
CHECK_FALSE(ptr != ptr_oj);
|
||||||
|
CHECK_FALSE(ptr_j != ptr);
|
||||||
|
CHECK_FALSE(ptr_j != ptr_oj);
|
||||||
|
CHECK_FALSE(ptr_oj != ptr_j);
|
||||||
|
CHECK_FALSE(ptr_oj != ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cpp_char8_t)
|
||||||
|
SECTION("Using _json_pointer with char8_t literals #4945")
|
||||||
|
{
|
||||||
|
const json j = R"({"a": {"b": {"c": 123}}})"_json;
|
||||||
|
const auto p1 = "/a/b/c"_json_pointer;
|
||||||
|
CHECK(j[p1] == 123);
|
||||||
|
|
||||||
|
const auto p2 = u8"/a/b/c"_json_pointer;
|
||||||
|
CHECK(j[p2] == 123);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -752,14 +752,13 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK(ptr.to_string() == "/object/~1");
|
CHECK(ptr.to_string() == "/object/~1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
|
||||||
SECTION("equality comparison")
|
SECTION("equality comparison")
|
||||||
{
|
{
|
||||||
std::string ptr_string{"/foo/bar"};
|
std::string ptr_string{"/foo/bar"};
|
||||||
auto ptr1 = json::json_pointer(ptr_string);
|
auto ptr1 = json::json_pointer(ptr_string);
|
||||||
auto ptr2 = json::json_pointer(ptr_string);
|
auto ptr2 = json::json_pointer(ptr_string);
|
||||||
|
|
||||||
// build with C++20 to test rewritten candidates
|
|
||||||
// JSON_HAS_CPP_20
|
|
||||||
|
|
||||||
CHECK(ptr1 == ptr2);
|
CHECK(ptr1 == ptr2);
|
||||||
|
|
||||||
@@ -802,6 +801,7 @@ TEST_CASE("JSON pointers")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("less-than comparison")
|
SECTION("less-than comparison")
|
||||||
{
|
{
|
||||||
@@ -811,12 +811,6 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK(ptr1 < ptr2);
|
CHECK(ptr1 < ptr2);
|
||||||
CHECK_FALSE(ptr2 < ptr1);
|
CHECK_FALSE(ptr2 < ptr1);
|
||||||
|
|
||||||
// build with C++20
|
|
||||||
// JSON_HAS_CPP_20
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
|
|
||||||
CHECK(ptr2 > ptr1);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("usable as map key")
|
SECTION("usable as map key")
|
||||||
@@ -872,10 +866,9 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK_FALSE(ptr != ptr_j);
|
CHECK_FALSE(ptr != ptr_j);
|
||||||
CHECK_FALSE(ptr != ptr_oj);
|
CHECK_FALSE(ptr != ptr_oj);
|
||||||
|
|
||||||
|
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
|
||||||
SECTION("equality comparison")
|
SECTION("equality comparison")
|
||||||
{
|
{
|
||||||
// build with C++20 to test rewritten candidates
|
|
||||||
// JSON_HAS_CPP_20
|
|
||||||
|
|
||||||
CHECK(ptr == ptr_j);
|
CHECK(ptr == ptr_j);
|
||||||
CHECK(ptr == ptr_oj);
|
CHECK(ptr == ptr_oj);
|
||||||
@@ -891,6 +884,7 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK_FALSE(ptr_oj != ptr_j);
|
CHECK_FALSE(ptr_oj != ptr_j);
|
||||||
CHECK_FALSE(ptr_oj != ptr);
|
CHECK_FALSE(ptr_oj != ptr);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("value(json_pointer, default) with ordered_json #5664")
|
SECTION("value(json_pointer, default) with ordered_json #5664")
|
||||||
@@ -912,19 +906,6 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK(j.value(ptr_missing, 42) == 42);
|
CHECK(j.value(ptr_missing, 42) == 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build with C++20
|
|
||||||
// JSON_HAS_CPP_20
|
|
||||||
#if defined(__cpp_char8_t)
|
|
||||||
SECTION("Using _json_pointer with char8_t literals #4945")
|
|
||||||
{
|
|
||||||
const json j = R"({"a": {"b": {"c": 123}}})"_json;
|
|
||||||
const auto p1 = "/a/b/c"_json_pointer;
|
|
||||||
CHECK(j[p1] == 123);
|
|
||||||
|
|
||||||
const auto p2 = u8"/a/b/c"_json_pointer;
|
|
||||||
CHECK(j[p2] == 123);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ void reference_merge_patch(json& target, const json& patch)
|
|||||||
|
|
||||||
// objects nested `depth` levels deep under the key "a", with members that
|
// objects nested `depth` levels deep under the key "a", with members that
|
||||||
// differ by `variant` on the way down
|
// differ by `variant` on the way down
|
||||||
std::string nested_objects(const std::size_t depth, const int variant)
|
std::string nested_objects_merge_patch(const std::size_t depth, const int variant)
|
||||||
{
|
{
|
||||||
std::string text;
|
std::string text;
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
@@ -309,9 +309,9 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
|||||||
for (int variant = 0; variant < 3; ++variant)
|
for (int variant = 0; variant < 3; ++variant)
|
||||||
{
|
{
|
||||||
CAPTURE(variant)
|
CAPTURE(variant)
|
||||||
const json patch = json::parse(nested_objects(depth, variant));
|
const json patch = json::parse(nested_objects_merge_patch(depth, variant));
|
||||||
|
|
||||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
json result = json::parse(nested_objects_merge_patch(depth, (variant + 1) % 3));
|
||||||
json expected = result;
|
json expected = result;
|
||||||
result.merge_patch(patch);
|
result.merge_patch(patch);
|
||||||
reference_merge_patch(expected, patch);
|
reference_merge_patch(expected, patch);
|
||||||
@@ -332,8 +332,8 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
|||||||
// applying a patch used to recurse once per nesting level. The result
|
// applying a patch used to recurse once per nesting level. The result
|
||||||
// is only walked, never copied or compared, since those recurse too.
|
// is only walked, never copied or compared, since those recurse too.
|
||||||
const std::size_t depth = 100000;
|
const std::size_t depth = 100000;
|
||||||
json target = json::parse(nested_objects(depth, 0));
|
json target = json::parse(nested_objects_merge_patch(depth, 0));
|
||||||
target.merge_patch(json::parse(nested_objects(depth, 1)));
|
target.merge_patch(json::parse(nested_objects_merge_patch(depth, 1)));
|
||||||
|
|
||||||
const json* p = ⌖
|
const json* p = ⌖
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
@@ -404,7 +404,7 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(depth)
|
CAPTURE(depth)
|
||||||
json j = json::parse(nested_objects(depth, 0));
|
json j = json::parse(nested_objects_merge_patch(depth, 0));
|
||||||
const json expected = j;
|
const json expected = j;
|
||||||
j.merge_patch(j);
|
j.merge_patch(j);
|
||||||
CHECK(j == expected);
|
CHECK(j == expected);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ void reference_update(json& target, const json& source)
|
|||||||
|
|
||||||
// objects nested `depth` levels deep under the key "a", with members that
|
// objects nested `depth` levels deep under the key "a", with members that
|
||||||
// differ by `variant` on the way down
|
// differ by `variant` on the way down
|
||||||
std::string nested_objects(const std::size_t depth, const int variant)
|
std::string nested_objects_update(const std::size_t depth, const int variant)
|
||||||
{
|
{
|
||||||
std::string text;
|
std::string text;
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
@@ -1131,8 +1131,8 @@ TEST_CASE("update() on deeply nested values")
|
|||||||
for (int variant = 0; variant < 3; ++variant)
|
for (int variant = 0; variant < 3; ++variant)
|
||||||
{
|
{
|
||||||
CAPTURE(variant)
|
CAPTURE(variant)
|
||||||
const json source = json::parse(nested_objects(depth, variant));
|
const json source = json::parse(nested_objects_update(depth, variant));
|
||||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
json result = json::parse(nested_objects_update(depth, (variant + 1) % 3));
|
||||||
json expected = result;
|
json expected = result;
|
||||||
result.update(source, true);
|
result.update(source, true);
|
||||||
reference_update(expected, source);
|
reference_update(expected, source);
|
||||||
@@ -1146,8 +1146,8 @@ TEST_CASE("update() on deeply nested values")
|
|||||||
// merging used to recurse once per nesting level. The result is only
|
// merging used to recurse once per nesting level. The result is only
|
||||||
// walked, never copied or compared, since those recurse too.
|
// walked, never copied or compared, since those recurse too.
|
||||||
const std::size_t depth = 100000;
|
const std::size_t depth = 100000;
|
||||||
json target = json::parse(nested_objects(depth, 0));
|
json target = json::parse(nested_objects_update(depth, 0));
|
||||||
target.update(json::parse(nested_objects(depth, 1)), true);
|
target.update(json::parse(nested_objects_update(depth, 1)), true);
|
||||||
|
|
||||||
const json* p = ⌖
|
const json* p = ⌖
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
@@ -1223,7 +1223,7 @@ TEST_CASE("update() with an argument that aliases *this (#5641)")
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(depth)
|
CAPTURE(depth)
|
||||||
json j = json::parse(nested_objects(depth, 0));
|
json j = json::parse(nested_objects_update(depth, 0));
|
||||||
const json expected = j;
|
const json expected = j;
|
||||||
j.update(j, true);
|
j.update(j, true);
|
||||||
CHECK(j == expected);
|
CHECK(j == expected);
|
||||||
|
|||||||
+10
-10
@@ -2285,7 +2285,7 @@ TEST_CASE("MessagePack Size above uint32 for object")
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
||||||
struct huge_string : std::string
|
struct msgpack_huge_string : std::string
|
||||||
{
|
{
|
||||||
using std::string::string;
|
using std::string::string;
|
||||||
|
|
||||||
@@ -2295,10 +2295,10 @@ struct huge_string : std::string
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_string_json = nlohmann::basic_json <
|
using msgpack_huge_string_json = nlohmann::basic_json <
|
||||||
std::map,
|
std::map,
|
||||||
std::vector,
|
std::vector,
|
||||||
huge_string,
|
msgpack_huge_string,
|
||||||
bool,
|
bool,
|
||||||
std::int64_t,
|
std::int64_t,
|
||||||
std::uint64_t,
|
std::uint64_t,
|
||||||
@@ -2310,16 +2310,16 @@ using huge_string_json = nlohmann::basic_json <
|
|||||||
|
|
||||||
TEST_CASE("MessagePack Size above uint32 for string")
|
TEST_CASE("MessagePack Size above uint32 for string")
|
||||||
{
|
{
|
||||||
const huge_string_json j = "hello";
|
const msgpack_huge_string_json j = "hello";
|
||||||
|
|
||||||
CHECK_THROWS_WITH_AS(
|
CHECK_THROWS_WITH_AS(
|
||||||
huge_string_json::to_msgpack(j),
|
msgpack_huge_string_json::to_msgpack(j),
|
||||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||||
json::out_of_range&);
|
json::out_of_range&);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct huge_binary : std::vector<std::uint8_t>
|
struct msgpack_huge_binary : std::vector<std::uint8_t>
|
||||||
{
|
{
|
||||||
using std::vector<std::uint8_t>::vector;
|
using std::vector<std::uint8_t>::vector;
|
||||||
|
|
||||||
@@ -2329,7 +2329,7 @@ struct huge_binary : std::vector<std::uint8_t>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_binary_json = nlohmann::basic_json <
|
using msgpack_huge_binary_json = nlohmann::basic_json <
|
||||||
std::map,
|
std::map,
|
||||||
std::vector,
|
std::vector,
|
||||||
std::string,
|
std::string,
|
||||||
@@ -2339,19 +2339,19 @@ using huge_binary_json = nlohmann::basic_json <
|
|||||||
double,
|
double,
|
||||||
std::allocator,
|
std::allocator,
|
||||||
nlohmann::adl_serializer,
|
nlohmann::adl_serializer,
|
||||||
huge_binary,
|
msgpack_huge_binary,
|
||||||
void >;
|
void >;
|
||||||
|
|
||||||
TEST_CASE("MessagePack Size above uint32 for binary")
|
TEST_CASE("MessagePack Size above uint32 for binary")
|
||||||
{
|
{
|
||||||
|
|
||||||
huge_binary_json j = huge_binary_json::binary(huge_binary{});
|
msgpack_huge_binary_json j = msgpack_huge_binary_json::binary(msgpack_huge_binary{});
|
||||||
|
|
||||||
j.get_binary().push_back(0x01);
|
j.get_binary().push_back(0x01);
|
||||||
j.get_binary().push_back(0x02);
|
j.get_binary().push_back(0x02);
|
||||||
|
|
||||||
CHECK_THROWS_WITH_AS(
|
CHECK_THROWS_WITH_AS(
|
||||||
huge_binary_json::to_msgpack(j),
|
msgpack_huge_binary_json::to_msgpack(j),
|
||||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||||
json::out_of_range&);
|
json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ using nlohmann::ordered_json;
|
|||||||
// this type, which is presumably why the gap was never noticed.
|
// this type, which is presumably why the gap was never noticed.
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace unit_ordered_json2_detail
|
||||||
|
{
|
||||||
class alt_string;
|
class alt_string;
|
||||||
bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage)
|
bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage)
|
||||||
void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-internal-linkage)
|
void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-internal-linkage)
|
||||||
@@ -217,7 +219,7 @@ void int_to_string(alt_string& target, std::size_t value)
|
|||||||
target = std::to_string(value).c_str();
|
target = std::to_string(value).c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
using alt_json = nlohmann::basic_json <
|
using ordered_json2_alt_json = nlohmann::basic_json <
|
||||||
std::map,
|
std::map,
|
||||||
std::vector,
|
std::vector,
|
||||||
alt_string,
|
alt_string,
|
||||||
@@ -232,6 +234,9 @@ bool operator<(const char* op1, const alt_string& op2) noexcept
|
|||||||
{
|
{
|
||||||
return op1 < op2.str_impl;
|
return op1 < op2.str_impl;
|
||||||
}
|
}
|
||||||
|
} // namespace unit_ordered_json2_detail
|
||||||
|
|
||||||
|
using unit_ordered_json2_detail::ordered_json2_alt_json;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -262,11 +267,11 @@ ordered_json make_rich_ordered_json()
|
|||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
|
||||||
alt_json make_rich_alt_json()
|
ordered_json2_alt_json make_rich_alt_json()
|
||||||
{
|
{
|
||||||
alt_json j;
|
ordered_json2_alt_json j;
|
||||||
j["zebra"] = 1;
|
j["zebra"] = 1;
|
||||||
j["apple"] = alt_json::array({1, 2, 3});
|
j["apple"] = ordered_json2_alt_json::array({1, 2, 3});
|
||||||
j["mango"]["z_nested"] = true;
|
j["mango"]["z_nested"] = true;
|
||||||
j["mango"]["a_nested"] = nullptr;
|
j["mango"]["a_nested"] = nullptr;
|
||||||
j["banana"] = "some text";
|
j["banana"] = "some text";
|
||||||
@@ -339,47 +344,47 @@ TEST_CASE("ordered_json across binary formats")
|
|||||||
|
|
||||||
TEST_CASE("alt_json (custom string_t) across binary formats")
|
TEST_CASE("alt_json (custom string_t) across binary formats")
|
||||||
{
|
{
|
||||||
const alt_json original = make_rich_alt_json();
|
const ordered_json2_alt_json original = make_rich_alt_json();
|
||||||
|
|
||||||
SECTION("CBOR")
|
SECTION("CBOR")
|
||||||
{
|
{
|
||||||
const auto bytes = alt_json::to_cbor(original);
|
const auto bytes = ordered_json2_alt_json::to_cbor(original);
|
||||||
const auto restored = alt_json::from_cbor(bytes);
|
const auto restored = ordered_json2_alt_json::from_cbor(bytes);
|
||||||
CHECK(restored == original);
|
CHECK(restored == original);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("MessagePack")
|
SECTION("MessagePack")
|
||||||
{
|
{
|
||||||
const auto bytes = alt_json::to_msgpack(original);
|
const auto bytes = ordered_json2_alt_json::to_msgpack(original);
|
||||||
const auto restored = alt_json::from_msgpack(bytes);
|
const auto restored = ordered_json2_alt_json::from_msgpack(bytes);
|
||||||
CHECK(restored == original);
|
CHECK(restored == original);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("UBJSON")
|
SECTION("UBJSON")
|
||||||
{
|
{
|
||||||
const auto bytes = alt_json::to_ubjson(original);
|
const auto bytes = ordered_json2_alt_json::to_ubjson(original);
|
||||||
const auto restored = alt_json::from_ubjson(bytes);
|
const auto restored = ordered_json2_alt_json::from_ubjson(bytes);
|
||||||
CHECK(restored == original);
|
CHECK(restored == original);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("BON8")
|
SECTION("BON8")
|
||||||
{
|
{
|
||||||
const auto bytes = alt_json::to_bon8(original);
|
const auto bytes = ordered_json2_alt_json::to_bon8(original);
|
||||||
const auto restored = alt_json::from_bon8(bytes);
|
const auto restored = ordered_json2_alt_json::from_bon8(bytes);
|
||||||
CHECK(restored == original);
|
CHECK(restored == original);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("BSON")
|
SECTION("BSON")
|
||||||
{
|
{
|
||||||
const auto bytes = alt_json::to_bson(original);
|
const auto bytes = ordered_json2_alt_json::to_bson(original);
|
||||||
const auto restored = alt_json::from_bson(bytes);
|
const auto restored = ordered_json2_alt_json::from_bson(bytes);
|
||||||
CHECK(restored == original);
|
CHECK(restored == original);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("BJData")
|
SECTION("BJData")
|
||||||
{
|
{
|
||||||
const auto bytes = alt_json::to_bjdata(original);
|
const auto bytes = ordered_json2_alt_json::to_bjdata(original);
|
||||||
const auto restored = alt_json::from_bjdata(bytes);
|
const auto restored = ordered_json2_alt_json::from_bjdata(bytes);
|
||||||
CHECK(restored == original);
|
CHECK(restored == original);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-ordered_map.cpp (ordered_map::find with
|
||||||
|
// std::string_view keys). It is kept in a separate translation unit so the (much larger)
|
||||||
|
// unit-ordered_map.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::ordered_map;
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
TEST_CASE("ordered_map (C++17)")
|
||||||
|
{
|
||||||
|
SECTION("find")
|
||||||
|
{
|
||||||
|
ordered_map<std::string, std::string> om;
|
||||||
|
om["eins"] = "one";
|
||||||
|
om["zwei"] = "two";
|
||||||
|
om["drei"] = "three";
|
||||||
|
const auto com = om;
|
||||||
|
|
||||||
|
const std::string eins("eins");
|
||||||
|
const std::string vier("vier");
|
||||||
|
|
||||||
|
CHECK(om.find(std::string_view("eins")) == om.begin());
|
||||||
|
CHECK(com.find(std::string_view("eins")) == com.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -360,11 +360,6 @@ TEST_CASE("ordered_map")
|
|||||||
CHECK(com.find("vier") == com.end());
|
CHECK(com.find("vier") == com.end());
|
||||||
CHECK(com.find(std::string("vier")) == com.end());
|
CHECK(com.find(std::string("vier")) == com.end());
|
||||||
CHECK(com.find(vier) == com.end());
|
CHECK(com.find(vier) == com.end());
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
CHECK(om.find(std::string_view("eins")) == om.begin());
|
|
||||||
CHECK(com.find(std::string_view("eins")) == com.begin());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("insert")
|
SECTION("insert")
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-regression2.cpp (std::variant, std::any
|
||||||
|
// and std::optional regression tests). It is kept in a separate translation unit so the
|
||||||
|
// (much larger) unit-regression2.cpp is built for C++11 only and not rebuilt for every C++
|
||||||
|
// standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||||
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
#include <experimental/optional>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// for #4804
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||||
|
|
||||||
|
TEST_CASE("regression tests 2 (C++17)")
|
||||||
|
{
|
||||||
|
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
|
||||||
|
{
|
||||||
|
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
|
||||||
|
{
|
||||||
|
// std::variant<json> must not be retrievable via get<>(), because otherwise the
|
||||||
|
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
|
||||||
|
// converting constructor, routing a number through the string from_json overload
|
||||||
|
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
|
||||||
|
"std::variant<json> must not be retrievable via get<>()");
|
||||||
|
|
||||||
|
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
|
||||||
|
#if !(defined(__clang__) && __clang_major__ < 7)
|
||||||
|
// push_back, not emplace_back: #5066 needs the implicit conversion
|
||||||
|
// from json to the vector's value type
|
||||||
|
std::vector<std::variant<json>> v;
|
||||||
|
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
|
||||||
|
CHECK(std::get<0>(v[0]) == 1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-regression2.cpp (std::span and ranges
|
||||||
|
// regression tests). It is kept in a separate translation unit so the (much larger) unit-
|
||||||
|
// regression2.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||||
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if __has_include(<span>)
|
||||||
|
#include <span>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// for #4440
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
#if JSON_HAS_RANGES == 1
|
||||||
|
#include <ranges>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("regression tests 2 (C++20)")
|
||||||
|
{
|
||||||
|
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
|
||||||
|
// classic Intel ICC reports <span> as includable but cannot actually compile
|
||||||
|
// std::span/std::as_bytes usage below
|
||||||
|
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
|
||||||
|
SECTION("issue #2546 - parsing containers of std::byte")
|
||||||
|
{
|
||||||
|
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
// exclude the trailing '\0' that string-literal initialization adds to
|
||||||
|
// DATA: std::span(DATA) would span the full array extent (including
|
||||||
|
// that NUL), which is only silently accepted as end-of-input by default
|
||||||
|
// and would fail under JSON_STRICT_NUL_HANDLING
|
||||||
|
const auto s = std::as_bytes(std::span(DATA, sizeof(DATA) - 1));
|
||||||
|
const json j = json::parse(s);
|
||||||
|
CHECK(j.dump() == "\"Hello, world!\"");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-regression2-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -49,30 +48,6 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
|
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <any>
|
|
||||||
#include <variant>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
#include <experimental/optional>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #4804
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
#if __has_include(<span>)
|
|
||||||
#include <span>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// for #4825 - explicitly instantiating basic_json must compile; this
|
// for #4825 - explicitly instantiating basic_json must compile; this
|
||||||
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
||||||
@@ -81,13 +56,6 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
template class nlohmann::basic_json<>;
|
template class nlohmann::basic_json<>;
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #4440
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
#if JSON_HAS_RANGES == 1
|
|
||||||
#include <ranges>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
@@ -285,18 +253,18 @@ struct adl_serializer<NonDefaultConstructible>
|
|||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
class my_allocator : public std::allocator<T>
|
class my_allocator_2982 : public std::allocator<T>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using std::allocator<T>::allocator;
|
using std::allocator<T>::allocator;
|
||||||
|
|
||||||
my_allocator() = default;
|
my_allocator_2982() = default;
|
||||||
template<class U> my_allocator(const my_allocator<U>& /*unused*/) { }
|
template<class U> my_allocator_2982(const my_allocator_2982<U>& /*unused*/) { }
|
||||||
|
|
||||||
template <class U>
|
template <class U>
|
||||||
struct rebind
|
struct rebind
|
||||||
{
|
{
|
||||||
using other = my_allocator<U>;
|
using other = my_allocator_2982<U>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -468,13 +436,6 @@ TEST_CASE("regression tests 2")
|
|||||||
CHECK(diffs.size() == 1); // Note the change here, was 2
|
CHECK(diffs.size() == 1); // Note the change here, was 2
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
|
|
||||||
{
|
|
||||||
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("issue #1299 - compile error in from_json converting to container "
|
SECTION("issue #1299 - compile error in from_json converting to container "
|
||||||
"with std::pair")
|
"with std::pair")
|
||||||
{
|
{
|
||||||
@@ -718,26 +679,6 @@ TEST_CASE("regression tests 2")
|
|||||||
CHECK(j.dump() == "{}");
|
CHECK(j.dump() == "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
|
|
||||||
// classic Intel ICC reports <span> as includable but cannot actually compile
|
|
||||||
// std::span/std::as_bytes usage below
|
|
||||||
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
|
|
||||||
SECTION("issue #2546 - parsing containers of std::byte")
|
|
||||||
{
|
|
||||||
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
// exclude the trailing '\0' that string-literal initialization adds to
|
|
||||||
// DATA: std::span(DATA) would span the full array extent (including
|
|
||||||
// that NUL), which is only silently accepted as end-of-input by default
|
|
||||||
// and would fail under JSON_STRICT_NUL_HANDLING
|
|
||||||
const auto s = std::as_bytes(std::span(DATA, sizeof(DATA) - 1));
|
|
||||||
const json j = json::parse(s);
|
|
||||||
CHECK(j.dump() == "\"Hello, world!\"");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
|
SECTION("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
|
||||||
{
|
{
|
||||||
SECTION("std::array")
|
SECTION("std::array")
|
||||||
@@ -868,7 +809,7 @@ TEST_CASE("regression tests 2")
|
|||||||
|
|
||||||
SECTION("issue #2982 - to_{binary format} does not provide a mechanism for specifying a custom allocator for the returned type")
|
SECTION("issue #2982 - to_{binary format} does not provide a mechanism for specifying a custom allocator for the returned type")
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t, my_allocator<std::uint8_t>> my_vector;
|
std::vector<std::uint8_t, my_allocator_2982<std::uint8_t>> my_vector;
|
||||||
const json j = {1, 2, 3, 4};
|
const json j = {1, 2, 3, 4};
|
||||||
json::to_cbor(j, my_vector);
|
json::to_cbor(j, my_vector);
|
||||||
json k = json::from_cbor(my_vector);
|
json k = json::from_cbor(my_vector);
|
||||||
@@ -884,26 +825,6 @@ TEST_CASE("regression tests 2")
|
|||||||
CHECK(node.dump(-1, ' ', true, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}");
|
CHECK(node.dump(-1, ' ', true, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
|
|
||||||
{
|
|
||||||
// std::variant<json> must not be retrievable via get<>(), because otherwise the
|
|
||||||
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
|
|
||||||
// converting constructor, routing a number through the string from_json overload
|
|
||||||
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
|
|
||||||
"std::variant<json> must not be retrievable via get<>()");
|
|
||||||
|
|
||||||
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
|
|
||||||
#if !(defined(__clang__) && __clang_major__ < 7)
|
|
||||||
// push_back, not emplace_back: #5066 needs the implicit conversion
|
|
||||||
// from json to the vector's value type
|
|
||||||
std::vector<std::variant<json>> v;
|
|
||||||
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
|
|
||||||
CHECK(std::get<0>(v[0]) == 1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
|
SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
|
||||||
{
|
{
|
||||||
const Issue3669Holder h{};
|
const Issue3669Holder h{};
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-regression3.cpp (std::any, std::optional,
|
||||||
|
// std::variant and std::filesystem regression tests). It is kept in a separate translation
|
||||||
|
// unit so the (much larger) unit-regression3.cpp is built for C++11 only and not rebuilt
|
||||||
|
// for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
// skip tests if JSON_DisableEnumSerialization=ON (#4384): std::byte is a
|
||||||
|
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||||||
|
// from a plain JSON array, not just from an already-binary value) relies on
|
||||||
|
// enum serialization being enabled
|
||||||
|
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||||
|
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
using ordered_json = nlohmann::ordered_json;
|
||||||
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||||
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// for #4804
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// for #4740
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
struct Example_4740
|
||||||
|
{
|
||||||
|
std::optional<std::string> host = std::nullopt;
|
||||||
|
std::optional<int> port = std::nullopt;
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("regression tests 3 (C++17)")
|
||||||
|
{
|
||||||
|
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||||
|
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
|
||||||
|
{
|
||||||
|
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
|
||||||
|
const json j(text_path);
|
||||||
|
|
||||||
|
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
|
||||||
|
CHECK(j_path == text_path);
|
||||||
|
|
||||||
|
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
|
||||||
|
// only known to work on Clang and GCC >=8.4
|
||||||
|
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||||
|
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
|
||||||
|
{
|
||||||
|
const json j;
|
||||||
|
const std::any a1 = j;
|
||||||
|
std::any&& a2 = j;
|
||||||
|
|
||||||
|
CHECK(a1.type() == typeid(j));
|
||||||
|
CHECK(a2.type() == typeid(j));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("issue #4740 - build issue with std::optional")
|
||||||
|
{
|
||||||
|
const auto t1 = Example_4740();
|
||||||
|
const auto j1 = nlohmann::json(t1);
|
||||||
|
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
|
||||||
|
const auto t2 = j1.get<Example_4740>();
|
||||||
|
CHECK(!t2.host.has_value());
|
||||||
|
CHECK(!t2.port.has_value());
|
||||||
|
|
||||||
|
// improve coverage
|
||||||
|
auto t3 = Example_4740();
|
||||||
|
t3.port = 80;
|
||||||
|
t3.host = "example.com";
|
||||||
|
const auto j2 = nlohmann::json(t3);
|
||||||
|
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
|
||||||
|
const auto t4 = j2.get<Example_4740>();
|
||||||
|
CHECK(t4.host.has_value());
|
||||||
|
CHECK(t4.port.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> data = {0x80};
|
||||||
|
const auto decoded = json_4804::from_cbor(data);
|
||||||
|
CHECK((decoded == json_4804::array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||||
|
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||||
|
{
|
||||||
|
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||||
|
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||||
|
const json_4804 j = original;
|
||||||
|
CHECK(j.is_binary());
|
||||||
|
CHECK(!j.is_array());
|
||||||
|
|
||||||
|
// Test round-tripping: extracting the binary value back as the custom container type
|
||||||
|
const auto extracted = j.get<std::vector<std::byte>>();
|
||||||
|
CHECK(extracted == original);
|
||||||
|
|
||||||
|
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||||
|
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||||
|
CHECK(default_json.is_array());
|
||||||
|
CHECK(!default_json.is_binary());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||||
|
{
|
||||||
|
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||||
|
// (not just from a binary-typed node)
|
||||||
|
const auto j = json_4804::parse("[1,2,3]");
|
||||||
|
CHECK(j.is_array());
|
||||||
|
CHECK(!j.is_binary());
|
||||||
|
|
||||||
|
// Extracting as custom BinaryType should work from arrays
|
||||||
|
const auto extracted = j.get<std::vector<std::byte>>();
|
||||||
|
CHECK(extracted.size() == 3);
|
||||||
|
CHECK(extracted[0] == std::byte{1});
|
||||||
|
CHECK(extracted[1] == std::byte{2});
|
||||||
|
CHECK(extracted[2] == std::byte{3});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||||||
|
{
|
||||||
|
const json jval{};
|
||||||
|
auto GetValue = [](const json & valRoot) -> std::optional<json>
|
||||||
|
{
|
||||||
|
if (valRoot.contains("default"))
|
||||||
|
{
|
||||||
|
return valRoot.at("default");
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
auto result = GetValue(jval);
|
||||||
|
CHECK(!result.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-regression3.cpp (C++20 regression tests
|
||||||
|
// (operator<=> related aggregates, ranges)). It is kept in a separate translation unit so
|
||||||
|
// the (much larger) unit-regression3.cpp is built for C++11 only and not rebuilt for every
|
||||||
|
// C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
// skip tests if JSON_DisableEnumSerialization=ON (#4384): std::byte is a
|
||||||
|
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||||||
|
// from a plain JSON array, not just from an already-binary value) relies on
|
||||||
|
// enum serialization being enabled
|
||||||
|
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||||
|
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
using ordered_json = nlohmann::ordered_json;
|
||||||
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||||
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if __has_include(<span>)
|
||||||
|
#include <span>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// for #4440
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
#if JSON_HAS_RANGES == 1
|
||||||
|
#include <ranges>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// for #3312
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
struct for_3312
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
|
||||||
|
{
|
||||||
|
j.at("name").get_to(obj.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("regression tests 3 (C++20)")
|
||||||
|
{
|
||||||
|
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
|
||||||
|
{
|
||||||
|
// see test for #3171
|
||||||
|
const ordered_json j = {{"name", "class"}};
|
||||||
|
for_3312 obj{};
|
||||||
|
|
||||||
|
j.get_to(obj);
|
||||||
|
|
||||||
|
CHECK(obj.name == "class");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGES == 1
|
||||||
|
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
|
||||||
|
{
|
||||||
|
auto noOpFilter = std::views::filter([](auto&&) noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
json j = {1, 2, 3};
|
||||||
|
auto filtered = j | noOpFilter;
|
||||||
|
CHECK(*filtered.begin() == 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||||
|
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
|
||||||
|
{
|
||||||
|
std::vector<int> nums{1, 2, 37, 42, 21};
|
||||||
|
auto filteredNums = nums | std::views::filter([](int i)
|
||||||
|
{
|
||||||
|
return i > 10;
|
||||||
|
});
|
||||||
|
json const j(filteredNums);
|
||||||
|
CHECK(j.type() == json::value_t::array);
|
||||||
|
CHECK(j == json({37, 42, 21}));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// owning_view is not available in libstdc++ < 12
|
||||||
|
#if JSON_HAS_RANGE_VIEW_CONVERSION && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
|
||||||
|
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
|
||||||
|
{
|
||||||
|
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
|
||||||
|
{
|
||||||
|
return i > 10;
|
||||||
|
}));
|
||||||
|
CHECK(j.type() == json::value_t::array);
|
||||||
|
CHECK(j == json({37, 42, 21}));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||||
|
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
|
||||||
|
{
|
||||||
|
std::vector<int> nums{1, 2, 3};
|
||||||
|
auto t = nums | std::views::transform([](int i) noexcept
|
||||||
|
{
|
||||||
|
return i * 2;
|
||||||
|
});
|
||||||
|
json const j(t);
|
||||||
|
CHECK(j.type() == json::value_t::array);
|
||||||
|
CHECK(j == json({2, 4, 6}));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-regression3-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -48,38 +47,8 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <any>
|
|
||||||
#include <variant>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #4804
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
#if __has_include(<span>)
|
|
||||||
#include <span>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// the explicit instantiation for #4825 is in unit-explicit_instantiation.cpp
|
// the explicit instantiation for #4825 is in unit-explicit_instantiation.cpp
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #4440
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
#if JSON_HAS_RANGES == 1
|
|
||||||
#include <ranges>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
@@ -194,22 +163,6 @@ inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-inter
|
|||||||
tb._from_json(j);
|
tb._from_json(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #3312
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
struct for_3312
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
|
|
||||||
{
|
|
||||||
j.at("name").get_to(obj.name);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// for #3204
|
// for #3204
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
@@ -273,37 +226,8 @@ struct Example_3810
|
|||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage)
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage)
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #4740
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
struct Example_4740
|
|
||||||
{
|
|
||||||
std::optional<std::string> host = std::nullopt;
|
|
||||||
std::optional<int> port = std::nullopt;
|
|
||||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("regression tests 3")
|
TEST_CASE("regression tests 3")
|
||||||
{
|
{
|
||||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
|
||||||
// JSON_HAS_CPP_17 (do not remove; see note at top of file)
|
|
||||||
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
|
|
||||||
{
|
|
||||||
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
|
|
||||||
const json j(text_path);
|
|
||||||
|
|
||||||
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
|
|
||||||
CHECK(j_path == text_path);
|
|
||||||
|
|
||||||
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
|
|
||||||
// only known to work on Clang and GCC >=8.4
|
|
||||||
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("issue #3077 - explicit constructor with default does not compile")
|
SECTION("issue #3077 - explicit constructor with default does not compile")
|
||||||
{
|
{
|
||||||
@@ -354,31 +278,6 @@ TEST_CASE("regression tests 3")
|
|||||||
CHECK(td.str == "value");
|
CHECK(td.str == "value");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
|
|
||||||
{
|
|
||||||
// see test for #3171
|
|
||||||
const ordered_json j = {{"name", "class"}};
|
|
||||||
for_3312 obj{};
|
|
||||||
|
|
||||||
j.get_to(obj);
|
|
||||||
|
|
||||||
CHECK(obj.name == "class");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(JSON_HAS_CPP_17) && JSON_USE_IMPLICIT_CONVERSIONS
|
|
||||||
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
|
|
||||||
{
|
|
||||||
const json j;
|
|
||||||
const std::any a1 = j;
|
|
||||||
std::any&& a2 = j;
|
|
||||||
|
|
||||||
CHECK(a1.type() == typeid(j));
|
|
||||||
CHECK(a2.type() == typeid(j));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("issue #3204 - ambiguous regression")
|
SECTION("issue #3204 - ambiguous regression")
|
||||||
{
|
{
|
||||||
const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
|
const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
|
||||||
@@ -421,28 +320,6 @@ TEST_CASE("regression tests 3")
|
|||||||
CHECK(oj["test"].dump() == expected);
|
CHECK(oj["test"].dump() == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
SECTION("issue #4740 - build issue with std::optional")
|
|
||||||
{
|
|
||||||
const auto t1 = Example_4740();
|
|
||||||
const auto j1 = nlohmann::json(t1);
|
|
||||||
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
|
|
||||||
const auto t2 = j1.get<Example_4740>();
|
|
||||||
CHECK(!t2.host.has_value());
|
|
||||||
CHECK(!t2.port.has_value());
|
|
||||||
|
|
||||||
// improve coverage
|
|
||||||
auto t3 = Example_4740();
|
|
||||||
t3.port = 80;
|
|
||||||
t3.host = "example.com";
|
|
||||||
const auto j2 = nlohmann::json(t3);
|
|
||||||
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
|
|
||||||
const auto t4 = j2.get<Example_4740>();
|
|
||||||
CHECK(t4.host.has_value());
|
|
||||||
CHECK(t4.port.has_value());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(_MSVC_LANG)
|
#if !defined(_MSVC_LANG)
|
||||||
// MSVC returns garbage on invalid enum values, so this test is excluded
|
// MSVC returns garbage on invalid enum values, so this test is excluded
|
||||||
// there.
|
// there.
|
||||||
@@ -457,119 +334,7 @@ TEST_CASE("regression tests 3")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> data = {0x80};
|
|
||||||
const auto decoded = json_4804::from_cbor(data);
|
|
||||||
CHECK((decoded == json_4804::array()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
|
||||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
|
||||||
{
|
|
||||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
|
||||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
|
||||||
const json_4804 j = original;
|
|
||||||
CHECK(j.is_binary());
|
|
||||||
CHECK(!j.is_array());
|
|
||||||
|
|
||||||
// Test round-tripping: extracting the binary value back as the custom container type
|
|
||||||
const auto extracted = j.get<std::vector<std::byte>>();
|
|
||||||
CHECK(extracted == original);
|
|
||||||
|
|
||||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
|
||||||
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
|
||||||
CHECK(default_json.is_array());
|
|
||||||
CHECK(!default_json.is_binary());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
|
||||||
{
|
|
||||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
|
||||||
// (not just from a binary-typed node)
|
|
||||||
const auto j = json_4804::parse("[1,2,3]");
|
|
||||||
CHECK(j.is_array());
|
|
||||||
CHECK(!j.is_binary());
|
|
||||||
|
|
||||||
// Extracting as custom BinaryType should work from arrays
|
|
||||||
const auto extracted = j.get<std::vector<std::byte>>();
|
|
||||||
CHECK(extracted.size() == 3);
|
|
||||||
CHECK(extracted[0] == std::byte{1});
|
|
||||||
CHECK(extracted[1] == std::byte{2});
|
|
||||||
CHECK(extracted[2] == std::byte{3});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
|
||||||
{
|
|
||||||
const json jval{};
|
|
||||||
auto GetValue = [](const json & valRoot) -> std::optional<json>
|
|
||||||
{
|
|
||||||
if (valRoot.contains("default"))
|
|
||||||
{
|
|
||||||
return valRoot.at("default");
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
};
|
|
||||||
auto result = GetValue(jval);
|
|
||||||
CHECK(!result.has_value());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_HAS_RANGES == 1
|
|
||||||
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
|
|
||||||
{
|
|
||||||
auto noOpFilter = std::views::filter([](auto&&) noexcept
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
json j = {1, 2, 3};
|
|
||||||
auto filtered = j | noOpFilter;
|
|
||||||
CHECK(*filtered.begin() == 1);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
|
||||||
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
|
|
||||||
{
|
|
||||||
std::vector<int> nums{1, 2, 37, 42, 21};
|
|
||||||
auto filteredNums = nums | std::views::filter([](int i)
|
|
||||||
{
|
|
||||||
return i > 10;
|
|
||||||
});
|
|
||||||
json const j(filteredNums);
|
|
||||||
CHECK(j.type() == json::value_t::array);
|
|
||||||
CHECK(j == json({37, 42, 21}));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// owning_view is not available in libstdc++ < 12
|
// owning_view is not available in libstdc++ < 12
|
||||||
#if JSON_HAS_RANGE_VIEW_CONVERSION && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
|
|
||||||
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
|
|
||||||
{
|
|
||||||
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
|
|
||||||
{
|
|
||||||
return i > 10;
|
|
||||||
}));
|
|
||||||
CHECK(j.type() == json::value_t::array);
|
|
||||||
CHECK(j == json({37, 42, 21}));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
|
||||||
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
|
|
||||||
{
|
|
||||||
std::vector<int> nums{1, 2, 3};
|
|
||||||
auto t = nums | std::views::transform([](int i) noexcept
|
|
||||||
{
|
|
||||||
return i * 2;
|
|
||||||
});
|
|
||||||
json const j(t);
|
|
||||||
CHECK(j.type() == json::value_t::array);
|
|
||||||
CHECK(j == json({2, 4, 6}));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
@@ -914,7 +679,6 @@ TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with
|
|||||||
CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}}));
|
CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("regression test #5476 - array type without reserve()")
|
TEST_CASE("regression test #5476 - array type without reserve()")
|
||||||
{
|
{
|
||||||
// the capacity reserved for definite-length arrays must not require the
|
// the capacity reserved for definite-length arrays must not require the
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. This whole file tests
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// std::format (C++20), so it cannot be split: it is built for C++20 as well:
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// JSON_HAS_CPP_20 (do not remove)
|
||||||
// version macro in a comment close by, like this:
|
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -19,7 +17,6 @@
|
|||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
using ordered_json = nlohmann::ordered_json;
|
using ordered_json = nlohmann::ordered_json;
|
||||||
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
#if JSON_HAS_STD_FORMAT
|
#if JSON_HAS_STD_FORMAT
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// This file contains the C++20-only part of unit-user_defined_input.cpp (parsing with
|
||||||
|
// std::counted_iterator and std::default_sentinel_t). It is kept in a separate translation
|
||||||
|
// unit so the (much larger) unit-user_defined_input.cpp is built for C++11 only and not
|
||||||
|
// rebuilt for every C++ standard.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_20
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_concepts)
|
||||||
|
#include <iterator>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_concepts)
|
||||||
|
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
|
||||||
|
{
|
||||||
|
using iterator_type = std::string::const_iterator;
|
||||||
|
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
|
||||||
|
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
|
||||||
|
|
||||||
|
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
|
||||||
|
const json j = json::parse(first, std::default_sentinel);
|
||||||
|
CHECK(j["key"] == "value");
|
||||||
|
CHECK(j["array"].size() == 3);
|
||||||
|
|
||||||
|
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
|
||||||
|
CHECK(json::accept(first2, std::default_sentinel));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
|
||||||
|
{
|
||||||
|
// A sized sentinel makes the remaining element count computable in O(1), so
|
||||||
|
// std::counted_iterator over a contiguous iterator must reach the same bulk
|
||||||
|
// string/number scanners as a plain pointer - not just the byte-at-a-time
|
||||||
|
// fallback (see #5268 for the equivalent memcpy fast path).
|
||||||
|
#if JSON_HAS_RANGES
|
||||||
|
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
|
||||||
|
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
|
||||||
|
// to the byte-at-a-time scanner; everything below still has to work there.
|
||||||
|
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
|
||||||
|
CHECK(adapter_type::supports_bulk_scan);
|
||||||
|
CHECK(adapter_type::supports_seek);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
|
||||||
|
// integer/floating-point numbers
|
||||||
|
const std::string json_str =
|
||||||
|
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
|
||||||
|
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
|
||||||
|
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
|
||||||
|
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
|
||||||
|
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
|
||||||
|
|
||||||
|
const std::counted_iterator<const char*> first(json_str.data(), len);
|
||||||
|
const json j = json::parse(first, std::default_sentinel);
|
||||||
|
|
||||||
|
// parsing through the pointer adapter must give exactly the same result
|
||||||
|
CHECK(j == json::parse(json_str));
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// Diagnostics that quote the offending token are reconstructed from the
|
||||||
|
// already-consumed input (supports_seek), a path a sized sentinel only
|
||||||
|
// reaches now; check a few that include the "last read" text. Parsing
|
||||||
|
// invalid input aborts when exceptions are off, hence the guard.
|
||||||
|
// Raw strings and explicit bytes: an escaped literal and two literals
|
||||||
|
// written next to each other both read as mistakes to static analysis.
|
||||||
|
const auto byte = [](int value)
|
||||||
|
{
|
||||||
|
return std::string(1, static_cast<char>(value));
|
||||||
|
};
|
||||||
|
const std::vector<std::string> diagnostic_docs =
|
||||||
|
{
|
||||||
|
"1\nx",
|
||||||
|
"truX",
|
||||||
|
"[tru]",
|
||||||
|
R"("abc)",
|
||||||
|
R"(["\ud834"])",
|
||||||
|
R"(["a)" + byte(0x01) + R"(b"])",
|
||||||
|
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
|
||||||
|
"[1e]",
|
||||||
|
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& text : diagnostic_docs)
|
||||||
|
{
|
||||||
|
CAPTURE(text)
|
||||||
|
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
|
||||||
|
std::string counted_message;
|
||||||
|
std::string string_message;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json counted_result = json::parse(it, std::default_sentinel);
|
||||||
|
static_cast<void>(counted_result);
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
counted_message = e.what();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json string_result = json::parse(text);
|
||||||
|
static_cast<void>(string_result);
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
string_message = e.what();
|
||||||
|
}
|
||||||
|
CHECK_FALSE(counted_message.empty());
|
||||||
|
CHECK(counted_message == string_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and errors must still be reported identically
|
||||||
|
const std::string bad = "[01\n]";
|
||||||
|
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
|
||||||
|
std::string counted_what;
|
||||||
|
std::string string_what;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json counted_result = json::parse(bad_first, std::default_sentinel);
|
||||||
|
static_cast<void>(counted_result);
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
counted_what = e.what();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json string_result = json::parse(bad);
|
||||||
|
static_cast<void>(string_result);
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
string_what = e.what();
|
||||||
|
}
|
||||||
|
CHECK_FALSE(counted_what.empty());
|
||||||
|
CHECK(counted_what == string_what);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// several cases below are truncated on purpose, and parsing invalid input
|
||||||
|
// aborts when exceptions are off
|
||||||
|
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
|
||||||
|
{
|
||||||
|
// The count, not the size of the underlying buffer, is the end of the
|
||||||
|
// input: the bulk scanners must never look at the bytes behind it, even
|
||||||
|
// though they are readable. Each case is compared against parsing the
|
||||||
|
// equivalent prefix as a std::string.
|
||||||
|
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
|
||||||
|
{
|
||||||
|
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json j = json::parse(first, std::default_sentinel);
|
||||||
|
return "OK|" + j.dump();
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
return {e.what()};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json j = json::parse(buf.substr(0, n));
|
||||||
|
return "OK|" + j.dump();
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
return {e.what()};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
||||||
|
{
|
||||||
|
const char* buffer;
|
||||||
|
std::size_t count;
|
||||||
|
};
|
||||||
|
const std::vector<testcase> cases =
|
||||||
|
{
|
||||||
|
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
|
||||||
|
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
|
||||||
|
{"[\"abc\"]____", 6}, // cut just before the closing quote
|
||||||
|
{"[12345]xxxxx", 4}, // cut inside a number
|
||||||
|
{"[123]999999", 5}, // number ends exactly at the count
|
||||||
|
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
|
||||||
|
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
|
||||||
|
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
|
||||||
|
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
|
||||||
|
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& tc : cases)
|
||||||
|
{
|
||||||
|
CAPTURE(tc.buffer)
|
||||||
|
CAPTURE(tc.count)
|
||||||
|
const std::string buffer = tc.buffer;
|
||||||
|
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||||
// When using macros that are only defined for particular versions of the standard
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||||
// version macro in a comment close by, like this:
|
// go into a separate file unit-user_defined_input-cpp<NN>.cpp. This file stays C++11-only.
|
||||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -25,10 +24,6 @@ using nlohmann::json;
|
|||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
|
||||||
#include <iterator>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
TEST_CASE("Use arbitrary stdlib container")
|
TEST_CASE("Use arbitrary stdlib container")
|
||||||
@@ -277,196 +272,4 @@ TEST_CASE("Contiguous byte containers take the pointer adapter")
|
|||||||
CHECK(json::parse(buffer) == json({1, 2, 3, 4, 5}));
|
CHECK(json::parse(buffer) == json({1, 2, 3, 4, 5}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
|
||||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
||||||
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
|
|
||||||
{
|
|
||||||
using iterator_type = std::string::const_iterator;
|
|
||||||
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
|
|
||||||
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
|
|
||||||
|
|
||||||
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
|
|
||||||
const json j = json::parse(first, std::default_sentinel);
|
|
||||||
CHECK(j["key"] == "value");
|
|
||||||
CHECK(j["array"].size() == 3);
|
|
||||||
|
|
||||||
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
|
|
||||||
CHECK(json::accept(first2, std::default_sentinel));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
|
|
||||||
{
|
|
||||||
// A sized sentinel makes the remaining element count computable in O(1), so
|
|
||||||
// std::counted_iterator over a contiguous iterator must reach the same bulk
|
|
||||||
// string/number scanners as a plain pointer - not just the byte-at-a-time
|
|
||||||
// fallback (see #5268 for the equivalent memcpy fast path).
|
|
||||||
#if JSON_HAS_RANGES
|
|
||||||
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
|
|
||||||
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
|
|
||||||
// to the byte-at-a-time scanner; everything below still has to work there.
|
|
||||||
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
|
|
||||||
CHECK(adapter_type::supports_bulk_scan);
|
|
||||||
CHECK(adapter_type::supports_seek);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
|
|
||||||
// integer/floating-point numbers
|
|
||||||
const std::string json_str =
|
|
||||||
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
|
|
||||||
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
|
|
||||||
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
|
|
||||||
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
|
|
||||||
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
|
|
||||||
|
|
||||||
const std::counted_iterator<const char*> first(json_str.data(), len);
|
|
||||||
const json j = json::parse(first, std::default_sentinel);
|
|
||||||
|
|
||||||
// parsing through the pointer adapter must give exactly the same result
|
|
||||||
CHECK(j == json::parse(json_str));
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
// Diagnostics that quote the offending token are reconstructed from the
|
|
||||||
// already-consumed input (supports_seek), a path a sized sentinel only
|
|
||||||
// reaches now; check a few that include the "last read" text. Parsing
|
|
||||||
// invalid input aborts when exceptions are off, hence the guard.
|
|
||||||
// Raw strings and explicit bytes: an escaped literal and two literals
|
|
||||||
// written next to each other both read as mistakes to static analysis.
|
|
||||||
const auto byte = [](int value)
|
|
||||||
{
|
|
||||||
return std::string(1, static_cast<char>(value));
|
|
||||||
};
|
|
||||||
const std::vector<std::string> diagnostic_docs =
|
|
||||||
{
|
|
||||||
"1\nx",
|
|
||||||
"truX",
|
|
||||||
"[tru]",
|
|
||||||
R"("abc)",
|
|
||||||
R"(["\ud834"])",
|
|
||||||
R"(["a)" + byte(0x01) + R"(b"])",
|
|
||||||
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
|
|
||||||
"[1e]",
|
|
||||||
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& text : diagnostic_docs)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
|
|
||||||
std::string counted_message;
|
|
||||||
std::string string_message;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json counted_result = json::parse(it, std::default_sentinel);
|
|
||||||
static_cast<void>(counted_result);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
counted_message = e.what();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json string_result = json::parse(text);
|
|
||||||
static_cast<void>(string_result);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
string_message = e.what();
|
|
||||||
}
|
|
||||||
CHECK_FALSE(counted_message.empty());
|
|
||||||
CHECK(counted_message == string_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// and errors must still be reported identically
|
|
||||||
const std::string bad = "[01\n]";
|
|
||||||
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
|
|
||||||
std::string counted_what;
|
|
||||||
std::string string_what;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json counted_result = json::parse(bad_first, std::default_sentinel);
|
|
||||||
static_cast<void>(counted_result);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
counted_what = e.what();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json string_result = json::parse(bad);
|
|
||||||
static_cast<void>(string_result);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
string_what = e.what();
|
|
||||||
}
|
|
||||||
CHECK_FALSE(counted_what.empty());
|
|
||||||
CHECK(counted_what == string_what);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
// several cases below are truncated on purpose, and parsing invalid input
|
|
||||||
// aborts when exceptions are off
|
|
||||||
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
|
|
||||||
{
|
|
||||||
// The count, not the size of the underlying buffer, is the end of the
|
|
||||||
// input: the bulk scanners must never look at the bytes behind it, even
|
|
||||||
// though they are readable. Each case is compared against parsing the
|
|
||||||
// equivalent prefix as a std::string.
|
|
||||||
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
|
|
||||||
{
|
|
||||||
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json j = json::parse(first, std::default_sentinel);
|
|
||||||
return "OK|" + j.dump();
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
return {e.what()};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json j = json::parse(buf.substr(0, n));
|
|
||||||
return "OK|" + j.dump();
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
return {e.what()};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
|
||||||
{
|
|
||||||
const char* buffer;
|
|
||||||
std::size_t count;
|
|
||||||
};
|
|
||||||
const std::vector<testcase> cases =
|
|
||||||
{
|
|
||||||
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
|
|
||||||
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
|
|
||||||
{"[\"abc\"]____", 6}, // cut just before the closing quote
|
|
||||||
{"[12345]xxxxx", 4}, // cut inside a number
|
|
||||||
{"[123]999999", 5}, // number ends exactly at the count
|
|
||||||
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
|
|
||||||
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
|
|
||||||
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
|
|
||||||
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
|
|
||||||
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& tc : cases)
|
|
||||||
{
|
|
||||||
CAPTURE(tc.buffer)
|
|
||||||
CAPTURE(tc.count)
|
|
||||||
const std::string buffer = tc.buffer;
|
|
||||||
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
Reference in new issue
Block a user