This is an automated email from the ASF dual-hosted git repository. nickva pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git
commit c93691c63c72d75720a19c263a250c34d724d0c5 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Thu Apr 16 15:53:43 2026 -0400 Improve coverage even more We slipped in some cases, for example when we bumped the reduction count. When adding more tests for yielding found out the `byte_per_iter` could become < 1 (made the test stuck) so add a fix to clip it to 1. Then there a few more odds and ends for string escaping and options. --- c_src/util.c | 10 +++++- test/jiffy_04_string_tests.erl | 23 +++++++++++-- test/jiffy_12_error_tests.erl | 26 ++++++++++---- test/jiffy_16_dedupe_keys_tests.erl | 23 +++++++++++++ test/jiffy_19_yielding_tests.erl | 68 ++++++++++++++++++++++++++++++++++--- test/jiffy_util.hrl | 13 ++++--- 6 files changed, 146 insertions(+), 17 deletions(-) diff --git a/c_src/util.c b/c_src/util.c index cc23a15..a84eabd 100644 --- a/c_src/util.c +++ b/c_src/util.c @@ -38,8 +38,12 @@ get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi) return 0; } - // Calculate the number of bytes per reduction + // Calculate the number of bytes per reduction. Clamp to 1 so we + // avoid a divide-by-zero in bump_used_reds. *bpi = (size_t) (bytes / DEFAULT_ERLANG_REDUCTION_COUNT); + if(*bpi == 0) { + *bpi = 1; + } return 1; } @@ -68,7 +72,11 @@ get_bytes_per_red(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi) return 0; } + // Same get_bytes_per_iter, clamp to 1 to avoid a divide by 0 *bpi = (size_t) bytes; + if(*bpi == 0) { + *bpi = 1; + } return 1; } diff --git a/test/jiffy_04_string_tests.erl b/test/jiffy_04_string_tests.erl index a8c9446..c96e014 100644 --- a/test/jiffy_04_string_tests.erl +++ b/test/jiffy_04_string_tests.erl @@ -146,7 +146,13 @@ cases(error) -> % Truncated \uXX (not enough hex digits) <<"\"\\u00\"">>, % Invalid hex digit in \u escape - <<"\"\\uZZZZ\"">> + <<"\"\\uZZZZ\"">>, + % Same story as \uD834\n but with more trailers to pass the length + % guard and reach the '\u' check for the low surrogate. We're down in + % the weeds, as it were. + <<"\"\\uD834\\nabcdef\"">>, + % \uD834\u<bad hex> low surrogate hex error + <<"\"\\uD834\\uZZZZ\"">> ]; cases(utf8) -> @@ -205,5 +211,18 @@ cases(bad_utf8_key) -> cases(escaped_slashes) -> [ - {<<"\"\\/\"">>, <<"/">>} + {<<"\"\\/\"">>, <<"/">>}, + {<<"\"foo\\/bar\\/baz\"">>, <<"foo/bar/baz">>} + ]. + + +atom_escaped_slashes_test_() -> + [ + ?_assertEqual(<<"\"a\\/b\"">>, + enc('a/b', [escape_forward_slashes])), + ?_assertEqual(<<"\"a/b\"">>, enc('a/b')), + ?_assertEqual(<<"{\"a\\/b\":1}">>, + enc({[{'a/b', 1}]}, [escape_forward_slashes])), + ?_assertEqual(<<"\"foo\\/bar\\/baz\\/potato\"">>, + enc('foo/bar/baz/potato', [escape_forward_slashes])) ]. diff --git a/test/jiffy_12_error_tests.erl b/test/jiffy_12_error_tests.erl index fa636f2..7a1ed9f 100644 --- a/test/jiffy_12_error_tests.erl +++ b/test/jiffy_12_error_tests.erl @@ -82,12 +82,26 @@ enc_invalid_object_member_key_test_() -> ]}. -encode_bad_option_test() -> - ?assertError(badarg, jiffy:encode(1, [not_a_valid_option])). - - -decode_bad_option_test() -> - ?assertError(badarg, jiffy:decode(<<"1">>, [not_a_valid_option])). +decode_bad_option_test_() -> + [?_assertError(badarg, jiffy:decode(<<"1">>, [O])) || O <- [ + not_a_valid_option, + <<"foo">>, + {foo, bar, baz}, + {bytes_per_iter, not_an_int}, + {bytes_per_red, not_an_int}, + {some_other_opt, value}, + {null_term, 123} + ]]. + + +encode_bad_option_test_() -> + [?_assertError(badarg, jiffy:encode(1, [O])) || O <- [ + not_a_valid_option, + <<"foo">>, + {foo, bar, baz}, + {bytes_per_iter, not_an_int}, + {bytes_per_red, not_an_int} + ]]. enc_error(Type, Obj, Case) -> diff --git a/test/jiffy_16_dedupe_keys_tests.erl b/test/jiffy_16_dedupe_keys_tests.erl index 7713e51..d78d3a6 100644 --- a/test/jiffy_16_dedupe_keys_tests.erl +++ b/test/jiffy_16_dedupe_keys_tests.erl @@ -4,6 +4,7 @@ -module(jiffy_16_dedupe_keys_tests). -include_lib("eunit/include/eunit.hrl"). +-include("jiffy_util.hrl"). % Duplicate keys with `return_maps`. We settled on % last value wins semantics in such cases so test that @@ -98,3 +99,25 @@ dedupe_keys_test_() -> dedupe_keys_empty_test() -> ?assertEqual({[]}, jiffy:decode(<<"{}">>, [dedupe_keys])). + +% Exercise the heap-allocated dedupe hash table path when count > 64 and hit +% more than HT_STACK_SLOTS. We're padding those coverage stats here, really. +dedupe_keys_large_test_() -> + N = 100, + KV = fun(I) -> [<<"\"">>, i2b(I), <<"\":">>, i2b(I)] end, + Body = iol2b(lists:join(",", [KV(I) || I <- lists:seq(1, N)])), + Dupes = iol2b(lists:join(",", [KV(I) || I <- lists:seq(1, N)])), + JUnique = <<"{", Body/binary, "}">>, + JDupes = <<"{", Body/binary, ",", Dupes/binary, "}">>, + [ + {"Unique large object", + fun() -> + {Pairs} = jiffy:decode(JUnique, [dedupe_keys]), + ?assertEqual(N, length(Pairs)) + end}, + {"All keys duplicated once", + fun() -> + {Pairs} = jiffy:decode(JDupes, [dedupe_keys]), + ?assertEqual(N, length(Pairs)) + end} + ]. diff --git a/test/jiffy_19_yielding_tests.erl b/test/jiffy_19_yielding_tests.erl index d12ee88..c64bc72 100644 --- a/test/jiffy_19_yielding_tests.erl +++ b/test/jiffy_19_yielding_tests.erl @@ -31,15 +31,75 @@ encode_both_bytes_opts_test() -> % otheriwse. % decode_large_with_bytes_per_red_test() -> - Data = iolist_to_binary([ + Data = iol2b([ <<"[">>, lists:join(<<",">>, [<<"1">> || _ <- lists:seq(1, 500)]), <<"]">> ]), - Result = jiffy:decode(Data, [{bytes_per_red, 20}]), + Result = dec(Data, [{bytes_per_red, 20}]), ?assertEqual(500, length(Result)). encode_large_with_bytes_per_red_test() -> Data = lists:duplicate(500, 1), - Encoded = iolist_to_binary(jiffy:encode(Data, [{bytes_per_red, 20}])), - ?assertEqual(Data, jiffy:decode(Encoded)). + Encoded = enc(Data, [{bytes_per_red, 20}]), + ?assertEqual(Data, dec(Encoded)). + +% Nested object but a large binary at the bottom. We want the yield to fire +% while the term stack holds a large (> SMALL_TERMSTACK_SIZE) entries. Then +% small byte_per_red to force yielding. +encode_deep_nesting_yield_test() -> + Depth = 12, + Big = binary:copy(<<"a">>, 10000), + Seq = lists:seq(1, Depth), + Nested = lists:foldl(fun(_, Acc) -> {[{<<"k">>, Acc}]} end, Big, Seq), + Encoded = enc(Nested, [{bytes_per_red, 1}]), + ?assertEqual(Nested, dec(Encoded)). + +% Force yielding and test restore/save and schedule bits. +decode_excessive_yield_test_() -> + Data = iol2b([ + <<"[">>, + lists:join(<<",">>, [i2b(I) || I <- lists:seq(1, 2000)]), + <<"]">> + ]), + Expected = lists:seq(1, 2000), + [ + {"bytes_per_red = 1", + ?_assertEqual(Expected, dec(Data, [{bytes_per_red, 1}]))}, + {"bytes_per_red = 0 (clamped to 1)", + ?_assertEqual(Expected, dec(Data, [{bytes_per_red, 0}]))}, + {"bytes_per_iter = 1", + ?_assertEqual(Expected, dec(Data, [{bytes_per_iter, 1}]))} + ]. + +encode_excessive_yield_test_() -> + Data = lists:seq(1, 2000), + [ + {"bytes_per_red = 1", + fun() -> + Enc = enc(Data, [{bytes_per_red, 1}]), + ?assertEqual(Data, dec(Enc)) + end}, + {"bytes_per_red = 0 (clamped to 1)", + fun() -> + Enc = enc(Data, [{bytes_per_red, 0}]), + ?assertEqual(Data, dec(Enc)) + end}, + {"bytes_per_iter = 1", + fun() -> + Enc = enc(Data, [{bytes_per_iter, 1}]), + ?assertEqual(Data, dec(Enc)) + end} + ]. + +% Single large string encoded/decoded + low yield threshold, so +% we hopefully hit the pct_used > 100 in bump_used_reds +large_string_yield_test_() -> + Big = binary:copy(<<"abc">>, 50000), + Json = <<"\"", Big/binary, "\"">>, + [ + {"Decode", + ?_assertEqual(Big, dec(Json, [{bytes_per_red, 1}]))}, + {"Encode", + ?_assertEqual(Json, enc(Big, [{bytes_per_red, 1}]))} + ]. diff --git a/test/jiffy_util.hrl b/test/jiffy_util.hrl index 983f7e3..9fd7d6b 100644 --- a/test/jiffy_util.hrl +++ b/test/jiffy_util.hrl @@ -4,12 +4,18 @@ -compile(export_all). -compile(nowarn_export_all). +iol2b(X) -> + iolist_to_binary(X). + +i2b(X) -> + integer_to_binary(X). + msg(Fmt, Args) -> M1 = io_lib:format(Fmt, Args), M2 = re:replace(M1, <<"\r">>, <<"\\\\r">>, [global]), M3 = re:replace(M2, <<"\n">>, <<"\\\\n">>, [global]), M4 = re:replace(M3, <<"\t">>, <<"\\\\t">>, [global]), - iolist_to_binary(M4). + iol2b(M4). hex(Bin) when is_binary(Bin) -> @@ -27,12 +33,11 @@ dec(V, Opts) -> enc(V) -> - iolist_to_binary(jiffy:encode(V)). + iol2b(jiffy:encode(V)). enc(V, Opts) -> - iolist_to_binary(jiffy:encode(V, Opts)). - + iol2b(jiffy:encode(V, Opts)). %% rebar runs eunit with PWD as .eunit/ %% rebar3 runs eunit with PWD as ./
