This is an automated email from the ASF dual-hosted git repository.

chewbranca pushed a commit to branch couch-stats-resource-tracker-v3
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to 
refs/heads/couch-stats-resource-tracker-v3 by this push:
     new 64456e0ce Sort out core CSRT tests
64456e0ce is described below

commit 64456e0ce737abaccc4adae9ba6630f1a55740aa
Author: Russell Branca <[email protected]>
AuthorDate: Mon Mar 17 14:22:25 2025 -0700

    Sort out core CSRT tests
---
 src/couch/src/couch_query_servers.erl              |   6 +-
 .../src/couch_stats_resource_tracker.hrl           |   1 +
 src/couch_stats/src/csrt.erl                       |   8 +
 src/couch_stats/test/eunit/csrt_server_tests.erl   | 299 +++++++++++++++------
 4 files changed, 235 insertions(+), 79 deletions(-)

diff --git a/src/couch/src/couch_query_servers.erl 
b/src/couch/src/couch_query_servers.erl
index b5ddee312..2d5185806 100644
--- a/src/couch/src/couch_query_servers.erl
+++ b/src/couch/src/couch_query_servers.erl
@@ -555,9 +555,9 @@ filter_docs_int(Db, DDoc, FName, JsonReq, JsonDocs) ->
     %% Count usage in _int version as this can be repeated for OS error
     %% Pros & cons... might not have actually processed `length(JsonDocs)` docs
     %% but it certainly undercounts if we count in `filter_docs/5` above
-    %% TODO: wire in csrt tracking
-    couch_stats:increment_counter([couchdb, query_server, js_filter]),
-    couch_stats:increment_counter([couchdb, query_server, js_filtered_docs], 
length(JsonDocs)),
+    %% TODO: replace with couchdb.query_server.*.ddoc_filter stats once we can
+    %% funnel back the stats used in the couchjs process to this caller process
+    csrt:js_filtered(length(JsonDocs)),
     [true, Passes] = ddoc_prompt(
         Db,
         DDoc,
diff --git a/src/couch_stats/src/couch_stats_resource_tracker.hrl 
b/src/couch_stats/src/couch_stats_resource_tracker.hrl
index 238b56a0e..998621bfb 100644
--- a/src/couch_stats/src/couch_stats_resource_tracker.hrl
+++ b/src/couch_stats/src/couch_stats_resource_tracker.hrl
@@ -48,6 +48,7 @@
     [couchdb, btree, get_node, kv_node] => ?COUCH_BT_GET_KV_NODE,
     [couchdb, btree, write_node, kp_node] => ?COUCH_BT_WRITE_KP_NODE,
     [couchdb, btree, write_node, kv_node] => ?COUCH_BT_WRITE_KV_NODE,
+    %% NOTE: these stats are not local to the RPC worker, need forwarding
     [couchdb, query_server, calls, ddoc_filter] => ?COUCH_JS_FILTER,
     [couchdb, query_server, volume, ddoc_filter] => ?COUCH_JS_FILTERED_DOCS
 }).
diff --git a/src/couch_stats/src/csrt.erl b/src/couch_stats/src/csrt.erl
index 932d6878b..f66755c4c 100644
--- a/src/couch_stats/src/csrt.erl
+++ b/src/couch_stats/src/csrt.erl
@@ -61,6 +61,7 @@
     inc/1,
     inc/2,
     ioq_called/0,
+    js_filtered/1,
     make_delta/0,
     rctx_delta/2,
     maybe_add_delta/1,
@@ -322,6 +323,13 @@ should_track(_Metric) ->
 ioq_called() ->
     inc(ioq_calls).
 
+%% we cannot yet use stats couchdb.query_server.*.ddoc_filter because those
+%% are collected in a dedicated process.
+%% TODO: funnel back stats from background worker processes to the RPC worker
+js_filtered(N) ->
+    inc(js_filter),
+    inc(js_filtered_docs, N).
+
 docs_written(N) ->
     inc(docs_written, N).
 
diff --git a/src/couch_stats/test/eunit/csrt_server_tests.erl 
b/src/couch_stats/test/eunit/csrt_server_tests.erl
index fc4e6198b..f3cf07a83 100644
--- a/src/couch_stats/test/eunit/csrt_server_tests.erl
+++ b/src/couch_stats/test/eunit/csrt_server_tests.erl
@@ -17,6 +17,7 @@
 -include_lib("couch_mrview/include/couch_mrview.hrl").
 
 -define(DOCS_COUNT, 100).
+-define(DDOCS_COUNT, 1).
 -define(DB_Q, 8).
 
 -define(DEBUG_ENABLED, false).
@@ -32,130 +33,179 @@ csrt_context_test_() ->
         ])
     }.
 
-csrt_fabric_test_() ->
+test_funs() ->
+    [
+        ?TDEF_FE(t_all_docs_include_false),
+        ?TDEF_FE(t_all_docs_include_true),
+        ?TDEF_FE(t_all_docs_limit_zero),
+        ?TDEF_FE(t_get_doc),
+        ?TDEF_FE(t_put_doc),
+        ?TDEF_FE(t_delete_doc),
+        ?TDEF_FE(t_update_docs),
+        ?TDEF_FE(t_changes),
+        ?TDEF_FE(t_changes_limit_zero),
+        ?TDEF_FE(t_changes_filtered),
+        ?TDEF_FE(t_view_query),
+        ?TDEF_FE(t_view_query_include_docs)
+    ].
+
+ddoc_test_funs() ->
+    [
+        ?TDEF_FE(t_changes_js_filtered)
+        | test_funs()
+    ].
+
+csrt_fabric_no_ddoc_test_() ->
     {
-        setup,
+        "CSRT fabric tests with no DDoc present",
+        foreach,
         fun setup/0,
         fun teardown/1,
-        with([
-            ?TDEF(t_all_docs_include_false),
-            ?TDEF(t_all_docs_include_true),
-            ?TDEF(t_all_docs_limit_zero),
-            ?TDEF(t_get_doc),
-            ?TDEF(t_put_doc),
-            ?TDEF(t_delete_doc),
-            ?TDEF(t_update_docs),
-            ?TDEF(t_changes),
-            ?TDEF(t_changes_limit_zero),
-            ?TDEF(t_changes_filtered),
-            ?TDEF(t_changes_js_filtered),
-            ?TDEF(t_view_query)
-        ])
+        test_funs()
     }.
 
+csrt_fabric_test_() ->
+    {
+        "CSRT fabric tests with a DDoc present",
+        foreach,
+        fun() ->  setup_ddoc(<<"_design/foo">>, <<"bar">>) end,
+        fun teardown/1,
+        ddoc_test_funs()
+    }.
+
+make_docs(Count) ->
+    lists:map(
+        fun(I) ->
+            #doc{
+                id = ?l2b("foo_" ++ integer_to_list(I)),
+                body={[{<<"value">>, I}]}
+            }
+        end,
+        lists:seq(1, Count)).
+
 setup() ->
     Ctx = test_util:start_couch([fabric, couch_stats]),
     DbName = ?tempdb(),
     ok = fabric:create_db(DbName, [{q, ?DB_Q}, {n, 1}]),
-    Docs = [#doc{id = ?l2b("foo_" ++ integer_to_list(I))} || I <- lists:seq(1, 
?DOCS_COUNT)],
+    Docs = make_docs(?DOCS_COUNT),
     Opts = [],
     {ok, _} = fabric:update_docs(DbName, Docs, Opts),
-    {Ctx, DbName}.
+    {Ctx, DbName, undefined}.
 
-teardown({Ctx, DbName}) ->
+teardown({Ctx, DbName, _View}) ->
     ok = fabric:delete_db(DbName, [?ADMIN_CTX]),
     test_util:stop_couch(Ctx).
 
-t_context_setting({_Ctx, _DbName}) ->
+setup_ddoc(DDocId, ViewName) ->
+    {Ctx, DbName, undefined} = setup(),
+    DDoc = couch_doc:from_json_obj(
+        {[
+            {<<"_id">>, DDocId},
+            {<<"language">>, <<"javascript">>},
+            {
+                <<"views">>,
+                {[{
+                    ViewName,
+                    {[
+                        {<<"map">>, <<"function(doc) { emit(doc.value, null); 
}">>}
+                    ]}
+                }]}
+            },
+            {
+                <<"filters">>,
+                {[{
+                    <<"even">>,
+                    <<"function(doc) { return (doc.value % 2 == 0); }">>
+                }]}
+            }
+        ]}
+    ),
+    {ok, _Rev} = fabric:update_doc(DbName, DDoc, [?ADMIN_CTX]),
+    {Ctx, DbName, {DDocId, ViewName}}.
+
+t_context_setting({_Ctx, _DbName, _View}) ->
     false.
 
-t_all_docs_limit_zero({_Ctx, DbName}) ->
+t_all_docs_limit_zero({_Ctx, DbName, _View}) ->
     Context = #{
         method => 'GET',
         path => "/" ++ ?b2l(DbName) ++ "/_all_docs"
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     MArgs = #mrargs{include_docs = false, limit = 0},
     _Res = fabric:all_docs(DbName, [?ADMIN_CTX], fun view_cb/2, [], MArgs),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
-    ?debugFmt("RCTX: ~p~n", [Rctx]),
+    Rctx = load_rctx(PidRef),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
         db_open => ?DB_Q,
         rows_read => 0,
         docs_read => 0,
         docs_written => 0,
+        ioq_calls => assert_gt(),
         pid_ref => PidRef
     }),
     ok = nonzero_local_io_assert(Rctx),
-    ?assert(maps:get(ioq_calls, Rctx) > 0),
-    ?assert(maps:get(get_kp_node, Rctx) > 0),
-    ?assert(maps:get(get_kv_node, Rctx) > 0),
     ok = assert_teardown(PidRef).
 
-
-t_all_docs_include_false({_Ctx, DbName}) ->
+t_all_docs_include_false({_Ctx, DbName, View}) ->
     Context = #{
         method => 'GET',
         path => "/" ++ ?b2l(DbName) ++ "/_all_docs"
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     MArgs = #mrargs{include_docs = false},
     _Res = fabric:all_docs(DbName, [?ADMIN_CTX], fun view_cb/2, [], MArgs),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
         db_open => ?DB_Q,
-        rows_read => ?DOCS_COUNT,
+        rows_read => docs_count(View),
         docs_read => 0,
         docs_written => 0,
         pid_ref => PidRef
     }),
     ok = nonzero_local_io_assert(Rctx),
-    ?assert(maps:get(ioq_calls, Rctx) > 0),
-    ?assert(maps:get(get_kp_node, Rctx) > 0),
-    ?assert(maps:get(get_kv_node, Rctx) > 0),
     ok = assert_teardown(PidRef).
 
-t_all_docs_include_true({_Ctx, DbName}) ->
+t_all_docs_include_true({_Ctx, DbName, View}) ->
     pdebug(dbname, DbName),
     Context = #{
         method => 'GET',
         path => "/" ++ ?b2l(DbName) ++ "/_all_docs"
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     MArgs = #mrargs{include_docs = true},
     _Res = fabric:all_docs(DbName, [?ADMIN_CTX], fun view_cb/2, [], MArgs),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
         db_open => ?DB_Q,
-        rows_read => ?DOCS_COUNT,
-        docs_read => ?DOCS_COUNT,
+        rows_read => docs_count(View),
+        docs_read => docs_count(View),
         docs_written => 0,
         pid_ref => PidRef
     }),
     ok = nonzero_local_io_assert(Rctx),
     ok = assert_teardown(PidRef).
 
-t_update_docs({_Ctx, DbName}) ->
+t_update_docs({_Ctx, DbName, View}) ->
     pdebug(dbname, DbName),
     Context = #{
         method => 'POST',
         path => "/" ++ ?b2l(DbName)
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     Docs = [#doc{id = ?l2b("bar_" ++ integer_to_list(I))} || I <- lists:seq(1, 
?DOCS_COUNT)],
     _Res = fabric:update_docs(DbName, Docs, [?ADMIN_CTX]),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     pdebug(rctx, Rctx),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
@@ -165,10 +215,10 @@ t_update_docs({_Ctx, DbName}) ->
         docs_written => ?DOCS_COUNT,
         pid_ref => PidRef
     }),
-    ok = zero_local_io_assert(Rctx),
+    ok = ddoc_dependent_local_io_assert(Rctx, View),
     ok = assert_teardown(PidRef).
 
-t_get_doc({_Ctx, DbName}) ->
+t_get_doc({_Ctx, DbName, _View}) ->
     pdebug(dbname, DbName),
     DocId = "foo_17",
     Context = #{
@@ -176,10 +226,10 @@ t_get_doc({_Ctx, DbName}) ->
         path => "/" ++ ?b2l(DbName) ++ "/" ++ DocId
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     _Res = fabric:open_doc(DbName, DocId, [?ADMIN_CTX]),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     pdebug(rctx, Rctx),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
@@ -193,7 +243,7 @@ t_get_doc({_Ctx, DbName}) ->
     ok = assert_teardown(PidRef).
 
 
-t_put_doc({_Ctx, DbName}) ->
+t_put_doc({_Ctx, DbName, View}) ->
     pdebug(dbname, DbName),
     DocId = "bar_put_1919",
     Context = #{
@@ -201,11 +251,11 @@ t_put_doc({_Ctx, DbName}) ->
         path => "/" ++ ?b2l(DbName) ++ "/" ++ DocId
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     Doc = #doc{id = ?l2b(DocId)},
     _Res = fabric:update_doc(DbName, Doc, [?ADMIN_CTX]),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     pdebug(rctx, Rctx),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
@@ -215,10 +265,10 @@ t_put_doc({_Ctx, DbName}) ->
         docs_written => 1,
         pid_ref => PidRef
     }),
-    ok = zero_local_io_assert(Rctx),
+    ok = ddoc_dependent_local_io_assert(Rctx, View),
     ok = assert_teardown(PidRef).
 
-t_delete_doc({_Ctx, DbName}) ->
+t_delete_doc({_Ctx, DbName, View}) ->
     pdebug(dbname, DbName),
     DocId = "foo_17",
     {ok, Doc0} = fabric:open_doc(DbName, DocId, [?ADMIN_CTX]),
@@ -228,10 +278,10 @@ t_delete_doc({_Ctx, DbName}) ->
         path => "/" ++ ?b2l(DbName) ++ "/" ++ DocId
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     _Res = fabric:update_doc(DbName, Doc, [?ADMIN_CTX]),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     pdebug(rctx, Rctx),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
@@ -241,25 +291,25 @@ t_delete_doc({_Ctx, DbName}) ->
         docs_written => 1,
         pid_ref => PidRef
     }),
-    ok = zero_local_io_assert(Rctx),
+    ok = ddoc_dependent_local_io_assert(Rctx, View),
     ok = assert_teardown(PidRef).
 
-t_changes({_Ctx, DbName}) ->
+t_changes({_Ctx, DbName, View}) ->
     pdebug(dbname, DbName),
     Context = #{
         method => 'GET',
         path => "/" ++ ?b2l(DbName) ++ "/_changes"
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
-    _Res = fabric:changes(DbName, fun changes_cb/2, [], 
#changes_args{limit=0}),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    _Res = fabric:changes(DbName, fun changes_cb/2, [], #changes_args{}),
+    Rctx = load_rctx(PidRef),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
         db_open => ?DB_Q,
-        rows_read => false,
-        changes_returned => false,
+        rows_read => assert_gte(?DB_Q),
+        changes_returned => docs_count(View),
         docs_read => 0,
         docs_written => 0,
         pid_ref => PidRef
@@ -269,22 +319,19 @@ t_changes({_Ctx, DbName}) ->
     ?assert(maps:get(rows_read, Rctx) >= ?DB_Q, rows_read),
     ?assert(maps:get(changes_returned, Rctx) >= ?DB_Q, changes_returned),
     ok = nonzero_local_io_assert(Rctx),
-    ?assert(maps:get(ioq_calls, Rctx) > 0),
-    ?assert(maps:get(get_kp_node, Rctx) > 0),
-    ?assert(maps:get(get_kv_node, Rctx) > 0),
     ok = assert_teardown(PidRef).
 
 
-t_changes_limit_zero({_Ctx, DbName}) ->
+t_changes_limit_zero({_Ctx, DbName, _View}) ->
     Context = #{
         method => 'GET',
         path => "/" ++ ?b2l(DbName) ++ "/_changes"
     },
     {PidRef, Nonce} = coordinator_context(Context),
-    Rctx0 = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx0 = load_rctx(PidRef),
     ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
     _Res = fabric:changes(DbName, fun changes_cb/2, [], 
#changes_args{limit=0}),
-    Rctx = csrt_util:to_json(csrt:get_resource(PidRef)),
+    Rctx = load_rctx(PidRef),
     ok = rctx_assert(Rctx, #{
         nonce => Nonce,
         db_open => ?DB_Q,
@@ -297,19 +344,85 @@ t_changes_limit_zero({_Ctx, DbName}) ->
     ?assert(maps:get(rows_read, Rctx) >= ?DB_Q, rows),
     ?assert(maps:get(changes_returned, Rctx) >= ?DB_Q, rows),
     ok = nonzero_local_io_assert(Rctx),
-    ?assert(maps:get(ioq_calls, Rctx) > 0),
-    ?assert(maps:get(get_kp_node, Rctx) > 0),
-    ?assert(maps:get(get_kv_node, Rctx) > 0),
     ok = assert_teardown(PidRef).
 
-t_changes_filtered({_Ctx, DbName}) ->
-    ?assert(false, DbName).
+%% TODO: stub in non JS filter with selector
+t_changes_filtered({_Ctx, _DbName, _View}) ->
+    false.
 
-t_changes_js_filtered({_Ctx, DbName}) ->
-    ?assert(false, DbName).
+t_changes_js_filtered({_Ctx, DbName, {DDocId, _ViewName}=View}) ->
+    pdebug(dbname, DbName),
+    Method = 'GET',
+    Path = "/" ++ ?b2l(DbName) ++ "/_changes",
+    Context = #{
+        method => Method,
+        path => Path
+    },
+    {PidRef, Nonce} = coordinator_context(Context),
+    Req = {json_req, null},
+    Rctx0 = load_rctx(PidRef),
+    ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
+    Filter = configure_filter(DbName, DDocId, Req),
+    Args = #changes_args{filter_fun = Filter},
+    _Res = fabric:changes(DbName, fun changes_cb/2, [], Args),
+    Rctx = load_rctx(PidRef),
+    ok = rctx_assert(Rctx, #{
+        nonce => Nonce,
+        db_open => assert_gte(?DB_Q),
+        rows_read => assert_gte(docs_count(View)),
+        changes_returned => round(?DOCS_COUNT / 2),
+        docs_read => assert_gte(docs_count(View)),
+        docs_written => 0,
+        pid_ref => PidRef,
+        js_filter => docs_count(View),
+        js_filtered_docs => docs_count(View)
+    }),
+    ok = nonzero_local_io_assert(Rctx),
+    ok = assert_teardown(PidRef).
 
-t_view_query({_Ctx, DbName}) ->
-    ?assert(false, DbName).
+t_view_query({_Ctx, DbName, View}) ->
+    Context = #{
+        method => 'GET',
+        path => "/" ++ ?b2l(DbName) ++ "/_design/foo/_view/bar"
+    },
+    {PidRef, Nonce} = coordinator_context(Context),
+    Rctx0 = load_rctx(PidRef),
+    ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
+    MArgs = #mrargs{include_docs = false},
+    _Res = fabric:all_docs(DbName, [?ADMIN_CTX], fun view_cb/2, [], MArgs),
+    Rctx = load_rctx(PidRef),
+    ok = rctx_assert(Rctx, #{
+        nonce => Nonce,
+        db_open => ?DB_Q,
+        rows_read => docs_count(View),
+        docs_read => 0,
+        docs_written => 0,
+        pid_ref => PidRef
+    }),
+    ok = nonzero_local_io_assert(Rctx),
+    ok = assert_teardown(PidRef).
+
+t_view_query_include_docs({_Ctx, DbName, View}) ->
+    Context = #{
+        method => 'GET',
+        path => "/" ++ ?b2l(DbName) ++ "/_design/foo/_view/bar"
+    },
+    {PidRef, Nonce} = coordinator_context(Context),
+    Rctx0 = load_rctx(PidRef),
+    ok = fresh_rctx_assert(Rctx0, PidRef, Nonce),
+    MArgs = #mrargs{include_docs = true},
+    _Res = fabric:all_docs(DbName, [?ADMIN_CTX], fun view_cb/2, [], MArgs),
+    Rctx = load_rctx(PidRef),
+    ok = rctx_assert(Rctx, #{
+        nonce => Nonce,
+        db_open => ?DB_Q,
+        rows_read => docs_count(View),
+        docs_read => docs_count(View),
+        docs_written => 0,
+        pid_ref => PidRef
+    }),
+    ok = nonzero_local_io_assert(Rctx),
+    ok = assert_teardown(PidRef).
 
 assert_teardown(PidRef) ->
     ?assertEqual(ok, csrt:destroy_context(PidRef)),
@@ -371,6 +484,8 @@ rctx_assert(Rctx, Asserts0) ->
         fun
             (_K, false) ->
                 ok;
+            (K, Fun) when is_function(Fun) ->
+                Fun(K, maps:get(K, Rctx));
             (K, V) ->
                 case maps:get(K, Rctx) of
                     false ->
@@ -409,6 +524,11 @@ nonzero_local_io_assert(Rctx, io_separate) ->
     ?assert(maps:get(get_kv_node, Rctx) > 0),
     ok.
 
+ddoc_dependent_local_io_assert(Rctx, undefined) ->
+    zero_local_io_assert(Rctx);
+ddoc_dependent_local_io_assert(Rctx, {_DDoc, _ViewName}) ->
+    nonzero_local_io_assert(Rctx, io_sum).
+
 coordinator_context(#{method := Method, path := Path}) ->
     Nonce = couch_util:to_hex(crypto:strong_rand_bytes(5)),
     Req = #httpd{method=Method, nonce=Nonce},
@@ -426,3 +546,30 @@ fresh_rctx_assert(Rctx, PidRef, Nonce) ->
         pid_ref => PidRef
     },
     rctx_assert(Rctx, FreshAsserts).
+
+assert_gt() ->
+    assert_gt(0).
+
+assert_gt(N) ->
+        fun(K, RV) -> ?assert(RV > N, {K, RV, N}) end.
+
+assert_gte(N) ->
+    fun(K, RV) -> ?assert(RV >= N, {K, RV, N}) end.
+
+docs_count(undefined) ->
+    ?DOCS_COUNT;
+docs_count({_, _}) ->
+    ?DOCS_COUNT + ?DDOCS_COUNT.
+
+configure_filter(DbName, DDocId, Req) ->
+    configure_filter(DbName, DDocId, Req, <<"even">>).
+
+configure_filter(DbName, DDocId, Req, FName) ->
+    {ok, DDoc} = ddoc_cache:open_doc(DbName, DDocId),
+    DIR = fabric_util:doc_id_and_rev(DDoc),
+    Style = main_only,
+    {fetch, custom, Style, Req, DIR, FName}.
+
+load_rctx(PidRef) ->
+    timer:sleep(50), %% Add slight delay to accumulate RPC response deltas
+    csrt_util:to_json(csrt:get_resource(PidRef)).

Reply via email to