From b09bcea99a24ff3b28582913dcb33ee3c5c9857b Mon Sep 17 00:00:00 2001 From: speeddragon Date: Fri, 1 May 2026 15:35:37 +0100 Subject: [PATCH 1/3] impr: change parallel test from _test_parallel to _parallel_test --- src/dev_arweave.erl | 70 +++++------ src/dev_bundler.erl | 42 +++---- src/dev_copycat_arweave.erl | 34 +++--- src/dev_copycat_graphql.erl | 18 +-- src/dev_manifest.erl | 12 +- src/dev_name.erl | 20 ++-- src/dev_process_test_vectors.erl | 34 +++--- src/dev_query_test_vectors.erl | 36 +++--- src/dev_router.erl | 44 +++---- src/dev_scheduler.erl | 30 ++--- src/hb_test_parallel.erl | 198 ++++++++++++++++++++----------- src/include/hb.hrl | 5 +- 12 files changed, 300 insertions(+), 243 deletions(-) diff --git a/src/dev_arweave.erl b/src/dev_arweave.erl index 9b249b56c..f3d34d925 100644 --- a/src/dev_arweave.erl +++ b/src/dev_arweave.erl @@ -1050,7 +1050,7 @@ event_request(Path, Method, Status, Extra) -> %% @doc A fixed bad interior offset from a live TX is rejected by %% bundle_header/3 as invalid_bundle_header. -bundle_header_garbage_guard_test_parallel() -> +bundle_header_garbage_guard_parallel_test() -> ServerOpts = #{ <<"store">> => [hb_test_utils:test_store()] }, _Server = hb_http_server:start_node(ServerOpts), ProbeOffset = 376836336327208, @@ -1061,7 +1061,7 @@ bundle_header_garbage_guard_test_parallel() -> ). -post_ans104_message_test_parallel() -> +post_ans104_message_parallel_test() -> Port = rand:uniform(10000) + 10000, ServerOpts = #{ <<"store">> => [hb_test_utils:test_store()], @@ -1117,7 +1117,7 @@ post_ans104_message_test_parallel() -> dev_bundler:stop_server() end. -post_tx_message_test_parallel() -> +post_tx_message_parallel_test() -> ServerOpts = #{ <<"store">> => [hb_test_utils:test_store()] }, Server = hb_http_server:start_node(ServerOpts), ClientOpts = @@ -1153,7 +1153,7 @@ post_tx_message_test_parallel() -> ?assertEqual(<<"Transaction verification failed.">>, Body), ok. -post_tx_json_failure_test_parallel() -> +post_tx_json_failure_parallel_test() -> ServerOpts = #{ <<"store">> => [hb_test_utils:test_store()] }, Server = hb_http_server:start_node(ServerOpts), ClientOpts = post_tx_json_client_opts(), @@ -1166,7 +1166,7 @@ post_tx_json_failure_test_parallel() -> ?assertEqual(<<"Transaction verification failed.">>, Body), ok. -post_tx_json_success_test_parallel() -> +post_tx_json_success_parallel_test() -> {Response, Node1Posts, Node2Posts} = post_tx_json_two_node_test({200, <<"OK-1">>}, {200, <<"OK-2">>}), ?assertMatch({ok, #{ <<"status">> := 200 }}, Response), @@ -1174,7 +1174,7 @@ post_tx_json_success_test_parallel() -> ?assertEqual(1, length(Node2Posts)), ok. -post_tx_json_mixed_status_prefers_success_test_parallel() -> +post_tx_json_mixed_status_prefers_success_parallel_test() -> {Response, Node1Posts, Node2Posts} = post_tx_json_two_node_test( {400, <<"Transaction verification failed.">>}, @@ -1185,7 +1185,7 @@ post_tx_json_mixed_status_prefers_success_test_parallel() -> ?assertEqual(1, length(Node2Posts)), ok. -best_response_handles_failed_connect_entries_test_parallel() -> +best_response_handles_failed_connect_entries_parallel_test() -> FailedConnect = {failed_connect, [ @@ -1202,7 +1202,7 @@ best_response_handles_failed_connect_entries_test_parallel() -> best_response(Responses) ). -best_response_non_map_error_round_trips_test_parallel() -> +best_response_non_map_error_round_trips_parallel_test() -> FailedConnect = {failed_connect, [ @@ -1367,7 +1367,7 @@ tx_index_block(<<"jI0A4BASHaUdCCsdv249BxDX6IlE0Ko391TuI6REATw">>) -> 1289677; tx_index_block(<<"4FnBmvgWmqXWEEprjVqBsV5aRpAgF6_yJX_GTGsSZjY">>) -> 753012; tx_index_block(<<"YR9m4c3CrlljCRYEWBLeoKekbAyYZRMo2Kpz61IeNp8">>) -> 1233918. -get_tx_basic_data_test_parallel() -> +get_tx_basic_data_parallel_test() -> {ok, Structured} = hb_ao:resolve( #{ <<"device">> => <<"arweave@2.9">> }, #{ @@ -1395,7 +1395,7 @@ get_tx_basic_data_test_parallel() -> ok. %% @doc The data for this transaction ends with two smaller chunks. -get_tx_split_chunk_test_parallel() -> +get_tx_split_chunk_parallel_test() -> {ok, Structured} = hb_ao:resolve( #{ <<"device">> => <<"arweave@2.9">> }, #{ @@ -1424,7 +1424,7 @@ get_tx_split_chunk_test_parallel() -> hb_message:id(Child, signed)), ok. -get_tx_basic_data_exclude_data_test_parallel() -> +get_tx_basic_data_exclude_data_parallel_test() -> TXID = <<"ptBC0UwDmrUTBQX3MqZ1lB57ex20ygwzkjjCrQjIx3o">>, Opts = setup_arweave_index_opts([TXID]), {ok, Structured} = hb_ao:resolve( @@ -1461,7 +1461,7 @@ get_tx_basic_data_exclude_data_test_parallel() -> ?assertEqual(<<"PEShWA1ER2jq7CatAPpOZ30TeLrjOSpaf_Po7_hKPo4">>, DataHash), ok. -get_tx_data_tag_exclude_data_test_parallel() -> +get_tx_data_tag_exclude_data_parallel_test() -> TXID = <<"jI0A4BASHaUdCCsdv249BxDX6IlE0Ko391TuI6REATw">>, Opts = setup_arweave_index_opts([TXID]), {ok, Structured} = hb_ao:resolve( @@ -1497,7 +1497,7 @@ get_tx_data_tag_exclude_data_test_parallel() -> ?assertEqual(<<"IHyJ9BlQaHLWVwwklMwV1XEYXGjwx2B6HXNJZ4yJXeQ">>, DataHash), ok. -head_raw_tx_test_parallel() -> +head_raw_tx_parallel_test() -> TXID = <<"ptBC0UwDmrUTBQX3MqZ1lB57ex20ygwzkjjCrQjIx3o">>, Opts = setup_arweave_index_opts([TXID]), {ok, Result} = @@ -1524,7 +1524,7 @@ head_raw_tx_test_parallel() -> hb_maps:find(<<"header-length">>, Result, Opts) ). -head_raw_ans104_test_parallel() -> +head_raw_ans104_parallel_test() -> Opts = setup_arweave_index_opts([]), DataItemID = <<"0vy2Ey8bWkSDcRIvWQJjxDeVGYOrTSmYIIhBILJntY8">>, BlockBin = hb_util:bin(1_827_942), @@ -1551,7 +1551,7 @@ head_raw_ans104_test_parallel() -> hb_maps:find(<<"content-length">>, Result, Opts) ). -get_raw_range_tx_test_parallel() -> +get_raw_range_tx_parallel_test() -> DataItemID = <<"ptBC0UwDmrUTBQX3MqZ1lB57ex20ygwzkjjCrQjIx3o">>, Opts = setup_arweave_index_opts([DataItemID]), {ok, Result} = @@ -1591,7 +1591,7 @@ get_raw_range_tx_test_parallel() -> hb_maps:find(<<"body">>, Result2, Opts) ). -get_raw_range_ans104_test_parallel() -> +get_raw_range_ans104_parallel_test() -> Opts = setup_arweave_index_opts([]), DataItemID = <<"0vy2Ey8bWkSDcRIvWQJjxDeVGYOrTSmYIIhBILJntY8">>, BlockBin = hb_util:bin(1_827_942), @@ -1636,7 +1636,7 @@ get_raw_range_ans104_test_parallel() -> hb_maps:find(<<"body">>, Result2, Opts) ). -get_tx_rsa_nested_bundle_test_parallel() -> +get_tx_rsa_nested_bundle_parallel_test() -> Node = hb_http_server:start_node(), Path = <<"/~arweave@2.9/tx=bndIwac23-s0K11TLC1N7z472sLGAkiOdhds87ZywoE">>, {ok, Root} = hb_http:get(Node, Path, #{}), @@ -1679,7 +1679,7 @@ get_tx_rsa_large_bundle_test_disabled() -> ok end}. -get_bad_tx_test_parallel() -> +get_bad_tx_parallel_test() -> Node = hb_http_server:start_node(), Path = <<"/~arweave@2.9/tx=INVALID-ID">>, Res = hb_http:get(Node, Path, #{}), @@ -1718,7 +1718,7 @@ serialize_data_item_test_disabled() -> ?assert(ar_bundles:verify_item(VerifiedItem)), ok. -get_partial_chunk_post_split_test_parallel() -> +get_partial_chunk_post_split_parallel_test() -> %% https://arweave.net/tx/QL7_EnmrFtx-0wVgPr2IwaGWQT8vmPcF3R20CKMO3D4/offset %% Offset = 378092137521399, @@ -1739,7 +1739,7 @@ get_partial_chunk_post_split_test_parallel() -> ), ok. -get_full_chunk_post_split_test_parallel() -> +get_full_chunk_post_split_parallel_test() -> %% https://arweave.net/tx/QL7_EnmrFtx-0wVgPr2IwaGWQT8vmPcF3R20CKMO3D4/offset %% Offset = 378092137521399, @@ -1760,7 +1760,7 @@ get_full_chunk_post_split_test_parallel() -> ), ok. -get_multi_chunk_post_split_test_parallel() -> +get_multi_chunk_post_split_parallel_test() -> %% https://arweave.net/tx/QL7_EnmrFtx-0wVgPr2IwaGWQT8vmPcF3R20CKMO3D4/offset %% Offset = 378092137521399, @@ -1783,7 +1783,7 @@ get_multi_chunk_post_split_test_parallel() -> %% @doc Query a chunk range that starts and ends in the middle of a chunk. -get_mid_chunk_post_split_test_parallel() -> +get_mid_chunk_post_split_parallel_test() -> %% https://arweave.net/tx/QL7_EnmrFtx-0wVgPr2IwaGWQT8vmPcF3R20CKMO3D4/offset %% Offset = 378092137521399 + 200_000, @@ -1804,7 +1804,7 @@ get_mid_chunk_post_split_test_parallel() -> ), ok. -get_partial_chunk_pre_split_test_parallel() -> +get_partial_chunk_pre_split_parallel_test() -> %% https://arweave.net/tx/v4ophPvV-cNp5gkpkjMuUZ-lf-fBfm1Wk-pB4vJb00E/offset %% Offset = 30575701172109, @@ -1825,7 +1825,7 @@ get_partial_chunk_pre_split_test_parallel() -> ), ok. -get_full_chunk_pre_split_test_parallel() -> +get_full_chunk_pre_split_parallel_test() -> %% https://arweave.net/tx/v4ophPvV-cNp5gkpkjMuUZ-lf-fBfm1Wk-pB4vJb00E/offset %% Offset = 30575701172109, @@ -1846,7 +1846,7 @@ get_full_chunk_pre_split_test_parallel() -> ), ok. -get_multi_chunk_pre_split_test_parallel() -> +get_multi_chunk_pre_split_parallel_test() -> %% https://arweave.net/tx/v4ophPvV-cNp5gkpkjMuUZ-lf-fBfm1Wk-pB4vJb00E/offset %% Offset = 30575701172109, @@ -1867,7 +1867,7 @@ get_multi_chunk_pre_split_test_parallel() -> ), ok. -get_mid_chunk_pre_split_test_parallel() -> +get_mid_chunk_pre_split_parallel_test() -> %% https://arweave.net/tx/v4ophPvV-cNp5gkpkjMuUZ-lf-fBfm1Wk-pB4vJb00E/offset %% Offset = 30575701172109 + 200_000, @@ -1888,7 +1888,7 @@ get_mid_chunk_pre_split_test_parallel() -> ), ok. -get_pre_split_small_chunks_test_parallel() -> +get_pre_split_small_chunks_parallel_test() -> TXID = <<"4FnBmvgWmqXWEEprjVqBsV5aRpAgF6_yJX_GTGsSZjY">>, Opts = setup_arweave_index_opts([TXID]), assert_chunk_range( @@ -1900,7 +1900,7 @@ get_pre_split_small_chunks_test_parallel() -> Opts ). -get_post_split_small_chunks_test_parallel() -> +get_post_split_small_chunks_parallel_test() -> TXID = <<"YR9m4c3CrlljCRYEWBLeoKekbAyYZRMo2Kpz61IeNp8">>, Opts = setup_arweave_index_opts([TXID]), assert_chunk_range( @@ -1912,7 +1912,7 @@ get_post_split_small_chunks_test_parallel() -> Opts ). -get_pre_split_gap_test_parallel() -> +get_pre_split_gap_parallel_test() -> TXID = <<"VexuG68KCNpw21fGZw1ycRCYBtQMHhl274zGDBh3kQE">>, Opts = setup_arweave_index_opts([TXID]), assert_chunk_range( @@ -1924,7 +1924,7 @@ get_pre_split_gap_test_parallel() -> Opts ). -get_pre_split_small_tx_test_parallel() -> +get_pre_split_small_tx_parallel_test() -> TXID = <<"K4C4dLZ7V4ffYJcR9JtVQwIXCTLD1mMCUaPbHuUdFgw">>, Opts = setup_arweave_index_opts([TXID]), assert_chunk_range( @@ -1938,7 +1938,7 @@ get_pre_split_small_tx_test_parallel() -> %% @doc Checks an item that begins in the middle of a chunk - without %% special handling get_chunk_range() used to leave off the last few bytes -get_ed25519_item_test_parallel() -> +get_ed25519_item_parallel_test() -> TXID = <<"jTFA8XDI_rqmUB6-hhoJF4Yi7p6ZpS_0AByFLU1OPrU">>, DataItemID = <<"1rTy7gQuK9lJydlKqCEhtGLp2WWG-GOrVo5JdiCmaxs">>, Opts = setup_arweave_index_opts([TXID]), @@ -1953,7 +1953,7 @@ get_ed25519_item_test_parallel() -> %% @doc this test fails if the chunks are queried with %% the `x-bucket-based-offset' header set. -bucket_based_offset_fail_test_parallel() -> +bucket_based_offset_fail_parallel_test() -> TXID = <<"T2pluNnaavL7-S2GkO_m3pASLUqMH_XQ9IiIhZKfySs">>, DataItemID = <<"z-oKJfhMq5qoVFrljEfiBKgumaJmCWVxNJaavR5aPE8">>, Opts = setup_arweave_index_opts([TXID]), @@ -1968,7 +1968,7 @@ bucket_based_offset_fail_test_parallel() -> %% @doc this dataitem needs the 'x-bucket-based-offset' header set OR %% special handling. -bucket_based_offset_pass_test_parallel() -> +bucket_based_offset_pass_parallel_test() -> DataItemID = <<"cTI07T1OrF0KZEqPmZji1VTdbeKJG7kMAVlLu7KQvyw">>, Opts = setup_arweave_index_opts([]), assert_chunk_range( @@ -1980,10 +1980,10 @@ bucket_based_offset_pass_test_parallel() -> Opts ). -reassemble_bundle1_test_parallel() -> +reassemble_bundle1_parallel_test() -> assert_bundle_tx(<<"c1-FkhQd-Ul-VpIMR5Vs77lK__BlzHzena2zgNh_hME">>). -reassemble_bundle2_test_parallel() -> +reassemble_bundle2_parallel_test() -> assert_bundle_tx(<<"OVjj52NvyIys7u84Rv1uqRG2vswlF95QDVPSmsmlwLk">>). %% @doc This asserts that a bundle is correctly represented in the weave. diff --git a/src/dev_bundler.erl b/src/dev_bundler.erl index 4cd7a6933..80e70c6fd 100644 --- a/src/dev_bundler.erl +++ b/src/dev_bundler.erl @@ -547,22 +547,22 @@ recover_bundle(CommittedTX, Items, State = #state{opts = Opts}) -> %%% Four test cases below (`idle_test', `bundle_dispatch_delay_test', %%% `dispatch_blocking_test', `exponential_backoff_timing_test') assert %%% wall-clock timing against the bundler's internal timers. They use -%%% EUnit's plain `_test/0' convention rather than `_test_parallel/0' +%%% EUnit's plain `_test/0' convention rather than `_parallel_test/0' %%% so they run sequentially, before the parse_transform-injected %%% `all_parallel_test_/0' inparallel batch runs the other 20 cases. %%% Keeping them out of the batch avoids a ~20% flake seen when %%% `timer:sleep/1' returns late under same-module scheduler pressure. -bundle_count_test_parallel() -> +bundle_count_parallel_test() -> test_bundle(#{ <<"bundler-max-items">> => 3 }). -bundle_size_test_parallel() -> +bundle_size_parallel_test() -> test_bundle(#{ <<"bundler-max-size">> => floor(3.6 * ?DATA_CHUNK_SIZE) }). bundle_dispatch_delay_test() -> test_bundle(#{ <<"bundler-max-bundle-dispatch-delay">> => 3000 }). -nested_bundle_test_parallel() -> +nested_bundle_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, % NodeOpts redirects arweave gateway requests to the mock server. @@ -600,19 +600,19 @@ nested_bundle_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -price_error_test_parallel() -> +price_error_parallel_test() -> test_api_error(#{ price => {500, <<"error">>}, tx_anchor => {200, hb_util:encode(rand:bytes(32))} }). -anchor_error_test_parallel() -> +anchor_error_parallel_test() -> test_api_error(#{ price => {200, <<"12345">>}, tx_anchor => {500, <<"error">>} }). -tx_error_test_parallel() -> +tx_error_parallel_test() -> {ServerHandle, NodeOpts} = start_mock_gateway( #{ tx => {400, <<"Transaction verification failed.">>}, @@ -641,7 +641,7 @@ tx_error_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -unsigned_dataitem_test_parallel() -> +unsigned_dataitem_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, % NodeOpts redirects arweave gateway requests to the mock server. @@ -789,7 +789,7 @@ dispatch_blocking_test() -> %% @doc Test that items are recovered and posted while respecting the %% max_items limit. -recover_respects_max_items_test_parallel() -> +recover_respects_max_items_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, {ServerHandle, NodeOpts} = start_mock_gateway(#{ @@ -829,7 +829,7 @@ recover_respects_max_items_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -complete_task_sequence_test_parallel() -> +complete_task_sequence_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, {ServerHandle, NodeOpts} = start_mock_gateway(#{ @@ -873,7 +873,7 @@ complete_task_sequence_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -recover_bundles_test_parallel() -> +recover_bundles_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, {ServerHandle, NodeOpts} = start_mock_gateway(#{ @@ -930,7 +930,7 @@ recover_bundles_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -post_tx_price_failure_retry_test_parallel() -> +post_tx_price_failure_retry_parallel_test() -> Anchor = rand:bytes(32), FailCount = 3, setup_test_counter(price_attempts_counter), @@ -966,7 +966,7 @@ post_tx_price_failure_retry_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -post_tx_anchor_failure_retry_test_parallel() -> +post_tx_anchor_failure_retry_parallel_test() -> Price = 12345, FailCount = 3, setup_test_counter(anchor_attempts_counter), @@ -1002,7 +1002,7 @@ post_tx_anchor_failure_retry_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -post_tx_post_failure_retry_test_parallel() -> +post_tx_post_failure_retry_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, FailCount = 4, @@ -1040,7 +1040,7 @@ post_tx_post_failure_retry_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -post_proof_failure_retry_test_parallel() -> +post_proof_failure_retry_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, FailCount = 2, @@ -1080,7 +1080,7 @@ post_proof_failure_retry_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -rapid_dispatch_test_parallel() -> +rapid_dispatch_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, {ServerHandle, NodeOpts} = start_mock_gateway(#{ @@ -1114,7 +1114,7 @@ rapid_dispatch_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -one_bundle_fails_others_continue_test_parallel() -> +one_bundle_fails_others_continue_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, setup_test_counter(mixed_attempts_counter), @@ -1151,7 +1151,7 @@ one_bundle_fails_others_continue_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -parallel_task_execution_test_parallel() -> +parallel_task_execution_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, SleepTime = 120, @@ -1242,7 +1242,7 @@ exponential_backoff_timing_test() -> stop_test_servers(ServerHandle, NodeOpts) end. -independent_task_retry_counts_test_parallel() -> +independent_task_retry_counts_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, setup_test_counter(independent_retry_counter), @@ -1281,7 +1281,7 @@ independent_task_retry_counts_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -invalid_item_test_parallel() -> +invalid_item_parallel_test() -> Anchor = rand:bytes(32), Price = 12345, {ServerHandle, NodeOpts} = start_mock_gateway(#{ @@ -1324,7 +1324,7 @@ invalid_item_test_parallel() -> stop_test_servers(ServerHandle, NodeOpts) end. -cache_write_failure_test_parallel() -> +cache_write_failure_parallel_test() -> GoodOpts = #{<<"store">> => hb_test_utils:test_store()}, BadOpts = #{ <<"store">> => undefined, diff --git a/src/dev_copycat_arweave.erl b/src/dev_copycat_arweave.erl index d8db2f845..86ffdf5e8 100644 --- a/src/dev_copycat_arweave.erl +++ b/src/dev_copycat_arweave.erl @@ -486,7 +486,7 @@ observe_event(MetricName, Fun) -> %%% Tests -index_ids_test_parallel() -> +index_ids_parallel_test() -> %% Test block: https://viewblock.io/arweave/block/1827942 %% Note: this block includes a data item with an Ethereum signature. This %% signature type is not yet (as of Jan 2026) supported by ar_bundles.erl, @@ -557,7 +557,7 @@ index_ids_test_parallel() -> ok. %% @doc Test a bundle header that fits in a single chunk. -small_bundle_header_test_parallel() -> +small_bundle_header_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), TXID = <<"29TsnbqPQ_7rQ_r4KF5qRr995W1wBw_mTy6WEMy40aw">>, {ok, #{ <<"body">> := OffsetBody }} = @@ -578,7 +578,7 @@ small_bundle_header_test_parallel() -> ok. %% @doc Test a bundle header that doesn't fit in a single chunk. -large_bundle_header_test_parallel() -> +large_bundle_header_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), TXID = <<"bnMTI7LglBGSaK5EdV_juh6GNtXLm0cd5lkd2q4nlT0">>, {ok, #{ <<"body">> := OffsetBody }} = @@ -598,7 +598,7 @@ large_bundle_header_test_parallel() -> ?assertEqual(960032, HeaderSize), ok. -invalid_bundle_header_test_parallel() -> +invalid_bundle_header_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), TXID = <<"cGNURX2IUt98VKVIeXSfYe6eulNwPEqijaQfvatzd_o">>, {ok, #{ <<"body">> := OffsetBody }} = @@ -616,7 +616,7 @@ invalid_bundle_header_test_parallel() -> download_bundle_header(EndOffset, Size, Opts)), ok. -invalid_bundle_test_parallel() -> +invalid_bundle_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), Block = 1307606, {ok, Block} = @@ -636,7 +636,7 @@ invalid_bundle_test_parallel() -> assert_item_read(<<"cGNURX2IUt98VKVIeXSfYe6eulNwPEqijaQfvatzd_o">>, Opts), ok. -block_with_large_integer_test_parallel() -> +block_with_large_integer_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), Block = 633719, {ok, Block} = @@ -649,7 +649,7 @@ block_with_large_integer_test_parallel() -> assert_item_read(<<"UXpcKTl6Mh34eTFSgny4NcIqoUjBcgYIcMqromcS6_Q">>, Opts), ok. -empty_block_test_parallel() -> +empty_block_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), Block = 1865858, {ok, Block} = @@ -724,7 +724,7 @@ tx_with_data_tag_test_disabled() -> assert_item_read(<<"jI0A4BASHaUdCCsdv249BxDX6IlE0Ko391TuI6REATw">>, Opts), ok. -tx_with_no_data_test_parallel() -> +tx_with_no_data_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), Block = 1826700, BlockBin = hb_util:bin(Block), @@ -785,13 +785,13 @@ tx_with_no_data_test_parallel() -> ?assertEqual([ ], maps:get(<<"not-indexed">>, BlockInfo)), ok. -non_string_tags_test_parallel() -> +non_string_tags_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), Res = resolve_tx_header(<<"752P6t4cOjMabYHqzC6hyLhxyo4YKZLblg7va_J21YE">>, Opts), ?assertEqual(error, Res), ok. -list_index_test_parallel() -> +list_index_parallel_test() -> %% Test block: https://viewblock.io/arweave/block/1827942 {_TestStore, _StoreOpts, Opts} = setup_index_opts(), %% First index the block using write mode @@ -840,7 +840,7 @@ list_index_test_parallel() -> ?assertEqual([ ], maps:get(<<"not-indexed">>, BlockInfo)), ok. -auto_stop_on_indexed_block_test_parallel() -> +auto_stop_on_indexed_block_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), IndexedBlock = 1827941, Higher1 = IndexedBlock + 1, @@ -870,7 +870,7 @@ auto_stop_on_indexed_block_test_parallel() -> ?assertNot(has_any_indexed_tx(IndexedBlock-1, Opts)), ok. -explicit_to_reindexes_all_test_parallel() -> +explicit_to_reindexes_all_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), IndexedBlock = 1827942, LowerBlock = IndexedBlock - 1, @@ -900,7 +900,7 @@ explicit_to_reindexes_all_test_parallel() -> %% @doc Manually write to the index to simulate a partially indexed block. %% This should also trigger a stop when the `to` option is omitted. -auto_stop_partial_index_test_parallel() -> +auto_stop_partial_index_parallel_test() -> {_TestStore, StoreOpts, Opts} = setup_index_opts(), Block = 1826700, HigherBlock = Block + 1, @@ -937,7 +937,7 @@ auto_stop_partial_index_test_parallel() -> ?assertNot(has_any_indexed_tx(Block-1, Opts)), ok. -negative_parse_range_test_parallel() -> +negative_parse_range_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), {ok, Tip} = hb_ao:resolve( @@ -954,7 +954,7 @@ negative_parse_range_test_parallel() -> ?assertEqual(hb_util:int(Tip) - 3, NegativeTo), ok. -latest_height_failure_test_parallel() -> +latest_height_failure_parallel_test() -> {ok, MockURL, MockHandle} = hb_mock_server:start([ {"/block/current", block_current, {500, <<"Internal Server Error">>}} ]), @@ -991,7 +991,7 @@ latest_height_failure_test_parallel() -> hb_mock_server:stop(MockHandle) end. -negative_resolved_height_test_parallel() -> +negative_resolved_height_parallel_test() -> {ok, MockURL, MockHandle} = hb_mock_server:start([ {"/block/current", block_current, {200, <<"{\"height\": 5}">>}} @@ -1025,7 +1025,7 @@ negative_resolved_height_test_parallel() -> hb_mock_server:stop(MockHandle) end. -negative_from_index_test_parallel() -> +negative_from_index_parallel_test() -> {_TestStore, _StoreOpts, Opts} = setup_index_opts(), {ok, Tip} = latest_height(Opts), StopBlock = 1827942, diff --git a/src/dev_copycat_graphql.erl b/src/dev_copycat_graphql.erl index 64a03e5fa..591a73407 100644 --- a/src/dev_copycat_graphql.erl +++ b/src/dev_copycat_graphql.erl @@ -275,7 +275,7 @@ run_test_node() -> Node = hb_http_server:start_node(Opts), {Node ,Opts}. %% @doc Basic test to test copycat device -basic_test_parallel() -> +basic_parallel_test() -> {Node, _Opts} = run_test_node(), {ok, Res} = hb_http:get( @@ -288,7 +288,7 @@ basic_test_parallel() -> ?event({basic_test_result, Res}), ok. -query_test_parallel() -> +query_parallel_test() -> Base = #{ <<"query">> => #{ <<"tags">> => #{ @@ -319,7 +319,7 @@ query_test_parallel() -> ok. %% @doc Test tag/value pair format -tag_value_test_parallel() -> +tag_value_parallel_test() -> Base = #{<<"tag">> => <<"type">>, <<"value">> => <<"process">>}, {ok, Query} = parse_query(Base, #{}, #{}), ?event({tag_value_test, {query, Query}}), @@ -332,7 +332,7 @@ tag_value_test_parallel() -> ok. %% @doc Test owners filter with single value -owners_filter_test_parallel() -> +owners_filter_parallel_test() -> Base = #{<<"owners">> => <<"addr123">>}, {ok, Query} = parse_query(Base, #{}, #{}), ?event({owners_filter_test, {query, Query}}), @@ -345,7 +345,7 @@ owners_filter_test_parallel() -> ok. %% @doc Test recipients filter with array values -recipients_filter_test_parallel() -> +recipients_filter_parallel_test() -> Base = #{<<"recipients">> => [<<"rec1">>, <<"rec2">>]}, {ok, Query} = parse_query(Base, #{}, #{}), ?event({recipients_filter_test, {query, Query}}), @@ -358,7 +358,7 @@ recipients_filter_test_parallel() -> ok. %% @doc Test ids filter -ids_filter_test_parallel() -> +ids_filter_parallel_test() -> Base = #{<<"ids">> => [<<"id1">>, <<"id2">>, <<"id3">>]}, {ok, Query} = parse_query(Base, #{}, #{}), ?event({ids_filter_test, {query, Query}}), @@ -371,7 +371,7 @@ ids_filter_test_parallel() -> ok. %% @doc Test all filter type -all_filter_test_parallel() -> +all_filter_parallel_test() -> Base = #{<<"all">> => <<"true">>}, {ok, Query} = parse_query(Base, #{}, #{}), ?event({all_filter_test, {query, Query}}), @@ -384,7 +384,7 @@ all_filter_test_parallel() -> ok. %% @doc Test combined multiple filters in one query -combined_filters_test_parallel() -> +combined_filters_parallel_test() -> Base = #{ <<"query">> => #{ <<"tags">> => #{ @@ -429,7 +429,7 @@ combined_filters_test_parallel() -> ok. %% @doc Real world test with actual indexing -fetch_scheduler_location_test_parallel() -> +fetch_scheduler_location_parallel_test() -> {Node, _Opts} = run_test_node(), Res = hb_http:get( diff --git a/src/dev_manifest.erl b/src/dev_manifest.erl index bf0125148..763c63f15 100644 --- a/src/dev_manifest.erl +++ b/src/dev_manifest.erl @@ -203,7 +203,7 @@ linkify(Manifest, _Opts) -> %%% Tests -resolve_test_parallel() -> +resolve_parallel_test() -> Opts = #{ <<"store">> => hb_opts:get(store, no_viable_store, #{}), <<"on">> => #{ @@ -263,7 +263,7 @@ resolve_test_parallel() -> hb_http:get(Node, << LegacyManifestID/binary, "/nested/page2" >>, Opts)), ok. -manifest_default_fallback_test_parallel() -> +manifest_default_fallback_parallel_test() -> Opts = #{ <<"store">> => hb_opts:get(store, no_viable_store, #{}) }, {ok, ManifestID} = create_generic_manifest(Opts), ?event({manifest_id, ManifestID}), @@ -274,7 +274,7 @@ manifest_default_fallback_test_parallel() -> ), ok. -manifest_404_error_test_parallel() -> +manifest_404_error_parallel_test() -> Opts = #{ <<"store">> => hb_opts:get(store, no_viable_store, #{}), <<"manifest-404">> => error @@ -346,7 +346,7 @@ manifest_download_via_raw_endpoint_test_ignore() -> ). %% @doc Accessing `/TXID` of a manifest transaction should access the index key. -manifest_inner_redirect_test_parallel() -> +manifest_inner_redirect_parallel_test() -> Opts = test_env_opts(), Node = hb_http_server:start_node(Opts), %% Request manifest to node. @@ -360,7 +360,7 @@ manifest_inner_redirect_test_parallel() -> ). %% @doc Accessing `/TXID/assets/ArticleBlock-Dtwjc54T.js` should return valid message. -access_key_path_in_manifest_test_parallel() -> +access_key_path_in_manifest_parallel_test() -> Opts = test_env_opts(), Node = hb_http_server:start_node(Opts), ?assertMatch( @@ -374,7 +374,7 @@ access_key_path_in_manifest_test_parallel() -> %% This works with `not_found.js` but doesn't follow the logic if under a %% folder structure, like `assets/not_found.js . -manifest_should_fallback_on_not_found_path_test_parallel() -> +manifest_should_fallback_on_not_found_path_parallel_test() -> Opts = test_env_opts(), Node = hb_http_server:start_node(Opts), ?assertMatch( diff --git a/src/dev_name.erl b/src/dev_name.erl index a996e90c8..160c0d00d 100644 --- a/src/dev_name.erl +++ b/src/dev_name.erl @@ -187,7 +187,7 @@ name_from_host(ReqHost, RawNodeHost) -> %%% Tests. -no_resolvers_test_parallel() -> +no_resolvers_parallel_test() -> ?assertEqual( not_found, resolve(<<"hello">>, #{}, #{}, #{ <<"only">> => local }) @@ -211,7 +211,7 @@ device_resolver(Msg) -> } }. -single_resolver_test_parallel() -> +single_resolver_parallel_test() -> ?assertEqual( {ok, <<"world">>}, resolve( @@ -227,7 +227,7 @@ single_resolver_test_parallel() -> ). %% @doc Lookup a name in a message and return it. -message_lookup_test_parallel() -> +message_lookup_parallel_test() -> ?assertEqual( {ok, <<"world">>}, resolve( @@ -244,7 +244,7 @@ message_lookup_test_parallel() -> ) ). -multiple_resolvers_test_parallel() -> +multiple_resolvers_parallel_test() -> ?assertEqual( {ok, <<"bigger-world">>}, resolve( @@ -265,7 +265,7 @@ multiple_resolvers_test_parallel() -> ). %% @doc Test that we can resolve messages from a name loaded with the device. -load_and_execute_test_parallel() -> +load_and_execute_parallel_test() -> TestKey = <<"test-key", (hb_util:bin(erlang:system_time(millisecond)))/binary>>, {ok, ID} = hb_cache:write( #{ @@ -314,7 +314,7 @@ test_arns_opts() -> }. %% @doc Names from JSON test. -arns_json_snapshot_test_parallel() -> +arns_json_snapshot_parallel_test() -> Opts = test_arns_opts(), ?assertMatch( {ok, <<"text/html">>}, @@ -328,7 +328,7 @@ arns_json_snapshot_test_parallel() -> ) ). -arns_host_resolution_test_parallel() -> +arns_host_resolution_parallel_test() -> Opts = test_arns_opts(), Node = hb_http_server:start_node(Opts), ?assertMatch( @@ -343,7 +343,7 @@ arns_host_resolution_test_parallel() -> ) ). -arns_host_resolution_with_node_host_test_parallel() -> +arns_host_resolution_with_node_host_parallel_test() -> Opts = (test_arns_opts())#{ <<"node-host">> => <<"http://localhost">>, <<"port">> => 0 @@ -361,7 +361,7 @@ arns_host_resolution_with_node_host_test_parallel() -> ) ). -root_request_skips_name_resolution_test_parallel() -> +root_request_skips_name_resolution_parallel_test() -> BaseOpts = #{ <<"port">> => 0, @@ -390,7 +390,7 @@ root_request_skips_name_resolution_test_parallel() -> Check(<<"127.0.0.1:8734">>, #{}), Check(<<"ourweave.net:8734">>, #{ <<"node-host">> => <<"ourweave.net">> }). -name_from_host_test_parallel() -> +name_from_host_parallel_test() -> ?assertMatch({skip, _}, name_from_host(<<"127.0.0.1">>, no_host)), ?assertEqual({ok, <<"abc">>}, name_from_host(<<"abc.127.0.0.1">>, no_host)), ?assertEqual( diff --git a/src/dev_process_test_vectors.erl b/src/dev_process_test_vectors.erl index 1d2c4c095..b90462819 100644 --- a/src/dev_process_test_vectors.erl +++ b/src/dev_process_test_vectors.erl @@ -170,7 +170,7 @@ schedule_wasm_call(Base, FuncName, Params, Opts) -> ), ?assertMatch({ok, _}, hb_ao:resolve(Base, Req, Opts)). -schedule_on_process_test_parallel_() -> +schedule_on_process_parallel_test_() -> {timeout, 30, fun()-> Opts = test_opts(), Base = aos_process(Opts), @@ -192,7 +192,7 @@ schedule_on_process_test_parallel_() -> ) end}. -get_scheduler_slot_test_parallel() -> +get_scheduler_slot_parallel_test() -> Opts = test_opts(), Base = base_process(Opts), schedule_test_message(Base, <<"TEST TEXT 1">>, Opts), @@ -206,7 +206,7 @@ get_scheduler_slot_test_parallel() -> hb_ao:resolve(Base, Req, Opts) ). -recursive_path_resolution_test_parallel() -> +recursive_path_resolution_parallel_test() -> Opts = test_opts(), Base = base_process(Opts), schedule_test_message(Base, <<"TEST TEXT 1">>, Opts), @@ -223,7 +223,7 @@ recursive_path_resolution_test_parallel() -> ), ok. -test_device_compute_test_parallel() -> +test_device_compute_parallel_test() -> Opts = test_opts(), Base = test_process(Opts), schedule_test_message(Base, <<"TEST TEXT 1">>, Opts), @@ -242,7 +242,7 @@ test_device_compute_test_parallel() -> ?assertEqual(1, hb_ao:get(<<"results/assignment-slot">>, Res, Opts)), ?assertEqual([1,1,0,0], hb_ao:get(<<"already-seen">>, Res, Opts)). -wasm_compute_test_parallel() -> +wasm_compute_parallel_test() -> Opts = test_opts(), Base = wasm_process(<<"test/test-64.wasm">>, Opts), schedule_wasm_call(Base, <<"fac">>, [2.0], Opts), @@ -285,7 +285,7 @@ wasm_compute_test_parallel() -> % ?assertEqual([2.0], hb_ao:get(<<"results/output">>, Slot0Res, Opts)), % ?assertEqual([6.0], hb_ao:get(<<"results/output">>, Slot1Res, Opts)). -wasm_compute_from_id_test_parallel() -> +wasm_compute_from_id_parallel_test() -> Opts = test_opts(#{ <<"cache-control">> => <<"always">> }), Base = wasm_process(<<"test/test-64.wasm">>, Opts), schedule_wasm_call(Base, <<"fac">>, [5.0], Opts), @@ -295,7 +295,7 @@ wasm_compute_from_id_test_parallel() -> ?event(process_compute, {computed_message, {res, Res}}), ?assertEqual([120.0], hb_ao:get(<<"results/output">>, Res, Opts)). -http_wasm_process_by_id_test_parallel() -> +http_wasm_process_by_id_parallel_test() -> rand:seed(default), SchedWallet = ar_wallet:new(), Node = hb_http_server:start_node(Opts = #{ @@ -343,7 +343,7 @@ http_wasm_process_by_id_test_parallel() -> ?event({compute_msg_res, {msg4, Msg4}}), ?assertEqual([120.0], hb_ao:get(<<"results/output">>, Msg4, Opts)). -aos_compute_test_parallel_() -> +aos_compute_parallel_test_() -> {timeout, 30, fun() -> Opts = test_opts(), Base = aos_process(Opts), @@ -362,7 +362,7 @@ aos_compute_test_parallel_() -> {ok, Res3} end}. -aos_browsable_state_test_parallel_() -> +aos_browsable_state_parallel_test_() -> {timeout, 30, fun() -> Opts = test_opts(#{ <<"cache-control">> => <<"always">> }), Base = aos_process(Opts), @@ -384,7 +384,7 @@ aos_browsable_state_test_parallel_() -> ?assertEqual(4, Res) end}. -aos_state_access_via_http_test_parallel_() -> +aos_state_access_via_http_parallel_test_() -> {timeout, 60, fun() -> rand:seed(default), Wallet = ar_wallet:new(), @@ -443,7 +443,7 @@ aos_state_access_via_http_test_parallel_() -> ok end}. -aos_state_patch_test_parallel_() -> +aos_state_patch_parallel_test_() -> {timeout, 30, fun() -> Wallet = hb:wallet(), Opts = test_opts(), @@ -486,7 +486,7 @@ aos_state_patch_test_parallel_() -> end}. %% @doc Manually test state restoration without using the cache. -restore_test_parallel_() -> {timeout, 30, fun do_test_restore/0}. +restore_parallel_test_() -> {timeout, 30, fun do_test_restore/0}. do_test_restore() -> % Init the process and schedule 3 messages: @@ -516,7 +516,7 @@ do_test_restore() -> ?event({result_b, ResultB}), ?assertEqual(<<"1337">>, hb_ao:get(<<"results/data">>, ResultB, Opts)). -now_results_test_parallel_() -> +now_results_parallel_test_() -> {timeout, 30, fun() -> Opts = test_opts(), Base = aos_process(Opts), @@ -525,7 +525,7 @@ now_results_test_parallel_() -> ?assertEqual({ok, <<"4">>}, hb_ao:resolve(Base, <<"now/results/data">>, Opts)) end}. -prior_results_accessible_test_parallel_() -> +prior_results_accessible_parallel_test_() -> {timeout, 30, fun() -> Opts = test_opts(), Base = aos_process(Opts), @@ -547,7 +547,7 @@ prior_results_accessible_test_parallel_() -> ) end}. -persistent_process_test_parallel() -> +persistent_process_parallel_test() -> {timeout, 30, fun() -> Opts = test_opts(), Base = aos_process(Opts), @@ -581,7 +581,7 @@ persistent_process_test_parallel() -> ?assert(T2 - T1 < ((T1 - T0)/2)) end}. -simple_wasm_persistent_worker_benchmark_test_parallel() -> +simple_wasm_persistent_worker_benchmark_parallel_test() -> Opts = test_opts(), BenchTime = 0.05, Base = wasm_process(<<"test/test-64.wasm">>, Opts), @@ -620,7 +620,7 @@ simple_wasm_persistent_worker_benchmark_test_parallel() -> ?assert(Iterations >= 1), ok. -aos_persistent_worker_benchmark_test_parallel_() -> +aos_persistent_worker_benchmark_parallel_test_() -> {timeout, 30, fun() -> BenchTime = 0.25, init(), diff --git a/src/dev_query_test_vectors.erl b/src/dev_query_test_vectors.erl index 7cf54df89..2b8cc0831 100644 --- a/src/dev_query_test_vectors.erl +++ b/src/dev_query_test_vectors.erl @@ -95,7 +95,7 @@ write_test_message_with_recipient(Recipient, Opts) -> %%% Tests -simple_blocks_query_test_parallel() -> +simple_blocks_query_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -141,7 +141,7 @@ simple_blocks_query_test_parallel() -> dev_query_graphql:test_query(Node, Query, #{}, Opts) ). -block_by_height_query_test_parallel() -> +block_by_height_query_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -193,7 +193,7 @@ block_by_height_query_test_parallel() -> dev_query_graphql:test_query(Node, Query, #{}, Opts) ). -simple_ans104_query_test_parallel() -> +simple_ans104_query_parallel_test() -> Opts = #{ <<"priv-wallet">> => Wallet = ar_wallet:new(), @@ -260,7 +260,7 @@ simple_ans104_query_test_parallel() -> ). %% @doc Test transactions query with tags filter -transactions_query_tags_test_parallel() -> +transactions_query_tags_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -323,7 +323,7 @@ transactions_query_tags_test_parallel() -> ). %% @doc Test transactions query with owners filter -transactions_query_owners_test_parallel() -> +transactions_query_owners_parallel_test() -> Opts = #{ <<"priv-wallet">> => Wallet = ar_wallet:new(), @@ -385,7 +385,7 @@ transactions_query_owners_test_parallel() -> ). %% @doc Test transactions query with recipients filter -transactions_query_recipients_test_parallel() -> +transactions_query_recipients_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -450,7 +450,7 @@ transactions_query_recipients_test_parallel() -> ). %% @doc Test transactions query with ids filter -transactions_query_ids_test_parallel() -> +transactions_query_ids_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -512,7 +512,7 @@ transactions_query_ids_test_parallel() -> ). %% @doc Test transactions query with combined filters -transactions_query_combined_test_parallel() -> +transactions_query_combined_parallel_test() -> Opts = #{ <<"priv-wallet">> => Wallet = ar_wallet:new(), @@ -578,7 +578,7 @@ transactions_query_combined_test_parallel() -> Res ). -transactions_query_sort_by_block_test_parallel() -> +transactions_query_sort_by_block_parallel_test() -> {ok, Node, Opts} = test_env_with_blocks(1892159, 1892158), EarlierID = <<"xBpOR2KOjYEgv5HmddMlAgYa-yMvfEVl-0XzRIfm2uY">>, LaterID = <<"HVr7EpRhlPkbwdnoXKHf25p7BPa0qJOs6C7XueLthA0">>, @@ -621,7 +621,7 @@ transactions_query_sort_by_block_test_parallel() -> VerifyFun(<<"HEIGHT_ASC">>, EarlierID, LaterID), VerifyFun(<<"HEIGHT_DESC">>, LaterID, EarlierID). -transactions_query_filter_by_block_test_parallel() -> +transactions_query_filter_by_block_parallel_test() -> {ok, Node, Opts} = test_env_with_blocks(1892159, 1892158), EarlierID = <<"xBpOR2KOjYEgv5HmddMlAgYa-yMvfEVl-0XzRIfm2uY">>, LaterID = <<"HVr7EpRhlPkbwdnoXKHf25p7BPa0qJOs6C7XueLthA0">>, @@ -668,7 +668,7 @@ transactions_query_filter_by_block_test_parallel() -> VerifyFun(1892157, 1892158, [EarlierID], [LaterID]), VerifyFun(1892159, 1892160, [LaterID], [EarlierID]). -transactions_query_filter_by_block_excludes_unknown_offsets_test_parallel() -> +transactions_query_filter_by_block_excludes_unknown_offsets_parallel_test() -> {ok, _Node, Opts} = test_env_with_blocks(1892159, 1892158), {ok, ID} = hb_cache:write( @@ -701,7 +701,7 @@ transactions_query_filter_by_block_excludes_unknown_offsets_test_parallel() -> ) ). -transactions_query_filter_by_block_can_ignore_ranges_test_parallel() -> +transactions_query_filter_by_block_can_ignore_ranges_parallel_test() -> {ok, _Node, BaseOpts} = test_env_with_blocks(1892159, 1892158), Opts = BaseOpts#{ <<"query-arweave-ignore-block-ranges">> => true }, {ok, ID} = @@ -736,7 +736,7 @@ transactions_query_filter_by_block_can_ignore_ranges_test_parallel() -> ) ). -transactions_query_ids_preserve_arweave_tx_id_test_parallel() -> +transactions_query_ids_preserve_arweave_tx_id_parallel_test() -> {ok, _Node, Opts} = test_env_with_blocks(1892487, 1892487), ID = <<"mT7pIQx9ORnemXoIzWmKwymiZJxtOSvzxm3P44M9C1A">>, ?assertMatch( @@ -767,7 +767,7 @@ transactions_query_ids_preserve_arweave_tx_id_test_parallel() -> ) ). -transactions_query_cursor_by_offset_test_parallel() -> +transactions_query_cursor_by_offset_parallel_test() -> {ok, Node, Opts} = test_env_with_blocks(1892159, 1892158), EarlierID = <<"xBpOR2KOjYEgv5HmddMlAgYa-yMvfEVl-0XzRIfm2uY">>, LaterID = <<"HVr7EpRhlPkbwdnoXKHf25p7BPa0qJOs6C7XueLthA0">>, @@ -878,7 +878,7 @@ transactions_query_cursor_by_offset_test_parallel() -> ). %% @doc Test single transaction query by ID -transaction_query_by_id_test_parallel() -> +transaction_query_by_id_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -928,7 +928,7 @@ transaction_query_by_id_test_parallel() -> ). %% @doc Test single transaction query with more fields -transaction_query_full_test_parallel() -> +transaction_query_full_parallel_test() -> Opts = #{ <<"priv-wallet">> => SenderKey = ar_wallet:new(), @@ -1005,7 +1005,7 @@ transaction_query_full_test_parallel() -> ). %% @doc Test single transaction query with non-existent ID -transaction_query_not_found_test_parallel() -> +transaction_query_not_found_parallel_test() -> Opts = #{ <<"priv-wallet">> => ar_wallet:new(), @@ -1041,7 +1041,7 @@ transaction_query_not_found_test_parallel() -> ). %% @doc Test parsing, storing, and querying a transaction with an anchor. -transaction_query_with_anchor_test_parallel() -> +transaction_query_with_anchor_parallel_test() -> Opts = #{ <<"priv-wallet">> => Wallet = ar_wallet:new(), diff --git a/src/dev_router.erl b/src/dev_router.erl index 26fcae0d2..6434ea0cc 100644 --- a/src/dev_router.erl +++ b/src/dev_router.erl @@ -838,7 +838,7 @@ preprocess(Base, RawReq, Opts) -> %%% Tests -test_provider_test_parallel() -> +test_provider_parallel_test() -> Node = hb_http_server:start_node(Opts = #{ @@ -863,7 +863,7 @@ test_provider_test_parallel() -> hb_http:get(Node, <<"/~router@1.0/routes/1/node">>, Opts) ). -dynamic_provider_test_parallel() -> +dynamic_provider_parallel_test() -> {ok, Script} = file:read_file("test/test.lua"), Node = hb_http_server:start_node(#{ <<"store">> => hb_test_utils:test_store(), @@ -885,7 +885,7 @@ dynamic_provider_test_parallel() -> hb_http:get(Node, <<"/~router@1.0/routes/1/node">>, #{}) ). -local_process_provider_test_parallel_() -> +local_process_provider_parallel_test_() -> {timeout, 30, fun local_process_provider/0}. local_process_provider() -> {ok, Script} = file:read_file("test/test.lua"), @@ -935,7 +935,7 @@ local_process_provider() -> %% @doc Example of a Lua module being used as the `<<"provider">>' for a %% HyperBEAM node. The module utilized in this example dynamically adjusts the %% likelihood of routing to a given node, depending upon price and performance. -local_dynamic_router_test_parallel_() -> +local_dynamic_router_parallel_test_() -> {timeout, 60, fun local_dynamic_router/0}. local_dynamic_router() -> BenchRoutes = 50, @@ -1059,7 +1059,7 @@ local_dynamic_router() -> %% likelihood based on price and performance factors %% - Request preprocessing and routing happens correctly between nodes %% - Non-chargeable routes are properly handled via template patterns -dynamic_router_pricing_test_parallel_() -> +dynamic_router_pricing_parallel_test_() -> {timeout, 30, fun dynamic_router_pricing/0}. dynamic_router_pricing() -> {ok, Module} = file:read_file(<<"scripts/dynamic-router.lua">>), @@ -1218,7 +1218,7 @@ dynamic_router_pricing() -> %% HyperBEAM node. The module utilized in this example dynamically adjusts the %% likelihood of routing to a given node, depending upon price and performance. %% also include preprocessing support for routing -dynamic_router_test_parallel_() -> +dynamic_router_parallel_test_() -> {timeout, 30, fun dynamic_router/0}. dynamic_router() -> {ok, Module} = file:read_file(<<"scripts/dynamic-router.lua">>), @@ -1339,7 +1339,7 @@ dynamic_router() -> %% according to the real-time performance of nodes. This test utilizes the %% `dynamic-router' script to manage routes and recalculate weights based on the %% reported performance. -dynamic_routing_by_performance_test_parallel_() -> +dynamic_routing_by_performance_parallel_test_() -> {timeout, 60, fun dynamic_routing_by_performance/0}. dynamic_routing_by_performance() -> % Setup test parameters @@ -1498,7 +1498,7 @@ dynamic_routing_by_performance() -> ), ok. -weighted_random_strategy_test_parallel() -> +weighted_random_strategy_parallel_test() -> Nodes = [ #{ <<"host">> => <<"1">>, <<"weight">> => 1 }, @@ -1511,7 +1511,7 @@ weighted_random_strategy_test_parallel() -> ?assert(ProportionOfFirstHost < 0.05), ?assert(ProportionOfFirstHost >= 0.0001). -shuffled_strategy_test_parallel() -> +shuffled_strategy_parallel_test() -> Opts = #{}, Nodes = [ @@ -1550,7 +1550,7 @@ shuffled_strategy_test_parallel() -> ) ). -range_limited_route_filtering_test_parallel() -> +range_limited_route_filtering_parallel_test() -> Opts = #{}, Nodes = [ #{ <<"id">> => 0, <<"max">> => 20 }, @@ -1603,7 +1603,7 @@ range_limited_route_filtering_test_parallel() -> lists:seq(1, 10) ). -strategy_suite_test_parallel_() -> +strategy_suite_parallel_test_() -> lists:map( fun(Strategy) -> {foreach, @@ -1628,7 +1628,7 @@ strategy_suite_test_parallel_() -> %% @doc Ensure that `By-Base' always chooses the same node for the same %% hashpath. -by_base_determinism_test_parallel() -> +by_base_determinism_parallel_test() -> FirstN = 5, Nodes = generate_nodes(5), HashPaths = generate_hashpaths(100), @@ -1670,7 +1670,7 @@ unique_nodes(Simulation) -> Simulation ). -route_template_message_matches_test_parallel() -> +route_template_message_matches_parallel_test() -> Routes = [ #{ <<"template">> => #{ <<"other-key">> => <<"other-value">> }, @@ -1703,7 +1703,7 @@ route_template_message_matches_test_parallel() -> ) ). -route_regex_matches_test_parallel() -> +route_regex_matches_parallel_test() -> Routes = [ #{ <<"template">> => <<"/.*/compute">>, @@ -1727,7 +1727,7 @@ route_regex_matches_test_parallel() -> route(#{ <<"path">> => <<"/a/b/c/bad-key">> }, #{ <<"routes">> => Routes }) ). -explicit_route_test_parallel() -> +explicit_route_parallel_test() -> Routes = [ #{ <<"template">> => <<"*">>, @@ -1762,7 +1762,7 @@ explicit_route_test_parallel() -> ) ). -device_call_from_singleton_test_parallel() -> +device_call_from_singleton_parallel_test() -> % Try with a real-world example, taken from a GET request to the router. NodeOpts = #{ <<"routes">> => Routes = [#{ <<"template">> => <<"/some/path">>, @@ -1777,7 +1777,7 @@ device_call_from_singleton_test_parallel() -> ). -get_routes_test_parallel() -> +get_routes_parallel_test() -> Node = hb_http_server:start_node( #{ <<"force-signed">> => false, @@ -1795,7 +1795,7 @@ get_routes_test_parallel() -> {ok, Recvd} = Res, ?assertMatch(<<"our_node">>, Recvd). -add_route_test_parallel() -> +add_route_parallel_test() -> Owner = ar_wallet:new(), Node = hb_http_server:start_node( #{ @@ -1833,7 +1833,7 @@ add_route_test_parallel() -> %% @doc Test that the `preprocess/3' function re-routes a request to remote %% peers via `~relay@1.0', according to the node's routing table. -request_hook_reroute_to_nearest_test_parallel() -> +request_hook_reroute_to_nearest_parallel_test() -> Peer1 = hb_http_server:start_node(#{ <<"priv-wallet">> => W1 = ar_wallet:new() }), Peer2 = hb_http_server:start_node(#{ <<"priv-wallet">> => W2 = ar_wallet:new() }), Address1 = hb_util:human_id(ar_wallet:to_address(W1)), @@ -1891,7 +1891,7 @@ request_hook_reroute_to_nearest_test_parallel() -> ), ?assert(HasValidSigner). -route_nearest_integer_preserves_opts_test_parallel() -> +route_nearest_integer_preserves_opts_parallel_test() -> Routes = [ #{ @@ -1958,7 +1958,7 @@ route_nearest_integer_preserves_opts_test_parallel() -> SelectedURIs ). -route_multirequest_parallel_limit_test_parallel_() -> +route_multirequest_parallel_limit_parallel_test_() -> {timeout, 30, fun route_multirequest_parallel_limit/0}. route_multirequest_parallel_limit() -> DelayMs = 300, @@ -2035,7 +2035,7 @@ route_multirequest_parallel_limit() -> %% typical config.json) resolves every request type correctly: single-node %% prefix routes, multi-node All-strategy routes, Nearest-Integer chunk %% routes, match/with regex routes, and fallback routes. -full_route_config_test_parallel() -> +full_route_config_parallel_test() -> Routes = [ #{ diff --git a/src/dev_scheduler.erl b/src/dev_scheduler.erl index 638c8101c..d748454a3 100644 --- a/src/dev_scheduler.erl +++ b/src/dev_scheduler.erl @@ -1450,7 +1450,7 @@ test_process(Address) -> <<"test-random-seed">> => rand:uniform(1337) }. -status_test_parallel() -> +status_parallel_test() -> start(), ?assertMatch( #{<<"processes">> := Processes, @@ -1459,7 +1459,7 @@ status_test_parallel() -> hb_ao:get(status, test_process()) ). -register_new_process_test_parallel() -> +register_new_process_parallel_test() -> start(), Opts = #{ <<"priv-wallet">> => hb:wallet() }, Base = hb_message:commit(test_process(Opts), Opts), @@ -1485,7 +1485,7 @@ register_new_process_test_parallel() -> ) ). -schedule_message_and_get_slot_test_parallel() -> +schedule_message_and_get_slot_parallel_test() -> start(), Base = hb_message:commit(test_process(), #{ <<"priv-wallet">> => hb:wallet() }), Req = #{ @@ -1510,7 +1510,7 @@ schedule_message_and_get_slot_test_parallel() -> when CurrentSlot > 0, hb_ao:resolve(Base, Res, #{})). -redirect_to_hint_test_parallel() -> +redirect_to_hint_parallel_test() -> start(), RandAddr = hb_util:human_id(crypto:strong_rand_bytes(32)), TestLoc = <<"http://test.computer">>, @@ -1536,7 +1536,7 @@ redirect_to_hint_test_parallel() -> ) ). -redirect_from_graphql_test_parallel_() -> +redirect_from_graphql_parallel_test_() -> {timeout, 60, fun redirect_from_graphql/0}. redirect_from_graphql() -> start(), @@ -1571,7 +1571,7 @@ redirect_from_graphql() -> ) ). -get_local_schedule_test_parallel() -> +get_local_schedule_parallel_test() -> start(), Base = hb_message:commit(test_process(), #{ <<"priv-wallet">> => hb:wallet() }), Req = #{ @@ -1664,7 +1664,7 @@ http_get_schedule(N, PMsg, From, To, Format) -> <<"accept">> => Format }, #{ <<"priv-wallet">> => Wallet }), #{}). -http_get_schedule_redirect_test_parallel_() -> +http_get_schedule_redirect_parallel_test_() -> {timeout, 60, fun http_get_schedule_redirect/0}. http_get_schedule_redirect() -> Opts = @@ -1682,7 +1682,7 @@ http_get_schedule_redirect() -> Res = hb_http:get(N, <<"/", ProcID/binary, "/schedule">>, Opts), ?assertMatch({ok, #{ <<"location">> := Location }} when is_binary(Location), Res). -http_post_schedule_test_parallel_() -> +http_post_schedule_parallel_test_() -> {timeout, 60, fun http_post_schedule/0}. http_post_schedule() -> start(), @@ -1704,7 +1704,7 @@ http_post_schedule() -> ?assertEqual(<<"test-message">>, hb_ao:get(<<"body/inner">>, Res2, Opts)), ?assertMatch({ok, #{ <<"current">> := 1 }}, http_get_slot(N, PMsg)). -http_get_schedule_test_parallel_() -> +http_get_schedule_parallel_test_() -> {timeout, 20, fun() -> {Node, Opts} = http_init(), PMsg = hb_message:commit(test_process(Opts), Opts), @@ -1747,7 +1747,7 @@ http_get_schedule_test_parallel_() -> end}. -http_get_legacy_schedule_test_parallel_() -> +http_get_legacy_schedule_parallel_test_() -> {timeout, 60, fun() -> Target = <<"hGLuIZscb7b_2UBnDE_WoyIJF0sH6BU9u4veyEqE8g4">>, {Node, Opts} = http_init(), @@ -1757,7 +1757,7 @@ http_get_legacy_schedule_test_parallel_() -> ?assertMatch(#{ <<"assignments">> := As } when map_size(As) > 0, LoadedRes) end}. -http_get_legacy_slot_test_parallel_() -> +http_get_legacy_slot_parallel_test_() -> {timeout, 60, fun() -> Target = <<"hGLuIZscb7b_2UBnDE_WoyIJF0sH6BU9u4veyEqE8g4">>, {Node, Opts} = http_init(), @@ -1765,7 +1765,7 @@ http_get_legacy_slot_test_parallel_() -> ?assertMatch({ok, #{ <<"current">> := Slot }} when Slot > 0, Res) end}. -http_get_legacy_schedule_slot_range_test_parallel_() -> +http_get_legacy_schedule_slot_range_parallel_test_() -> {timeout, 60, fun() -> Target = <<"hGLuIZscb7b_2UBnDE_WoyIJF0sH6BU9u4veyEqE8g4">>, {Node, Opts} = http_init(), @@ -1777,7 +1777,7 @@ http_get_legacy_schedule_slot_range_test_parallel_() -> ?assertMatch(#{ <<"assignments">> := As } when map_size(As) == 5, LoadedRes) end}. -http_get_legacy_schedule_as_aos2_test_parallel_() -> +http_get_legacy_schedule_as_aos2_parallel_test_() -> {timeout, 60, fun() -> Target = <<"hGLuIZscb7b_2UBnDE_WoyIJF0sH6BU9u4veyEqE8g4">>, {Node, Opts} = http_init(), @@ -1827,7 +1827,7 @@ http_post_legacy_schedule_test_disabled() -> ) end}. -http_get_json_schedule_test_parallel_() -> +http_get_json_schedule_parallel_test_() -> {timeout, 60, fun() -> {Node, Opts} = http_init(), PMsg = hb_message:commit(test_process(Opts), Opts), @@ -1936,7 +1936,7 @@ many_clients(Opts) -> ?event(bench, {res, Res}), ?assert(Iterations > 10). -benchmark_suite_test_parallel_() -> +benchmark_suite_parallel_test_() -> {timeout, 10, fun() -> Bench = [ {benchmark, "benchmark", fun single_resolution/1}, diff --git a/src/hb_test_parallel.erl b/src/hb_test_parallel.erl index 3bf74f8fd..abf120449 100644 --- a/src/hb_test_parallel.erl +++ b/src/hb_test_parallel.erl @@ -1,16 +1,17 @@ %%% @doc A tiny parse_transform plus runtime helper that lets EUnit modules %%% opt in to parallel test execution by name. %%% -%%% Any 0-arity function whose name ends in `_test_parallel' or -%%% `_test_parallel_' is treated as a parallel test: the transform -%%% auto-exports it and -- when the module does not already define one -- -%%% injects an `all_parallel_test_/0' generator that runs all such -%%% functions in a single `{inparallel, ...}' EUnit batch. +%%% Any 0-arity function whose name ends in `_parallel_test' or +%%% `_parallel_test_' is treated as a parallel test: the transform +%%% renames it to an internal implementation name, auto-exports it, +%%% and -- when the module does not already define one -- injects an +%%% `all_parallel_test_/0' generator that runs all such functions in +%%% a single `{inparallel, ...}' EUnit batch. %%% -%%% Because the `_test_parallel' suffix does not match EUnit's own -%%% `_test'/`_test_' auto-discovery, the original function names are not -%%% renamed. The name you write is the name that gets compiled, and every -%%% test runs exactly once. +%%% The internal rename (e.g. `foo_parallel_test' -> `foo_par_impl') +%%% prevents EUnit's own autoexport transform from treating the +%%% functions as individual sequential tests while the parallel +%%% generator still runs them all concurrently. %%% %%% Activation is by including `hb.hrl', which wires the transform in %%% under `-ifdef(TEST)'. Example: @@ -18,26 +19,27 @@ %%% ``` %%% -include("include/hb.hrl"). %%% -%%% foo_test_parallel() -> ?assertEqual(1, 1). -%%% bar_test_parallel_() -> {timeout, 30, fun() -> ?assert(true) end}. +%%% foo_parallel_test() -> ?assertEqual(1, 1). +%%% bar_parallel_test_() -> {timeout, 30, fun() -> ?assert(true) end}. %%% ''' %%% %%% That is the whole contract. No manual exports, no hand-written -%%% generator, and nothing renamed. +%%% generator, and nothing renamed by the developer. -module(hb_test_parallel). -export([parse_transform/2, all/1]). --define(SIMPLE_SUFFIX, "_test_parallel"). --define(GENERATOR_SUFFIX, "_test_parallel_"). +-define(SIMPLE_SUFFIX, "_parallel_test"). +-define(GENERATOR_SUFFIX, "_parallel_test_"). +-define(IMPL_SUFFIX, "_par_impl"). +-define(GEN_IMPL_SUFFIX, "_par_gen"). -define(GENERATOR_NAME, all_parallel_test_). -%% @doc Runtime helper invoked by the injected `all_parallel_test_/0' -%% generator. Returns an `{inparallel, [...]}' EUnit test spec covering -%% every `_test_parallel[_]/0' function exported by `Module'. +%% @doc Runtime helper invoked for REPL debugging. Returns an +%% `{inparallel, [...]}' EUnit test spec covering every renamed +%% implementation function exported by `Module'. %% -%% Safe to call from a REPL (`hb_test_parallel:all(dev_name).') to inspect -%% what the generator will run, which is the primary debugging hook if a -%% test unexpectedly does or does not appear in the parallel batch. +%% Safe to call from a REPL (`hb_test_parallel:all(dev_name).') to +%% inspect what the generator will run. all(Module) -> Funs = lists:sort( @@ -45,7 +47,7 @@ all(Module) -> F || {F, 0} <- Module:module_info(exports), - is_parallel_test_name(F) + is_impl_name(F) ] ), {inparallel, @@ -60,21 +62,30 @@ all(Module) -> %% @doc Invoked by the Erlang compiler when a module is compiled with %% `-compile({parse_transform, hb_test_parallel}).'. Scans the module's -%% abstract forms, adds any missing exports for `_test_parallel[_]/0' -%% functions, and injects `all_parallel_test_/0' when the module does -%% not supply its own. +%% abstract forms, renames `_parallel_test[_]/0' functions to internal +%% impl names, exports the impl functions plus the generator, and +%% injects `all_parallel_test_/0' when the module does not supply its own. parse_transform(Forms, _Options) -> {Matching, HasGenerator} = scan(Forms), case Matching of [] -> - %% No parallel tests in this module; leave the forms alone. Forms; _ -> - Exports = exports_to_inject(Matching, HasGenerator), - Forms1 = inject_exports(Forms, Exports), + RenameMap = build_rename_map(Matching), + Forms1 = rename_functions(Forms, RenameMap), + Forms1b = update_existing_exports(Forms1, RenameMap), + AlreadyExported = already_exported(Forms1b), + ImplNames = [ImplName || {_, ImplName} <- maps:to_list(RenameMap)], + ImplExports = [{N, 0} || N <- ImplNames, not sets:is_element(N, AlreadyExported)], + GenExports = + case HasGenerator of + true -> []; + false -> [{?GENERATOR_NAME, 0}] + end, + Forms2 = inject_exports(Forms1b, ImplExports ++ GenExports), case HasGenerator of - true -> Forms1; - false -> inject_generator(Forms1) + true -> Forms2; + false -> inject_generator(Forms2, RenameMap) end end. @@ -101,26 +112,74 @@ scan(Forms) -> Forms ). -%% @doc True when `Name' ends in `_test_parallel' or `_test_parallel_'. +%% @doc True when `Name' ends in `_parallel_test' or `_parallel_test_'. is_parallel_test_name(Name) -> Str = atom_to_list(Name), lists:suffix(?SIMPLE_SUFFIX, Str) orelse lists:suffix(?GENERATOR_SUFFIX, Str). -%% @doc Build the list of `{Name, 0}' entries the transform needs to add -%% to the module's export table: every matching test, plus the generator -%% when the transform is going to inject one. -exports_to_inject(Matching, HasGenerator) -> - BaseExports = [{F, 0} || F <- Matching], - case HasGenerator of - true -> BaseExports; - false -> [{?GENERATOR_NAME, 0} | BaseExports] +%% @doc True when `Name' is an internal impl name produced by this transform. +is_impl_name(Name) -> + Str = atom_to_list(Name), + lists:suffix(?IMPL_SUFFIX, Str) orelse lists:suffix(?GEN_IMPL_SUFFIX, Str). + +%% @doc Build a map from original function name to its renamed impl name. +build_rename_map(Names) -> + maps:from_list([{N, impl_name(N)} || N <- Names]). + +%% @doc Derive the internal impl name by replacing the parallel-test +%% suffix with a non-`_test'-ending suffix so EUnit autoexport ignores it. +impl_name(Name) -> + Str = atom_to_list(Name), + case lists:suffix(?GENERATOR_SUFFIX, Str) of + true -> + Prefix = lists:sublist(Str, length(Str) - length(?GENERATOR_SUFFIX)), + list_to_atom(Prefix ++ ?GEN_IMPL_SUFFIX); + false -> + Prefix = lists:sublist(Str, length(Str) - length(?SIMPLE_SUFFIX)), + list_to_atom(Prefix ++ ?IMPL_SUFFIX) end. +%% @doc Return a set of function names already present in export attributes. +already_exported(Forms) -> + sets:from_list( + [ + Name + || + {attribute, _, export, Exports} <- Forms, + {Name, _Arity} <- Exports + ] + ). + +%% @doc Update any existing `-export' attributes to reflect renamed functions. +%% This handles the case where `eunit_autoexport' runs before this transform +%% (because `eunit.hrl' is included before `hb.hrl') and has already added +%% export entries for the original `_parallel_test' names. +update_existing_exports(Forms, RenameMap) -> + [update_exports_form(Form, RenameMap) || Form <- Forms]. + +update_exports_form({attribute, Line, export, Exports}, RenameMap) -> + NewExports = [{maps:get(Name, RenameMap, Name), Arity} || {Name, Arity} <- Exports], + {attribute, Line, export, NewExports}; +update_exports_form(Form, _RenameMap) -> + Form. + +%% @doc Rename matching function definitions in the abstract forms. +rename_functions(Forms, RenameMap) -> + [rename_form(Form, RenameMap) || Form <- Forms]. + +rename_form({function, Line, Name, 0, Clauses}, RenameMap) -> + case maps:find(Name, RenameMap) of + {ok, ImplName} -> {function, Line, ImplName, 0, Clauses}; + error -> {function, Line, Name, 0, Clauses} + end; +rename_form(Form, _RenameMap) -> + Form. + %% @doc Insert a single `-export([...])' attribute just before the first -%% function definition in `Forms'. The position does not matter for -%% correctness, but sitting next to the function body makes the injected -%% attribute easy to find in compiler error messages. +%% function definition in `Forms'. +inject_exports(Forms, []) -> + Forms; inject_exports(Forms, Exports) -> inject_exports(Forms, Exports, []). @@ -134,42 +193,39 @@ inject_exports( inject_exports([Form | Rest], Exports, Seen) -> inject_exports(Rest, Exports, [Form | Seen]); inject_exports([], _Exports, Seen) -> - %% No function definitions in the module; nothing useful to inject - %% against. Return the forms unchanged. lists:reverse(Seen). -%% @doc Inject the stub -%% -%% ``` -%% all_parallel_test_() -> hb_test_parallel:all(?MODULE). -%% ''' -%% -%% just before the module's `eof' marker. The body is a single remote -%% call; all of the discovery logic lives in `all/1' so that it stays -%% debuggable at runtime. -inject_generator(Forms) -> +%% @doc Inject the `all_parallel_test_/0' generator with compile-time +%% local fun references to every renamed impl function. +inject_generator(Forms, RenameMap) -> {Before, [Eof]} = lists:split(length(Forms) - 1, Forms), Line = case Eof of {eof, L} -> L; _ -> 1 end, - Before ++ [generator_form(Line, module_of(Forms)), Eof]. - -%% @doc Extract the module name from a list of abstract forms. -module_of(Forms) -> - hd([M || {attribute, _, module, M} <- Forms]). - -%% @doc Build the abstract form for -%% `all_parallel_test_() -> hb_test_parallel:all(Module).'. -generator_form(Line, Module) -> - Call = - {call, Line, - {remote, Line, - {atom, Line, ?MODULE}, - {atom, Line, all} - }, - [{atom, Line, Module}] - }, - Clause = {clause, Line, [], [], [Call]}, + Before ++ [generator_form(Line, RenameMap), Eof]. + +%% @doc Build the abstract form for `all_parallel_test_/0', which returns +%% `{inparallel, [{"OrigName", fun ImplName/0}, ...]}' with local funs. +generator_form(Line, RenameMap) -> + TestList = + lists:sort( + [{atom_to_list(Orig), Impl} || {Orig, Impl} <- maps:to_list(RenameMap)] + ), + TestTuples = [make_test_tuple(Line, Label, Impl) || {Label, Impl} <- TestList], + ListExpr = make_list(Line, TestTuples), + InParallel = {tuple, Line, [{atom, Line, inparallel}, ListExpr]}, + Clause = {clause, Line, [], [], [InParallel]}, {function, Line, ?GENERATOR_NAME, 0, [Clause]}. + +make_test_tuple(Line, Label, Impl) -> + {tuple, Line, [ + {string, Line, Label}, + {'fun', Line, {function, Impl, 0}} + ]}. + +make_list(Line, []) -> + {nil, Line}; +make_list(Line, [H | T]) -> + {cons, Line, H, make_list(Line, T)}. diff --git a/src/include/hb.hrl b/src/include/hb.hrl index 147f4af9a..034311fb2 100644 --- a/src/include/hb.hrl +++ b/src/include/hb.hrl @@ -52,8 +52,9 @@ %%% Parallel-test auto-wiring. In a test build, every module that includes %%% `hb.hrl' has `hb_test_parallel' activated: 0-arity functions whose -%%% names end in `_test_parallel' or `_test_parallel_' are exported and -%%% collected into an injected `all_parallel_test_/0' EUnit generator. +%%% names end in `_parallel_test' or `_parallel_test_' are renamed to +%%% internal impl names and collected into an injected `all_parallel_test_/0' +%%% EUnit generator that runs them all concurrently. %%% See `hb_test_parallel' for the contract. -ifdef(TEST). -compile({parse_transform, hb_test_parallel}). From 966fb211909679d95145d82f14c89055c260cfa2 Mon Sep 17 00:00:00 2001 From: speeddragon Date: Fri, 1 May 2026 17:30:12 +0100 Subject: [PATCH 2/3] impr: Revert AI comment/structure changes --- src/hb_test_parallel.erl | 189 ++++++++++++++------------------------- 1 file changed, 69 insertions(+), 120 deletions(-) diff --git a/src/hb_test_parallel.erl b/src/hb_test_parallel.erl index abf120449..c5eb921d3 100644 --- a/src/hb_test_parallel.erl +++ b/src/hb_test_parallel.erl @@ -3,15 +3,14 @@ %%% %%% Any 0-arity function whose name ends in `_parallel_test' or %%% `_parallel_test_' is treated as a parallel test: the transform -%%% renames it to an internal implementation name, auto-exports it, -%%% and -- when the module does not already define one -- injects an -%%% `all_parallel_test_/0' generator that runs all such functions in -%%% a single `{inparallel, ...}' EUnit batch. +%%% auto-exports it and -- when the module does not already define one -- +%%% injects an `all_parallel_test_/0' generator that runs all such +%%% functions in a single `{inparallel, ...}' EUnit batch. %%% -%%% The internal rename (e.g. `foo_parallel_test' -> `foo_par_impl') -%%% prevents EUnit's own autoexport transform from treating the -%%% functions as individual sequential tests while the parallel -%%% generator still runs them all concurrently. +%%% Because the `_parallel_test' suffix ends in `_test', EUnit's own +%%% autoexport transform also discovers these functions. The injected +%%% `all_parallel_test_/0' generator supersedes the individual sequential +%%% runs by collecting them into a single parallel batch. %%% %%% Activation is by including `hb.hrl', which wires the transform in %%% under `-ifdef(TEST)'. Example: @@ -24,22 +23,21 @@ %%% ''' %%% %%% That is the whole contract. No manual exports, no hand-written -%%% generator, and nothing renamed by the developer. +%%% generator, and nothing renamed. -module(hb_test_parallel). -export([parse_transform/2, all/1]). -define(SIMPLE_SUFFIX, "_parallel_test"). -define(GENERATOR_SUFFIX, "_parallel_test_"). --define(IMPL_SUFFIX, "_par_impl"). --define(GEN_IMPL_SUFFIX, "_par_gen"). -define(GENERATOR_NAME, all_parallel_test_). -%% @doc Runtime helper invoked for REPL debugging. Returns an -%% `{inparallel, [...]}' EUnit test spec covering every renamed -%% implementation function exported by `Module'. +%% @doc Runtime helper invoked by the injected `all_parallel_test_/0' +%% generator. Returns an `{inparallel, [...]}' EUnit test spec covering +%% every `_parallel_test[_]/0' function exported by `Module'. %% -%% Safe to call from a REPL (`hb_test_parallel:all(dev_name).') to -%% inspect what the generator will run. +%% Safe to call from a REPL (`hb_test_parallel:all(dev_name).') to inspect +%% what the generator will run, which is the primary debugging hook if a +%% test unexpectedly does or does not appear in the parallel batch. all(Module) -> Funs = lists:sort( @@ -47,7 +45,7 @@ all(Module) -> F || {F, 0} <- Module:module_info(exports), - is_impl_name(F) + is_parallel_test_name(F) ] ), {inparallel, @@ -62,30 +60,26 @@ all(Module) -> %% @doc Invoked by the Erlang compiler when a module is compiled with %% `-compile({parse_transform, hb_test_parallel}).'. Scans the module's -%% abstract forms, renames `_parallel_test[_]/0' functions to internal -%% impl names, exports the impl functions plus the generator, and -%% injects `all_parallel_test_/0' when the module does not supply its own. +%% abstract forms, adds any missing exports for `_parallel_test[_]/0' +%% functions, and injects `all_parallel_test_/0' when the module does +%% not supply its own. parse_transform(Forms, _Options) -> {Matching, HasGenerator} = scan(Forms), case Matching of [] -> + %% No parallel tests in this module; leave the forms alone. Forms; _ -> - RenameMap = build_rename_map(Matching), - Forms1 = rename_functions(Forms, RenameMap), - Forms1b = update_existing_exports(Forms1, RenameMap), - AlreadyExported = already_exported(Forms1b), - ImplNames = [ImplName || {_, ImplName} <- maps:to_list(RenameMap)], - ImplExports = [{N, 0} || N <- ImplNames, not sets:is_element(N, AlreadyExported)], - GenExports = - case HasGenerator of - true -> []; - false -> [{?GENERATOR_NAME, 0}] - end, - Forms2 = inject_exports(Forms1b, ImplExports ++ GenExports), + %% Because `_parallel_test' ends in `_test', `eunit_autoexport' + %% may have already exported these functions (e.g. when + %% `eunit.hrl' is included before `hb.hrl'). Skip any that are + %% already exported to avoid "already exported" warnings. + AlreadyExported = sets:from_list([N || {attribute, _, export, E} <- Forms, {N, _} <- E]), + Exports = exports_to_inject([F || F <- Matching, not sets:is_element(F, AlreadyExported)], HasGenerator), + Forms1 = inject_exports(Forms, Exports), case HasGenerator of - true -> Forms2; - false -> inject_generator(Forms2, RenameMap) + true -> Forms1; + false -> inject_generator(Forms1) end end. @@ -118,68 +112,20 @@ is_parallel_test_name(Name) -> lists:suffix(?SIMPLE_SUFFIX, Str) orelse lists:suffix(?GENERATOR_SUFFIX, Str). -%% @doc True when `Name' is an internal impl name produced by this transform. -is_impl_name(Name) -> - Str = atom_to_list(Name), - lists:suffix(?IMPL_SUFFIX, Str) orelse lists:suffix(?GEN_IMPL_SUFFIX, Str). - -%% @doc Build a map from original function name to its renamed impl name. -build_rename_map(Names) -> - maps:from_list([{N, impl_name(N)} || N <- Names]). - -%% @doc Derive the internal impl name by replacing the parallel-test -%% suffix with a non-`_test'-ending suffix so EUnit autoexport ignores it. -impl_name(Name) -> - Str = atom_to_list(Name), - case lists:suffix(?GENERATOR_SUFFIX, Str) of - true -> - Prefix = lists:sublist(Str, length(Str) - length(?GENERATOR_SUFFIX)), - list_to_atom(Prefix ++ ?GEN_IMPL_SUFFIX); - false -> - Prefix = lists:sublist(Str, length(Str) - length(?SIMPLE_SUFFIX)), - list_to_atom(Prefix ++ ?IMPL_SUFFIX) +%% @doc Build the list of `{Name, 0}' entries the transform needs to add +%% to the module's export table: every matching test, plus the generator +%% when the transform is going to inject one. +exports_to_inject(Matching, HasGenerator) -> + BaseExports = [{F, 0} || F <- Matching], + case HasGenerator of + true -> BaseExports; + false -> [{?GENERATOR_NAME, 0} | BaseExports] end. -%% @doc Return a set of function names already present in export attributes. -already_exported(Forms) -> - sets:from_list( - [ - Name - || - {attribute, _, export, Exports} <- Forms, - {Name, _Arity} <- Exports - ] - ). - -%% @doc Update any existing `-export' attributes to reflect renamed functions. -%% This handles the case where `eunit_autoexport' runs before this transform -%% (because `eunit.hrl' is included before `hb.hrl') and has already added -%% export entries for the original `_parallel_test' names. -update_existing_exports(Forms, RenameMap) -> - [update_exports_form(Form, RenameMap) || Form <- Forms]. - -update_exports_form({attribute, Line, export, Exports}, RenameMap) -> - NewExports = [{maps:get(Name, RenameMap, Name), Arity} || {Name, Arity} <- Exports], - {attribute, Line, export, NewExports}; -update_exports_form(Form, _RenameMap) -> - Form. - -%% @doc Rename matching function definitions in the abstract forms. -rename_functions(Forms, RenameMap) -> - [rename_form(Form, RenameMap) || Form <- Forms]. - -rename_form({function, Line, Name, 0, Clauses}, RenameMap) -> - case maps:find(Name, RenameMap) of - {ok, ImplName} -> {function, Line, ImplName, 0, Clauses}; - error -> {function, Line, Name, 0, Clauses} - end; -rename_form(Form, _RenameMap) -> - Form. - %% @doc Insert a single `-export([...])' attribute just before the first -%% function definition in `Forms'. -inject_exports(Forms, []) -> - Forms; +%% function definition in `Forms'. The position does not matter for +%% correctness, but sitting next to the function body makes the injected +%% attribute easy to find in compiler error messages. inject_exports(Forms, Exports) -> inject_exports(Forms, Exports, []). @@ -193,39 +139,42 @@ inject_exports( inject_exports([Form | Rest], Exports, Seen) -> inject_exports(Rest, Exports, [Form | Seen]); inject_exports([], _Exports, Seen) -> + %% No function definitions in the module; nothing useful to inject + %% against. Return the forms unchanged. lists:reverse(Seen). -%% @doc Inject the `all_parallel_test_/0' generator with compile-time -%% local fun references to every renamed impl function. -inject_generator(Forms, RenameMap) -> +%% @doc Inject the stub +%% +%% ``` +%% all_parallel_test_() -> hb_test_parallel:all(?MODULE). +%% ''' +%% +%% just before the module's `eof' marker. The body is a single remote +%% call; all of the discovery logic lives in `all/1' so that it stays +%% debuggable at runtime. +inject_generator(Forms) -> {Before, [Eof]} = lists:split(length(Forms) - 1, Forms), Line = case Eof of {eof, L} -> L; _ -> 1 end, - Before ++ [generator_form(Line, RenameMap), Eof]. - -%% @doc Build the abstract form for `all_parallel_test_/0', which returns -%% `{inparallel, [{"OrigName", fun ImplName/0}, ...]}' with local funs. -generator_form(Line, RenameMap) -> - TestList = - lists:sort( - [{atom_to_list(Orig), Impl} || {Orig, Impl} <- maps:to_list(RenameMap)] - ), - TestTuples = [make_test_tuple(Line, Label, Impl) || {Label, Impl} <- TestList], - ListExpr = make_list(Line, TestTuples), - InParallel = {tuple, Line, [{atom, Line, inparallel}, ListExpr]}, - Clause = {clause, Line, [], [], [InParallel]}, + Before ++ [generator_form(Line, module_of(Forms)), Eof]. + +%% @doc Extract the module name from a list of abstract forms. +module_of(Forms) -> + hd([M || {attribute, _, module, M} <- Forms]). + +%% @doc Build the abstract form for +%% `all_parallel_test_() -> hb_test_parallel:all(Module).'. +generator_form(Line, Module) -> + Call = + {call, Line, + {remote, Line, + {atom, Line, ?MODULE}, + {atom, Line, all} + }, + [{atom, Line, Module}] + }, + Clause = {clause, Line, [], [], [Call]}, {function, Line, ?GENERATOR_NAME, 0, [Clause]}. - -make_test_tuple(Line, Label, Impl) -> - {tuple, Line, [ - {string, Line, Label}, - {'fun', Line, {function, Impl, 0}} - ]}. - -make_list(Line, []) -> - {nil, Line}; -make_list(Line, [H | T]) -> - {cons, Line, H, make_list(Line, T)}. From 2b1eab5fb7438c3bef96e8130c0c107d8638e4f6 Mon Sep 17 00:00:00 2001 From: speeddragon Date: Fri, 1 May 2026 23:23:47 +0100 Subject: [PATCH 3/3] impr: Fix double test execution --- src/hb_test_parallel.erl | 108 ++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/src/hb_test_parallel.erl b/src/hb_test_parallel.erl index c5eb921d3..01266cc36 100644 --- a/src/hb_test_parallel.erl +++ b/src/hb_test_parallel.erl @@ -2,15 +2,17 @@ %%% opt in to parallel test execution by name. %%% %%% Any 0-arity function whose name ends in `_parallel_test' or -%%% `_parallel_test_' is treated as a parallel test: the transform -%%% auto-exports it and -- when the module does not already define one -- -%%% injects an `all_parallel_test_/0' generator that runs all such -%%% functions in a single `{inparallel, ...}' EUnit batch. +%%% `_parallel_test_' is treated as a parallel test: the transform renames +%%% it to an internal name (e.g. `foo_par_impl'), auto-exports it, and -- +%%% when the module does not already define one -- injects an +%%% `all_parallel_test_/0' generator that runs all such functions in a +%%% single `{inparallel, ...}' EUnit batch. %%% -%%% Because the `_parallel_test' suffix ends in `_test', EUnit's own -%%% autoexport transform also discovers these functions. The injected -%%% `all_parallel_test_/0' generator supersedes the individual sequential -%%% runs by collecting them into a single parallel batch. +%%% The rename is necessary because `_parallel_test' ends in `_test', which +%%% causes EUnit's own autoexport transform to also discover these functions +%%% as individual sequential tests -- resulting in every test running twice. +%%% Renaming to a non-`_test' suffix prevents individual discovery while +%%% keeping the parallel batch intact. %%% %%% Activation is by including `hb.hrl', which wires the transform in %%% under `-ifdef(TEST)'. Example: @@ -23,17 +25,19 @@ %%% ''' %%% %%% That is the whole contract. No manual exports, no hand-written -%%% generator, and nothing renamed. +%%% generator, and nothing renamed by the developer. -module(hb_test_parallel). -export([parse_transform/2, all/1]). -define(SIMPLE_SUFFIX, "_parallel_test"). -define(GENERATOR_SUFFIX, "_parallel_test_"). +-define(IMPL_SUFFIX, "_par_impl"). +-define(GEN_IMPL_SUFFIX, "_par_gen"). -define(GENERATOR_NAME, all_parallel_test_). %% @doc Runtime helper invoked by the injected `all_parallel_test_/0' %% generator. Returns an `{inparallel, [...]}' EUnit test spec covering -%% every `_parallel_test[_]/0' function exported by `Module'. +%% every renamed implementation function exported by `Module'. %% %% Safe to call from a REPL (`hb_test_parallel:all(dev_name).') to inspect %% what the generator will run, which is the primary debugging hook if a @@ -45,12 +49,12 @@ all(Module) -> F || {F, 0} <- Module:module_info(exports), - is_parallel_test_name(F) + is_impl_name(F) ] ), {inparallel, [ - {atom_to_list(F), fun Module:F/0} + {original_name(F), fun Module:F/0} || F <- Funs ] @@ -60,9 +64,9 @@ all(Module) -> %% @doc Invoked by the Erlang compiler when a module is compiled with %% `-compile({parse_transform, hb_test_parallel}).'. Scans the module's -%% abstract forms, adds any missing exports for `_parallel_test[_]/0' -%% functions, and injects `all_parallel_test_/0' when the module does -%% not supply its own. +%% abstract forms, renames `_parallel_test[_]/0' functions to internal +%% impl names, exports them, and injects `all_parallel_test_/0' when the +%% module does not supply its own. parse_transform(Forms, _Options) -> {Matching, HasGenerator} = scan(Forms), case Matching of @@ -70,16 +74,22 @@ parse_transform(Forms, _Options) -> %% No parallel tests in this module; leave the forms alone. Forms; _ -> - %% Because `_parallel_test' ends in `_test', `eunit_autoexport' - %% may have already exported these functions (e.g. when - %% `eunit.hrl' is included before `hb.hrl'). Skip any that are - %% already exported to avoid "already exported" warnings. - AlreadyExported = sets:from_list([N || {attribute, _, export, E} <- Forms, {N, _} <- E]), - Exports = exports_to_inject([F || F <- Matching, not sets:is_element(F, AlreadyExported)], HasGenerator), - Forms1 = inject_exports(Forms, Exports), + RenameMap = maps:from_list([{F, impl_name(F)} || F <- Matching]), + Forms1 = rename_functions(Forms, RenameMap), + %% If `eunit_autoexport' ran first (i.e. `eunit.hrl' included + %% before `hb.hrl'), it will have exported the original names. + %% Update those entries to the new impl names so the compiler + %% does not see a reference to a now-nonexistent function. + Forms2 = update_existing_exports(Forms1, RenameMap), + ImplNames = maps:values(RenameMap), + %% Skip impl names already present to avoid "already exported" + %% warnings when `update_existing_exports' already covered them. + AlreadyExported = sets:from_list([N || {attribute, _, export, E} <- Forms2, {N, _} <- E]), + Exports = exports_to_inject([F || F <- ImplNames, not sets:is_element(F, AlreadyExported)], HasGenerator), + Forms3 = inject_exports(Forms2, Exports), case HasGenerator of - true -> Forms1; - false -> inject_generator(Forms1) + true -> Forms3; + false -> inject_generator(Forms3) end end. @@ -112,6 +122,56 @@ is_parallel_test_name(Name) -> lists:suffix(?SIMPLE_SUFFIX, Str) orelse lists:suffix(?GENERATOR_SUFFIX, Str). +%% @doc True when `Name' is an internal impl name produced by this transform. +is_impl_name(Name) -> + Str = atom_to_list(Name), + lists:suffix(?IMPL_SUFFIX, Str) orelse lists:suffix(?GEN_IMPL_SUFFIX, Str). + +%% @doc Derive the internal impl name by replacing the `_parallel_test[_]' +%% suffix with a non-`_test' suffix so EUnit's autoexport transform does +%% not discover these functions as individual tests. +impl_name(Name) -> + Str = atom_to_list(Name), + case lists:suffix(?GENERATOR_SUFFIX, Str) of + true -> + Prefix = lists:sublist(Str, length(Str) - length(?GENERATOR_SUFFIX)), + list_to_atom(Prefix ++ ?GEN_IMPL_SUFFIX); + false -> + Prefix = lists:sublist(Str, length(Str) - length(?SIMPLE_SUFFIX)), + list_to_atom(Prefix ++ ?IMPL_SUFFIX) + end. + +%% @doc Reconstruct the original `_parallel_test[_]' label from an impl name. +%% Used by `all/1' so that test output shows the human-readable original name. +original_name(ImplName) -> + Str = atom_to_list(ImplName), + case lists:suffix(?GEN_IMPL_SUFFIX, Str) of + true -> + Prefix = lists:sublist(Str, length(Str) - length(?GEN_IMPL_SUFFIX)), + Prefix ++ ?GENERATOR_SUFFIX; + false -> + Prefix = lists:sublist(Str, length(Str) - length(?IMPL_SUFFIX)), + Prefix ++ ?SIMPLE_SUFFIX + end. + +%% @doc Rename matching function definitions in the abstract forms. +rename_functions(Forms, RenameMap) -> + [case Form of + {function, Line, Name, 0, Clauses} -> + {function, Line, maps:get(Name, RenameMap, Name), 0, Clauses}; + _ -> + Form + end || Form <- Forms]. + +%% @doc Update existing `-export' attributes to use renamed impl names. +update_existing_exports(Forms, RenameMap) -> + [case Form of + {attribute, Line, export, Exports} -> + {attribute, Line, export, [{maps:get(N, RenameMap, N), A} || {N, A} <- Exports]}; + _ -> + Form + end || Form <- Forms]. + %% @doc Build the list of `{Name, 0}' entries the transform needs to add %% to the module's export table: every matching test, plus the generator %% when the transform is going to inject one.