From b8426584fc0f5a2f74c2d0dd67adea577d47aa7d Mon Sep 17 00:00:00 2001 From: John Keiser Date: Sat, 19 Dec 2020 10:16:22 -0800 Subject: [PATCH 01/10] Make field lookup order-insensitive. --- .../generic/ondemand/json_iterator-inl.h | 8 ++ .../simdjson/generic/ondemand/json_iterator.h | 3 + .../generic/ondemand/token_iterator-inl.h | 4 + .../generic/ondemand/token_iterator.h | 5 + .../generic/ondemand/value_iterator-inl.h | 105 ++++++++++++++++-- tests/ondemand/ondemand_basictests.cpp | 24 ++-- 6 files changed, 125 insertions(+), 24 deletions(-) diff --git a/include/simdjson/generic/ondemand/json_iterator-inl.h b/include/simdjson/generic/ondemand/json_iterator-inl.h index c61925e51..30c2e4ddf 100644 --- a/include/simdjson/generic/ondemand/json_iterator-inl.h +++ b/include/simdjson/generic/ondemand/json_iterator-inl.h @@ -156,6 +156,14 @@ simdjson_really_inline error_code json_iterator::report_error(error_code _error, return error; } +simdjson_really_inline const uint32_t *json_iterator::checkpoint() const noexcept { + return token.checkpoint(); +} +simdjson_really_inline void json_iterator::restore_checkpoint(const uint32_t *target_checkpoint) noexcept { + token.restore_checkpoint(target_checkpoint); +} + + simdjson_really_inline error_code json_iterator::optional_error(error_code _error, const char *message) noexcept { SIMDJSON_ASSUME(_error == INCORRECT_TYPE || _error == NO_SUCH_FIELD); logger::log_error(*this, message); diff --git a/include/simdjson/generic/ondemand/json_iterator.h b/include/simdjson/generic/ondemand/json_iterator.h index 8d4c4da35..944a99593 100644 --- a/include/simdjson/generic/ondemand/json_iterator.h +++ b/include/simdjson/generic/ondemand/json_iterator.h @@ -163,6 +163,9 @@ public: template simdjson_warn_unused simdjson_really_inline bool peek_to_buffer(uint8_t (&tmpbuf)[N]) noexcept; template simdjson_warn_unused simdjson_really_inline bool advance_to_buffer(uint8_t (&tmpbuf)[N]) noexcept; + simdjson_really_inline const uint32_t *checkpoint() const noexcept; + simdjson_really_inline void restore_checkpoint(const uint32_t *target_checkpoint) noexcept; + protected: simdjson_really_inline json_iterator(ondemand::parser *parser) noexcept; diff --git a/include/simdjson/generic/ondemand/token_iterator-inl.h b/include/simdjson/generic/ondemand/token_iterator-inl.h index c16d324ea..0398e2221 100644 --- a/include/simdjson/generic/ondemand/token_iterator-inl.h +++ b/include/simdjson/generic/ondemand/token_iterator-inl.h @@ -43,6 +43,10 @@ simdjson_really_inline const uint32_t *token_iterator::checkpoint() const noexce return index; } +simdjson_really_inline void token_iterator::restore_checkpoint(const uint32_t *target_checkpoint) noexcept { + index = target_checkpoint; +} + } // namespace ondemand } // namespace SIMDJSON_IMPLEMENTATION } // namespace simdjson diff --git a/include/simdjson/generic/ondemand/token_iterator.h b/include/simdjson/generic/ondemand/token_iterator.h index 76869fc5b..2db1fa7b3 100644 --- a/include/simdjson/generic/ondemand/token_iterator.h +++ b/include/simdjson/generic/ondemand/token_iterator.h @@ -54,6 +54,11 @@ public: */ simdjson_really_inline const uint32_t *checkpoint() const noexcept; + /** + * Reset to a previously saved index. + */ + simdjson_really_inline void restore_checkpoint(const uint32_t *target_checkpoint) noexcept; + // NOTE: we don't support a full C++ iterator interface, because we expect people to make // different calls to advance the iterator based on *their own* state. diff --git a/include/simdjson/generic/ondemand/value_iterator-inl.h b/include/simdjson/generic/ondemand/value_iterator-inl.h index 9dcc0c5af..be187f6ea 100644 --- a/include/simdjson/generic/ondemand/value_iterator-inl.h +++ b/include/simdjson/generic/ondemand/value_iterator-inl.h @@ -51,37 +51,124 @@ simdjson_warn_unused simdjson_really_inline simdjson_result value_iterator } } -/** - * Find the field with the given key. May be used in place of ++. - */ simdjson_warn_unused simdjson_really_inline simdjson_result value_iterator::find_field_raw(const std::string_view key) noexcept { - if (!is_open()) { return false; } - - // Unless this is the first field, we need to advance past the , and check for } error_code error; bool has_value; + + // + // Initially, the object can be in one of a few different places: + // + // 1. The start of the object, at the first field: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2, index 1) + // ``` + // if (at_first_field()) { + // If we're at the beginning of the object, we definitely have a field has_value = true; + + // 2. When a previous search did not yield a value or the object is empty: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 0) + // { } + // ^ (depth 0, index 2) + // ``` + // + } else if (!is_open()) { + has_value = false; + + // 3. When a previous search found a field or an iterator yielded a value: + // + // ``` + // // When a field was not fully consumed (or not even touched at all) + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2) + // // When a field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // // When the last field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // ``` + // } else { + // Finish the previous value and see if , or } is next if ((error = skip_child() )) { abandon(); return error; } if ((error = has_next_field().get(has_value) )) { abandon(); return error; } } + + // After initial processing, we will be in one of two states: + // + // ``` + // // At the beginning of a field + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // // At the end of the object + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 0) + // ``` + // + + // First, we scan from that point to the end. + // If we don't find a match, we loop back around, and scan from the beginning to that point. + const uint32_t *search_start = _json_iter->checkpoint(); + + // Next, we find a match starting from the current position. while (has_value) { - // Get the key + SIMDJSON_ASSUME( _json_iter->_depth == _depth + 1 ); // We must be at the start of a field + + // Get the key and colon, stopping at the value. raw_json_string actual_key; if ((error = field_key().get(actual_key) )) { abandon(); return error; }; if ((error = field_value() )) { abandon(); return error; } - // Check if it matches + // If it matches, stop and return if (actual_key == key) { logger::log_event(*this, "match", key, -2); return true; } + + // No match: skip the value and see if , or } is next logger::log_event(*this, "no match", key, -2); - SIMDJSON_TRY( skip_child() ); // Skip the value entirely + SIMDJSON_TRY( skip_child() ); if ((error = has_next_field().get(has_value) )) { abandon(); return error; } } + // If we reach the end without finding a match, search the rest of the fields starting at the + // beginning of the object. + // (We have already run through the object before, so we've already validated its structure. We + // don't check errors in this bit.) + _json_iter->restore_checkpoint(_start_index + 1); + _json_iter->descend_to(_depth); + + has_value = started_object(); + while (_json_iter->checkpoint() < search_start) { + SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object + SIMDJSON_ASSUME( _json_iter->_depth == _depth + 1 ); // We must be at the start of a field + + // Get the key and colon, stopping at the value. + raw_json_string actual_key; + error = field_key().get(actual_key); SIMDJSON_ASSUME(!error); + error = field_value(); SIMDJSON_ASSUME(!error); + + // If it matches, stop and return + if (actual_key == key) { + logger::log_event(*this, "match", key, -2); + return true; + } + + // No match: skip the value and see if , or } is next + logger::log_event(*this, "no match", key, -2); + SIMDJSON_TRY( skip_child() ); + error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error); + } + // If the loop ended, we're out of fields to look at. return false; } diff --git a/tests/ondemand/ondemand_basictests.cpp b/tests/ondemand/ondemand_basictests.cpp index 86ea4e5a2..dbfd9b002 100644 --- a/tests/ondemand/ondemand_basictests.cpp +++ b/tests/ondemand/ondemand_basictests.cpp @@ -1166,7 +1166,7 @@ namespace dom_api_tests { ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); - ASSERT_ERROR( object["a"], NO_SUCH_FIELD ); + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); return true; })); @@ -1178,7 +1178,7 @@ namespace dom_api_tests { ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); - ASSERT_ERROR( object["a"], NO_SUCH_FIELD ); + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); return true; })); @@ -1189,7 +1189,7 @@ namespace dom_api_tests { ASSERT_EQUAL( doc["b"].get_uint64().first, 2 ); ASSERT_EQUAL( doc["c/d"].get_uint64().first, 3 ); - ASSERT_ERROR( doc["a"], NO_SUCH_FIELD ); + ASSERT_EQUAL( doc["a"].get_uint64().first, 1 ); ASSERT_ERROR( doc["d"], NO_SUCH_FIELD ); return true; })); @@ -1198,7 +1198,7 @@ namespace dom_api_tests { ASSERT_EQUAL( doc_result["b"].get_uint64().first, 2 ); ASSERT_EQUAL( doc_result["c/d"].get_uint64().first, 3 ); - ASSERT_ERROR( doc_result["a"], NO_SUCH_FIELD ); + ASSERT_EQUAL( doc_result["a"].get_uint64().first, 1 ); ASSERT_ERROR( doc_result["d"], NO_SUCH_FIELD ); return true; })); @@ -1437,19 +1437,13 @@ namespace ordering_tests { double z{0}; for (ondemand::object point_object : doc["coordinates"]) { z += double(point_object["z"]); - try { - x += double(point_object["x"]); - return false; - } catch(simdjson_error&) {} - try { - y += double(point_object["y"]); - return false; - } catch(simdjson_error&) {} + x += double(point_object["x"]); + y += double(point_object["y"]); } - return (x == 0) && (y == 0) && (z == 3.3); + return (x == 1.1) && (y == 2.2) && (z == 3.3); } - bool robust_order() { + bool foreach_lookup() { TEST_START(); ondemand::parser parser{}; auto doc = parser.iterate(json); @@ -1472,7 +1466,7 @@ namespace ordering_tests { #if SIMDJSON_EXCEPTIONS in_order() && out_of_order() && - robust_order() && + foreach_lookup() && #endif true; } From 195acc3e45c059049e1d02e6da21020854233a79 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Sat, 19 Dec 2020 13:01:57 -0800 Subject: [PATCH 02/10] Add find_field / find_field_unordered to object --- .../simdjson/generic/ondemand/document-inl.h | 76 ++++-- include/simdjson/generic/ondemand/document.h | 62 ++++- .../generic/ondemand/json_iterator-inl.h | 2 + .../simdjson/generic/ondemand/object-inl.h | 78 +++--- include/simdjson/generic/ondemand/object.h | 59 +++-- include/simdjson/generic/ondemand/value-inl.h | 90 ++++++- include/simdjson/generic/ondemand/value.h | 147 +++++++++-- .../generic/ondemand/value_iterator-inl.h | 75 ++++++ .../generic/ondemand/value_iterator.h | 28 +- tests/ondemand/ondemand_basictests.cpp | 247 +++++++++++++++++- 10 files changed, 732 insertions(+), 132 deletions(-) diff --git a/include/simdjson/generic/ondemand/document-inl.h b/include/simdjson/generic/ondemand/document-inl.h index e9a66995a..289e10463 100644 --- a/include/simdjson/generic/ondemand/document-inl.h +++ b/include/simdjson/generic/ondemand/document-inl.h @@ -12,43 +12,46 @@ simdjson_really_inline document document::start(json_iterator &&iter) noexcept { return document(std::forward(iter)); } -simdjson_really_inline value document::as_value() noexcept { - return as_value_iterator(); +simdjson_really_inline value_iterator document::resume_value_iterator() noexcept { + return value_iterator(&iter, 1, iter.root_checkpoint()); } -simdjson_really_inline value_iterator document::as_value_iterator() noexcept { +simdjson_really_inline value_iterator document::get_root_value_iterator() noexcept { iter.assert_at_root(); - return value_iterator(&iter, 1, iter.root_checkpoint()); + return resume_value_iterator(); } -simdjson_really_inline value_iterator document::as_non_root_value_iterator() noexcept { - return value_iterator(&iter, 1, iter.root_checkpoint()); +simdjson_really_inline value document::resume_value() noexcept { + return resume_value_iterator(); +} +simdjson_really_inline value document::get_root_value() noexcept { + return get_root_value_iterator(); } simdjson_really_inline simdjson_result document::get_array() & noexcept { - return as_value().get_array(); + return get_root_value().get_array(); } simdjson_really_inline simdjson_result document::get_object() & noexcept { - return as_value().get_object(); + return get_root_value().get_object(); } simdjson_really_inline simdjson_result document::get_uint64() noexcept { - return as_value_iterator().require_root_uint64(); + return get_root_value_iterator().require_root_uint64(); } simdjson_really_inline simdjson_result document::get_int64() noexcept { - return as_value_iterator().require_root_int64(); + return get_root_value_iterator().require_root_int64(); } simdjson_really_inline simdjson_result document::get_double() noexcept { - return as_value_iterator().require_root_double(); + return get_root_value_iterator().require_root_double(); } simdjson_really_inline simdjson_result document::get_string() & noexcept { - return as_value().get_string(); + return get_root_value().get_string(); } simdjson_really_inline simdjson_result document::get_raw_json_string() & noexcept { - return as_value().get_raw_json_string(); + return get_root_value().get_raw_json_string(); } simdjson_really_inline simdjson_result document::get_bool() noexcept { - return as_value_iterator().require_root_bool(); + return get_root_value_iterator().require_root_bool(); } simdjson_really_inline bool document::is_null() noexcept { - return as_value_iterator().is_root_null(); + return get_root_value_iterator().is_root_null(); } template<> simdjson_really_inline simdjson_result document::get() & noexcept { return get_array(); } @@ -89,21 +92,24 @@ simdjson_really_inline simdjson_result document::begin() & noexc simdjson_really_inline simdjson_result document::end() & noexcept { return {}; } + +simdjson_really_inline simdjson_result document::find_field(std::string_view key) & noexcept { + return resume_value().find_field(key); +} +simdjson_really_inline simdjson_result document::find_field(const char *key) & noexcept { + return resume_value().find_field(key); +} +simdjson_really_inline simdjson_result document::find_field_unordered(std::string_view key) & noexcept { + return resume_value().find_field_unordered(key); +} +simdjson_really_inline simdjson_result document::find_field_unordered(const char *key) & noexcept { + return resume_value().find_field_unordered(key); +} simdjson_really_inline simdjson_result document::operator[](std::string_view key) & noexcept { - if (iter.at_root()) { - return get_object()[key]; - } else { - // If we're not at the root, this is not the first key we've grabbed - return object::resume(as_non_root_value_iterator())[key]; - } + return resume_value()[key]; } simdjson_really_inline simdjson_result document::operator[](const char *key) & noexcept { - if (iter.at_root()) { - return get_object()[key]; - } else { - // If we're not at the root, this is not the first key we've grabbed - return object::resume(as_non_root_value_iterator())[key]; - } + return resume_value()[key]; } } // namespace ondemand @@ -136,6 +142,14 @@ simdjson_really_inline simdjson_result simdjson_result::end() & noexcept { return {}; } +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} simdjson_really_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { if (error()) { return error(); } return first[key]; @@ -144,6 +158,14 @@ simdjson_really_inline simdjson_result if (error()) { return error(); } return first[key]; } +simdjson_really_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} simdjson_really_inline simdjson_result simdjson_result::get_array() & noexcept { if (error()) { return error(); } return first.get_array(); diff --git a/include/simdjson/generic/ondemand/document.h b/include/simdjson/generic/ondemand/document.h index e83c2bf69..01c4ffebb 100644 --- a/include/simdjson/generic/ondemand/document.h +++ b/include/simdjson/generic/ondemand/document.h @@ -206,30 +206,64 @@ public: simdjson_really_inline simdjson_result end() & noexcept; /** - * Look up a field by name on an object. + * Look up a field by name on an object (order-sensitive). * - * Important notes: + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: * - * * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. - * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. - * * **Once Only:** You may only look up a single field on a document. To look up multiple fields, - * use `.get_object()` or cast to `object`. + * ```c++ + * simdjson::builtin::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. * * @param key The key to look up. - * @returns The value of the field, NO_SUCH_FIELD if the field is not in the object, or - * INCORRECT_TYPE if the JSON value is not an array. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. */ + simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(const char *key) & noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field wasn't there when they aren't). + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(const char *key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](const char *key) & noexcept; protected: simdjson_really_inline document(ondemand::json_iterator &&iter) noexcept; simdjson_really_inline const uint8_t *text(uint32_t idx) const noexcept; - simdjson_really_inline value as_value() noexcept; - simdjson_really_inline value_iterator as_value_iterator() noexcept; - simdjson_really_inline value_iterator as_non_root_value_iterator() noexcept; + simdjson_really_inline value_iterator resume_value_iterator() noexcept; + simdjson_really_inline value_iterator get_root_value_iterator() noexcept; + simdjson_really_inline value resume_value() noexcept; + simdjson_really_inline value get_root_value() noexcept; static simdjson_really_inline document start(ondemand::json_iterator &&iter) noexcept; // @@ -290,8 +324,12 @@ public: simdjson_really_inline simdjson_result begin() & noexcept; simdjson_really_inline simdjson_result end() & noexcept; + simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_really_inline simdjson_result find_field(const char *key) & noexcept; simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; simdjson_really_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_really_inline simdjson_result find_field_unordered(const char *key) & noexcept; }; } // namespace simdjson diff --git a/include/simdjson/generic/ondemand/json_iterator-inl.h b/include/simdjson/generic/ondemand/json_iterator-inl.h index 30c2e4ddf..0d4caf5c4 100644 --- a/include/simdjson/generic/ondemand/json_iterator-inl.h +++ b/include/simdjson/generic/ondemand/json_iterator-inl.h @@ -132,11 +132,13 @@ simdjson_really_inline uint32_t json_iterator::peek_length(int32_t delta) const } simdjson_really_inline void json_iterator::ascend_to(depth_t parent_depth) noexcept { + SIMDJSON_ASSUME(parent_depth >= 0 && parent_depth < INT32_MAX - 1); SIMDJSON_ASSUME(_depth == parent_depth + 1); _depth = parent_depth; } simdjson_really_inline void json_iterator::descend_to(depth_t child_depth) noexcept { + SIMDJSON_ASSUME(child_depth >= 1 && child_depth < INT32_MAX); SIMDJSON_ASSUME(_depth == child_depth - 1); _depth = child_depth; } diff --git a/include/simdjson/generic/ondemand/object-inl.h b/include/simdjson/generic/ondemand/object-inl.h index 9aac2dc16..9dcb03244 100644 --- a/include/simdjson/generic/ondemand/object-inl.h +++ b/include/simdjson/generic/ondemand/object-inl.h @@ -2,55 +2,31 @@ namespace simdjson { namespace SIMDJSON_IMPLEMENTATION { namespace ondemand { -// -// ### Live States -// -// While iterating or looking up values, depth >= iter.depth. at_start may vary. Error is -// always SUCCESS: -// -// - Start: This is the state when the object is first found and the iterator is just past the {. -// In this state, at_start == true. -// - Next: After we hand a scalar value to the user, or an array/object which they then fully -// iterate over, the iterator is at the , or } before the next value. In this state, -// depth == iter.depth, at_start == false, and error == SUCCESS. -// - Unfinished Business: When we hand an array/object to the user which they do not fully -// iterate over, we need to finish that iteration by skipping child values until we reach the -// Next state. In this state, depth > iter.depth, at_start == false, and error == SUCCESS. -// -// ## Error States -// -// In error states, we will yield exactly one more value before stopping. iter.depth == depth -// and at_start is always false. We decrement after yielding the error, moving to the Finished -// state. -// -// - Chained Error: When the object iterator is part of an error chain--for example, in -// `for (auto tweet : doc["tweets"])`, where the tweet field may be missing or not be an -// object--we yield that error in the loop, exactly once. In this state, error != SUCCESS and -// iter.depth == depth, and at_start == false. We decrement depth when we yield the error. -// - Missing Comma Error: When the iterator ++ method discovers there is no comma between fields, -// we flag that as an error and treat it exactly the same as a Chained Error. In this state, -// error == TAPE_ERROR, iter.depth == depth, and at_start == false. -// -// Errors that occur while reading a field to give to the user (such as when the key is not a -// string or the field is missing a colon) are yielded immediately. Depth is then decremented, -// moving to the Finished state without transitioning through an Error state at all. -// -// ## Terminal State -// -// The terminal state has iter.depth < depth. at_start is always false. -// -// - Finished: When we have reached a }, we are finished. We signal this by decrementing depth. -// In this state, iter.depth < depth, at_start == false, and error == SUCCESS. -// - +simdjson_really_inline simdjson_result object::find_field_unordered(const std::string_view key) & noexcept { + bool has_value; + SIMDJSON_TRY( iter.find_field_unordered_raw(key).get(has_value) ); + if (!has_value) { return NO_SUCH_FIELD; } + return value(iter.child()); +} +simdjson_really_inline simdjson_result object::find_field_unordered(const std::string_view key) && noexcept { + bool has_value; + SIMDJSON_TRY( iter.find_field_unordered_raw(key).get(has_value) ); + if (!has_value) { return NO_SUCH_FIELD; } + return value(iter.child()); +} simdjson_really_inline simdjson_result object::operator[](const std::string_view key) & noexcept { + return find_field_unordered(key); +} +simdjson_really_inline simdjson_result object::operator[](const std::string_view key) && noexcept { + return find_field_unordered(key); +} +simdjson_really_inline simdjson_result object::find_field(const std::string_view key) & noexcept { bool has_value; SIMDJSON_TRY( iter.find_field_raw(key).get(has_value) ); if (!has_value) { return NO_SUCH_FIELD; } return value(iter.child()); } - -simdjson_really_inline simdjson_result object::operator[](const std::string_view key) && noexcept { +simdjson_really_inline simdjson_result object::find_field(const std::string_view key) && noexcept { bool has_value; SIMDJSON_TRY( iter.find_field_raw(key).get(has_value) ); if (!has_value) { return NO_SUCH_FIELD; } @@ -108,6 +84,14 @@ simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field_unordered(key); +} simdjson_really_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { if (error()) { return error(); } return first[key]; @@ -116,5 +100,13 @@ simdjson_really_inline simdjson_result if (error()) { return error(); } return std::forward(first)[key]; } +simdjson_really_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field(std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field(key); +} } // namespace simdjson diff --git a/include/simdjson/generic/ondemand/object.h b/include/simdjson/generic/ondemand/object.h index 019e2ae16..1063ed9a2 100644 --- a/include/simdjson/generic/ondemand/object.h +++ b/include/simdjson/generic/ondemand/object.h @@ -20,29 +20,54 @@ public: simdjson_really_inline object_iterator end() noexcept; /** - * Look up a field by name on an object. + * Look up a field by name on an object (order-sensitive). * - * Important notes: + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: * - * * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. - * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. - * * **Order Sensitive:** Each field lookup will only move forward in the object. In particular, - * the following code reads z, then y, then x, and thus will not retrieve x or y if fed the - * JSON `{ "x": 1, "y": 2, "z": 3 }`: + * ```c++ + * simdjson::builtin::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` * - * ```c++ - * simdjson::builtin::ondemand::parser parser; - * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); - * double z = obj["z"]; - * double y = obj["y"]; - * double x = obj["x"]; - * ``` + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. * * @param key The key to look up. * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. */ + simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(std::string_view key) && noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field wasn't there when they aren't). + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) && noexcept; protected: @@ -76,6 +101,10 @@ public: simdjson_really_inline simdjson_result begin() noexcept; simdjson_really_inline simdjson_result end() noexcept; + simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_really_inline simdjson_result find_field(std::string_view key) && noexcept; + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; simdjson_really_inline simdjson_result operator[](std::string_view key) && noexcept; }; diff --git a/include/simdjson/generic/ondemand/value-inl.h b/include/simdjson/generic/ondemand/value-inl.h index 9c8f222c2..a46dcc833 100644 --- a/include/simdjson/generic/ondemand/value-inl.h +++ b/include/simdjson/generic/ondemand/value-inl.h @@ -6,6 +6,13 @@ simdjson_really_inline value::value(const value_iterator &_iter) noexcept : iter{_iter} { } +simdjson_really_inline value value::start(const value_iterator &iter) noexcept { + return iter; +} +simdjson_really_inline value value::resume(const value_iterator &iter) noexcept { + return iter; +} + simdjson_really_inline simdjson_result value::get_array() && noexcept { return array::start(iter); } @@ -18,6 +25,21 @@ simdjson_really_inline simdjson_result value::get_object() && noexcept { simdjson_really_inline simdjson_result value::get_object() & noexcept { return object::try_start(iter); } +simdjson_really_inline simdjson_result value::start_or_resume_object() & noexcept { + if (iter.at_start()) { + return get_object(); + } else { + return object::resume(iter); + } +} +simdjson_really_inline simdjson_result value::start_or_resume_object() && noexcept { + if (iter.at_start()) { + return get_object(); + } else { + return object::resume(iter); + } +} + simdjson_really_inline simdjson_result value::get_raw_json_string() && noexcept { return iter.require_raw_json_string(); } @@ -145,17 +167,43 @@ simdjson_really_inline simdjson_result value::end() & noexcept { return {}; } +simdjson_really_inline simdjson_result value::find_field(std::string_view key) & noexcept { + return start_or_resume_object().find_field(key); +} +simdjson_really_inline simdjson_result value::find_field(std::string_view key) && noexcept { + return std::forward(*this).start_or_resume_object().find_field(key); +} +simdjson_really_inline simdjson_result value::find_field(const char *key) & noexcept { + return start_or_resume_object().find_field(key); +} +simdjson_really_inline simdjson_result value::find_field(const char *key) && noexcept { + return std::forward(*this).start_or_resume_object().find_field(key); +} + +simdjson_really_inline simdjson_result value::find_field_unordered(std::string_view key) & noexcept { + return start_or_resume_object().find_field_unordered(key); +} +simdjson_really_inline simdjson_result value::find_field_unordered(std::string_view key) && noexcept { + return std::forward(*this).start_or_resume_object().find_field_unordered(key); +} +simdjson_really_inline simdjson_result value::find_field_unordered(const char *key) & noexcept { + return start_or_resume_object().find_field_unordered(key); +} +simdjson_really_inline simdjson_result value::find_field_unordered(const char *key) && noexcept { + return std::forward(*this).start_or_resume_object().find_field_unordered(key); +} + simdjson_really_inline simdjson_result value::operator[](std::string_view key) & noexcept { - return get_object()[key]; + return start_or_resume_object()[key]; } simdjson_really_inline simdjson_result value::operator[](std::string_view key) && noexcept { - return std::forward(*this).get_object()[key]; + return std::forward(*this).start_or_resume_object()[key]; } simdjson_really_inline simdjson_result value::operator[](const char *key) & noexcept { - return get_object()[key]; + return start_or_resume_object()[key]; } simdjson_really_inline simdjson_result value::operator[](const char *key) && noexcept { - return std::forward(*this).get_object()[key]; + return std::forward(*this).start_or_resume_object()[key]; } } // namespace ondemand @@ -188,6 +236,40 @@ simdjson_really_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field(std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field(const char *key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field(key); +} + +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field_unordered(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_really_inline simdjson_result simdjson_result::find_field_unordered(const char *key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field_unordered(key); +} + simdjson_really_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { if (error()) { return error(); } return first[key]; diff --git a/include/simdjson/generic/ondemand/value.h b/include/simdjson/generic/ondemand/value.h index 46ba794e8..3faad26cc 100644 --- a/include/simdjson/generic/ondemand/value.h +++ b/include/simdjson/generic/ondemand/value.h @@ -245,32 +245,71 @@ public: simdjson_really_inline simdjson_result end() & noexcept; /** - * Look up a field by name on an object. + * Look up a field by name on an object (order-sensitive). * - * Important notes: + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: * - * * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. - * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. - * * **Once Only:** You may only look up a single field on a value. To look up multiple fields, - * you must cast to object or call `.get_object()`. + * ```c++ + * simdjson::builtin::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. * * @param key The key to look up. - * @returns The value of the field, NO_SUCH_FIELD if the field is not in the object, or - * INCORRECT_TYPE if the JSON value is not an array. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. */ + simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(std::string_view key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(const char *key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(const char *key) && noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field wasn't there when they aren't). + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(const char *key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(const char *key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) && noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](const char *key) & noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](const char *key) && noexcept; protected: /** * Create a value. - * - * Use value::read() instead of this. */ simdjson_really_inline value(const value_iterator &iter) noexcept; @@ -279,6 +318,25 @@ protected: */ simdjson_really_inline void skip() noexcept; + /** + * Start a value at the current position. + * + * (It should already be started; this is just a self-documentation method.) + */ + static simdjson_really_inline value start(const value_iterator &iter) noexcept; + + /** + * Resume a value. + */ + static simdjson_really_inline value resume(const value_iterator &iter) noexcept; + + /** + * Get the object, starting or resuming it as necessary + */ + simdjson_really_inline simdjson_result start_or_resume_object() & noexcept; + /** @overload simdjson_really_inline simdjson_result start_or_resume_object() & noexcept; */ + simdjson_really_inline simdjson_result start_or_resume_object() && noexcept; + // simdjson_really_inline void log_value(const char *type) const noexcept; // simdjson_really_inline void log_error(const char *message) const noexcept; @@ -362,25 +420,66 @@ public: simdjson_really_inline simdjson_result end() & noexcept; /** - * Look up a field by name on an object. + * Look up a field by name on an object (order-sensitive). * - * Important notes: + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: * - * * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. - * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. - * * **Once Only:** You may only look up a single field on a value. To look up multiple fields, - * you must cast to object or call `.get_object()`. + * ```c++ + * simdjson::builtin::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. * * @param key The key to look up. - * @returns The value of the field, NO_SUCH_FIELD if the field is not in the object, or - * INCORRECT_TYPE if the JSON value is not an array. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. */ + simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(std::string_view key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(const char *key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field(const char *key) && noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field wasn't there when they aren't). + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(const char *key) & noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_really_inline simdjson_result find_field_unordered(const char *key) && noexcept; + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](std::string_view key) && noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](const char *key) & noexcept; - /** @overload simdjson_really_inline simdjson_result operator[](std::string_view key) & noexcept; */ + /** @overload simdjson_really_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_really_inline simdjson_result operator[](const char *key) && noexcept; }; diff --git a/include/simdjson/generic/ondemand/value_iterator-inl.h b/include/simdjson/generic/ondemand/value_iterator-inl.h index be187f6ea..da8cb1382 100644 --- a/include/simdjson/generic/ondemand/value_iterator-inl.h +++ b/include/simdjson/generic/ondemand/value_iterator-inl.h @@ -55,6 +55,73 @@ simdjson_warn_unused simdjson_really_inline simdjson_result value_iterator error_code error; bool has_value; + // + // Initially, the object can be in one of a few different places: + // + // 1. The start of the object, at the first field: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2, index 1) + // ``` + // + // 2. When a previous search did not yield a value or the object is empty: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 0) + // { } + // ^ (depth 0, index 2) + // ``` + // + if (!is_open()) { return false; } + if (at_first_field()) { + has_value = true; + + // 3. When a previous search found a field or an iterator yielded a value: + // + // ``` + // // When a field was not fully consumed (or not even touched at all) + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2) + // // When a field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // // When the last field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // ``` + // + } else { + if ((error = skip_child() )) { abandon(); return error; } + if ((error = has_next_field().get(has_value) )) { abandon(); return error; } + } + while (has_value) { + // Get the key and colon, stopping at the value. + raw_json_string actual_key; + if ((error = field_key().get(actual_key) )) { abandon(); return error; }; + if ((error = field_value() )) { abandon(); return error; } + + // If it matches, stop and return + if (actual_key == key) { + logger::log_event(*this, "match", key, -2); + return true; + } + + // No match: skip the value and see if , or } is next + logger::log_event(*this, "no match", key, -2); + SIMDJSON_TRY( skip_child() ); // Skip the value entirely + if ((error = has_next_field().get(has_value) )) { abandon(); return error; } + } + + // If the loop ended, we're out of fields to look at. + return false; +} + +simdjson_warn_unused simdjson_really_inline simdjson_result value_iterator::find_field_unordered_raw(const std::string_view key) noexcept { + error_code error; + bool has_value; + // // Initially, the object can be in one of a few different places: // @@ -476,6 +543,14 @@ simdjson_really_inline bool value_iterator::is_open() const noexcept { return _json_iter->depth() >= depth(); } +simdjson_really_inline bool value_iterator::at_eof() const noexcept { + return _json_iter->at_eof(); +} + +simdjson_really_inline bool value_iterator::at_start() const noexcept { + return _json_iter->token.index == _start_index; +} + simdjson_really_inline bool value_iterator::at_first_field() const noexcept { SIMDJSON_ASSUME( _json_iter->token.index > _start_index ); return _json_iter->token.index == _start_index + 1; diff --git a/include/simdjson/generic/ondemand/value_iterator.h b/include/simdjson/generic/ondemand/value_iterator.h index 6f1ee546d..2ff0b3399 100644 --- a/include/simdjson/generic/ondemand/value_iterator.h +++ b/include/simdjson/generic/ondemand/value_iterator.h @@ -50,6 +50,11 @@ public: */ simdjson_really_inline bool at_eof() const noexcept; + /** + * Tell whether the iterator is at the start of the value + */ + simdjson_really_inline bool at_start() const noexcept; + /** * Tell whether the value is open--if the value has not been used, or the array/object is still open. */ @@ -148,7 +153,8 @@ public: simdjson_warn_unused simdjson_really_inline error_code find_field(const std::string_view key) noexcept; /** - * Find the next field with the given key, *without* unescaping. + * Find the next field with the given key, *without* unescaping. This assumes object order: it + * will not find the field if it was already passed when looking for some *other* field. * * Assumes you have called next_field() or otherwise matched the previous value. * @@ -165,6 +171,26 @@ public: */ simdjson_warn_unused simdjson_really_inline simdjson_result find_field_raw(const std::string_view key) noexcept; + /** + * Find the field with the given key without regard to order, and *without* unescaping. + * + * This is an unordered object lookup: if the field is not found initially, it will cycle around and scan from the beginning. + * + * Assumes you have called next_field() or otherwise matched the previous value. + * + * This means the iterator must be sitting at the next key: + * + * ``` + * { "a": 1, "b": 2 } + * ^ + * ``` + * + * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to + * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may + * fail to match some keys with escapes (\u, \n, etc.). + */ + simdjson_warn_unused simdjson_really_inline simdjson_result find_field_unordered_raw(const std::string_view key) noexcept; + /** @} */ /** diff --git a/tests/ondemand/ondemand_basictests.cpp b/tests/ondemand/ondemand_basictests.cpp index dbfd9b002..14ff71df8 100644 --- a/tests/ondemand/ondemand_basictests.cpp +++ b/tests/ondemand/ondemand_basictests.cpp @@ -1202,6 +1202,175 @@ namespace dom_api_tests { ASSERT_ERROR( doc_result["d"], NO_SUCH_FIELD ); return true; })); + + json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value object; + ASSERT_SUCCESS( doc_result["outer"].get(object) ); + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); + return true; + })); + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result["outer"]; + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); + return true; + })); + TEST_SUCCEED(); + } + + bool object_find_field_unordered() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object; + object = doc_result.get_object(); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( doc.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( doc.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc_result.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc_result.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( doc_result.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( doc_result.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + + json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value object; + ASSERT_SUCCESS( doc_result.find_field_unordered("outer").get(object) ); + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result.find_field_unordered("outer"); + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + TEST_SUCCEED(); + } + + bool object_find_field() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object; + object = doc_result.get_object(); + + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( doc.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( doc.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc_result.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc_result.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( doc_result.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( doc_result.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + + json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value object; + ASSERT_SUCCESS( doc_result.find_field("outer").get(object) ); + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result.find_field("outer"); + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); TEST_SUCCEED(); } @@ -1387,6 +1556,8 @@ namespace dom_api_tests { boolean_values() && null_value() && object_index() && + object_find_field_unordered() && + object_find_field() && nested_object_index() && iterate_object_partial_children() && iterate_array_partial_children() && @@ -1413,7 +1584,7 @@ namespace ordering_tests { auto json = "{\"coordinates\":[{\"x\":1.1,\"y\":2.2,\"z\":3.3}]}"_padded; - bool in_order() { + bool in_order_object_index() { TEST_START(); ondemand::parser parser{}; auto doc = parser.iterate(json); @@ -1428,7 +1599,37 @@ namespace ordering_tests { return (x == 1.1) && (y == 2.2) && (z == 3.3); } - bool out_of_order() { + bool in_order_object_find_field_unordered() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + x += double(point_object.find_field_unordered("x")); + y += double(point_object.find_field_unordered("y")); + z += double(point_object.find_field_unordered("z")); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool in_order_object_find_field() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + x += double(point_object.find_field("x")); + y += double(point_object.find_field("y")); + z += double(point_object.find_field("z")); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool out_of_order_object_index() { TEST_START(); ondemand::parser parser{}; auto doc = parser.iterate(json); @@ -1443,7 +1644,37 @@ namespace ordering_tests { return (x == 1.1) && (y == 2.2) && (z == 3.3); } - bool foreach_lookup() { + bool out_of_order_object_find_field_unordered() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + z += double(point_object.find_field_unordered("z")); + x += double(point_object.find_field_unordered("x")); + y += double(point_object.find_field_unordered("y")); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool out_of_order_object_find_field() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + z += double(point_object.find_field("z")); + ASSERT_ERROR( point_object.find_field("x"), NO_SUCH_FIELD ); + ASSERT_ERROR( point_object.find_field("y"), NO_SUCH_FIELD ); + } + return (x == 0) && (y == 0) && (z == 3.3); + } + + bool foreach_object_field_lookup() { TEST_START(); ondemand::parser parser{}; auto doc = parser.iterate(json); @@ -1464,9 +1695,13 @@ namespace ordering_tests { bool run() { return #if SIMDJSON_EXCEPTIONS - in_order() && - out_of_order() && - foreach_lookup() && + in_order_object_index() && + in_order_object_find_field_unordered() && + in_order_object_find_field() && + out_of_order_object_index() && + out_of_order_object_find_field_unordered() && + out_of_order_object_find_field() && + foreach_object_field_lookup() && #endif true; } From e7e09e444c2c89308b356a0d2c5adad91d07130f Mon Sep 17 00:00:00 2001 From: John Keiser Date: Sat, 19 Dec 2020 13:03:10 -0800 Subject: [PATCH 03/10] Use find_field in benchmark --- benchmark/distinctuserid/ondemand.h | 8 ++++---- benchmark/find_tweet/ondemand.h | 6 +++--- benchmark/kostya/ondemand.h | 12 ++++++------ benchmark/largerandom/ondemand.h | 8 ++++---- benchmark/partial_tweets/ondemand.h | 18 +++++++++--------- include/simdjson/generic/ondemand/object-inl.h | 2 +- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/benchmark/distinctuserid/ondemand.h b/benchmark/distinctuserid/ondemand.h index 933e630fe..fd3963401 100644 --- a/benchmark/distinctuserid/ondemand.h +++ b/benchmark/distinctuserid/ondemand.h @@ -33,15 +33,15 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { ids.clear(); // Walk the document, parsing as we go auto doc = parser.iterate(json); - for (ondemand::object tweet : doc["statuses"]) { + for (ondemand::object tweet : doc.find_field("statuses")) { // We believe that all statuses have a matching // user, and we are willing to throw when they do not. - ids.push_back(tweet["user"]["id"]); + ids.push_back(tweet.find_field("user").find_field("id")); // Not all tweets have a "retweeted_status", but when they do // we want to go and find the user within. - auto retweet = tweet["retweeted_status"]; + auto retweet = tweet.find_field("retweeted_status"); if(!retweet.error()) { - ids.push_back(retweet["user"]["id"]); + ids.push_back(retweet.find_field("user").find_field("id")); } } remove_duplicates(ids); diff --git a/benchmark/find_tweet/ondemand.h b/benchmark/find_tweet/ondemand.h index 0f8576c0f..92e72dc28 100644 --- a/benchmark/find_tweet/ondemand.h +++ b/benchmark/find_tweet/ondemand.h @@ -33,9 +33,9 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { text = ""; // Walk the document, parsing as we go auto doc = parser.iterate(json); - for (ondemand::object tweet : doc["statuses"]) { - if (uint64_t(tweet["id"]) == TWEET_ID) { - text = tweet["text"]; + for (ondemand::object tweet : doc.find_field("statuses")) { + if (uint64_t(tweet.find_field("id")) == TWEET_ID) { + text = tweet.find_field("text"); return true; } } diff --git a/benchmark/kostya/ondemand.h b/benchmark/kostya/ondemand.h index 369ffedb1..1da401205 100644 --- a/benchmark/kostya/ondemand.h +++ b/benchmark/kostya/ondemand.h @@ -27,8 +27,8 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { using std::endl; auto doc = parser.iterate(json); - for (ondemand::object coord : doc["coordinates"]) { - container.emplace_back(my_point{coord["x"], coord["y"], coord["z"]}); + for (ondemand::object coord : doc.find_field("coordinates")) { + container.emplace_back(my_point{coord.find_field("x"), coord.find_field("y"), coord.find_field("z")}); } return true; @@ -56,10 +56,10 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { count = 0; auto doc = parser.iterate(json); - for (ondemand::object coord : doc["coordinates"]) { - sum.x += double(coord["x"]); - sum.y += double(coord["y"]); - sum.z += double(coord["z"]); + for (ondemand::object coord : doc.find_field("coordinates")) { + sum.x += double(coord.find_field("x")); + sum.y += double(coord.find_field("y")); + sum.z += double(coord.find_field("z")); count++; } diff --git a/benchmark/largerandom/ondemand.h b/benchmark/largerandom/ondemand.h index 42c4a9b99..4151e3114 100644 --- a/benchmark/largerandom/ondemand.h +++ b/benchmark/largerandom/ondemand.h @@ -25,7 +25,7 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { auto doc = parser.iterate(json); for (ondemand::object coord : doc) { - container.emplace_back(my_point{coord["x"], coord["y"], coord["z"]}); + container.emplace_back(my_point{coord.find_field("x"), coord.find_field("y"), coord.find_field("z")}); } return true; @@ -54,9 +54,9 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { auto doc = parser.iterate(json); for (ondemand::object coord : doc.get_array()) { - sum.x += double(coord["x"]); - sum.y += double(coord["y"]); - sum.z += double(coord["z"]); + sum.x += double(coord.find_field("x")); + sum.y += double(coord.find_field("y")); + sum.z += double(coord.find_field("z")); count++; } diff --git a/benchmark/partial_tweets/ondemand.h b/benchmark/partial_tweets/ondemand.h index 8ba2079bb..def6e8323 100644 --- a/benchmark/partial_tweets/ondemand.h +++ b/benchmark/partial_tweets/ondemand.h @@ -32,7 +32,7 @@ private: } simdjson_really_inline twitter_user read_user(ondemand::object user) { - return { user["id"], user["screen_name"] }; + return { user.find_field("id"), user.find_field("screen_name") }; } static inline bool displayed_implementation = false; @@ -43,15 +43,15 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { // Walk the document, parsing the tweets as we go auto doc = parser.iterate(json); - for (ondemand::object tweet : doc["statuses"]) { + for (ondemand::object tweet : doc.find_field("statuses")) { tweets.emplace_back(partial_tweets::tweet{ - tweet["created_at"], - tweet["id"], - tweet["text"], - nullable_int(tweet["in_reply_to_status_id"]), - read_user(tweet["user"]), - tweet["retweet_count"], - tweet["favorite_count"] + tweet.find_field("created_at"), + tweet.find_field("id"), + tweet.find_field("text"), + nullable_int(tweet.find_field("in_reply_to_status_id")), + read_user(tweet.find_field("user")), + tweet.find_field("retweet_count"), + tweet.find_field("favorite_count") }); } return true; diff --git a/include/simdjson/generic/ondemand/object-inl.h b/include/simdjson/generic/ondemand/object-inl.h index 9dcb03244..45a46dc94 100644 --- a/include/simdjson/generic/ondemand/object-inl.h +++ b/include/simdjson/generic/ondemand/object-inl.h @@ -18,7 +18,7 @@ simdjson_really_inline simdjson_result object::operator[](const std::stri return find_field_unordered(key); } simdjson_really_inline simdjson_result object::operator[](const std::string_view key) && noexcept { - return find_field_unordered(key); + return std::forward(*this).find_field_unordered(key); } simdjson_really_inline simdjson_result object::find_field(const std::string_view key) & noexcept { bool has_value; From dfc510f009cc62b9f5452fc5981c514c03984952 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Sun, 20 Dec 2020 10:19:12 -0800 Subject: [PATCH 04/10] Add bench_ondemand_largerandom to check theory about executable format --- benchmark/CMakeLists.txt | 5 ++- benchmark/largerandom/CMakeLists.txt | 4 ++ .../bench_ondemand_largerandom.cpp | 14 +++++++ benchmark/largerandom/dom.h | 32 --------------- benchmark/largerandom/iter.h | 39 ------------------- benchmark/largerandom/largerandom.h | 16 +++----- benchmark/largerandom/ondemand.h | 33 ---------------- 7 files changed, 28 insertions(+), 115 deletions(-) create mode 100644 benchmark/largerandom/CMakeLists.txt create mode 100644 benchmark/largerandom/bench_ondemand_largerandom.cpp diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index f15c73ff8..b3c796b2c 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,13 +1,15 @@ include_directories( . linux ) link_libraries(simdjson-windows-headers test-data) - +# bench_sax links against the source if (TARGET benchmark::benchmark) add_executable(bench_sax bench_sax.cpp) target_link_libraries(bench_sax PRIVATE simdjson-internal-flags simdjson-include-source benchmark::benchmark) endif (TARGET benchmark::benchmark) +# Everything else links against simdjson proper link_libraries(simdjson simdjson-flags) + add_executable(benchfeatures benchfeatures.cpp) add_executable(get_corpus_benchmark get_corpus_benchmark.cpp) add_executable(perfdiff perfdiff.cpp) @@ -42,6 +44,7 @@ endif() if (TARGET benchmark::benchmark) link_libraries(benchmark::benchmark) + add_subdirectory(largerandom) add_executable(bench_parse_call bench_parse_call.cpp) add_executable(bench_dom_api bench_dom_api.cpp) add_executable(bench_ondemand bench_ondemand.cpp) diff --git a/benchmark/largerandom/CMakeLists.txt b/benchmark/largerandom/CMakeLists.txt new file mode 100644 index 000000000..f537b0e7a --- /dev/null +++ b/benchmark/largerandom/CMakeLists.txt @@ -0,0 +1,4 @@ +if (TARGET benchmark::benchmark) + link_libraries(benchmark::benchmark) + add_executable(bench_ondemand_largerandom bench_ondemand_largerandom.cpp) +endif() diff --git a/benchmark/largerandom/bench_ondemand_largerandom.cpp b/benchmark/largerandom/bench_ondemand_largerandom.cpp new file mode 100644 index 000000000..e1f35f6b2 --- /dev/null +++ b/benchmark/largerandom/bench_ondemand_largerandom.cpp @@ -0,0 +1,14 @@ +#include "simdjson.h" +#include +#include +#include +#include +SIMDJSON_PUSH_DISABLE_ALL_WARNINGS +#include +SIMDJSON_POP_DISABLE_WARNINGS + +#define BENCHMARK_NO_DOM + +#include "largerandom/ondemand.h" + +BENCHMARK_MAIN(); diff --git a/benchmark/largerandom/dom.h b/benchmark/largerandom/dom.h index 4148eeee0..a2642c04c 100644 --- a/benchmark/largerandom/dom.h +++ b/benchmark/largerandom/dom.h @@ -32,38 +32,6 @@ simdjson_really_inline bool Dom::Run(const padded_string &json) { BENCHMARK_TEMPLATE(LargeRandom, Dom); -namespace sum { - -class Dom { -public: - simdjson_really_inline bool Run(const padded_string &json); - - simdjson_really_inline my_point &Result() { return sum; } - simdjson_really_inline size_t ItemCount() { return count; } - -private: - dom::parser parser{}; - my_point sum{}; - size_t count{}; -}; - -simdjson_really_inline bool Dom::Run(const padded_string &json) { - sum = { 0, 0, 0 }; - count = 0; - - for (auto coord : parser.parse(json)) { - sum.x += double(coord["x"]); - sum.y += double(coord["y"]); - sum.z += double(coord["z"]); - count++; - } - - return true; -} - -BENCHMARK_TEMPLATE(LargeRandomSum, Dom); - -} // namespace sum } // namespace largerandom #endif // SIMDJSON_EXCEPTIONS \ No newline at end of file diff --git a/benchmark/largerandom/iter.h b/benchmark/largerandom/iter.h index 10ab2354e..bb4b2cb8e 100644 --- a/benchmark/largerandom/iter.h +++ b/benchmark/largerandom/iter.h @@ -48,45 +48,6 @@ simdjson_really_inline bool Iter::Run(const padded_string &json) { BENCHMARK_TEMPLATE(LargeRandom, Iter); - -namespace sum { - -class Iter { -public: - simdjson_really_inline bool Run(const padded_string &json); - - simdjson_really_inline my_point &Result() { return sum; } - simdjson_really_inline size_t ItemCount() { return count; } - -private: - ondemand::parser parser{}; - my_point sum{}; - size_t count{}; -}; - -simdjson_really_inline bool Iter::Run(const padded_string &json) { - sum = {0,0,0}; - count = 0; - - auto iter = parser.iterate_raw(json).value(); - if (!iter.start_array()) { return false; } - do { - if (!iter.start_object() || iter.field_key().value() != "x" || iter.field_value()) { return false; } - sum.x += iter.consume_double(); - if (!iter.has_next_field() || iter.field_key().value() != "y" || iter.field_value()) { return false; } - sum.y += iter.consume_double(); - if (!iter.has_next_field() || iter.field_key().value() != "z" || iter.field_value()) { return false; } - sum.z += iter.consume_double(); - if (*iter.advance() != '}') { return false; } - count++; - } while (iter.has_next_element()); - - return true; -} - -BENCHMARK_TEMPLATE(LargeRandomSum, Iter); - -} // namespace sum } // namespace largerandom #endif // SIMDJSON_EXCEPTIONS diff --git a/benchmark/largerandom/largerandom.h b/benchmark/largerandom/largerandom.h index c8bf3d7a8..e7fc6d5ad 100644 --- a/benchmark/largerandom/largerandom.h +++ b/benchmark/largerandom/largerandom.h @@ -8,9 +8,6 @@ namespace largerandom { template static void LargeRandom(benchmark::State &state); -namespace sum { -template static void LargeRandomSum(benchmark::State &state); -} using namespace simdjson; @@ -59,22 +56,21 @@ simdjson_unused static std::ostream &operator<<(std::ostream &o, const my_point // #include #include "event_counter.h" +#ifndef BENCHMARK_NO_DOM #include "dom.h" +#endif #include "json_benchmark.h" namespace largerandom { template static void LargeRandom(benchmark::State &state) { +#ifdef BENCHMARK_NO_DOM + JsonBenchmark(state, get_built_json_array()); +#else JsonBenchmark(state, get_built_json_array()); +#endif } -namespace sum { - -template static void LargeRandomSum(benchmark::State &state) { - JsonBenchmark(state, get_built_json_array()); -} - -} } // namespace largerandom #endif // SIMDJSON_EXCEPTIONS diff --git a/benchmark/largerandom/ondemand.h b/benchmark/largerandom/ondemand.h index 4151e3114..f3b5fcaac 100644 --- a/benchmark/largerandom/ondemand.h +++ b/benchmark/largerandom/ondemand.h @@ -33,39 +33,6 @@ simdjson_really_inline bool OnDemand::Run(const padded_string &json) { BENCHMARK_TEMPLATE(LargeRandom, OnDemand); - -namespace sum { - -class OnDemand { -public: - simdjson_really_inline bool Run(const padded_string &json); - simdjson_really_inline my_point &Result() { return sum; } - simdjson_really_inline size_t ItemCount() { return count; } - -private: - ondemand::parser parser{}; - my_point sum{}; - size_t count{}; -}; - -simdjson_really_inline bool OnDemand::Run(const padded_string &json) { - sum = {0,0,0}; - count = 0; - - auto doc = parser.iterate(json); - for (ondemand::object coord : doc.get_array()) { - sum.x += double(coord.find_field("x")); - sum.y += double(coord.find_field("y")); - sum.z += double(coord.find_field("z")); - count++; - } - - return true; -} - -BENCHMARK_TEMPLATE(LargeRandomSum, OnDemand); - -} // namespace sum } // namespace largerandom #endif // SIMDJSON_EXCEPTIONS From a1cf588d5f9f505302651cc86c3839bb83d02cc6 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Sun, 20 Dec 2020 20:06:52 -0800 Subject: [PATCH 05/10] Fix GCC 7 warning when inlining does its job --- include/simdjson/common_defs.h | 2 + .../generic/ondemand/json_iterator-inl.h | 7 +++ tests/ondemand/ondemand_basictests.cpp | 62 ++++++++++--------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/include/simdjson/common_defs.h b/include/simdjson/common_defs.h index d50bde062..bea4f833b 100644 --- a/include/simdjson/common_defs.h +++ b/include/simdjson/common_defs.h @@ -100,6 +100,7 @@ constexpr size_t DEFAULT_MAX_DEPTH = 1024; #endif #define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_VS_WARNING(4996) + #define SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING #define SIMDJSON_POP_DISABLE_WARNINGS __pragma(warning( pop )) #else // SIMDJSON_REGULAR_VISUAL_STUDIO @@ -139,6 +140,7 @@ constexpr size_t DEFAULT_MAX_DEPTH = 1024; #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS #endif #define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wdeprecated-declarations) + #define SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wstrict-overflow) #define SIMDJSON_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop") diff --git a/include/simdjson/generic/ondemand/json_iterator-inl.h b/include/simdjson/generic/ondemand/json_iterator-inl.h index 0d4caf5c4..447373fc4 100644 --- a/include/simdjson/generic/ondemand/json_iterator-inl.h +++ b/include/simdjson/generic/ondemand/json_iterator-inl.h @@ -29,6 +29,11 @@ simdjson_really_inline json_iterator::json_iterator(ondemand::parser *_parser) n logger::log_headers(); } +// GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller +// relating depth and parent_depth, which is a desired effect. The warning does not show up if the +// skip_child() function is not marked inline). +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING simdjson_warn_unused simdjson_really_inline error_code json_iterator::skip_child(depth_t parent_depth) noexcept { if (depth() <= parent_depth) { return SUCCESS; } @@ -90,6 +95,8 @@ simdjson_warn_unused simdjson_really_inline error_code json_iterator::skip_child return report_error(TAPE_ERROR, "not enough close braces"); } +SIMDJSON_POP_DISABLE_WARNINGS + simdjson_really_inline bool json_iterator::at_root() const noexcept { return token.checkpoint() == root_checkpoint(); } diff --git a/tests/ondemand/ondemand_basictests.cpp b/tests/ondemand/ondemand_basictests.cpp index 14ff71df8..8cc193742 100644 --- a/tests/ondemand/ondemand_basictests.cpp +++ b/tests/ondemand/ondemand_basictests.cpp @@ -1215,7 +1215,7 @@ namespace dom_api_tests { ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); return true; })); - SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { simdjson_result object = doc_result["outer"]; ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); @@ -1690,7 +1690,7 @@ namespace ordering_tests { } return (x == 1.1) && (y == 2.2) && (z == 3.3); } -#endif +#endif // SIMDJSON_EXCEPTIONS bool run() { return @@ -1725,6 +1725,7 @@ namespace twitter_tests { })); TEST_SUCCEED(); } + #if SIMDJSON_EXCEPTIONS bool twitter_example() { TEST_START(); @@ -1746,7 +1747,7 @@ namespace twitter_tests { } TEST_SUCCEED(); } -#endif +#endif // SIMDJSON_EXCEPTIONS bool twitter_default_profile() { TEST_START(); @@ -1785,11 +1786,21 @@ namespace twitter_tests { auto media = tweet["entities"]["media"]; if (!media.error()) { for (auto image : media) { + uint64_t id_val; + std::string_view id_string; + ASSERT_SUCCESS( image["id"].get(id_val) ); + ASSERT_SUCCESS( image["id_str"].get(id_string) ); + std::cout << "id = " << id_val << std::endl; + std::cout << "id_string = " << id_string << std::endl; + for (auto size : image["sizes"].get_object()) { - auto size_value = size.value().get_object(); + std::string_view size_key; + ASSERT_SUCCESS( size.unescaped_key().get(size_key) ); + std::cout << "Type of image size = " << size_key << std::endl; + uint64_t width, height; - ASSERT_SUCCESS( size_value["w"].get(width) ); - ASSERT_SUCCESS( size_value["h"].get(height) ); + ASSERT_SUCCESS( size.value()["w"].get(width) ); + ASSERT_SUCCESS( size.value()["h"].get(height) ); image_sizes.insert(make_pair(width, height)); } } @@ -1836,6 +1847,14 @@ namespace twitter_tests { TEST_SUCCEED(); } + /* + * Fun fact: id and id_str can differ: + * 505866668485386240 and 505866668485386241. + * Presumably, it is because doubles are used + * at some point in the process and the number + * 505866668485386241 cannot be represented as a double. + * (not our fault) + */ bool twitter_image_sizes_exception() { TEST_START(); padded_string json = padded_string::load(TWITTER_JSON); @@ -1845,30 +1864,13 @@ namespace twitter_tests { for (auto tweet : doc_result["statuses"]) { auto media = tweet["entities"]["media"]; if (!media.error()) { - for (ondemand::object image : media) { - /** - * Fun fact: id and id_str can differ: - * 505866668485386240 and 505866668485386241. - * Presumably, it is because doubles are used - * at some point in the process and the number - * 505866668485386241 cannot be represented as a double. - * (not our fault) - */ - uint64_t id_val = image["id"]; - std::cout << "id = " << id_val << std::endl; - std::string_view id_string = image["id_str"]; - std::cout << "id_string = " << id_string << std::endl; + for (auto image : media) { + std::cout << "id = " << uint64_t(image["id"]) << std::endl; + std::cout << "id_string = " << std::string_view(image["id_str"]) << std::endl; for (auto size : image["sizes"].get_object()) { - /** - * We want to know the key that describes the size. - */ - std::string_view raw_size_key_v = size.unescaped_key(); - std::cout << "Type of image size = " << raw_size_key_v << std::endl; - ondemand::object size_value = size.value(); - int64_t width = size_value["w"]; - int64_t height = size_value["h"]; - std::cout << width << " x " << height << std::endl; - image_sizes.insert(make_pair(width, height)); + std::cout << "Type of image size = " << std::string_view(size.unescaped_key()) << std::endl; + // NOTE: the uint64_t is required so that each value is actually parsed before the pair is created + image_sizes.insert(make_pair(size.value()["w"], size.value()["h"])); } } } @@ -1879,7 +1881,7 @@ namespace twitter_tests { TEST_SUCCEED(); } -#endif +#endif // SIMDJSON_EXCEPTIONS bool run() { return From a405173d593511df0482c1d8188749286e634a02 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Mon, 21 Dec 2020 10:29:36 -0800 Subject: [PATCH 06/10] Break up ondemand_basictests into smaller executables (Allows for faster compilation speeds) --- tests/ondemand/CMakeLists.txt | 22 +- tests/ondemand/ondemand_active_tests.cpp | 85 + tests/ondemand/ondemand_basictests.cpp | 2370 ----------------- tests/ondemand/ondemand_compilation_tests.cpp | 80 + tests/ondemand/ondemand_dom_api_tests.cpp | 1208 +++++++++ tests/ondemand/ondemand_error_tests.cpp | 438 +++ tests/ondemand/ondemand_key_string_tests.cpp | 47 + tests/ondemand/ondemand_number_tests.cpp | 203 ++ tests/ondemand/ondemand_ordering_tests.cpp | 154 ++ tests/ondemand/ondemand_parse_api_tests.cpp | 57 + tests/ondemand/ondemand_twitter_tests.cpp | 210 ++ tests/ondemand/test_ondemand.h | 59 + tests/test_macros.h | 1 - 13 files changed, 2561 insertions(+), 2373 deletions(-) create mode 100644 tests/ondemand/ondemand_active_tests.cpp delete mode 100644 tests/ondemand/ondemand_basictests.cpp create mode 100644 tests/ondemand/ondemand_compilation_tests.cpp create mode 100644 tests/ondemand/ondemand_dom_api_tests.cpp create mode 100644 tests/ondemand/ondemand_error_tests.cpp create mode 100644 tests/ondemand/ondemand_key_string_tests.cpp create mode 100644 tests/ondemand/ondemand_number_tests.cpp create mode 100644 tests/ondemand/ondemand_ordering_tests.cpp create mode 100644 tests/ondemand/ondemand_parse_api_tests.cpp create mode 100644 tests/ondemand/ondemand_twitter_tests.cpp diff --git a/tests/ondemand/CMakeLists.txt b/tests/ondemand/CMakeLists.txt index 1cc501fba..a6f479cf6 100644 --- a/tests/ondemand/CMakeLists.txt +++ b/tests/ondemand/CMakeLists.txt @@ -1,7 +1,25 @@ # All remaining tests link with simdjson proper link_libraries(simdjson) include_directories(..) -add_cpp_test(ondemand_basictests LABELS acceptance per_implementation) + +add_cpp_test(ondemand_active_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_compilation_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_dom_api_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_error_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_key_string_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_number_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_ordering_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_parse_api_tests LABELS acceptance per_implementation ondemand) +add_cpp_test(ondemand_twitter_tests LABELS acceptance per_implementation ondemand) + if(HAVE_POSIX_FORK AND HAVE_POSIX_WAIT) # assert tests use fork and wait, which aren't on MSVC - add_cpp_test(ondemand_assert_out_of_order_values LABELS per_implementation explicitonly assert) + add_cpp_test(ondemand_assert_out_of_order_values LABELS per_implementation explicitonly assert ondemand) endif() + +# Copy the simdjson dll into the tests directory +if(MSVC) + add_custom_command(TARGET ondemand_dom_api_tests POST_BUILD # Adds a post-build event + COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake -E copy_if_different..." + "$" # <--this is in-file + "$") # <--this is out-file path +endif(MSVC) diff --git a/tests/ondemand/ondemand_active_tests.cpp b/tests/ondemand/ondemand_active_tests.cpp new file mode 100644 index 000000000..020561fe8 --- /dev/null +++ b/tests/ondemand/ondemand_active_tests.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace active_tests { + +#if SIMDJSON_EXCEPTIONS + + bool parser_child() { + TEST_START(); + ondemand::parser parser; + const padded_string json = R"({ "parent": {"child1": {"name": "John"} , "child2": {"name": "Daniel"}} })"_padded; + auto doc = parser.iterate(json); + ondemand::object parent = doc["parent"]; + { + ondemand::object c1 = parent["child1"]; + if(std::string_view(c1["name"]) != "John") { return false; } + } + { + ondemand::object c2 = parent["child2"]; + if(std::string_view(c2["name"]) != "Daniel") { return false; } + } + return true; + } + + bool parser_doc_correct() { + TEST_START(); + ondemand::parser parser; + const padded_string json = R"({ "key1": 1, "key2":2, "key3": 3 })"_padded; + auto doc = parser.iterate(json); + ondemand::object root_object = doc.get_object(); + int64_t k1 = root_object["key1"]; + int64_t k2 = root_object["key2"]; + int64_t k3 = root_object["key3"]; + return (k1 == 1) && (k2 == 2) && (k3 == 3); + } + + bool parser_doc_limits() { + TEST_START(); + ondemand::parser parser; + const padded_string json = R"({ "key1": 1, "key2":2, "key3": 3 })"_padded; + auto doc = parser.iterate(json); + int64_t k1 = doc["key1"]; + try { + int64_t k2 = doc["key2"]; + (void) k2; + } catch (simdjson::simdjson_error &) { + return true; // we expect to fail. + } + (void) k1; + return false; + } + +#endif // SIMDJSON_EXCEPTIONS + + bool run() { + return +#if SIMDJSON_EXCEPTIONS + parser_child() && + parser_doc_correct() && + // parser_doc_limits() && // Failure is dependent on build type here ... +#endif // SIMDJSON_EXCEPTIONS + true; + } + +} // namespace active_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, active_tests::run); +} diff --git a/tests/ondemand/ondemand_basictests.cpp b/tests/ondemand/ondemand_basictests.cpp deleted file mode 100644 index 8cc193742..000000000 --- a/tests/ondemand/ondemand_basictests.cpp +++ /dev/null @@ -1,2370 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "simdjson.h" -#include "test_ondemand.h" - -// const size_t AMAZON_CELLPHONES_NDJSON_DOC_COUNT = 793; -#define SIMDJSON_SHOW_DEFINE(x) printf("%s=%s\n", #x, STRINGIFY(x)) - -using namespace simdjson; -using namespace simdjson::builtin; - -#if SIMDJSON_EXCEPTIONS - -// bogus functions for compilation tests -void process1(int ) {} -void process2(int ) {} -void process3(int ) {} - -// Do not run this, it is only meant to compile -void compilation_test_1() { - const padded_string bogus = ""_padded; - ondemand::parser parser; - auto doc = parser.iterate(bogus); - for (ondemand::object my_object : doc["mykey"]) { - for (auto field : my_object) { - if (field.key() == "key_value1") { process1(field.value()); } - else if (field.key() == "key_value2") { process2(field.value()); } - else if (field.key() == "key_value3") { process3(field.value()); } - } - } -} - - -// Do not run this, it is only meant to compile - void compilation_test_2() { - const padded_string bogus = ""_padded; - ondemand::parser parser; - auto doc = parser.iterate(bogus); - std::set default_users; - ondemand::array tweets = doc["statuses"].get_array(); - for (auto tweet_value : tweets) { - auto tweet = tweet_value.get_object(); - ondemand::object user = tweet["user"].get_object(); - std::string_view screen_name = user["screen_name"].get_string(); - bool default_profile = user["default_profile"].get_bool(); - if (default_profile) { default_users.insert(screen_name); } - } -} - - -// Do not run this, it is only meant to compile -void compilation_test_3() { - const padded_string bogus = ""_padded; - ondemand::parser parser; - auto doc = parser.iterate(bogus); - ondemand::array tweets; - if(! doc["statuses"].get(tweets)) { return; } - for (auto tweet_value : tweets) { - auto tweet = tweet_value.get_object(); - for (auto field : tweet) { - std::string_view key = field.unescaped_key().value(); - std::cout << "key = " << key << std::endl; - std::string_view val = std::string_view(field.value()); - std::cout << "value (assuming it is a string) = " << val << std::endl; - } - } -} -#endif - -#define ONDEMAND_SUBTEST(NAME, JSON, TEST) \ -{ \ - std::cout << "- Subtest " << (NAME) << " - JSON: " << (JSON) << " ..." << std::endl; \ - if (!test_ondemand_doc(JSON##_padded, [&](auto doc) { \ - return (TEST); \ - })) { \ - return false; \ - } \ -} - - -namespace key_string_tests { -#if SIMDJSON_EXCEPTIONS - bool parser_key_value() { - TEST_START(); - ondemand::parser parser; - const padded_string json = R"({ "1": "1", "2": "2", "3": "3", "abc": "abc", "\u0075": "\u0075" })"_padded; - auto doc = parser.iterate(json); - for(auto field : doc.get_object()) { - std::string_view keyv = field.unescaped_key(); - std::string_view valuev = field.value(); - if(keyv != valuev) { return false; } - } - return true; - } -#endif - bool run() { - return -#if SIMDJSON_EXCEPTIONS - parser_key_value() && -#endif - true; - } - -} - -namespace active_tests { -#if SIMDJSON_EXCEPTIONS - bool parser_child() { - TEST_START(); - ondemand::parser parser; - const padded_string json = R"({ "parent": {"child1": {"name": "John"} , "child2": {"name": "Daniel"}} })"_padded; - auto doc = parser.iterate(json); - ondemand::object parent = doc["parent"]; - { - ondemand::object c1 = parent["child1"]; - if(std::string_view(c1["name"]) != "John") { return false; } - } - { - ondemand::object c2 = parent["child2"]; - if(std::string_view(c2["name"]) != "Daniel") { return false; } - } - return true; - } - bool parser_doc_correct() { - TEST_START(); - ondemand::parser parser; - const padded_string json = R"({ "key1": 1, "key2":2, "key3": 3 })"_padded; - auto doc = parser.iterate(json); - ondemand::object root_object = doc.get_object(); - int64_t k1 = root_object["key1"]; - int64_t k2 = root_object["key2"]; - int64_t k3 = root_object["key3"]; - return (k1 == 1) && (k2 == 2) && (k3 == 3); - } - - bool parser_doc_limits() { - TEST_START(); - ondemand::parser parser; - const padded_string json = R"({ "key1": 1, "key2":2, "key3": 3 })"_padded; - auto doc = parser.iterate(json); - int64_t k1 = doc["key1"]; - try { - int64_t k2 = doc["key2"]; - (void) k2; - } catch (simdjson::simdjson_error &) { - return true; // we expect to fail. - } - (void) k1; - return false; - } -#endif - bool run() { - return -#if SIMDJSON_EXCEPTIONS - parser_child() && - parser_doc_correct() && - // parser_doc_limits() && // Failure is dependent on build type here ... -#endif - true; - } - -} -namespace number_tests { - - bool small_integers() { - std::cout << __func__ << std::endl; - for (int64_t m = 10; m < 20; m++) { - for (int64_t i = -1024; i < 1024; i++) { - if(!test_ondemand(std::to_string(i), - [&](int64_t actual) { - ASSERT_EQUAL(actual, i); - return true; - })) { - return false; - } // if - } // for i - } // for m - return true; - } - - bool powers_of_two() { - std::cout << __func__ << std::endl; - - // converts the double "expected" to a padded string - auto format_into_padded=[](const double expected) -> padded_string - { - char buf[1024]; - const auto n = std::snprintf(buf, - sizeof(buf), - "%.*e", - std::numeric_limits::max_digits10 - 1, - expected); - const auto nz=static_cast(n); - if (n<0 || nz >= sizeof(buf)) { std::abort(); } - return padded_string(buf, nz); - }; - - for (int i = -1075; i < 1024; ++i) {// large negative values should be zero. - const double expected = std::pow(2, i); - const auto buf=format_into_padded(expected); - std::fflush(nullptr); - if(!test_ondemand(buf, - [&](double actual) { - if(actual!=expected) { - std::cerr << "JSON '" << buf << " parsed to "; - std::fprintf( stderr," %18.18g instead of %18.18g\n", actual, expected); // formatting numbers is easier with printf - SIMDJSON_SHOW_DEFINE(FLT_EVAL_METHOD); - return false; - } - return true; - })) { - return false; - } // if - } // for i - return true; - } - - static const double testing_power_of_ten[] = { - 1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299, - 1e-298, 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291, 1e-290, - 1e-289, 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282, 1e-281, - 1e-280, 1e-279, 1e-278, 1e-277, 1e-276, 1e-275, 1e-274, 1e-273, 1e-272, - 1e-271, 1e-270, 1e-269, 1e-268, 1e-267, 1e-266, 1e-265, 1e-264, 1e-263, - 1e-262, 1e-261, 1e-260, 1e-259, 1e-258, 1e-257, 1e-256, 1e-255, 1e-254, - 1e-253, 1e-252, 1e-251, 1e-250, 1e-249, 1e-248, 1e-247, 1e-246, 1e-245, - 1e-244, 1e-243, 1e-242, 1e-241, 1e-240, 1e-239, 1e-238, 1e-237, 1e-236, - 1e-235, 1e-234, 1e-233, 1e-232, 1e-231, 1e-230, 1e-229, 1e-228, 1e-227, - 1e-226, 1e-225, 1e-224, 1e-223, 1e-222, 1e-221, 1e-220, 1e-219, 1e-218, - 1e-217, 1e-216, 1e-215, 1e-214, 1e-213, 1e-212, 1e-211, 1e-210, 1e-209, - 1e-208, 1e-207, 1e-206, 1e-205, 1e-204, 1e-203, 1e-202, 1e-201, 1e-200, - 1e-199, 1e-198, 1e-197, 1e-196, 1e-195, 1e-194, 1e-193, 1e-192, 1e-191, - 1e-190, 1e-189, 1e-188, 1e-187, 1e-186, 1e-185, 1e-184, 1e-183, 1e-182, - 1e-181, 1e-180, 1e-179, 1e-178, 1e-177, 1e-176, 1e-175, 1e-174, 1e-173, - 1e-172, 1e-171, 1e-170, 1e-169, 1e-168, 1e-167, 1e-166, 1e-165, 1e-164, - 1e-163, 1e-162, 1e-161, 1e-160, 1e-159, 1e-158, 1e-157, 1e-156, 1e-155, - 1e-154, 1e-153, 1e-152, 1e-151, 1e-150, 1e-149, 1e-148, 1e-147, 1e-146, - 1e-145, 1e-144, 1e-143, 1e-142, 1e-141, 1e-140, 1e-139, 1e-138, 1e-137, - 1e-136, 1e-135, 1e-134, 1e-133, 1e-132, 1e-131, 1e-130, 1e-129, 1e-128, - 1e-127, 1e-126, 1e-125, 1e-124, 1e-123, 1e-122, 1e-121, 1e-120, 1e-119, - 1e-118, 1e-117, 1e-116, 1e-115, 1e-114, 1e-113, 1e-112, 1e-111, 1e-110, - 1e-109, 1e-108, 1e-107, 1e-106, 1e-105, 1e-104, 1e-103, 1e-102, 1e-101, - 1e-100, 1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93, 1e-92, - 1e-91, 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84, 1e-83, - 1e-82, 1e-81, 1e-80, 1e-79, 1e-78, 1e-77, 1e-76, 1e-75, 1e-74, - 1e-73, 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66, 1e-65, - 1e-64, 1e-63, 1e-62, 1e-61, 1e-60, 1e-59, 1e-58, 1e-57, 1e-56, - 1e-55, 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48, 1e-47, - 1e-46, 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40, 1e-39, 1e-38, - 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, - 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20, - 1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, - 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, - 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, - 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, - 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, - 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32, 1e33, 1e34, - 1e35, 1e36, 1e37, 1e38, 1e39, 1e40, 1e41, 1e42, 1e43, - 1e44, 1e45, 1e46, 1e47, 1e48, 1e49, 1e50, 1e51, 1e52, - 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59, 1e60, 1e61, - 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69, 1e70, - 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79, - 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, - 1e89, 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, - 1e98, 1e99, 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, - 1e107, 1e108, 1e109, 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, - 1e116, 1e117, 1e118, 1e119, 1e120, 1e121, 1e122, 1e123, 1e124, - 1e125, 1e126, 1e127, 1e128, 1e129, 1e130, 1e131, 1e132, 1e133, - 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, 1e140, 1e141, 1e142, - 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, 1e150, 1e151, - 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159, 1e160, - 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168, 1e169, - 1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177, 1e178, - 1e179, 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186, 1e187, - 1e188, 1e189, 1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, - 1e197, 1e198, 1e199, 1e200, 1e201, 1e202, 1e203, 1e204, 1e205, - 1e206, 1e207, 1e208, 1e209, 1e210, 1e211, 1e212, 1e213, 1e214, - 1e215, 1e216, 1e217, 1e218, 1e219, 1e220, 1e221, 1e222, 1e223, - 1e224, 1e225, 1e226, 1e227, 1e228, 1e229, 1e230, 1e231, 1e232, - 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239, 1e240, 1e241, - 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249, 1e250, - 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259, - 1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267, 1e268, - 1e269, 1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276, 1e277, - 1e278, 1e279, 1e280, 1e281, 1e282, 1e283, 1e284, 1e285, 1e286, - 1e287, 1e288, 1e289, 1e290, 1e291, 1e292, 1e293, 1e294, 1e295, - 1e296, 1e297, 1e298, 1e299, 1e300, 1e301, 1e302, 1e303, 1e304, - 1e305, 1e306, 1e307, 1e308}; - - - - bool powers_of_ten() { - std::cout << __func__ << std::endl; - char buf[1024]; - - const bool is_pow_correct{1e-308 == std::pow(10,-308)}; - const int start_point = is_pow_correct ? -10000 : -307; - if(!is_pow_correct) { - std::cout << "On your system, the pow function is busted. Sorry about that. " << std::endl; - } - for (int i = start_point; i <= 308; ++i) {// large negative values should be zero. - const size_t n = std::snprintf(buf, sizeof(buf), "1e%d", i); - if (n >= sizeof(buf)) { std::abort(); } - std::fflush(nullptr); - const double expected = ((i >= -307) ? testing_power_of_ten[i + 307]: std::pow(10, i)); - - if(!test_ondemand(padded_string(buf, n), [&](double actual) { - if(actual!=expected) { - std::cerr << "JSON '" << buf << " parsed to "; - std::fprintf( stderr," %18.18g instead of %18.18g\n", actual, expected); // formatting numbers is easier with printf - SIMDJSON_SHOW_DEFINE(FLT_EVAL_METHOD); - return false; - } - return true; - })) { - return false; - } // if - } // for i - std::printf("Powers of 10 can be parsed.\n"); - return true; - } - - void github_issue_1273() { - padded_string bad(std::string_view("0.0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000122978293824")); - simdjson::builtin::ondemand::parser parser; - simdjson_unused auto blah=parser.iterate(bad); - double x; - simdjson_unused auto blah2=blah.get(x); - } - - bool old_crashes() { - github_issue_1273(); - return true; - } - - bool run() { - return small_integers() && - powers_of_two() && - powers_of_ten() && - old_crashes(); - } -} - - -namespace parse_api_tests { - using namespace std; - using namespace simdjson; - using namespace simdjson::dom; - - const padded_string BASIC_JSON = "[1,2,3]"_padded; - const padded_string BASIC_NDJSON = "[1,2,3]\n[4,5,6]"_padded; - const padded_string EMPTY_NDJSON = ""_padded; - - bool parser_iterate() { - TEST_START(); - ondemand::parser parser; - auto doc = parser.iterate(BASIC_JSON); - ASSERT_SUCCESS( doc.get_array() ); - return true; - } - -#if SIMDJSON_EXCEPTIONS - bool parser_iterate_exception() { - TEST_START(); - ondemand::parser parser; - auto doc = parser.iterate(BASIC_JSON); - simdjson_unused ondemand::array array = doc; - return true; - } -#endif - - bool run() { - return parser_iterate() && -#if SIMDJSON_EXCEPTIONS - parser_iterate_exception() && -#endif - true; - } -} - -namespace dom_api_tests { - using namespace std; - using namespace simdjson; - using namespace simdjson::dom; - - bool iterate_object() { - TEST_START(); - auto json = R"({ "a": 1, "b": 2, "c": 3 })"_padded; - const char* expected_key[] = { "a", "b", "c" }; - const uint64_t expected_value[] = { 1, 2, 3 }; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - size_t i = 0; - for (auto [ field, error ] : object) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL( field.key(), expected_key[i]); - ASSERT_EQUAL( field.value().get_uint64().first, expected_value[i] ); - i++; - } - ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object_result = doc_result.get_object(); - size_t i = 0; - for (auto [ field, error ] : object_result) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL( field.key(), expected_key[i] ); - ASSERT_EQUAL( field.value().get_uint64().first, expected_value[i] ); - i++; - } - ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) ); - return true; - })); - TEST_SUCCEED(); - } - - bool iterate_array() { - TEST_START(); - const auto json = R"([ 1, 10, 100 ])"_padded; - const uint64_t expected_value[] = { 1, 10, 100 }; - - SUBTEST("ondemand::array", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::array array; - ASSERT_SUCCESS( doc_result.get(array) ); - size_t i=0; - for (auto value : array) { - int64_t actual; - ASSERT_SUCCESS( value.get(actual) ); - ASSERT_EQUAL(actual, expected_value[i]); - i++; - } - ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result array = doc_result.get_array(); - size_t i=0; - for (simdjson_unused auto value : array) { int64_t actual; ASSERT_SUCCESS( value.get(actual) ); ASSERT_EQUAL(actual, expected_value[i]); i++; } - ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); - return true; - })); - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - size_t i=0; - for (simdjson_unused auto value : doc) { int64_t actual; ASSERT_SUCCESS( value.get(actual) ); ASSERT_EQUAL(actual, expected_value[i]); i++; } - ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - size_t i=0; - for (simdjson_unused auto value : doc_result) { int64_t actual; ASSERT_SUCCESS( value.get(actual) ); ASSERT_EQUAL(actual, expected_value[i]); i++; } - ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); - return true; - })); - TEST_SUCCEED(); - } - - bool iterate_object_partial_children() { - TEST_START(); - auto json = R"( - { - "scalar_ignore": 0, - "empty_array_ignore": [], - "empty_object_ignore": {}, - "object_break": { "x": 3, "y": 33 }, - "object_break_unused": { "x": 4, "y": 44 }, - "object_index": { "x": 5, "y": 55 }, - "object_index_unused": { "x": 6, "y": 66 }, - "array_break": [ 7, 77, 777 ], - "array_break_unused": [ 8, 88, 888 ], - "quadruple_nested_break": { "a": [ { "b": [ 9, 99 ], "c": 999 }, 9999 ], "d": 99999 }, - "actual_value": 10 - } - )"_padded; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - size_t i = 0; - for (auto field : object) { - ondemand::raw_json_string key; - ASSERT_SUCCESS( field.key().get(key) ); - - switch (i) { - case 0: { - ASSERT_EQUAL(key, "scalar_ignore"); - std::cout << " - After ignoring empty scalar ..." << std::endl; - break; - } - case 1: { - ASSERT_EQUAL(key, "empty_array_ignore"); - std::cout << " - After ignoring empty array ..." << std::endl; - break; - } - case 2: { - ASSERT_EQUAL(key, "empty_object_ignore"); - std::cout << " - After ignoring empty object ..." << std::endl; - break; - } - // Break after using first value in child object - case 3: { - ASSERT_EQUAL(key, "object_break"); - - for (auto [ child_field, error ] : field.value().get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - uint64_t x; - ASSERT_SUCCESS( child_field.value().get(x) ); - ASSERT_EQUAL(x, 3); - break; // Break after the first value - } - std::cout << " - After using first value in child object ..." << std::endl; - break; - } - - // Break without using first value in child object - case 4: { - ASSERT_EQUAL(key, "object_break_unused"); - - for (auto [ child_field, error ] : field.value().get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - break; - } - std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; - break; - } - - // Only look up one field in child object - case 5: { - ASSERT_EQUAL(key, "object_index"); - - uint64_t x; - ASSERT_SUCCESS( field.value()["x"].get(x) ); - ASSERT_EQUAL( x, 5 ); - std::cout << " - After looking up one field in child object ..." << std::endl; - break; - } - - // Only look up one field in child object, but don't use it - case 6: { - ASSERT_EQUAL(key, "object_index_unused"); - - ASSERT_SUCCESS( field.value()["x"] ); - std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; - break; - } - - // Break after first value in child array - case 7: { - ASSERT_EQUAL(key, "array_break"); - for (auto child_value : field.value()) { - uint64_t x; - ASSERT_SUCCESS( child_value.get(x) ); - ASSERT_EQUAL( x, 7 ); - break; - } - std::cout << " - After using first value in child array ..." << std::endl; - break; - } - - // Break without using first value in child array - case 8: { - ASSERT_EQUAL(key, "array_break_unused"); - for (auto child_value : field.value()) { - ASSERT_SUCCESS(child_value); - break; - } - std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; - break; - } - - // Break out of multiple child loops - case 9: { - ASSERT_EQUAL(key, "quadruple_nested_break"); - for (auto child1 : field.value().get_object()) { - for (auto child2 : child1.value().get_array()) { - for (auto child3 : child2.get_object()) { - for (auto child4 : child3.value().get_array()) { - uint64_t x; - ASSERT_SUCCESS( child4.get(x) ); - ASSERT_EQUAL( x, 9 ); - break; - } - break; - } - break; - } - break; - } - std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; - break; - } - - // Test the actual value - case 10: { - ASSERT_EQUAL(key, "actual_value"); - uint64_t actual_value; - ASSERT_SUCCESS( field.value().get(actual_value) ); - ASSERT_EQUAL( actual_value, 10 ); - break; - } - } - - i++; - } - ASSERT_EQUAL( i, 11 ); // Make sure we found all the keys we expected - return true; - })); - return true; - } - - bool iterate_array_partial_children() { - TEST_START(); - auto json = R"( - [ - 0, - [], - {}, - { "x": 3, "y": 33 }, - { "x": 4, "y": 44 }, - { "x": 5, "y": 55 }, - { "x": 6, "y": 66 }, - [ 7, 77, 777 ], - [ 8, 88, 888 ], - { "a": [ { "b": [ 9, 99 ], "c": 999 }, 9999 ], "d": 99999 }, - 10 - ] - )"_padded; - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - size_t i = 0; - for (auto value : doc_result) { - ASSERT_SUCCESS(value); - - switch (i) { - case 0: { - std::cout << " - After ignoring empty scalar ..." << std::endl; - break; - } - case 1: { - std::cout << " - After ignoring empty array ..." << std::endl; - break; - } - case 2: { - std::cout << " - After ignoring empty object ..." << std::endl; - break; - } - // Break after using first value in child object - case 3: { - for (auto [ child_field, error ] : value.get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - uint64_t x; - ASSERT_SUCCESS( child_field.value().get(x) ); - ASSERT_EQUAL(x, 3); - break; // Break after the first value - } - std::cout << " - After using first value in child object ..." << std::endl; - break; - } - - // Break without using first value in child object - case 4: { - for (auto [ child_field, error ] : value.get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - break; - } - std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; - break; - } - - // Only look up one field in child object - case 5: { - uint64_t x; - ASSERT_SUCCESS( value["x"].get(x) ); - ASSERT_EQUAL( x, 5 ); - std::cout << " - After looking up one field in child object ..." << std::endl; - break; - } - - // Only look up one field in child object, but don't use it - case 6: { - ASSERT_SUCCESS( value["x"] ); - std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; - break; - } - - // Break after first value in child array - case 7: { - for (auto [ child_value, error ] : value) { - ASSERT_SUCCESS(error); - uint64_t x; - ASSERT_SUCCESS( child_value.get(x) ); - ASSERT_EQUAL( x, 7 ); - break; - } - std::cout << " - After using first value in child array ..." << std::endl; - break; - } - - // Break without using first value in child array - case 8: { - for (auto child_value : value) { - ASSERT_SUCCESS(child_value); - break; - } - std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; - break; - } - - // Break out of multiple child loops - case 9: { - for (auto child1 : value.get_object()) { - for (auto child2 : child1.value().get_array()) { - for (auto child3 : child2.get_object()) { - for (auto child4 : child3.value().get_array()) { - uint64_t x; - ASSERT_SUCCESS( child4.get(x) ); - ASSERT_EQUAL( x, 9 ); - break; - } - break; - } - break; - } - break; - } - std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; - break; - } - - // Test the actual value - case 10: { - uint64_t actual_value; - ASSERT_SUCCESS( value.get(actual_value) ); - ASSERT_EQUAL( actual_value, 10 ); - break; - } - } - - i++; - } - ASSERT_EQUAL( i, 11 ); // Make sure we found all the keys we expected - return true; - })); - return true; - } - - bool object_index_partial_children() { - TEST_START(); - auto json = R"( - { - "scalar_ignore": 0, - "empty_array_ignore": [], - "empty_object_ignore": {}, - "object_break": { "x": 3, "y": 33 }, - "object_break_unused": { "x": 4, "y": 44 }, - "object_index": { "x": 5, "y": 55 }, - "object_index_unused": { "x": 6, "y": 66 }, - "array_break": [ 7, 77, 777 ], - "array_break_unused": [ 8, 88, 888 ], - "quadruple_nested_break": { "a": [ { "b": [ 9, 99 ], "c": 999 }, 9999 ], "d": 99999 }, - "actual_value": 10 - } - )"_padded; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - - ASSERT_SUCCESS( object["scalar_ignore"] ); - std::cout << " - After ignoring empty scalar ..." << std::endl; - - ASSERT_SUCCESS( object["empty_array_ignore"] ); - std::cout << " - After ignoring empty array ..." << std::endl; - - ASSERT_SUCCESS( object["empty_object_ignore"] ); - std::cout << " - After ignoring empty object ..." << std::endl; - - // Break after using first value in child object - { - auto value = object["object_break"]; - for (auto [ child_field, error ] : value.get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - uint64_t x; - ASSERT_SUCCESS( child_field.value().get(x) ); - ASSERT_EQUAL(x, 3); - break; // Break after the first value - } - std::cout << " - After using first value in child object ..." << std::endl; - } - - // Break without using first value in child object - { - auto value = object["object_break_unused"]; - for (auto [ child_field, error ] : value.get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - break; - } - std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; - } - - // Only look up one field in child object - { - auto value = object["object_index"]; - - uint64_t x; - ASSERT_SUCCESS( value["x"].get(x) ); - ASSERT_EQUAL( x, 5 ); - std::cout << " - After looking up one field in child object ..." << std::endl; - } - - // Only look up one field in child object, but don't use it - { - auto value = object["object_index_unused"]; - - ASSERT_SUCCESS( value["x"] ); - std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; - } - - // Break after first value in child array - { - auto value = object["array_break"]; - - for (auto child_value : value) { - uint64_t x; - ASSERT_SUCCESS( child_value.get(x) ); - ASSERT_EQUAL( x, 7 ); - break; - } - std::cout << " - After using first value in child array ..." << std::endl; - } - - // Break without using first value in child array - { - auto value = object["array_break_unused"]; - - for (auto child_value : value) { - ASSERT_SUCCESS(child_value); - break; - } - std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; - } - - // Break out of multiple child loops - { - auto value = object["quadruple_nested_break"]; - for (auto child1 : value.get_object()) { - for (auto child2 : child1.value().get_array()) { - for (auto child3 : child2.get_object()) { - for (auto child4 : child3.value().get_array()) { - uint64_t x; - ASSERT_SUCCESS( child4.get(x) ); - ASSERT_EQUAL( x, 9 ); - break; - } - break; - } - break; - } - break; - } - std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; - } - - // Test the actual value - { - auto value = object["actual_value"]; - uint64_t actual_value; - ASSERT_SUCCESS( value.get(actual_value) ); - ASSERT_EQUAL( actual_value, 10 ); - } - - return true; - })); - - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_SUCCESS( doc_result["scalar_ignore"] ); - std::cout << " - After ignoring empty scalar ..." << std::endl; - - ASSERT_SUCCESS( doc_result["empty_array_ignore"] ); - std::cout << " - After ignoring empty array ..." << std::endl; - - ASSERT_SUCCESS( doc_result["empty_object_ignore"] ); - std::cout << " - After ignoring empty doc_result ..." << std::endl; - - // Break after using first value in child object - { - auto value = doc_result["object_break"]; - for (auto [ child_field, error ] : value.get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - uint64_t x; - ASSERT_SUCCESS( child_field.value().get(x) ); - ASSERT_EQUAL(x, 3); - break; // Break after the first value - } - std::cout << " - After using first value in child object ..." << std::endl; - } - - // Break without using first value in child object - { - auto value = doc_result["object_break_unused"]; - for (auto [ child_field, error ] : value.get_object()) { - ASSERT_SUCCESS(error); - ASSERT_EQUAL(child_field.key(), "x"); - break; - } - std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; - } - - // Only look up one field in child object - { - auto value = doc_result["object_index"]; - - uint64_t x; - ASSERT_SUCCESS( value["x"].get(x) ); - ASSERT_EQUAL( x, 5 ); - std::cout << " - After looking up one field in child object ..." << std::endl; - } - - // Only look up one field in child object, but don't use it - { - auto value = doc_result["object_index_unused"]; - - ASSERT_SUCCESS( value["x"] ); - std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; - } - - // Break after first value in child array - { - auto value = doc_result["array_break"]; - - for (auto child_value : value) { - uint64_t x; - ASSERT_SUCCESS( child_value.get(x) ); - ASSERT_EQUAL( x, 7 ); - break; - } - std::cout << " - After using first value in child array ..." << std::endl; - } - - // Break without using first value in child array - { - auto value = doc_result["array_break_unused"]; - - for (auto child_value : value) { - ASSERT_SUCCESS(child_value); - break; - } - std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; - } - - // Break out of multiple child loops - { - auto value = doc_result["quadruple_nested_break"]; - for (auto child1 : value.get_object()) { - for (auto child2 : child1.value().get_array()) { - for (auto child3 : child2.get_object()) { - for (auto child4 : child3.value().get_array()) { - uint64_t x; - ASSERT_SUCCESS( child4.get(x) ); - ASSERT_EQUAL( x, 9 ); - break; - } - break; - } - break; - } - break; - } - std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; - } - - // Test the actual value - { - auto value = doc_result["actual_value"]; - uint64_t actual_value; - ASSERT_SUCCESS( value.get(actual_value) ); - ASSERT_EQUAL( actual_value, 10 ); - } - - return true; - })); - - return true; - } - - bool iterate_empty_object() { - TEST_START(); - auto json = R"({})"_padded; - - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - for (simdjson_unused auto field : object) { - TEST_FAIL("Unexpected field"); - } - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object_result = doc_result.get_object(); - for (simdjson_unused auto field : object_result) { - TEST_FAIL("Unexpected field"); - } - return true; - })); - TEST_SUCCEED(); - } - - bool iterate_empty_array() { - TEST_START(); - auto json = "[]"_padded; - SUBTEST("ondemand::array", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::array array; - ASSERT_SUCCESS( doc_result.get(array) ); - for (simdjson_unused auto value : array) { TEST_FAIL("Unexpected value"); } - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result array_result = doc_result.get_array(); - for (simdjson_unused auto value : array_result) { TEST_FAIL("Unexpected value"); } - return true; - })); - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - for (simdjson_unused auto value : doc) { TEST_FAIL("Unexpected value"); } - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - for (simdjson_unused auto value : doc_result) { TEST_FAIL("Unexpected value"); } - return true; - })); - TEST_SUCCEED(); - } - - template - bool test_scalar_value(const padded_string &json, const T &expected) { - std::cout << "- JSON: " << json << endl; - SUBTEST( "simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - T actual; - ASSERT_SUCCESS( doc_result.get(actual) ); - ASSERT_EQUAL( expected, actual ); - return true; - })); - SUBTEST( "document", test_ondemand_doc(json, [&](auto doc_result) { - T actual; - ASSERT_SUCCESS( doc_result.get(actual) ); - ASSERT_EQUAL( expected, actual ); - return true; - })); - padded_string array_json = std::string("[") + std::string(json) + "]"; - std::cout << "- JSON: " << array_json << endl; - SUBTEST( "simdjson_result", test_ondemand_doc(array_json, [&](auto doc_result) { - int count = 0; - for (simdjson_result val_result : doc_result) { - T actual; - ASSERT_SUCCESS( val_result.get(actual) ); - ASSERT_EQUAL(expected, actual); - count++; - } - ASSERT_EQUAL(count, 1); - return true; - })); - SUBTEST( "ondemand::value", test_ondemand_doc(array_json, [&](auto doc_result) { - int count = 0; - for (simdjson_result val_result : doc_result) { - ondemand::value val; - ASSERT_SUCCESS( val_result.get(val) ); - T actual; - ASSERT_SUCCESS( val.get(actual) ); - ASSERT_EQUAL(expected, actual); - count++; - } - ASSERT_EQUAL(count, 1); - return true; - })); - TEST_SUCCEED(); - } - bool string_value() { - TEST_START(); - return test_scalar_value(R"("hi")"_padded, std::string_view("hi")); - } - - bool numeric_values() { - TEST_START(); - if (!test_scalar_value ("0"_padded, 0)) { return false; } - if (!test_scalar_value("0"_padded, 0)) { return false; } - if (!test_scalar_value ("0"_padded, 0)) { return false; } - if (!test_scalar_value ("1"_padded, 1)) { return false; } - if (!test_scalar_value("1"_padded, 1)) { return false; } - if (!test_scalar_value ("1"_padded, 1)) { return false; } - if (!test_scalar_value ("-1"_padded, -1)) { return false; } - if (!test_scalar_value ("-1"_padded, -1)) { return false; } - if (!test_scalar_value ("1.1"_padded, 1.1)) { return false; } - TEST_SUCCEED(); - } - - bool boolean_values() { - TEST_START(); - if (!test_scalar_value ("true"_padded, true)) { return false; } - if (!test_scalar_value ("false"_padded, false)) { return false; } - TEST_SUCCEED(); - } - - bool null_value() { - TEST_START(); - auto json = "null"_padded; - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - ASSERT_EQUAL( doc.is_null(), true ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( doc_result.is_null(), true ); - return true; - })); - json = "[null]"_padded; - SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { - int count = 0; - for (auto value_result : doc_result) { - ondemand::value value; - ASSERT_SUCCESS( value_result.get(value) ); - ASSERT_EQUAL( value.is_null(), true ); - count++; - } - ASSERT_EQUAL( count, 1 ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - int count = 0; - for (auto value_result : doc_result) { - ASSERT_EQUAL( value_result.is_null(), true ); - count++; - } - ASSERT_EQUAL( count, 1 ); - return true; - })); - return true; - } - - bool object_index() { - TEST_START(); - auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); - ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); - - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object; - object = doc_result.get_object(); - - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); - ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); - - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); - return true; - })); - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - ASSERT_EQUAL( doc["a"].get_uint64().first, 1 ); - ASSERT_EQUAL( doc["b"].get_uint64().first, 2 ); - ASSERT_EQUAL( doc["c/d"].get_uint64().first, 3 ); - - ASSERT_EQUAL( doc["a"].get_uint64().first, 1 ); - ASSERT_ERROR( doc["d"], NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( doc_result["a"].get_uint64().first, 1 ); - ASSERT_EQUAL( doc_result["b"].get_uint64().first, 2 ); - ASSERT_EQUAL( doc_result["c/d"].get_uint64().first, 3 ); - - ASSERT_EQUAL( doc_result["a"].get_uint64().first, 1 ); - ASSERT_ERROR( doc_result["d"], NO_SUCH_FIELD ); - return true; - })); - - json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; - SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::value object; - ASSERT_SUCCESS( doc_result["outer"].get(object) ); - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); - ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); - - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object = doc_result["outer"]; - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); - ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); - - ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); - ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); - return true; - })); - TEST_SUCCEED(); - } - - bool object_find_field_unordered() { - TEST_START(); - auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); - - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object; - object = doc_result.get_object(); - - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); - - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - ASSERT_EQUAL( doc.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_EQUAL( doc.find_field_unordered("b").get_uint64().first, 2 ); - ASSERT_EQUAL( doc.find_field_unordered("c/d").get_uint64().first, 3 ); - - ASSERT_EQUAL( doc.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_ERROR( doc.find_field_unordered("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( doc_result.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_EQUAL( doc_result.find_field_unordered("b").get_uint64().first, 2 ); - ASSERT_EQUAL( doc_result.find_field_unordered("c/d").get_uint64().first, 3 ); - - ASSERT_EQUAL( doc_result.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_ERROR( doc_result.find_field_unordered("d"), NO_SUCH_FIELD ); - return true; - })); - - json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; - SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::value object; - ASSERT_SUCCESS( doc_result.find_field_unordered("outer").get(object) ); - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); - - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object = doc_result.find_field_unordered("outer"); - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); - - ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); - ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); - return true; - })); - TEST_SUCCEED(); - } - - bool object_find_field() { - TEST_START(); - auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - - ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); - - ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); - ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object; - object = doc_result.get_object(); - - ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); - - ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); - ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - ASSERT_EQUAL( doc.find_field("a").get_uint64().first, 1 ); - ASSERT_EQUAL( doc.find_field("b").get_uint64().first, 2 ); - ASSERT_EQUAL( doc.find_field("c/d").get_uint64().first, 3 ); - - ASSERT_ERROR( doc.find_field("a"), NO_SUCH_FIELD ); - ASSERT_ERROR( doc.find_field("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( doc_result.find_field("a").get_uint64().first, 1 ); - ASSERT_EQUAL( doc_result.find_field("b").get_uint64().first, 2 ); - ASSERT_EQUAL( doc_result.find_field("c/d").get_uint64().first, 3 ); - - ASSERT_ERROR( doc_result.find_field("a"), NO_SUCH_FIELD ); - ASSERT_ERROR( doc_result.find_field("d"), NO_SUCH_FIELD ); - return true; - })); - - json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; - SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::value object; - ASSERT_SUCCESS( doc_result.find_field("outer").get(object) ); - ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); - - ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); - ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object = doc_result.find_field("outer"); - ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); - ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); - ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); - - ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); - ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); - return true; - })); - TEST_SUCCEED(); - } - - bool nested_object_index() { - TEST_START(); - auto json = R"({ "x": { "y": { "z": 2 } } }})"_padded; - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( doc_result["x"]["y"]["z"].get_uint64().first, 2 ); - return true; - })); - SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::document doc; - ASSERT_SUCCESS( std::move(doc_result).get(doc) ); - ASSERT_EQUAL( doc["x"]["y"]["z"].get_uint64().first, 2 ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result object = doc_result.get_object(); - ASSERT_EQUAL( object["x"]["y"]["z"].get_uint64().first, 2 ); - return true; - })); - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object; - ASSERT_SUCCESS( doc_result.get(object) ); - ASSERT_EQUAL( object["x"]["y"]["z"].get_uint64().first, 2 ); - return true; - })); - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - simdjson_result x = doc_result["x"]; - ASSERT_EQUAL( x["y"]["z"].get_uint64().first, 2 ); - return true; - })); - SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::value x; - ASSERT_SUCCESS( doc_result["x"].get(x) ); - ASSERT_EQUAL( x["y"]["z"].get_uint64().first, 2 ); - return true; - })); - TEST_SUCCEED(); - } - -#if SIMDJSON_EXCEPTIONS - - bool iterate_object_exception() { - TEST_START(); - auto json = R"({ "a": 1, "b": 2, "c": 3 })"_padded; - const char* expected_key[] = { "a", "b", "c" }; - const uint64_t expected_value[] = { 1, 2, 3 }; - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - size_t i = 0; - for (ondemand::field field : doc_result.get_object()) { - ASSERT_EQUAL( field.key(), expected_key[i] ); - ASSERT_EQUAL( uint64_t(field.value()), expected_value[i] ); - i++; - } - ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) ); - return true; - })); - TEST_SUCCEED(); - } - - bool iterate_array_exception() { - TEST_START(); - auto json = R"([ 1, 10, 100 ])"_padded; - const uint64_t expected_value[] = { 1, 10, 100 }; - - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - size_t i=0; - for (int64_t actual : doc_result) { ASSERT_EQUAL(actual, expected_value[i]); i++; } - ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); - return true; - })); - TEST_SUCCEED(); - } - - bool iterate_empty_object_exception() { - TEST_START(); - auto json = R"({})"_padded; - - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - for (simdjson_unused ondemand::field field : doc_result.get_object()) { - TEST_FAIL("Unexpected field"); - } - return true; - })); - - TEST_SUCCEED(); - } - - bool iterate_empty_array_exception() { - TEST_START(); - auto json = "[]"_padded; - - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - for (simdjson_unused ondemand::value value : doc_result) { TEST_FAIL("Unexpected value"); } - return true; - })); - - TEST_SUCCEED(); - } - - template - bool test_scalar_value_exception(const padded_string &json, const T &expected) { - std::cout << "- JSON: " << json << endl; - SUBTEST( "document", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( expected, T(doc_result) ); - return true; - })); - padded_string array_json = std::string("[") + std::string(json) + "]"; - std::cout << "- JSON: " << array_json << endl; - SUBTEST( "value", test_ondemand_doc(array_json, [&](auto doc_result) { - int count = 0; - for (T actual : doc_result) { - ASSERT_EQUAL( expected, actual ); - count++; - } - ASSERT_EQUAL(count, 1); - return true; - })); - TEST_SUCCEED(); - } - bool string_value_exception() { - TEST_START(); - return test_scalar_value_exception(R"("hi")"_padded, std::string_view("hi")); - } - - bool numeric_values_exception() { - TEST_START(); - if (!test_scalar_value_exception ("0"_padded, 0)) { return false; } - if (!test_scalar_value_exception("0"_padded, 0)) { return false; } - if (!test_scalar_value_exception ("0"_padded, 0)) { return false; } - if (!test_scalar_value_exception ("1"_padded, 1)) { return false; } - if (!test_scalar_value_exception("1"_padded, 1)) { return false; } - if (!test_scalar_value_exception ("1"_padded, 1)) { return false; } - if (!test_scalar_value_exception ("-1"_padded, -1)) { return false; } - if (!test_scalar_value_exception ("-1"_padded, -1)) { return false; } - if (!test_scalar_value_exception ("1.1"_padded, 1.1)) { return false; } - TEST_SUCCEED(); - } - - bool boolean_values_exception() { - TEST_START(); - if (!test_scalar_value_exception ("true"_padded, true)) { return false; } - if (!test_scalar_value_exception ("false"_padded, false)) { return false; } - TEST_SUCCEED(); - } - - - bool object_index_exception() { - TEST_START(); - auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; - SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { - ondemand::object object = doc_result; - - ASSERT_EQUAL( uint64_t(object["a"]), 1 ); - ASSERT_EQUAL( uint64_t(object["b"]), 2 ); - ASSERT_EQUAL( uint64_t(object["c/d"]), 3 ); - - return true; - })); - TEST_SUCCEED(); - } - bool nested_object_index_exception() { - TEST_START(); - auto json = R"({ "x": { "y": { "z": 2 } } }})"_padded; - SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { - ASSERT_EQUAL( uint64_t(doc_result["x"]["y"]["z"]), 2 ); - return true; - })); - TEST_SUCCEED(); - } - -#endif - - bool run() { - return - iterate_array() && - iterate_empty_array() && - iterate_object() && - iterate_empty_object() && - string_value() && - numeric_values() && - boolean_values() && - null_value() && - object_index() && - object_find_field_unordered() && - object_find_field() && - nested_object_index() && - iterate_object_partial_children() && - iterate_array_partial_children() && - object_index_partial_children() && -#if SIMDJSON_EXCEPTIONS - iterate_object_exception() && - iterate_array_exception() && - string_value_exception() && - numeric_values_exception() && - boolean_values_exception() && - object_index_exception() && - nested_object_index_exception() && -#endif - true; - } -} - - -namespace ordering_tests { - using namespace std; - using namespace simdjson; - using namespace simdjson::dom; -#if SIMDJSON_EXCEPTIONS - - auto json = "{\"coordinates\":[{\"x\":1.1,\"y\":2.2,\"z\":3.3}]}"_padded; - - bool in_order_object_index() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - x += double(point_object["x"]); - y += double(point_object["y"]); - z += double(point_object["z"]); - } - return (x == 1.1) && (y == 2.2) && (z == 3.3); - } - - bool in_order_object_find_field_unordered() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - x += double(point_object.find_field_unordered("x")); - y += double(point_object.find_field_unordered("y")); - z += double(point_object.find_field_unordered("z")); - } - return (x == 1.1) && (y == 2.2) && (z == 3.3); - } - - bool in_order_object_find_field() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - x += double(point_object.find_field("x")); - y += double(point_object.find_field("y")); - z += double(point_object.find_field("z")); - } - return (x == 1.1) && (y == 2.2) && (z == 3.3); - } - - bool out_of_order_object_index() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - z += double(point_object["z"]); - x += double(point_object["x"]); - y += double(point_object["y"]); - } - return (x == 1.1) && (y == 2.2) && (z == 3.3); - } - - bool out_of_order_object_find_field_unordered() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - z += double(point_object.find_field_unordered("z")); - x += double(point_object.find_field_unordered("x")); - y += double(point_object.find_field_unordered("y")); - } - return (x == 1.1) && (y == 2.2) && (z == 3.3); - } - - bool out_of_order_object_find_field() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - z += double(point_object.find_field("z")); - ASSERT_ERROR( point_object.find_field("x"), NO_SUCH_FIELD ); - ASSERT_ERROR( point_object.find_field("y"), NO_SUCH_FIELD ); - } - return (x == 0) && (y == 0) && (z == 3.3); - } - - bool foreach_object_field_lookup() { - TEST_START(); - ondemand::parser parser{}; - auto doc = parser.iterate(json); - double x{0}; - double y{0}; - double z{0}; - for (ondemand::object point_object : doc["coordinates"]) { - for (auto field : point_object) { - if (field.key() == "z") { z += double(field.value()); } - else if (field.key() == "x") { x += double(field.value()); } - else if (field.key() == "y") { y += double(field.value()); } - } - } - return (x == 1.1) && (y == 2.2) && (z == 3.3); - } -#endif // SIMDJSON_EXCEPTIONS - - bool run() { - return -#if SIMDJSON_EXCEPTIONS - in_order_object_index() && - in_order_object_find_field_unordered() && - in_order_object_find_field() && - out_of_order_object_index() && - out_of_order_object_find_field_unordered() && - out_of_order_object_find_field() && - foreach_object_field_lookup() && -#endif - true; - } - -} - -namespace twitter_tests { - using namespace std; - using namespace simdjson; - using namespace simdjson::dom; - - bool twitter_count() { - TEST_START(); - padded_string json; - ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - uint64_t count; - ASSERT_SUCCESS( doc_result["search_metadata"]["count"].get(count) ); - ASSERT_EQUAL( count, 100 ); - return true; - })); - TEST_SUCCEED(); - } - -#if SIMDJSON_EXCEPTIONS - bool twitter_example() { - TEST_START(); - padded_string json; - ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); - ondemand::parser parser; - auto doc = parser.iterate(json); - for (ondemand::object tweet : doc["statuses"]) { - uint64_t id = tweet["id"]; - std::string_view text = tweet["text"]; - std::string_view screen_name = tweet["user"]["screen_name"]; - uint64_t retweets = tweet["retweet_count"]; - uint64_t favorites = tweet["favorite_count"]; - (void) id; - (void) text; - (void) retweets; - (void) favorites; - (void) screen_name; - } - TEST_SUCCEED(); - } -#endif // SIMDJSON_EXCEPTIONS - - bool twitter_default_profile() { - TEST_START(); - padded_string json; - ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - // Print users with a default profile. - set default_users; - for (auto tweet : doc_result["statuses"]) { - auto user = tweet["user"].get_object(); - - // We have to get the screen name before default_profile because it appears first - std::string_view screen_name; - ASSERT_SUCCESS( user["screen_name"].get(screen_name) ); - - bool default_profile; - ASSERT_SUCCESS( user["default_profile"].get(default_profile) ); - if (default_profile) { - default_users.insert(screen_name); - } - } - ASSERT_EQUAL( default_users.size(), 86 ); - return true; - })); - TEST_SUCCEED(); - } - - bool twitter_image_sizes() { - TEST_START(); - padded_string json; - ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - // Print image names and sizes - set> image_sizes; - for (auto tweet : doc_result["statuses"]) { - auto media = tweet["entities"]["media"]; - if (!media.error()) { - for (auto image : media) { - uint64_t id_val; - std::string_view id_string; - ASSERT_SUCCESS( image["id"].get(id_val) ); - ASSERT_SUCCESS( image["id_str"].get(id_string) ); - std::cout << "id = " << id_val << std::endl; - std::cout << "id_string = " << id_string << std::endl; - - for (auto size : image["sizes"].get_object()) { - std::string_view size_key; - ASSERT_SUCCESS( size.unescaped_key().get(size_key) ); - std::cout << "Type of image size = " << size_key << std::endl; - - uint64_t width, height; - ASSERT_SUCCESS( size.value()["w"].get(width) ); - ASSERT_SUCCESS( size.value()["h"].get(height) ); - image_sizes.insert(make_pair(width, height)); - } - } - } - } - ASSERT_EQUAL( image_sizes.size(), 15 ); - return true; - })); - TEST_SUCCEED(); - } - -#if SIMDJSON_EXCEPTIONS - - bool twitter_count_exception() { - TEST_START(); - padded_string json; - ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - uint64_t count = doc_result["search_metadata"]["count"]; - ASSERT_EQUAL( count, 100 ); - return true; - })); - TEST_SUCCEED(); - } - - bool twitter_default_profile_exception() { - TEST_START(); - padded_string json = padded_string::load(TWITTER_JSON); - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - // Print users with a default profile. - set default_users; - for (auto tweet : doc_result["statuses"]) { - ondemand::object user = tweet["user"]; - - // We have to get the screen name before default_profile because it appears first - std::string_view screen_name = user["screen_name"]; - if (user["default_profile"]) { - default_users.insert(screen_name); - } - } - ASSERT_EQUAL( default_users.size(), 86 ); - return true; - })); - TEST_SUCCEED(); - } - - /* - * Fun fact: id and id_str can differ: - * 505866668485386240 and 505866668485386241. - * Presumably, it is because doubles are used - * at some point in the process and the number - * 505866668485386241 cannot be represented as a double. - * (not our fault) - */ - bool twitter_image_sizes_exception() { - TEST_START(); - padded_string json = padded_string::load(TWITTER_JSON); - ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { - // Print image names and sizes - set> image_sizes; - for (auto tweet : doc_result["statuses"]) { - auto media = tweet["entities"]["media"]; - if (!media.error()) { - for (auto image : media) { - std::cout << "id = " << uint64_t(image["id"]) << std::endl; - std::cout << "id_string = " << std::string_view(image["id_str"]) << std::endl; - for (auto size : image["sizes"].get_object()) { - std::cout << "Type of image size = " << std::string_view(size.unescaped_key()) << std::endl; - // NOTE: the uint64_t is required so that each value is actually parsed before the pair is created - image_sizes.insert(make_pair(size.value()["w"], size.value()["h"])); - } - } - } - } - ASSERT_EQUAL( image_sizes.size(), 15 ); - return true; - })); - TEST_SUCCEED(); - } - -#endif // SIMDJSON_EXCEPTIONS - - bool run() { - return - twitter_count() && - twitter_default_profile() && - twitter_image_sizes() && -#if SIMDJSON_EXCEPTIONS - twitter_count_exception() && - twitter_example() && - twitter_default_profile_exception() && - twitter_image_sizes_exception() && -#endif - true; - } -} - -namespace error_tests { - using namespace std; - using namespace simdjson; - using namespace simdjson::builtin; - - bool empty_document_error() { - TEST_START(); - ondemand::parser parser; - ASSERT_ERROR( parser.iterate(""_padded), EMPTY ); - TEST_SUCCEED(); - } - - namespace wrong_type { - -#define TEST_CAST_ERROR(JSON, TYPE, ERROR) \ - std::cout << "- Subtest: get_" << (#TYPE) << "() - JSON: " << (JSON) << std::endl; \ - if (!test_ondemand_doc((JSON##_padded), [&](auto doc_result) { \ - ASSERT_ERROR( doc_result.get_##TYPE(), (ERROR) ); \ - return true; \ - })) { \ - return false; \ - } \ - { \ - padded_string a_json(std::string(R"({ "a": )") + JSON + " })"); \ - std::cout << R"(- Subtest: get_)" << (#TYPE) << "() - JSON: " << a_json << std::endl; \ - if (!test_ondemand_doc(a_json, [&](auto doc_result) { \ - ASSERT_ERROR( doc_result["a"].get_##TYPE(), (ERROR) ); \ - return true; \ - })) { \ - return false; \ - }; \ - } - - bool wrong_type_array() { - TEST_START(); - TEST_CAST_ERROR("[]", object, INCORRECT_TYPE); - TEST_CAST_ERROR("[]", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("[]", int64, NUMBER_ERROR); - TEST_CAST_ERROR("[]", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("[]", double, NUMBER_ERROR); - TEST_CAST_ERROR("[]", string, INCORRECT_TYPE); - TEST_CAST_ERROR("[]", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_object() { - TEST_START(); - TEST_CAST_ERROR("{}", array, INCORRECT_TYPE); - TEST_CAST_ERROR("{}", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("{}", int64, NUMBER_ERROR); - TEST_CAST_ERROR("{}", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("{}", double, NUMBER_ERROR); - TEST_CAST_ERROR("{}", string, INCORRECT_TYPE); - TEST_CAST_ERROR("{}", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_true() { - TEST_START(); - TEST_CAST_ERROR("true", array, INCORRECT_TYPE); - TEST_CAST_ERROR("true", object, INCORRECT_TYPE); - TEST_CAST_ERROR("true", int64, NUMBER_ERROR); - TEST_CAST_ERROR("true", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("true", double, NUMBER_ERROR); - TEST_CAST_ERROR("true", string, INCORRECT_TYPE); - TEST_CAST_ERROR("true", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_false() { - TEST_START(); - TEST_CAST_ERROR("false", array, INCORRECT_TYPE); - TEST_CAST_ERROR("false", object, INCORRECT_TYPE); - TEST_CAST_ERROR("false", int64, NUMBER_ERROR); - TEST_CAST_ERROR("false", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("false", double, NUMBER_ERROR); - TEST_CAST_ERROR("false", string, INCORRECT_TYPE); - TEST_CAST_ERROR("false", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_null() { - TEST_START(); - TEST_CAST_ERROR("null", array, INCORRECT_TYPE); - TEST_CAST_ERROR("null", object, INCORRECT_TYPE); - TEST_CAST_ERROR("null", int64, NUMBER_ERROR); - TEST_CAST_ERROR("null", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("null", double, NUMBER_ERROR); - TEST_CAST_ERROR("null", string, INCORRECT_TYPE); - TEST_CAST_ERROR("null", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_1() { - TEST_START(); - TEST_CAST_ERROR("1", array, INCORRECT_TYPE); - TEST_CAST_ERROR("1", object, INCORRECT_TYPE); - TEST_CAST_ERROR("1", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("1", string, INCORRECT_TYPE); - TEST_CAST_ERROR("1", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_negative_1() { - TEST_START(); - TEST_CAST_ERROR("-1", array, INCORRECT_TYPE); - TEST_CAST_ERROR("-1", object, INCORRECT_TYPE); - TEST_CAST_ERROR("-1", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("-1", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("-1", string, INCORRECT_TYPE); - TEST_CAST_ERROR("-1", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_float() { - TEST_START(); - TEST_CAST_ERROR("1.1", array, INCORRECT_TYPE); - TEST_CAST_ERROR("1.1", object, INCORRECT_TYPE); - TEST_CAST_ERROR("1.1", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("1.1", int64, NUMBER_ERROR); - TEST_CAST_ERROR("1.1", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("1.1", string, INCORRECT_TYPE); - TEST_CAST_ERROR("1.1", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_negative_int64_overflow() { - TEST_START(); - TEST_CAST_ERROR("-9223372036854775809", array, INCORRECT_TYPE); - TEST_CAST_ERROR("-9223372036854775809", object, INCORRECT_TYPE); - TEST_CAST_ERROR("-9223372036854775809", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("-9223372036854775809", int64, NUMBER_ERROR); - TEST_CAST_ERROR("-9223372036854775809", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("-9223372036854775809", string, INCORRECT_TYPE); - TEST_CAST_ERROR("-9223372036854775809", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_int64_overflow() { - TEST_START(); - TEST_CAST_ERROR("9223372036854775808", array, INCORRECT_TYPE); - TEST_CAST_ERROR("9223372036854775808", object, INCORRECT_TYPE); - TEST_CAST_ERROR("9223372036854775808", bool, INCORRECT_TYPE); - // TODO BUG: this should be an error but is presently not - // TEST_CAST_ERROR("9223372036854775808", int64, NUMBER_ERROR); - TEST_CAST_ERROR("9223372036854775808", string, INCORRECT_TYPE); - TEST_CAST_ERROR("9223372036854775808", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool wrong_type_uint64_overflow() { - TEST_START(); - TEST_CAST_ERROR("18446744073709551616", array, INCORRECT_TYPE); - TEST_CAST_ERROR("18446744073709551616", object, INCORRECT_TYPE); - TEST_CAST_ERROR("18446744073709551616", bool, INCORRECT_TYPE); - TEST_CAST_ERROR("18446744073709551616", int64, NUMBER_ERROR); - // TODO BUG: this should be an error but is presently not - // TEST_CAST_ERROR("18446744073709551616", uint64, NUMBER_ERROR); - TEST_CAST_ERROR("18446744073709551616", string, INCORRECT_TYPE); - TEST_CAST_ERROR("18446744073709551616", raw_json_string, INCORRECT_TYPE); - TEST_SUCCEED(); - } - - bool run() { - return - wrong_type_1() && - wrong_type_array() && - wrong_type_false() && - wrong_type_float() && - wrong_type_int64_overflow() && - wrong_type_negative_1() && - wrong_type_negative_int64_overflow() && - wrong_type_null() && - wrong_type_object() && - wrong_type_true() && - wrong_type_uint64_overflow() && - true; - } - - } // namespace wrong_type - - template - bool assert_iterate(T array, V *expected, size_t N, simdjson::error_code *expected_error, size_t N2) { - size_t count = 0; - for (auto elem : std::forward(array)) { - V actual; - auto actual_error = elem.get(actual); - if (count >= N) { - if (count >= (N+N2)) { - std::cerr << "FAIL: Extra error reported: " << actual_error << std::endl; - return false; - } - ASSERT_ERROR(actual_error, expected_error[count - N]); - } else { - ASSERT_SUCCESS(actual_error); - ASSERT_EQUAL(actual, expected[count]); - } - count++; - } - ASSERT_EQUAL(count, N+N2); - return true; - } - - template - bool assert_iterate(T &array, V (&&expected)[N], simdjson::error_code (&&expected_error)[N2]) { - return assert_iterate(array, expected, N, expected_error, N2); - } - - template - bool assert_iterate(T &array, simdjson::error_code (&&expected_error)[N2]) { - return assert_iterate(array, nullptr, 0, expected_error, N2); - } - - template - bool assert_iterate(T &array, V (&&expected)[N]) { - return assert_iterate(array, expected, N, nullptr, 0); - } - - template - bool assert_iterate(T &&array, V (&&expected)[N], simdjson::error_code (&&expected_error)[N2]) { - return assert_iterate(std::forward(array), expected, N, expected_error, N2); - } - - template - bool assert_iterate(T &&array, simdjson::error_code (&&expected_error)[N2]) { - return assert_iterate(std::forward(array), nullptr, 0, expected_error, N2); - } - - template - bool assert_iterate(T &&array, V (&&expected)[N]) { - return assert_iterate(std::forward(array), expected, N, nullptr, 0); - } - - bool top_level_array_iterate_error() { - TEST_START(); - ONDEMAND_SUBTEST("missing comma", "[1 1]", assert_iterate(doc, { int64_t(1) }, { TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", "[1,,1]", assert_iterate(doc, { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", "[,]", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", "[,,]", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); - TEST_SUCCEED(); - } - bool top_level_array_iterate_unclosed_error() { - TEST_START(); - ONDEMAND_SUBTEST("unclosed extra comma", "[,", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", "[1 ", assert_iterate(doc, { int64_t(1) }, { TAPE_ERROR })); - // TODO These pass the user values that may run past the end of the buffer if they aren't careful - // In particular, if the padding is decorated with the wrong values, we could cause overrun! - ONDEMAND_SUBTEST("unclosed extra comma", "[,,", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", "[1,", assert_iterate(doc, { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", "[1", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", "[", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); - TEST_SUCCEED(); - } - - bool array_iterate_error() { - TEST_START(); - ONDEMAND_SUBTEST("missing comma", R"({ "a": [1 1] })", assert_iterate(doc["a"], { int64_t(1) }, { TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", R"({ "a": [1,,1] })", assert_iterate(doc["a"], { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", R"({ "a": [1,,] })", assert_iterate(doc["a"], { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", R"({ "a": [,] })", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("extra comma ", R"({ "a": [,,] })", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); - TEST_SUCCEED(); - } - bool array_iterate_unclosed_error() { - TEST_START(); - ONDEMAND_SUBTEST("unclosed extra comma", R"({ "a": [,)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed extra comma", R"({ "a": [,,)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", R"({ "a": [1 )", assert_iterate(doc["a"], { int64_t(1) }, { TAPE_ERROR })); - // TODO These pass the user values that may run past the end of the buffer if they aren't careful - // In particular, if the padding is decorated with the wrong values, we could cause overrun! - ONDEMAND_SUBTEST("unclosed ", R"({ "a": [1,)", assert_iterate(doc["a"], { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", R"({ "a": [1)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed ", R"({ "a": [)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); - TEST_SUCCEED(); - } - - template - bool assert_iterate_object(T &&object, const char **expected_key, V *expected, size_t N, simdjson::error_code *expected_error, size_t N2) { - size_t count = 0; - for (auto field : object) { - V actual; - auto actual_error = field.value().get(actual); - if (count >= N) { - ASSERT((count - N) < N2, "Extra error reported"); - ASSERT_ERROR(actual_error, expected_error[count - N]); - } else { - ASSERT_SUCCESS(actual_error); - ASSERT_EQUAL(field.key().first, expected_key[count]); - ASSERT_EQUAL(actual, expected[count]); - } - count++; - } - ASSERT_EQUAL(count, N+N2); - return true; - } - - template - bool assert_iterate_object(T &&object, const char *(&&expected_key)[N], V (&&expected)[N], simdjson::error_code (&&expected_error)[N2]) { - return assert_iterate_object(std::forward(object), expected_key, expected, N, expected_error, N2); - } - - template - bool assert_iterate_object(T &&object, simdjson::error_code (&&expected_error)[N2]) { - return assert_iterate_object(std::forward(object), nullptr, nullptr, 0, expected_error, N2); - } - - template - bool assert_iterate_object(T &&object, const char *(&&expected_key)[N], V (&&expected)[N]) { - return assert_iterate_object(std::forward(object), expected_key, expected, N, nullptr, 0); - } - - bool object_iterate_error() { - TEST_START(); - ONDEMAND_SUBTEST("missing colon", R"({ "a" 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("missing key ", R"({ : 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("missing value", R"({ "a": , "b": 2 })", assert_iterate_object(doc.get_object(), { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", assert_iterate_object(doc.get_object(), { "a" }, { int64_t(1) }, { TAPE_ERROR })); - TEST_SUCCEED(); - } - bool object_iterate_wrong_key_type_error() { - TEST_START(); - ONDEMAND_SUBTEST("wrong key type", R"({ 1: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("wrong key type", R"({ true: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("wrong key type", R"({ false: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("wrong key type", R"({ null: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("wrong key type", R"({ []: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("wrong key type", R"({ {}: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - TEST_SUCCEED(); - } - bool object_iterate_unclosed_error() { - TEST_START(); - ONDEMAND_SUBTEST("unclosed", R"({ "a": 1, )", assert_iterate_object(doc.get_object(), { "a" }, { int64_t(1) }, { TAPE_ERROR })); - // TODO These next two pass the user a value that may run past the end of the buffer if they aren't careful. - // In particular, if the padding is decorated with the wrong values, we could cause overrun! - ONDEMAND_SUBTEST("unclosed", R"({ "a": 1 )", assert_iterate_object(doc.get_object(), { "a" }, { int64_t(1) }, { TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed", R"({ "a": )", assert_iterate_object(doc.get_object(), { NUMBER_ERROR, TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed", R"({ "a" )", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - ONDEMAND_SUBTEST("unclosed", R"({ )", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); - TEST_SUCCEED(); - } - - bool object_lookup_error() { - TEST_START(); - ONDEMAND_SUBTEST("missing colon", R"({ "a" 1, "b": 2 })", assert_error(doc["a"], TAPE_ERROR)); - ONDEMAND_SUBTEST("missing key ", R"({ : 1, "b": 2 })", assert_error(doc["a"], TAPE_ERROR)); - ONDEMAND_SUBTEST("missing value", R"({ "a": , "b": 2 })", assert_success(doc["a"])); - ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", assert_success(doc["a"])); - TEST_SUCCEED(); - } - bool object_lookup_unclosed_error() { - TEST_START(); - // TODO This one passes the user a value that may run past the end of the buffer if they aren't careful. - // In particular, if the padding is decorated with the wrong values, we could cause overrun! - ONDEMAND_SUBTEST("unclosed", R"({ "a": )", assert_success(doc["a"])); - ONDEMAND_SUBTEST("unclosed", R"({ "a" )", assert_error(doc["a"], TAPE_ERROR)); - ONDEMAND_SUBTEST("unclosed", R"({ )", assert_error(doc["a"], TAPE_ERROR)); - TEST_SUCCEED(); - } - - bool object_lookup_miss_error() { - TEST_START(); - ONDEMAND_SUBTEST("missing colon", R"({ "a" 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("missing key ", R"({ : 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("missing value", R"({ "a": , "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - TEST_SUCCEED(); - } - bool object_lookup_miss_wrong_key_type_error() { - TEST_START(); - ONDEMAND_SUBTEST("wrong key type", R"({ 1: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("wrong key type", R"({ true: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("wrong key type", R"({ false: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("wrong key type", R"({ null: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("wrong key type", R"({ []: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("wrong key type", R"({ {}: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); - TEST_SUCCEED(); - } - bool object_lookup_miss_unclosed_error() { - TEST_START(); - ONDEMAND_SUBTEST("unclosed", R"({ "a": 1, )", assert_error(doc["b"], TAPE_ERROR)); - // TODO These next two pass the user a value that may run past the end of the buffer if they aren't careful. - // In particular, if the padding is decorated with the wrong values, we could cause overrun! - ONDEMAND_SUBTEST("unclosed", R"({ "a": 1 )", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("unclosed", R"({ "a": )", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("unclosed", R"({ "a" )", assert_error(doc["b"], TAPE_ERROR)); - ONDEMAND_SUBTEST("unclosed", R"({ )", assert_error(doc["b"], TAPE_ERROR)); - TEST_SUCCEED(); - } - bool object_lookup_miss_next_error() { - TEST_START(); - ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", ([&]() { - auto obj = doc.get_object(); - return assert_result(obj["a"], 1) && assert_error(obj["b"], TAPE_ERROR); - })()); - TEST_SUCCEED(); - } - - bool run() { - return - empty_document_error() && - top_level_array_iterate_error() && - top_level_array_iterate_unclosed_error() && - array_iterate_error() && - array_iterate_unclosed_error() && - wrong_type::run() && - object_iterate_error() && - object_iterate_wrong_key_type_error() && - object_iterate_unclosed_error() && - object_lookup_error() && - object_lookup_unclosed_error() && - object_lookup_miss_error() && - object_lookup_miss_unclosed_error() && - object_lookup_miss_wrong_key_type_error() && - object_lookup_miss_next_error() && - true; - } -} - -int main(int argc, char *argv[]) { - std::cout << std::unitbuf; - int c; - while ((c = getopt(argc, argv, "a:")) != -1) { - switch (c) { - case 'a': { - const simdjson::implementation *impl = simdjson::available_implementations[optarg]; - if (!impl) { - std::fprintf(stderr, "Unsupported architecture value -a %s\n", optarg); - return EXIT_FAILURE; - } - simdjson::active_implementation = impl; - break; - } - default: - std::fprintf(stderr, "Unexpected argument %c\n", c); - return EXIT_FAILURE; - } - } - - // this is put here deliberately to check that the documentation is correct (README), - // should this fail to compile, you should update the documentation: - if (simdjson::active_implementation->name() == "unsupported") { - std::printf("unsupported CPU\n"); - std::abort(); - } - // We want to know what we are testing. - // Next line would be the runtime dispatched implementation but that's not necessarily what gets tested. - // std::cout << "Running tests against this implementation: " << simdjson::active_implementation->name(); - // Rather, we want to display builtin_implementation()->name(). - // In practice, by default, we often end up testing against fallback. - std::cout << "builtin_implementation -- " << builtin_implementation()->name() << std::endl; - std::cout << "------------------------------------------------------------" << std::endl; - - std::cout << "Running basic tests." << std::endl; - if ( - parse_api_tests::run() && - dom_api_tests::run() && - twitter_tests::run() && - number_tests::run() && - ordering_tests::run() && - key_string_tests::run() && - active_tests::run() && - error_tests::run() && - true - ) { - std::cout << "Basic tests are ok." << std::endl; - return EXIT_SUCCESS; - } else { - return EXIT_FAILURE; - } -} diff --git a/tests/ondemand/ondemand_compilation_tests.cpp b/tests/ondemand/ondemand_compilation_tests.cpp new file mode 100644 index 000000000..2b3acb29d --- /dev/null +++ b/tests/ondemand/ondemand_compilation_tests.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +#if SIMDJSON_EXCEPTIONS + +// bogus functions for compilation tests +void process1(int ) {} +void process2(int ) {} +void process3(int ) {} + +// Do not run this, it is only meant to compile +void compilation_test_1() { + const padded_string bogus = ""_padded; + ondemand::parser parser; + auto doc = parser.iterate(bogus); + for (ondemand::object my_object : doc["mykey"]) { + for (auto field : my_object) { + if (field.key() == "key_value1") { process1(field.value()); } + else if (field.key() == "key_value2") { process2(field.value()); } + else if (field.key() == "key_value3") { process3(field.value()); } + } + } +} + + +// Do not run this, it is only meant to compile + void compilation_test_2() { + const padded_string bogus = ""_padded; + ondemand::parser parser; + auto doc = parser.iterate(bogus); + std::set default_users; + ondemand::array tweets = doc["statuses"].get_array(); + for (auto tweet_value : tweets) { + auto tweet = tweet_value.get_object(); + ondemand::object user = tweet["user"].get_object(); + std::string_view screen_name = user["screen_name"].get_string(); + bool default_profile = user["default_profile"].get_bool(); + if (default_profile) { default_users.insert(screen_name); } + } +} + + +// Do not run this, it is only meant to compile +void compilation_test_3() { + const padded_string bogus = ""_padded; + ondemand::parser parser; + auto doc = parser.iterate(bogus); + ondemand::array tweets; + if(! doc["statuses"].get(tweets)) { return; } + for (auto tweet_value : tweets) { + auto tweet = tweet_value.get_object(); + for (auto field : tweet) { + std::string_view key = field.unescaped_key().value(); + std::cout << "key = " << key << std::endl; + std::string_view val = std::string_view(field.value()); + std::cout << "value (assuming it is a string) = " << val << std::endl; + } + } +} +#endif // SIMDJSON_EXCEPTIONS + +int main(void) { + return 0; +} diff --git a/tests/ondemand/ondemand_dom_api_tests.cpp b/tests/ondemand/ondemand_dom_api_tests.cpp new file mode 100644 index 000000000..e9339b463 --- /dev/null +++ b/tests/ondemand/ondemand_dom_api_tests.cpp @@ -0,0 +1,1208 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace dom_api_tests { + using namespace std; + + bool iterate_object() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c": 3 })"_padded; + const char* expected_key[] = { "a", "b", "c" }; + const uint64_t expected_value[] = { 1, 2, 3 }; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + size_t i = 0; + for (auto [ field, error ] : object) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL( field.key(), expected_key[i]); + ASSERT_EQUAL( field.value().get_uint64().first, expected_value[i] ); + i++; + } + ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object_result = doc_result.get_object(); + size_t i = 0; + for (auto [ field, error ] : object_result) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL( field.key(), expected_key[i] ); + ASSERT_EQUAL( field.value().get_uint64().first, expected_value[i] ); + i++; + } + ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) ); + return true; + })); + TEST_SUCCEED(); + } + + bool iterate_array() { + TEST_START(); + const auto json = R"([ 1, 10, 100 ])"_padded; + const uint64_t expected_value[] = { 1, 10, 100 }; + + SUBTEST("ondemand::array", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::array array; + ASSERT_SUCCESS( doc_result.get(array) ); + size_t i=0; + for (auto value : array) { + int64_t actual; + ASSERT_SUCCESS( value.get(actual) ); + ASSERT_EQUAL(actual, expected_value[i]); + i++; + } + ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result array = doc_result.get_array(); + size_t i=0; + for (simdjson_unused auto value : array) { int64_t actual; ASSERT_SUCCESS( value.get(actual) ); ASSERT_EQUAL(actual, expected_value[i]); i++; } + ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + size_t i=0; + for (simdjson_unused auto value : doc) { int64_t actual; ASSERT_SUCCESS( value.get(actual) ); ASSERT_EQUAL(actual, expected_value[i]); i++; } + ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + size_t i=0; + for (simdjson_unused auto value : doc_result) { int64_t actual; ASSERT_SUCCESS( value.get(actual) ); ASSERT_EQUAL(actual, expected_value[i]); i++; } + ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); + return true; + })); + TEST_SUCCEED(); + } + + bool iterate_object_partial_children() { + TEST_START(); + auto json = R"( + { + "scalar_ignore": 0, + "empty_array_ignore": [], + "empty_object_ignore": {}, + "object_break": { "x": 3, "y": 33 }, + "object_break_unused": { "x": 4, "y": 44 }, + "object_index": { "x": 5, "y": 55 }, + "object_index_unused": { "x": 6, "y": 66 }, + "array_break": [ 7, 77, 777 ], + "array_break_unused": [ 8, 88, 888 ], + "quadruple_nested_break": { "a": [ { "b": [ 9, 99 ], "c": 999 }, 9999 ], "d": 99999 }, + "actual_value": 10 + } + )"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + size_t i = 0; + for (auto field : object) { + ondemand::raw_json_string key; + ASSERT_SUCCESS( field.key().get(key) ); + + switch (i) { + case 0: { + ASSERT_EQUAL(key, "scalar_ignore"); + std::cout << " - After ignoring empty scalar ..." << std::endl; + break; + } + case 1: { + ASSERT_EQUAL(key, "empty_array_ignore"); + std::cout << " - After ignoring empty array ..." << std::endl; + break; + } + case 2: { + ASSERT_EQUAL(key, "empty_object_ignore"); + std::cout << " - After ignoring empty object ..." << std::endl; + break; + } + // Break after using first value in child object + case 3: { + ASSERT_EQUAL(key, "object_break"); + + for (auto [ child_field, error ] : field.value().get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + uint64_t x; + ASSERT_SUCCESS( child_field.value().get(x) ); + ASSERT_EQUAL(x, 3); + break; // Break after the first value + } + std::cout << " - After using first value in child object ..." << std::endl; + break; + } + + // Break without using first value in child object + case 4: { + ASSERT_EQUAL(key, "object_break_unused"); + + for (auto [ child_field, error ] : field.value().get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + break; + } + std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; + break; + } + + // Only look up one field in child object + case 5: { + ASSERT_EQUAL(key, "object_index"); + + uint64_t x; + ASSERT_SUCCESS( field.value()["x"].get(x) ); + ASSERT_EQUAL( x, 5 ); + std::cout << " - After looking up one field in child object ..." << std::endl; + break; + } + + // Only look up one field in child object, but don't use it + case 6: { + ASSERT_EQUAL(key, "object_index_unused"); + + ASSERT_SUCCESS( field.value()["x"] ); + std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; + break; + } + + // Break after first value in child array + case 7: { + ASSERT_EQUAL(key, "array_break"); + for (auto child_value : field.value()) { + uint64_t x; + ASSERT_SUCCESS( child_value.get(x) ); + ASSERT_EQUAL( x, 7 ); + break; + } + std::cout << " - After using first value in child array ..." << std::endl; + break; + } + + // Break without using first value in child array + case 8: { + ASSERT_EQUAL(key, "array_break_unused"); + for (auto child_value : field.value()) { + ASSERT_SUCCESS(child_value); + break; + } + std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; + break; + } + + // Break out of multiple child loops + case 9: { + ASSERT_EQUAL(key, "quadruple_nested_break"); + for (auto child1 : field.value().get_object()) { + for (auto child2 : child1.value().get_array()) { + for (auto child3 : child2.get_object()) { + for (auto child4 : child3.value().get_array()) { + uint64_t x; + ASSERT_SUCCESS( child4.get(x) ); + ASSERT_EQUAL( x, 9 ); + break; + } + break; + } + break; + } + break; + } + std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; + break; + } + + // Test the actual value + case 10: { + ASSERT_EQUAL(key, "actual_value"); + uint64_t actual_value; + ASSERT_SUCCESS( field.value().get(actual_value) ); + ASSERT_EQUAL( actual_value, 10 ); + break; + } + } + + i++; + } + ASSERT_EQUAL( i, 11 ); // Make sure we found all the keys we expected + return true; + })); + return true; + } + + bool iterate_array_partial_children() { + TEST_START(); + auto json = R"( + [ + 0, + [], + {}, + { "x": 3, "y": 33 }, + { "x": 4, "y": 44 }, + { "x": 5, "y": 55 }, + { "x": 6, "y": 66 }, + [ 7, 77, 777 ], + [ 8, 88, 888 ], + { "a": [ { "b": [ 9, 99 ], "c": 999 }, 9999 ], "d": 99999 }, + 10 + ] + )"_padded; + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + size_t i = 0; + for (auto value : doc_result) { + ASSERT_SUCCESS(value); + + switch (i) { + case 0: { + std::cout << " - After ignoring empty scalar ..." << std::endl; + break; + } + case 1: { + std::cout << " - After ignoring empty array ..." << std::endl; + break; + } + case 2: { + std::cout << " - After ignoring empty object ..." << std::endl; + break; + } + // Break after using first value in child object + case 3: { + for (auto [ child_field, error ] : value.get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + uint64_t x; + ASSERT_SUCCESS( child_field.value().get(x) ); + ASSERT_EQUAL(x, 3); + break; // Break after the first value + } + std::cout << " - After using first value in child object ..." << std::endl; + break; + } + + // Break without using first value in child object + case 4: { + for (auto [ child_field, error ] : value.get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + break; + } + std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; + break; + } + + // Only look up one field in child object + case 5: { + uint64_t x; + ASSERT_SUCCESS( value["x"].get(x) ); + ASSERT_EQUAL( x, 5 ); + std::cout << " - After looking up one field in child object ..." << std::endl; + break; + } + + // Only look up one field in child object, but don't use it + case 6: { + ASSERT_SUCCESS( value["x"] ); + std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; + break; + } + + // Break after first value in child array + case 7: { + for (auto [ child_value, error ] : value) { + ASSERT_SUCCESS(error); + uint64_t x; + ASSERT_SUCCESS( child_value.get(x) ); + ASSERT_EQUAL( x, 7 ); + break; + } + std::cout << " - After using first value in child array ..." << std::endl; + break; + } + + // Break without using first value in child array + case 8: { + for (auto child_value : value) { + ASSERT_SUCCESS(child_value); + break; + } + std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; + break; + } + + // Break out of multiple child loops + case 9: { + for (auto child1 : value.get_object()) { + for (auto child2 : child1.value().get_array()) { + for (auto child3 : child2.get_object()) { + for (auto child4 : child3.value().get_array()) { + uint64_t x; + ASSERT_SUCCESS( child4.get(x) ); + ASSERT_EQUAL( x, 9 ); + break; + } + break; + } + break; + } + break; + } + std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; + break; + } + + // Test the actual value + case 10: { + uint64_t actual_value; + ASSERT_SUCCESS( value.get(actual_value) ); + ASSERT_EQUAL( actual_value, 10 ); + break; + } + } + + i++; + } + ASSERT_EQUAL( i, 11 ); // Make sure we found all the keys we expected + return true; + })); + return true; + } + + bool object_index_partial_children() { + TEST_START(); + auto json = R"( + { + "scalar_ignore": 0, + "empty_array_ignore": [], + "empty_object_ignore": {}, + "object_break": { "x": 3, "y": 33 }, + "object_break_unused": { "x": 4, "y": 44 }, + "object_index": { "x": 5, "y": 55 }, + "object_index_unused": { "x": 6, "y": 66 }, + "array_break": [ 7, 77, 777 ], + "array_break_unused": [ 8, 88, 888 ], + "quadruple_nested_break": { "a": [ { "b": [ 9, 99 ], "c": 999 }, 9999 ], "d": 99999 }, + "actual_value": 10 + } + )"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + + ASSERT_SUCCESS( object["scalar_ignore"] ); + std::cout << " - After ignoring empty scalar ..." << std::endl; + + ASSERT_SUCCESS( object["empty_array_ignore"] ); + std::cout << " - After ignoring empty array ..." << std::endl; + + ASSERT_SUCCESS( object["empty_object_ignore"] ); + std::cout << " - After ignoring empty object ..." << std::endl; + + // Break after using first value in child object + { + auto value = object["object_break"]; + for (auto [ child_field, error ] : value.get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + uint64_t x; + ASSERT_SUCCESS( child_field.value().get(x) ); + ASSERT_EQUAL(x, 3); + break; // Break after the first value + } + std::cout << " - After using first value in child object ..." << std::endl; + } + + // Break without using first value in child object + { + auto value = object["object_break_unused"]; + for (auto [ child_field, error ] : value.get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + break; + } + std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; + } + + // Only look up one field in child object + { + auto value = object["object_index"]; + + uint64_t x; + ASSERT_SUCCESS( value["x"].get(x) ); + ASSERT_EQUAL( x, 5 ); + std::cout << " - After looking up one field in child object ..." << std::endl; + } + + // Only look up one field in child object, but don't use it + { + auto value = object["object_index_unused"]; + + ASSERT_SUCCESS( value["x"] ); + std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; + } + + // Break after first value in child array + { + auto value = object["array_break"]; + + for (auto child_value : value) { + uint64_t x; + ASSERT_SUCCESS( child_value.get(x) ); + ASSERT_EQUAL( x, 7 ); + break; + } + std::cout << " - After using first value in child array ..." << std::endl; + } + + // Break without using first value in child array + { + auto value = object["array_break_unused"]; + + for (auto child_value : value) { + ASSERT_SUCCESS(child_value); + break; + } + std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; + } + + // Break out of multiple child loops + { + auto value = object["quadruple_nested_break"]; + for (auto child1 : value.get_object()) { + for (auto child2 : child1.value().get_array()) { + for (auto child3 : child2.get_object()) { + for (auto child4 : child3.value().get_array()) { + uint64_t x; + ASSERT_SUCCESS( child4.get(x) ); + ASSERT_EQUAL( x, 9 ); + break; + } + break; + } + break; + } + break; + } + std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; + } + + // Test the actual value + { + auto value = object["actual_value"]; + uint64_t actual_value; + ASSERT_SUCCESS( value.get(actual_value) ); + ASSERT_EQUAL( actual_value, 10 ); + } + + return true; + })); + + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_SUCCESS( doc_result["scalar_ignore"] ); + std::cout << " - After ignoring empty scalar ..." << std::endl; + + ASSERT_SUCCESS( doc_result["empty_array_ignore"] ); + std::cout << " - After ignoring empty array ..." << std::endl; + + ASSERT_SUCCESS( doc_result["empty_object_ignore"] ); + std::cout << " - After ignoring empty doc_result ..." << std::endl; + + // Break after using first value in child object + { + auto value = doc_result["object_break"]; + for (auto [ child_field, error ] : value.get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + uint64_t x; + ASSERT_SUCCESS( child_field.value().get(x) ); + ASSERT_EQUAL(x, 3); + break; // Break after the first value + } + std::cout << " - After using first value in child object ..." << std::endl; + } + + // Break without using first value in child object + { + auto value = doc_result["object_break_unused"]; + for (auto [ child_field, error ] : value.get_object()) { + ASSERT_SUCCESS(error); + ASSERT_EQUAL(child_field.key(), "x"); + break; + } + std::cout << " - After reaching (but not using) first value in child object ..." << std::endl; + } + + // Only look up one field in child object + { + auto value = doc_result["object_index"]; + + uint64_t x; + ASSERT_SUCCESS( value["x"].get(x) ); + ASSERT_EQUAL( x, 5 ); + std::cout << " - After looking up one field in child object ..." << std::endl; + } + + // Only look up one field in child object, but don't use it + { + auto value = doc_result["object_index_unused"]; + + ASSERT_SUCCESS( value["x"] ); + std::cout << " - After looking up (but not using) one field in child object ..." << std::endl; + } + + // Break after first value in child array + { + auto value = doc_result["array_break"]; + + for (auto child_value : value) { + uint64_t x; + ASSERT_SUCCESS( child_value.get(x) ); + ASSERT_EQUAL( x, 7 ); + break; + } + std::cout << " - After using first value in child array ..." << std::endl; + } + + // Break without using first value in child array + { + auto value = doc_result["array_break_unused"]; + + for (auto child_value : value) { + ASSERT_SUCCESS(child_value); + break; + } + std::cout << " - After reaching (but not using) first value in child array ..." << std::endl; + } + + // Break out of multiple child loops + { + auto value = doc_result["quadruple_nested_break"]; + for (auto child1 : value.get_object()) { + for (auto child2 : child1.value().get_array()) { + for (auto child3 : child2.get_object()) { + for (auto child4 : child3.value().get_array()) { + uint64_t x; + ASSERT_SUCCESS( child4.get(x) ); + ASSERT_EQUAL( x, 9 ); + break; + } + break; + } + break; + } + break; + } + std::cout << " - After breaking out of quadruply-nested arrays and objects ..." << std::endl; + } + + // Test the actual value + { + auto value = doc_result["actual_value"]; + uint64_t actual_value; + ASSERT_SUCCESS( value.get(actual_value) ); + ASSERT_EQUAL( actual_value, 10 ); + } + + return true; + })); + + return true; + } + + bool iterate_empty_object() { + TEST_START(); + auto json = R"({})"_padded; + + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + for (simdjson_unused auto field : object) { + TEST_FAIL("Unexpected field"); + } + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object_result = doc_result.get_object(); + for (simdjson_unused auto field : object_result) { + TEST_FAIL("Unexpected field"); + } + return true; + })); + TEST_SUCCEED(); + } + + bool iterate_empty_array() { + TEST_START(); + auto json = "[]"_padded; + SUBTEST("ondemand::array", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::array array; + ASSERT_SUCCESS( doc_result.get(array) ); + for (simdjson_unused auto value : array) { TEST_FAIL("Unexpected value"); } + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result array_result = doc_result.get_array(); + for (simdjson_unused auto value : array_result) { TEST_FAIL("Unexpected value"); } + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + for (simdjson_unused auto value : doc) { TEST_FAIL("Unexpected value"); } + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + for (simdjson_unused auto value : doc_result) { TEST_FAIL("Unexpected value"); } + return true; + })); + TEST_SUCCEED(); + } + + template + bool test_scalar_value(const padded_string &json, const T &expected) { + std::cout << "- JSON: " << json << endl; + SUBTEST( "simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + T actual; + ASSERT_SUCCESS( doc_result.get(actual) ); + ASSERT_EQUAL( expected, actual ); + return true; + })); + SUBTEST( "document", test_ondemand_doc(json, [&](auto doc_result) { + T actual; + ASSERT_SUCCESS( doc_result.get(actual) ); + ASSERT_EQUAL( expected, actual ); + return true; + })); + padded_string array_json = std::string("[") + std::string(json) + "]"; + std::cout << "- JSON: " << array_json << endl; + SUBTEST( "simdjson_result", test_ondemand_doc(array_json, [&](auto doc_result) { + int count = 0; + for (simdjson_result val_result : doc_result) { + T actual; + ASSERT_SUCCESS( val_result.get(actual) ); + ASSERT_EQUAL(expected, actual); + count++; + } + ASSERT_EQUAL(count, 1); + return true; + })); + SUBTEST( "ondemand::value", test_ondemand_doc(array_json, [&](auto doc_result) { + int count = 0; + for (simdjson_result val_result : doc_result) { + ondemand::value val; + ASSERT_SUCCESS( val_result.get(val) ); + T actual; + ASSERT_SUCCESS( val.get(actual) ); + ASSERT_EQUAL(expected, actual); + count++; + } + ASSERT_EQUAL(count, 1); + return true; + })); + TEST_SUCCEED(); + } + bool string_value() { + TEST_START(); + return test_scalar_value(R"("hi")"_padded, std::string_view("hi")); + } + + bool numeric_values() { + TEST_START(); + if (!test_scalar_value ("0"_padded, 0)) { return false; } + if (!test_scalar_value("0"_padded, 0)) { return false; } + if (!test_scalar_value ("0"_padded, 0)) { return false; } + if (!test_scalar_value ("1"_padded, 1)) { return false; } + if (!test_scalar_value("1"_padded, 1)) { return false; } + if (!test_scalar_value ("1"_padded, 1)) { return false; } + if (!test_scalar_value ("-1"_padded, -1)) { return false; } + if (!test_scalar_value ("-1"_padded, -1)) { return false; } + if (!test_scalar_value ("1.1"_padded, 1.1)) { return false; } + TEST_SUCCEED(); + } + + bool boolean_values() { + TEST_START(); + if (!test_scalar_value ("true"_padded, true)) { return false; } + if (!test_scalar_value ("false"_padded, false)) { return false; } + TEST_SUCCEED(); + } + + bool null_value() { + TEST_START(); + auto json = "null"_padded; + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc.is_null(), true ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result.is_null(), true ); + return true; + })); + json = "[null]"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + int count = 0; + for (auto value_result : doc_result) { + ondemand::value value; + ASSERT_SUCCESS( value_result.get(value) ); + ASSERT_EQUAL( value.is_null(), true ); + count++; + } + ASSERT_EQUAL( count, 1 ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + int count = 0; + for (auto value_result : doc_result) { + ASSERT_EQUAL( value_result.is_null(), true ); + count++; + } + ASSERT_EQUAL( count, 1 ); + return true; + })); + return true; + } + + bool object_index() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object; + object = doc_result.get_object(); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( doc["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( doc["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( doc["a"].get_uint64().first, 1 ); + ASSERT_ERROR( doc["d"], NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( doc_result["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( doc_result["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( doc_result["a"].get_uint64().first, 1 ); + ASSERT_ERROR( doc_result["d"], NO_SUCH_FIELD ); + return true; + })); + + json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value object; + ASSERT_SUCCESS( doc_result["outer"].get(object) ); + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result["outer"]; + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_EQUAL( object["b"].get_uint64().first, 2 ); + ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 ); + + ASSERT_EQUAL( object["a"].get_uint64().first, 1 ); + ASSERT_ERROR( object["d"], NO_SUCH_FIELD ); + return true; + })); + TEST_SUCCEED(); + } + + bool object_find_field_unordered() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object; + object = doc_result.get_object(); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( doc.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( doc.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc_result.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc_result.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( doc_result.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( doc_result.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + + json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value object; + ASSERT_SUCCESS( doc_result.find_field_unordered("outer").get(object) ); + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result.find_field_unordered("outer"); + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field_unordered("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field_unordered("c/d").get_uint64().first, 3 ); + + ASSERT_EQUAL( object.find_field_unordered("a").get_uint64().first, 1 ); + ASSERT_ERROR( object.find_field_unordered("d"), NO_SUCH_FIELD ); + return true; + })); + TEST_SUCCEED(); + } + + bool object_find_field() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object; + object = doc_result.get_object(); + + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( doc.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( doc.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( doc_result.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( doc_result.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( doc_result.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( doc_result.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + + json = R"({ "outer": { "a": 1, "b": 2, "c/d": 3 } })"_padded; + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value object; + ASSERT_SUCCESS( doc_result.find_field("outer").get(object) ); + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result.find_field("outer"); + ASSERT_EQUAL( object.find_field("a").get_uint64().first, 1 ); + ASSERT_EQUAL( object.find_field("b").get_uint64().first, 2 ); + ASSERT_EQUAL( object.find_field("c/d").get_uint64().first, 3 ); + + ASSERT_ERROR( object.find_field("a"), NO_SUCH_FIELD ); + ASSERT_ERROR( object.find_field("d"), NO_SUCH_FIELD ); + return true; + })); + TEST_SUCCEED(); + } + + bool nested_object_index() { + TEST_START(); + auto json = R"({ "x": { "y": { "z": 2 } } }})"_padded; + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( doc_result["x"]["y"]["z"].get_uint64().first, 2 ); + return true; + })); + SUBTEST("ondemand::document", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::document doc; + ASSERT_SUCCESS( std::move(doc_result).get(doc) ); + ASSERT_EQUAL( doc["x"]["y"]["z"].get_uint64().first, 2 ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result object = doc_result.get_object(); + ASSERT_EQUAL( object["x"]["y"]["z"].get_uint64().first, 2 ); + return true; + })); + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object; + ASSERT_SUCCESS( doc_result.get(object) ); + ASSERT_EQUAL( object["x"]["y"]["z"].get_uint64().first, 2 ); + return true; + })); + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + simdjson_result x = doc_result["x"]; + ASSERT_EQUAL( x["y"]["z"].get_uint64().first, 2 ); + return true; + })); + SUBTEST("ondemand::value", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::value x; + ASSERT_SUCCESS( doc_result["x"].get(x) ); + ASSERT_EQUAL( x["y"]["z"].get_uint64().first, 2 ); + return true; + })); + TEST_SUCCEED(); + } + +#if SIMDJSON_EXCEPTIONS + + bool iterate_object_exception() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c": 3 })"_padded; + const char* expected_key[] = { "a", "b", "c" }; + const uint64_t expected_value[] = { 1, 2, 3 }; + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + size_t i = 0; + for (ondemand::field field : doc_result.get_object()) { + ASSERT_EQUAL( field.key(), expected_key[i] ); + ASSERT_EQUAL( uint64_t(field.value()), expected_value[i] ); + i++; + } + ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) ); + return true; + })); + TEST_SUCCEED(); + } + + bool iterate_array_exception() { + TEST_START(); + auto json = R"([ 1, 10, 100 ])"_padded; + const uint64_t expected_value[] = { 1, 10, 100 }; + + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + size_t i=0; + for (int64_t actual : doc_result) { ASSERT_EQUAL(actual, expected_value[i]); i++; } + ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value)); + return true; + })); + TEST_SUCCEED(); + } + + bool iterate_empty_object_exception() { + TEST_START(); + auto json = R"({})"_padded; + + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + for (simdjson_unused ondemand::field field : doc_result.get_object()) { + TEST_FAIL("Unexpected field"); + } + return true; + })); + + TEST_SUCCEED(); + } + + bool iterate_empty_array_exception() { + TEST_START(); + auto json = "[]"_padded; + + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + for (simdjson_unused ondemand::value value : doc_result) { TEST_FAIL("Unexpected value"); } + return true; + })); + + TEST_SUCCEED(); + } + + template + bool test_scalar_value_exception(const padded_string &json, const T &expected) { + std::cout << "- JSON: " << json << endl; + SUBTEST( "document", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( expected, T(doc_result) ); + return true; + })); + padded_string array_json = std::string("[") + std::string(json) + "]"; + std::cout << "- JSON: " << array_json << endl; + SUBTEST( "value", test_ondemand_doc(array_json, [&](auto doc_result) { + int count = 0; + for (T actual : doc_result) { + ASSERT_EQUAL( expected, actual ); + count++; + } + ASSERT_EQUAL(count, 1); + return true; + })); + TEST_SUCCEED(); + } + bool string_value_exception() { + TEST_START(); + return test_scalar_value_exception(R"("hi")"_padded, std::string_view("hi")); + } + + bool numeric_values_exception() { + TEST_START(); + if (!test_scalar_value_exception ("0"_padded, 0)) { return false; } + if (!test_scalar_value_exception("0"_padded, 0)) { return false; } + if (!test_scalar_value_exception ("0"_padded, 0)) { return false; } + if (!test_scalar_value_exception ("1"_padded, 1)) { return false; } + if (!test_scalar_value_exception("1"_padded, 1)) { return false; } + if (!test_scalar_value_exception ("1"_padded, 1)) { return false; } + if (!test_scalar_value_exception ("-1"_padded, -1)) { return false; } + if (!test_scalar_value_exception ("-1"_padded, -1)) { return false; } + if (!test_scalar_value_exception ("1.1"_padded, 1.1)) { return false; } + TEST_SUCCEED(); + } + + bool boolean_values_exception() { + TEST_START(); + if (!test_scalar_value_exception ("true"_padded, true)) { return false; } + if (!test_scalar_value_exception ("false"_padded, false)) { return false; } + TEST_SUCCEED(); + } + + + bool object_index_exception() { + TEST_START(); + auto json = R"({ "a": 1, "b": 2, "c/d": 3})"_padded; + SUBTEST("ondemand::object", test_ondemand_doc(json, [&](auto doc_result) { + ondemand::object object = doc_result; + + ASSERT_EQUAL( uint64_t(object["a"]), 1 ); + ASSERT_EQUAL( uint64_t(object["b"]), 2 ); + ASSERT_EQUAL( uint64_t(object["c/d"]), 3 ); + + return true; + })); + TEST_SUCCEED(); + } + bool nested_object_index_exception() { + TEST_START(); + auto json = R"({ "x": { "y": { "z": 2 } } }})"_padded; + SUBTEST("simdjson_result", test_ondemand_doc(json, [&](auto doc_result) { + ASSERT_EQUAL( uint64_t(doc_result["x"]["y"]["z"]), 2 ); + return true; + })); + TEST_SUCCEED(); + } + +#endif // SIMDJSON_EXCEPTIONS + + bool run() { + return + iterate_array() && + iterate_empty_array() && + iterate_object() && + iterate_empty_object() && + string_value() && + numeric_values() && + boolean_values() && + null_value() && + object_index() && + object_find_field_unordered() && + object_find_field() && + nested_object_index() && + iterate_object_partial_children() && + iterate_array_partial_children() && + object_index_partial_children() && +#if SIMDJSON_EXCEPTIONS + iterate_object_exception() && + iterate_array_exception() && + string_value_exception() && + numeric_values_exception() && + boolean_values_exception() && + object_index_exception() && + nested_object_index_exception() && +#endif // SIMDJSON_EXCEPTIONS + true; + } + +} // namespace dom_api_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, dom_api_tests::run); +} diff --git a/tests/ondemand/ondemand_error_tests.cpp b/tests/ondemand/ondemand_error_tests.cpp new file mode 100644 index 000000000..4e4cc4993 --- /dev/null +++ b/tests/ondemand/ondemand_error_tests.cpp @@ -0,0 +1,438 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace error_tests { + using namespace std; + + bool empty_document_error() { + TEST_START(); + ondemand::parser parser; + ASSERT_ERROR( parser.iterate(""_padded), EMPTY ); + TEST_SUCCEED(); + } + + namespace wrong_type { + +#define TEST_CAST_ERROR(JSON, TYPE, ERROR) \ + std::cout << "- Subtest: get_" << (#TYPE) << "() - JSON: " << (JSON) << std::endl; \ + if (!test_ondemand_doc((JSON##_padded), [&](auto doc_result) { \ + ASSERT_ERROR( doc_result.get_##TYPE(), (ERROR) ); \ + return true; \ + })) { \ + return false; \ + } \ + { \ + padded_string a_json(std::string(R"({ "a": )") + JSON + " })"); \ + std::cout << R"(- Subtest: get_)" << (#TYPE) << "() - JSON: " << a_json << std::endl; \ + if (!test_ondemand_doc(a_json, [&](auto doc_result) { \ + ASSERT_ERROR( doc_result["a"].get_##TYPE(), (ERROR) ); \ + return true; \ + })) { \ + return false; \ + }; \ + } + + bool wrong_type_array() { + TEST_START(); + TEST_CAST_ERROR("[]", object, INCORRECT_TYPE); + TEST_CAST_ERROR("[]", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("[]", int64, NUMBER_ERROR); + TEST_CAST_ERROR("[]", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("[]", double, NUMBER_ERROR); + TEST_CAST_ERROR("[]", string, INCORRECT_TYPE); + TEST_CAST_ERROR("[]", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_object() { + TEST_START(); + TEST_CAST_ERROR("{}", array, INCORRECT_TYPE); + TEST_CAST_ERROR("{}", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("{}", int64, NUMBER_ERROR); + TEST_CAST_ERROR("{}", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("{}", double, NUMBER_ERROR); + TEST_CAST_ERROR("{}", string, INCORRECT_TYPE); + TEST_CAST_ERROR("{}", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_true() { + TEST_START(); + TEST_CAST_ERROR("true", array, INCORRECT_TYPE); + TEST_CAST_ERROR("true", object, INCORRECT_TYPE); + TEST_CAST_ERROR("true", int64, NUMBER_ERROR); + TEST_CAST_ERROR("true", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("true", double, NUMBER_ERROR); + TEST_CAST_ERROR("true", string, INCORRECT_TYPE); + TEST_CAST_ERROR("true", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_false() { + TEST_START(); + TEST_CAST_ERROR("false", array, INCORRECT_TYPE); + TEST_CAST_ERROR("false", object, INCORRECT_TYPE); + TEST_CAST_ERROR("false", int64, NUMBER_ERROR); + TEST_CAST_ERROR("false", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("false", double, NUMBER_ERROR); + TEST_CAST_ERROR("false", string, INCORRECT_TYPE); + TEST_CAST_ERROR("false", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_null() { + TEST_START(); + TEST_CAST_ERROR("null", array, INCORRECT_TYPE); + TEST_CAST_ERROR("null", object, INCORRECT_TYPE); + TEST_CAST_ERROR("null", int64, NUMBER_ERROR); + TEST_CAST_ERROR("null", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("null", double, NUMBER_ERROR); + TEST_CAST_ERROR("null", string, INCORRECT_TYPE); + TEST_CAST_ERROR("null", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_1() { + TEST_START(); + TEST_CAST_ERROR("1", array, INCORRECT_TYPE); + TEST_CAST_ERROR("1", object, INCORRECT_TYPE); + TEST_CAST_ERROR("1", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("1", string, INCORRECT_TYPE); + TEST_CAST_ERROR("1", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_negative_1() { + TEST_START(); + TEST_CAST_ERROR("-1", array, INCORRECT_TYPE); + TEST_CAST_ERROR("-1", object, INCORRECT_TYPE); + TEST_CAST_ERROR("-1", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("-1", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("-1", string, INCORRECT_TYPE); + TEST_CAST_ERROR("-1", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_float() { + TEST_START(); + TEST_CAST_ERROR("1.1", array, INCORRECT_TYPE); + TEST_CAST_ERROR("1.1", object, INCORRECT_TYPE); + TEST_CAST_ERROR("1.1", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("1.1", int64, NUMBER_ERROR); + TEST_CAST_ERROR("1.1", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("1.1", string, INCORRECT_TYPE); + TEST_CAST_ERROR("1.1", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_negative_int64_overflow() { + TEST_START(); + TEST_CAST_ERROR("-9223372036854775809", array, INCORRECT_TYPE); + TEST_CAST_ERROR("-9223372036854775809", object, INCORRECT_TYPE); + TEST_CAST_ERROR("-9223372036854775809", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("-9223372036854775809", int64, NUMBER_ERROR); + TEST_CAST_ERROR("-9223372036854775809", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("-9223372036854775809", string, INCORRECT_TYPE); + TEST_CAST_ERROR("-9223372036854775809", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_int64_overflow() { + TEST_START(); + TEST_CAST_ERROR("9223372036854775808", array, INCORRECT_TYPE); + TEST_CAST_ERROR("9223372036854775808", object, INCORRECT_TYPE); + TEST_CAST_ERROR("9223372036854775808", bool, INCORRECT_TYPE); + // TODO BUG: this should be an error but is presently not + // TEST_CAST_ERROR("9223372036854775808", int64, NUMBER_ERROR); + TEST_CAST_ERROR("9223372036854775808", string, INCORRECT_TYPE); + TEST_CAST_ERROR("9223372036854775808", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool wrong_type_uint64_overflow() { + TEST_START(); + TEST_CAST_ERROR("18446744073709551616", array, INCORRECT_TYPE); + TEST_CAST_ERROR("18446744073709551616", object, INCORRECT_TYPE); + TEST_CAST_ERROR("18446744073709551616", bool, INCORRECT_TYPE); + TEST_CAST_ERROR("18446744073709551616", int64, NUMBER_ERROR); + // TODO BUG: this should be an error but is presently not + // TEST_CAST_ERROR("18446744073709551616", uint64, NUMBER_ERROR); + TEST_CAST_ERROR("18446744073709551616", string, INCORRECT_TYPE); + TEST_CAST_ERROR("18446744073709551616", raw_json_string, INCORRECT_TYPE); + TEST_SUCCEED(); + } + + bool run() { + return + wrong_type_1() && + wrong_type_array() && + wrong_type_false() && + wrong_type_float() && + wrong_type_int64_overflow() && + wrong_type_negative_1() && + wrong_type_negative_int64_overflow() && + wrong_type_null() && + wrong_type_object() && + wrong_type_true() && + wrong_type_uint64_overflow() && + true; + } + + } // namespace wrong_type + + template + bool assert_iterate(T array, V *expected, size_t N, simdjson::error_code *expected_error, size_t N2) { + size_t count = 0; + for (auto elem : std::forward(array)) { + V actual; + auto actual_error = elem.get(actual); + if (count >= N) { + if (count >= (N+N2)) { + std::cerr << "FAIL: Extra error reported: " << actual_error << std::endl; + return false; + } + ASSERT_ERROR(actual_error, expected_error[count - N]); + } else { + ASSERT_SUCCESS(actual_error); + ASSERT_EQUAL(actual, expected[count]); + } + count++; + } + ASSERT_EQUAL(count, N+N2); + return true; + } + + template + bool assert_iterate(T &array, V (&&expected)[N], simdjson::error_code (&&expected_error)[N2]) { + return assert_iterate(array, expected, N, expected_error, N2); + } + + template + bool assert_iterate(T &array, simdjson::error_code (&&expected_error)[N2]) { + return assert_iterate(array, nullptr, 0, expected_error, N2); + } + + template + bool assert_iterate(T &array, V (&&expected)[N]) { + return assert_iterate(array, expected, N, nullptr, 0); + } + + template + bool assert_iterate(T &&array, V (&&expected)[N], simdjson::error_code (&&expected_error)[N2]) { + return assert_iterate(std::forward(array), expected, N, expected_error, N2); + } + + template + bool assert_iterate(T &&array, simdjson::error_code (&&expected_error)[N2]) { + return assert_iterate(std::forward(array), nullptr, 0, expected_error, N2); + } + + template + bool assert_iterate(T &&array, V (&&expected)[N]) { + return assert_iterate(std::forward(array), expected, N, nullptr, 0); + } + + bool top_level_array_iterate_error() { + TEST_START(); + ONDEMAND_SUBTEST("missing comma", "[1 1]", assert_iterate(doc, { int64_t(1) }, { TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", "[1,,1]", assert_iterate(doc, { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", "[,]", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", "[,,]", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); + TEST_SUCCEED(); + } + bool top_level_array_iterate_unclosed_error() { + TEST_START(); + ONDEMAND_SUBTEST("unclosed extra comma", "[,", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", "[1 ", assert_iterate(doc, { int64_t(1) }, { TAPE_ERROR })); + // TODO These pass the user values that may run past the end of the buffer if they aren't careful + // In particular, if the padding is decorated with the wrong values, we could cause overrun! + ONDEMAND_SUBTEST("unclosed extra comma", "[,,", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", "[1,", assert_iterate(doc, { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", "[1", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", "[", assert_iterate(doc, { NUMBER_ERROR, TAPE_ERROR })); + TEST_SUCCEED(); + } + + bool array_iterate_error() { + TEST_START(); + ONDEMAND_SUBTEST("missing comma", R"({ "a": [1 1] })", assert_iterate(doc["a"], { int64_t(1) }, { TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", R"({ "a": [1,,1] })", assert_iterate(doc["a"], { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", R"({ "a": [1,,] })", assert_iterate(doc["a"], { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", R"({ "a": [,] })", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("extra comma ", R"({ "a": [,,] })", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); + TEST_SUCCEED(); + } + bool array_iterate_unclosed_error() { + TEST_START(); + ONDEMAND_SUBTEST("unclosed extra comma", R"({ "a": [,)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed extra comma", R"({ "a": [,,)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", R"({ "a": [1 )", assert_iterate(doc["a"], { int64_t(1) }, { TAPE_ERROR })); + // TODO These pass the user values that may run past the end of the buffer if they aren't careful + // In particular, if the padding is decorated with the wrong values, we could cause overrun! + ONDEMAND_SUBTEST("unclosed ", R"({ "a": [1,)", assert_iterate(doc["a"], { int64_t(1) }, { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", R"({ "a": [1)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed ", R"({ "a": [)", assert_iterate(doc["a"], { NUMBER_ERROR, TAPE_ERROR })); + TEST_SUCCEED(); + } + + template + bool assert_iterate_object(T &&object, const char **expected_key, V *expected, size_t N, simdjson::error_code *expected_error, size_t N2) { + size_t count = 0; + for (auto field : object) { + V actual; + auto actual_error = field.value().get(actual); + if (count >= N) { + ASSERT((count - N) < N2, "Extra error reported"); + ASSERT_ERROR(actual_error, expected_error[count - N]); + } else { + ASSERT_SUCCESS(actual_error); + ASSERT_EQUAL(field.key().first, expected_key[count]); + ASSERT_EQUAL(actual, expected[count]); + } + count++; + } + ASSERT_EQUAL(count, N+N2); + return true; + } + + template + bool assert_iterate_object(T &&object, const char *(&&expected_key)[N], V (&&expected)[N], simdjson::error_code (&&expected_error)[N2]) { + return assert_iterate_object(std::forward(object), expected_key, expected, N, expected_error, N2); + } + + template + bool assert_iterate_object(T &&object, simdjson::error_code (&&expected_error)[N2]) { + return assert_iterate_object(std::forward(object), nullptr, nullptr, 0, expected_error, N2); + } + + template + bool assert_iterate_object(T &&object, const char *(&&expected_key)[N], V (&&expected)[N]) { + return assert_iterate_object(std::forward(object), expected_key, expected, N, nullptr, 0); + } + + bool object_iterate_error() { + TEST_START(); + ONDEMAND_SUBTEST("missing colon", R"({ "a" 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("missing key ", R"({ : 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("missing value", R"({ "a": , "b": 2 })", assert_iterate_object(doc.get_object(), { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", assert_iterate_object(doc.get_object(), { "a" }, { int64_t(1) }, { TAPE_ERROR })); + TEST_SUCCEED(); + } + bool object_iterate_wrong_key_type_error() { + TEST_START(); + ONDEMAND_SUBTEST("wrong key type", R"({ 1: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("wrong key type", R"({ true: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("wrong key type", R"({ false: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("wrong key type", R"({ null: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("wrong key type", R"({ []: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("wrong key type", R"({ {}: 1, "b": 2 })", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + TEST_SUCCEED(); + } + bool object_iterate_unclosed_error() { + TEST_START(); + ONDEMAND_SUBTEST("unclosed", R"({ "a": 1, )", assert_iterate_object(doc.get_object(), { "a" }, { int64_t(1) }, { TAPE_ERROR })); + // TODO These next two pass the user a value that may run past the end of the buffer if they aren't careful. + // In particular, if the padding is decorated with the wrong values, we could cause overrun! + ONDEMAND_SUBTEST("unclosed", R"({ "a": 1 )", assert_iterate_object(doc.get_object(), { "a" }, { int64_t(1) }, { TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed", R"({ "a": )", assert_iterate_object(doc.get_object(), { NUMBER_ERROR, TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed", R"({ "a" )", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + ONDEMAND_SUBTEST("unclosed", R"({ )", assert_iterate_object(doc.get_object(), { TAPE_ERROR })); + TEST_SUCCEED(); + } + + bool object_lookup_error() { + TEST_START(); + ONDEMAND_SUBTEST("missing colon", R"({ "a" 1, "b": 2 })", assert_error(doc["a"], TAPE_ERROR)); + ONDEMAND_SUBTEST("missing key ", R"({ : 1, "b": 2 })", assert_error(doc["a"], TAPE_ERROR)); + ONDEMAND_SUBTEST("missing value", R"({ "a": , "b": 2 })", assert_success(doc["a"])); + ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", assert_success(doc["a"])); + TEST_SUCCEED(); + } + bool object_lookup_unclosed_error() { + TEST_START(); + // TODO This one passes the user a value that may run past the end of the buffer if they aren't careful. + // In particular, if the padding is decorated with the wrong values, we could cause overrun! + ONDEMAND_SUBTEST("unclosed", R"({ "a": )", assert_success(doc["a"])); + ONDEMAND_SUBTEST("unclosed", R"({ "a" )", assert_error(doc["a"], TAPE_ERROR)); + ONDEMAND_SUBTEST("unclosed", R"({ )", assert_error(doc["a"], TAPE_ERROR)); + TEST_SUCCEED(); + } + + bool object_lookup_miss_error() { + TEST_START(); + ONDEMAND_SUBTEST("missing colon", R"({ "a" 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("missing key ", R"({ : 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("missing value", R"({ "a": , "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + TEST_SUCCEED(); + } + bool object_lookup_miss_wrong_key_type_error() { + TEST_START(); + ONDEMAND_SUBTEST("wrong key type", R"({ 1: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("wrong key type", R"({ true: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("wrong key type", R"({ false: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("wrong key type", R"({ null: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("wrong key type", R"({ []: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("wrong key type", R"({ {}: 1, "b": 2 })", assert_error(doc["b"], TAPE_ERROR)); + TEST_SUCCEED(); + } + bool object_lookup_miss_unclosed_error() { + TEST_START(); + ONDEMAND_SUBTEST("unclosed", R"({ "a": 1, )", assert_error(doc["b"], TAPE_ERROR)); + // TODO These next two pass the user a value that may run past the end of the buffer if they aren't careful. + // In particular, if the padding is decorated with the wrong values, we could cause overrun! + ONDEMAND_SUBTEST("unclosed", R"({ "a": 1 )", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("unclosed", R"({ "a": )", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("unclosed", R"({ "a" )", assert_error(doc["b"], TAPE_ERROR)); + ONDEMAND_SUBTEST("unclosed", R"({ )", assert_error(doc["b"], TAPE_ERROR)); + TEST_SUCCEED(); + } + bool object_lookup_miss_next_error() { + TEST_START(); + ONDEMAND_SUBTEST("missing comma", R"({ "a": 1 "b": 2 })", ([&]() { + auto obj = doc.get_object(); + return assert_result(obj["a"], 1) && assert_error(obj["b"], TAPE_ERROR); + })()); + TEST_SUCCEED(); + } + + bool run() { + return + empty_document_error() && + top_level_array_iterate_error() && + top_level_array_iterate_unclosed_error() && + array_iterate_error() && + array_iterate_unclosed_error() && + wrong_type::run() && + object_iterate_error() && + object_iterate_wrong_key_type_error() && + object_iterate_unclosed_error() && + object_lookup_error() && + object_lookup_unclosed_error() && + object_lookup_miss_error() && + object_lookup_miss_unclosed_error() && + object_lookup_miss_wrong_key_type_error() && + object_lookup_miss_next_error() && + true; + } +} + +int main(int argc, char *argv[]) { + return test_main(argc, argv, error_tests::run); +} diff --git a/tests/ondemand/ondemand_key_string_tests.cpp b/tests/ondemand/ondemand_key_string_tests.cpp new file mode 100644 index 000000000..dbf61231c --- /dev/null +++ b/tests/ondemand/ondemand_key_string_tests.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace key_string_tests { +#if SIMDJSON_EXCEPTIONS + bool parser_key_value() { + TEST_START(); + ondemand::parser parser; + const padded_string json = R"({ "1": "1", "2": "2", "3": "3", "abc": "abc", "\u0075": "\u0075" })"_padded; + auto doc = parser.iterate(json); + for(auto field : doc.get_object()) { + std::string_view keyv = field.unescaped_key(); + std::string_view valuev = field.value(); + if(keyv != valuev) { return false; } + } + return true; + } +#endif // SIMDJSON_EXCEPTIONS + bool run() { + return +#if SIMDJSON_EXCEPTIONS + parser_key_value() && +#endif // SIMDJSON_EXCEPTIONS + true; + } + +} // namespace key_string_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, key_string_tests::run); +} diff --git a/tests/ondemand/ondemand_number_tests.cpp b/tests/ondemand/ondemand_number_tests.cpp new file mode 100644 index 000000000..f252afce7 --- /dev/null +++ b/tests/ondemand/ondemand_number_tests.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace number_tests { + + bool small_integers() { + std::cout << __func__ << std::endl; + for (int64_t m = 10; m < 20; m++) { + for (int64_t i = -1024; i < 1024; i++) { + if(!test_ondemand(std::to_string(i), + [&](int64_t actual) { + ASSERT_EQUAL(actual, i); + return true; + })) { + return false; + } // if + } // for i + } // for m + return true; + } + + bool powers_of_two() { + std::cout << __func__ << std::endl; + + // converts the double "expected" to a padded string + auto format_into_padded=[](const double expected) -> padded_string + { + char buf[1024]; + const auto n = std::snprintf(buf, + sizeof(buf), + "%.*e", + std::numeric_limits::max_digits10 - 1, + expected); + const auto nz=static_cast(n); + if (n<0 || nz >= sizeof(buf)) { std::abort(); } + return padded_string(buf, nz); + }; + + for (int i = -1075; i < 1024; ++i) {// large negative values should be zero. + const double expected = std::pow(2, i); + const auto buf=format_into_padded(expected); + std::fflush(nullptr); + if(!test_ondemand(buf, + [&](double actual) { + if(actual!=expected) { + std::cerr << "JSON '" << buf << " parsed to "; + std::fprintf( stderr," %18.18g instead of %18.18g\n", actual, expected); // formatting numbers is easier with printf + SIMDJSON_SHOW_DEFINE(FLT_EVAL_METHOD); + return false; + } + return true; + })) { + return false; + } // if + } // for i + return true; + } + + static const double testing_power_of_ten[] = { + 1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299, + 1e-298, 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291, 1e-290, + 1e-289, 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282, 1e-281, + 1e-280, 1e-279, 1e-278, 1e-277, 1e-276, 1e-275, 1e-274, 1e-273, 1e-272, + 1e-271, 1e-270, 1e-269, 1e-268, 1e-267, 1e-266, 1e-265, 1e-264, 1e-263, + 1e-262, 1e-261, 1e-260, 1e-259, 1e-258, 1e-257, 1e-256, 1e-255, 1e-254, + 1e-253, 1e-252, 1e-251, 1e-250, 1e-249, 1e-248, 1e-247, 1e-246, 1e-245, + 1e-244, 1e-243, 1e-242, 1e-241, 1e-240, 1e-239, 1e-238, 1e-237, 1e-236, + 1e-235, 1e-234, 1e-233, 1e-232, 1e-231, 1e-230, 1e-229, 1e-228, 1e-227, + 1e-226, 1e-225, 1e-224, 1e-223, 1e-222, 1e-221, 1e-220, 1e-219, 1e-218, + 1e-217, 1e-216, 1e-215, 1e-214, 1e-213, 1e-212, 1e-211, 1e-210, 1e-209, + 1e-208, 1e-207, 1e-206, 1e-205, 1e-204, 1e-203, 1e-202, 1e-201, 1e-200, + 1e-199, 1e-198, 1e-197, 1e-196, 1e-195, 1e-194, 1e-193, 1e-192, 1e-191, + 1e-190, 1e-189, 1e-188, 1e-187, 1e-186, 1e-185, 1e-184, 1e-183, 1e-182, + 1e-181, 1e-180, 1e-179, 1e-178, 1e-177, 1e-176, 1e-175, 1e-174, 1e-173, + 1e-172, 1e-171, 1e-170, 1e-169, 1e-168, 1e-167, 1e-166, 1e-165, 1e-164, + 1e-163, 1e-162, 1e-161, 1e-160, 1e-159, 1e-158, 1e-157, 1e-156, 1e-155, + 1e-154, 1e-153, 1e-152, 1e-151, 1e-150, 1e-149, 1e-148, 1e-147, 1e-146, + 1e-145, 1e-144, 1e-143, 1e-142, 1e-141, 1e-140, 1e-139, 1e-138, 1e-137, + 1e-136, 1e-135, 1e-134, 1e-133, 1e-132, 1e-131, 1e-130, 1e-129, 1e-128, + 1e-127, 1e-126, 1e-125, 1e-124, 1e-123, 1e-122, 1e-121, 1e-120, 1e-119, + 1e-118, 1e-117, 1e-116, 1e-115, 1e-114, 1e-113, 1e-112, 1e-111, 1e-110, + 1e-109, 1e-108, 1e-107, 1e-106, 1e-105, 1e-104, 1e-103, 1e-102, 1e-101, + 1e-100, 1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93, 1e-92, + 1e-91, 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84, 1e-83, + 1e-82, 1e-81, 1e-80, 1e-79, 1e-78, 1e-77, 1e-76, 1e-75, 1e-74, + 1e-73, 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66, 1e-65, + 1e-64, 1e-63, 1e-62, 1e-61, 1e-60, 1e-59, 1e-58, 1e-57, 1e-56, + 1e-55, 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48, 1e-47, + 1e-46, 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40, 1e-39, 1e-38, + 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, + 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20, + 1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, + 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, + 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, + 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, + 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, + 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32, 1e33, 1e34, + 1e35, 1e36, 1e37, 1e38, 1e39, 1e40, 1e41, 1e42, 1e43, + 1e44, 1e45, 1e46, 1e47, 1e48, 1e49, 1e50, 1e51, 1e52, + 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59, 1e60, 1e61, + 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69, 1e70, + 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79, + 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, + 1e89, 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, + 1e98, 1e99, 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, + 1e107, 1e108, 1e109, 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, + 1e116, 1e117, 1e118, 1e119, 1e120, 1e121, 1e122, 1e123, 1e124, + 1e125, 1e126, 1e127, 1e128, 1e129, 1e130, 1e131, 1e132, 1e133, + 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, 1e140, 1e141, 1e142, + 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, 1e150, 1e151, + 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159, 1e160, + 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168, 1e169, + 1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177, 1e178, + 1e179, 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186, 1e187, + 1e188, 1e189, 1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, + 1e197, 1e198, 1e199, 1e200, 1e201, 1e202, 1e203, 1e204, 1e205, + 1e206, 1e207, 1e208, 1e209, 1e210, 1e211, 1e212, 1e213, 1e214, + 1e215, 1e216, 1e217, 1e218, 1e219, 1e220, 1e221, 1e222, 1e223, + 1e224, 1e225, 1e226, 1e227, 1e228, 1e229, 1e230, 1e231, 1e232, + 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239, 1e240, 1e241, + 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249, 1e250, + 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259, + 1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267, 1e268, + 1e269, 1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276, 1e277, + 1e278, 1e279, 1e280, 1e281, 1e282, 1e283, 1e284, 1e285, 1e286, + 1e287, 1e288, 1e289, 1e290, 1e291, 1e292, 1e293, 1e294, 1e295, + 1e296, 1e297, 1e298, 1e299, 1e300, 1e301, 1e302, 1e303, 1e304, + 1e305, 1e306, 1e307, 1e308}; + + + + bool powers_of_ten() { + std::cout << __func__ << std::endl; + char buf[1024]; + + const bool is_pow_correct{1e-308 == std::pow(10,-308)}; + const int start_point = is_pow_correct ? -10000 : -307; + if(!is_pow_correct) { + std::cout << "On your system, the pow function is busted. Sorry about that. " << std::endl; + } + for (int i = start_point; i <= 308; ++i) {// large negative values should be zero. + const size_t n = std::snprintf(buf, sizeof(buf), "1e%d", i); + if (n >= sizeof(buf)) { std::abort(); } + std::fflush(nullptr); + const double expected = ((i >= -307) ? testing_power_of_ten[i + 307]: std::pow(10, i)); + + if(!test_ondemand(padded_string(buf, n), [&](double actual) { + if(actual!=expected) { + std::cerr << "JSON '" << buf << " parsed to "; + std::fprintf( stderr," %18.18g instead of %18.18g\n", actual, expected); // formatting numbers is easier with printf + SIMDJSON_SHOW_DEFINE(FLT_EVAL_METHOD); + return false; + } + return true; + })) { + return false; + } // if + } // for i + std::printf("Powers of 10 can be parsed.\n"); + return true; + } + + void github_issue_1273() { + padded_string bad(std::string_view("0.0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000122978293824")); + simdjson::builtin::ondemand::parser parser; + simdjson_unused auto blah=parser.iterate(bad); + double x; + simdjson_unused auto blah2=blah.get(x); + } + + bool old_crashes() { + github_issue_1273(); + return true; + } + + bool run() { + return small_integers() && + powers_of_two() && + powers_of_ten() && + old_crashes(); + } + +} // namespace number_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, number_tests::run); +} diff --git a/tests/ondemand/ondemand_ordering_tests.cpp b/tests/ondemand/ondemand_ordering_tests.cpp new file mode 100644 index 000000000..0988dfea2 --- /dev/null +++ b/tests/ondemand/ondemand_ordering_tests.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace ordering_tests { + using namespace std; + +#if SIMDJSON_EXCEPTIONS + + auto json = "{\"coordinates\":[{\"x\":1.1,\"y\":2.2,\"z\":3.3}]}"_padded; + + bool in_order_object_index() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + x += double(point_object["x"]); + y += double(point_object["y"]); + z += double(point_object["z"]); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool in_order_object_find_field_unordered() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + x += double(point_object.find_field_unordered("x")); + y += double(point_object.find_field_unordered("y")); + z += double(point_object.find_field_unordered("z")); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool in_order_object_find_field() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + x += double(point_object.find_field("x")); + y += double(point_object.find_field("y")); + z += double(point_object.find_field("z")); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool out_of_order_object_index() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + z += double(point_object["z"]); + x += double(point_object["x"]); + y += double(point_object["y"]); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool out_of_order_object_find_field_unordered() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + z += double(point_object.find_field_unordered("z")); + x += double(point_object.find_field_unordered("x")); + y += double(point_object.find_field_unordered("y")); + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } + + bool out_of_order_object_find_field() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + z += double(point_object.find_field("z")); + ASSERT_ERROR( point_object.find_field("x"), NO_SUCH_FIELD ); + ASSERT_ERROR( point_object.find_field("y"), NO_SUCH_FIELD ); + } + return (x == 0) && (y == 0) && (z == 3.3); + } + + bool foreach_object_field_lookup() { + TEST_START(); + ondemand::parser parser{}; + auto doc = parser.iterate(json); + double x{0}; + double y{0}; + double z{0}; + for (ondemand::object point_object : doc["coordinates"]) { + for (auto field : point_object) { + if (field.key() == "z") { z += double(field.value()); } + else if (field.key() == "x") { x += double(field.value()); } + else if (field.key() == "y") { y += double(field.value()); } + } + } + return (x == 1.1) && (y == 2.2) && (z == 3.3); + } +#endif // SIMDJSON_EXCEPTIONS + + bool run() { + return +#if SIMDJSON_EXCEPTIONS + in_order_object_index() && + in_order_object_find_field_unordered() && + in_order_object_find_field() && + out_of_order_object_index() && + out_of_order_object_find_field_unordered() && + out_of_order_object_find_field() && + foreach_object_field_lookup() && +#endif // SIMDJSON_EXCEPTIONS + true; + } + +} // namespace ordering_tests + + +int main(int argc, char *argv[]) { + return test_main(argc, argv, ordering_tests::run); +} diff --git a/tests/ondemand/ondemand_parse_api_tests.cpp b/tests/ondemand/ondemand_parse_api_tests.cpp new file mode 100644 index 000000000..80fddbb99 --- /dev/null +++ b/tests/ondemand/ondemand_parse_api_tests.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace parse_api_tests { + using namespace std; + + const padded_string BASIC_JSON = "[1,2,3]"_padded; + const padded_string BASIC_NDJSON = "[1,2,3]\n[4,5,6]"_padded; + const padded_string EMPTY_NDJSON = ""_padded; + + bool parser_iterate() { + TEST_START(); + ondemand::parser parser; + auto doc = parser.iterate(BASIC_JSON); + ASSERT_SUCCESS( doc.get_array() ); + return true; + } + +#if SIMDJSON_EXCEPTIONS + bool parser_iterate_exception() { + TEST_START(); + ondemand::parser parser; + auto doc = parser.iterate(BASIC_JSON); + simdjson_unused ondemand::array array = doc; + return true; + } +#endif // SIMDJSON_EXCEPTIONS + + bool run() { + return parser_iterate() && +#if SIMDJSON_EXCEPTIONS + parser_iterate_exception() && +#endif // SIMDJSON_EXCEPTIONS + true; + } +} + + +int main(int argc, char *argv[]) { + return test_main(argc, argv, parse_api_tests::run); +} diff --git a/tests/ondemand/ondemand_twitter_tests.cpp b/tests/ondemand/ondemand_twitter_tests.cpp new file mode 100644 index 000000000..1ca08a7e3 --- /dev/null +++ b/tests/ondemand/ondemand_twitter_tests.cpp @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "simdjson.h" +#include "test_ondemand.h" + +using namespace simdjson; +using namespace simdjson::builtin; + +namespace twitter_tests { + using namespace std; + + bool twitter_count() { + TEST_START(); + padded_string json; + ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + uint64_t count; + ASSERT_SUCCESS( doc_result["search_metadata"]["count"].get(count) ); + ASSERT_EQUAL( count, 100 ); + return true; + })); + TEST_SUCCEED(); + } +#if SIMDJSON_EXCEPTIONS + bool twitter_example() { + TEST_START(); + padded_string json; + ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); + ondemand::parser parser; + auto doc = parser.iterate(json); + for (ondemand::object tweet : doc["statuses"]) { + uint64_t id = tweet["id"]; + std::string_view text = tweet["text"]; + std::string_view screen_name = tweet["user"]["screen_name"]; + uint64_t retweets = tweet["retweet_count"]; + uint64_t favorites = tweet["favorite_count"]; + (void) id; + (void) text; + (void) retweets; + (void) favorites; + (void) screen_name; + } + TEST_SUCCEED(); + } +#endif // SIMDJSON_EXCEPTIONS + + bool twitter_default_profile() { + TEST_START(); + padded_string json; + ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + // Print users with a default profile. + set default_users; + for (auto tweet : doc_result["statuses"]) { + auto user = tweet["user"].get_object(); + + // We have to get the screen name before default_profile because it appears first + std::string_view screen_name; + ASSERT_SUCCESS( user["screen_name"].get(screen_name) ); + + bool default_profile; + ASSERT_SUCCESS( user["default_profile"].get(default_profile) ); + if (default_profile) { + default_users.insert(screen_name); + } + } + ASSERT_EQUAL( default_users.size(), 86 ); + return true; + })); + TEST_SUCCEED(); + } + + bool twitter_image_sizes() { + TEST_START(); + padded_string json; + ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + // Print image names and sizes + set> image_sizes; + for (auto tweet : doc_result["statuses"]) { + auto media = tweet["entities"]["media"]; + if (!media.error()) { + for (auto image : media) { + uint64_t id_val; + std::string_view id_string; + ASSERT_SUCCESS( image["id"].get(id_val) ); + ASSERT_SUCCESS( image["id_str"].get(id_string) ); + std::cout << "id = " << id_val << std::endl; + std::cout << "id_string = " << id_string << std::endl; + + for (auto size : image["sizes"].get_object()) { + std::string_view size_key; + ASSERT_SUCCESS( size.unescaped_key().get(size_key) ); + std::cout << "Type of image size = " << size_key << std::endl; + + uint64_t width, height; + ASSERT_SUCCESS( size.value()["w"].get(width) ); + ASSERT_SUCCESS( size.value()["h"].get(height) ); + image_sizes.insert(make_pair(width, height)); + } + } + } + } + ASSERT_EQUAL( image_sizes.size(), 15 ); + return true; + })); + TEST_SUCCEED(); + } + +#if SIMDJSON_EXCEPTIONS + + bool twitter_count_exception() { + TEST_START(); + padded_string json; + ASSERT_SUCCESS( padded_string::load(TWITTER_JSON).get(json) ); + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + uint64_t count = doc_result["search_metadata"]["count"]; + ASSERT_EQUAL( count, 100 ); + return true; + })); + TEST_SUCCEED(); + } + + bool twitter_default_profile_exception() { + TEST_START(); + padded_string json = padded_string::load(TWITTER_JSON); + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + // Print users with a default profile. + set default_users; + for (auto tweet : doc_result["statuses"]) { + ondemand::object user = tweet["user"]; + + // We have to get the screen name before default_profile because it appears first + std::string_view screen_name = user["screen_name"]; + if (user["default_profile"]) { + default_users.insert(screen_name); + } + } + ASSERT_EQUAL( default_users.size(), 86 ); + return true; + })); + TEST_SUCCEED(); + } + + /* + * Fun fact: id and id_str can differ: + * 505866668485386240 and 505866668485386241. + * Presumably, it is because doubles are used + * at some point in the process and the number + * 505866668485386241 cannot be represented as a double. + * (not our fault) + */ + bool twitter_image_sizes_exception() { + TEST_START(); + padded_string json = padded_string::load(TWITTER_JSON); + ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) { + // Print image names and sizes + set> image_sizes; + for (auto tweet : doc_result["statuses"]) { + auto media = tweet["entities"]["media"]; + if (!media.error()) { + for (auto image : media) { + std::cout << "id = " << uint64_t(image["id"]) << std::endl; + std::cout << "id_string = " << std::string_view(image["id_str"]) << std::endl; + for (auto size : image["sizes"].get_object()) { + std::cout << "Type of image size = " << std::string_view(size.unescaped_key()) << std::endl; + // NOTE: the uint64_t is required so that each value is actually parsed before the pair is created + image_sizes.insert(make_pair(size.value()["w"], size.value()["h"])); + } + } + } + } + ASSERT_EQUAL( image_sizes.size(), 15 ); + return true; + })); + TEST_SUCCEED(); + } + +#endif // SIMDJSON_EXCEPTIONS + + bool run() { + return + twitter_count() && + twitter_default_profile() && + twitter_image_sizes() && +#if SIMDJSON_EXCEPTIONS + twitter_count_exception() && + twitter_example() && + twitter_default_profile_exception() && + twitter_image_sizes_exception() && +#endif // SIMDJSON_EXCEPTIONS + true; + } + +} // namespace twitter_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, twitter_tests::run); +} diff --git a/tests/ondemand/test_ondemand.h b/tests/ondemand/test_ondemand.h index 16959688e..d4a0ec151 100644 --- a/tests/ondemand/test_ondemand.h +++ b/tests/ondemand/test_ondemand.h @@ -1,6 +1,7 @@ #ifndef ONDEMAND_TEST_ONDEMAND_H #define ONDEMAND_TEST_ONDEMAND_H +#include #include "simdjson.h" #include "cast_tester.h" #include "test_macros.h" @@ -28,4 +29,62 @@ bool test_ondemand_doc(const simdjson::padded_string &json, const F& f) { return test_ondemand_doc(parser, json, f); } +#define ONDEMAND_SUBTEST(NAME, JSON, TEST) \ +{ \ + std::cout << "- Subtest " << (NAME) << " - JSON: " << (JSON) << " ..." << std::endl; \ + if (!test_ondemand_doc(JSON##_padded, [&](auto doc) { \ + return (TEST); \ + })) { \ + return false; \ + } \ +} + +const size_t AMAZON_CELLPHONES_NDJSON_DOC_COUNT = 793; +#define SIMDJSON_SHOW_DEFINE(x) printf("%s=%s\n", #x, STRINGIFY(x)) + +template +int test_main(int argc, char *argv[], const F& test_function) { + std::cout << std::unitbuf; + int c; + while ((c = getopt(argc, argv, "a:")) != -1) { + switch (c) { + case 'a': { + const simdjson::implementation *impl = simdjson::available_implementations[optarg]; + if (!impl) { + std::fprintf(stderr, "Unsupported architecture value -a %s\n", optarg); + return EXIT_FAILURE; + } + simdjson::active_implementation = impl; + break; + } + default: + std::fprintf(stderr, "Unexpected argument %c\n", c); + return EXIT_FAILURE; + } + } + + // this is put here deliberately to check that the documentation is correct (README), + // should this fail to compile, you should update the documentation: + if (simdjson::active_implementation->name() == "unsupported") { + std::printf("unsupported CPU\n"); + std::abort(); + } + // We want to know what we are testing. + // Next line would be the runtime dispatched implementation but that's not necessarily what gets tested. + // std::cout << "Running tests against this implementation: " << simdjson::active_implementation->name(); + // Rather, we want to display builtin_implementation()->name(). + // In practice, by default, we often end up testing against fallback. + std::cout << "builtin_implementation -- " << simdjson::builtin_implementation()->name() << std::endl; + std::cout << "------------------------------------------------------------" << std::endl; + + std::cout << "Running tests." << std::endl; + if (test_function()) { + std::cout << "Success!" << std::endl; + return EXIT_SUCCESS; + } else { + std::cerr << "FAILED." << std::endl; + return EXIT_FAILURE; + } +} + #endif // ONDEMAND_TEST_ONDEMAND_H diff --git a/tests/test_macros.h b/tests/test_macros.h index 124d1d326..9e07e774f 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -101,5 +101,4 @@ simdjson_really_inline bool assert_true(bool value, const char *operation = "res #define TEST_FAIL(MESSAGE) do { std::cerr << "FAIL: " << (MESSAGE) << std::endl; return false; } while (0); #define TEST_SUCCEED() do { return true; } while (0); - #endif // TEST_MACROS_H \ No newline at end of file From 041d59cc177cd84cbbb90d007ac6255f43e17633 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 22 Dec 2020 12:25:16 -0800 Subject: [PATCH 07/10] Create acceptance_tests, all_tests, etc. make targets And use them for mingw build and test --- .appveyor.yml | 14 +-- .github/workflows/mingw-ci.yml | 4 +- .github/workflows/mingw64-ci.yml | 8 +- cmake/add_compile_only_test.cmake | 8 ++ cmake/add_cpp_test.cmake | 29 +++--- examples/quickstart/CMakeLists.txt | 6 +- singleheader/CMakeLists.txt | 4 +- src/CMakeLists.txt | 2 +- tests/CMakeLists.txt | 88 ++++++------------- .../compilation_failure_tests/CMakeLists.txt | 4 +- tests/ondemand/CMakeLists.txt | 20 ++--- 11 files changed, 85 insertions(+), 102 deletions(-) create mode 100644 cmake/add_compile_only_test.cmake diff --git a/.appveyor.yml b/.appveyor.yml index ec37094d3..701e9b865 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,24 +13,24 @@ environment: matrix: - job_name: VS2019 - CMAKE_ARGS: -A %Platform% + CMAKE_ARGS: -A %Platform% - job_name: VS2019ARM CMAKE_ARGS: -A ARM64 -DCMAKE_CROSSCOMPILING=1 -D SIMDJSON_GOOGLE_BENCHMARKS=OFF # Does Google Benchmark builds under VS ARM? - job_name: VS2017 (Static, No Threads) image: Visual Studio 2017 - CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF + CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF CTEST_ARGS: -LE explicitonly - job_name: VS2019 (Win32) platform: Win32 - CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON # This should be the default. Testing anyway. - CTEST_ARGS: -E "checkperf|ondemand_basictests" + CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON # This should be the default. Testing anyway. + CTEST_ARGS: -LE explicitonly - job_name: VS2019 (Win32, No Exceptions) platform: Win32 - CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON -DSIMDJSON_EXCEPTIONS=OFF - CTEST_ARGS: -E "checkperf|ondemand_basictests" + CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON -DSIMDJSON_EXCEPTIONS=OFF + CTEST_ARGS: -LE explicitonly - job_name: VS2015 image: Visual Studio 2015 - CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF + CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF CTEST_ARGS: -LE explicitonly build_script: diff --git a/.github/workflows/mingw-ci.yml b/.github/workflows/mingw-ci.yml index 55c32e55a..4631b0b84 100644 --- a/.github/workflows/mingw-ci.yml +++ b/.github/workflows/mingw-ci.yml @@ -61,5 +61,5 @@ jobs: mkdir build32 cd build32 cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF .. - cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose - ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure + cmake --build . --target acceptance_tests --verbose + ctest -L acceptance -LE no_mingw --output-on-failure diff --git a/.github/workflows/mingw64-ci.yml b/.github/workflows/mingw64-ci.yml index cf2fcab6c..136422191 100644 --- a/.github/workflows/mingw64-ci.yml +++ b/.github/workflows/mingw64-ci.yml @@ -61,11 +61,11 @@ jobs: mkdir build64 cd build64 cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF .. - cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose - ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure + cmake --build . --target acceptance_tests --verbose + ctest -L acceptance -LE no_mingw --output-on-failure cd .. mkdir build64debug cd build64debug cmake -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF .. - cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose - ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure + cmake --build . --target acceptance_tests --verbose + ctest -L acceptance -LE no_mingw --output-on-failure diff --git a/cmake/add_compile_only_test.cmake b/cmake/add_compile_only_test.cmake new file mode 100644 index 000000000..40ccf7e47 --- /dev/null +++ b/cmake/add_compile_only_test.cmake @@ -0,0 +1,8 @@ +function(add_compile_only_test TEST_NAME) + add_test( + NAME ${TEST_NAME} + COMMAND ${CMAKE_COMMAND} --build . --target ${TEST_NAME} --config $ + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + ) + set_target_properties(${TEST_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) +endfunction() \ No newline at end of file diff --git a/cmake/add_cpp_test.cmake b/cmake/add_cpp_test.cmake index b41087fa5..c88a6940e 100644 --- a/cmake/add_cpp_test.cmake +++ b/cmake/add_cpp_test.cmake @@ -3,12 +3,12 @@ # SOURCES defaults to testname.cpp if not specified. function(add_cpp_test TEST_NAME) # Parse arguments - cmake_parse_arguments(PARSE_ARGV 1 ARGS "COMPILE_ONLY;LIBRARY;WILL_FAIL" "" "SOURCES;LABELS") + cmake_parse_arguments(PARSE_ARGV 1 ARGS "COMPILE_ONLY;LIBRARY;WILL_FAIL" "" "SOURCES;LABELS;DEPENDENCY_OF") if (NOT ARGS_SOURCES) list(APPEND ARGS_SOURCES ${TEST_NAME}.cpp) endif() if (ARGS_COMPILE_ONLY) - list(APPEND ${ARGS_LABELS} compile) + list(APPEND ${ARGS_LABELS} compile_only) endif() # Add the compile target @@ -28,22 +28,29 @@ function(add_cpp_test TEST_NAME) set_target_properties(${TEST_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) else() add_test(${TEST_NAME} ${TEST_NAME}) + + # Add to