diff --git a/governance/governance.md b/governance/governance.md index 12118e588..71a83e678 100644 --- a/governance/governance.md +++ b/governance/governance.md @@ -52,6 +52,47 @@ Asset id: 93da681503353509367e241cda3234299dedbbad9ec851de31e900490807bf0c **End** 3412904 +--- + +### ZAP5: SOLX (Wrapped Solana) + +**Description** This proposal asks the Zano community to vote on whether to officially add the following Confidential Asset (CAs) to the Zano asset whitelist: + +SOLX (Wrapped Solana) +Asset id: 65b3bc549c8bc2c773781d5436f25f7af84644e61baaabd675d9867b007d17b4 + + +**Start** 3492190 + +**End** 3512350 + +--- + +### ZAP6: TONX (Wrapped TON) + +**Description** This proposal asks the Zano community to vote on whether to officially add the following Confidential Asset (CAs) to the Zano asset whitelist: + +TONX (Wrapped TON) +Asset id: bfa6609a94e39f418d9adb000f89edc7bd180fd120f1cd272201220e3070fb4f + + +**Start** 3492190 + +**End** 3512350 + +--- + +### ZAP7: BCHX (Wrapped Bitcoin Cash) + +**Description** This proposal asks the Zano community to vote on whether to officially add the following Confidential Asset (CAs) to the Zano asset whitelist: + +BCHX (Wrapped Bitcoin Cash) +Asset id: 3de9ad7243afa49e0ade6839e97a9f10a527c4958ece2fc9cb1b87a44032167d + + +**Start** 3492190 + +**End** 3512350 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 33dbb9e93..ee24b4faa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,8 @@ add_executable(net_load_tests_srv net_load_tests/srv.cpp) add_dependencies(coretests version) +target_include_directories(coretests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/core_tests) + target_link_libraries(coretests rpc wallet currency_core common crypto zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(functional_tests rpc wallet currency_core crypto common zlibstatic ethash libminiupnpc-static ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(hash-tests crypto ethash) diff --git a/tests/core_tests/chaingen_args.cpp b/tests/core_tests/chaingen_args.cpp new file mode 100644 index 000000000..158149f46 --- /dev/null +++ b/tests/core_tests/chaingen_args.cpp @@ -0,0 +1,21 @@ +#include "chaingen.h" +#include "chaingen_args.h" + +namespace chaingen_args +{ + const char* WORKERS_REPORT_FILENAME = "workers_report.json"; + + command_line::arg_descriptor arg_test_data_path ("test-data-path", "", ""); + command_line::arg_descriptor arg_generate_test_data ("generate-test-data", ""); + command_line::arg_descriptor arg_play_test_data ("play-test-data", ""); + command_line::arg_descriptor arg_generate_and_play_test_data ("generate-and-play-test-data", ""); + command_line::arg_descriptor arg_test_transactions ("test-transactions", ""); + command_line::arg_descriptor arg_run_single_test ("run-single-test", " TEST_NAME -- name of a single test to run, HF -- specific hardfork id to run the test for" ); + command_line::arg_descriptor arg_run_multiple_tests ("run-multiple-tests", "comma-separated list of tests to run, OR text file <@filename> containing list of tests"); + command_line::arg_descriptor arg_enable_debug_asserts ("enable-debug-asserts", "" ); + command_line::arg_descriptor arg_stop_on_fail ("stop-on-fail", ""); + command_line::arg_descriptor arg_processes ("multiprocess-run", "Run tests in parallel using the specified number of worker processes", 1); + command_line::arg_descriptor arg_worker_id ("multiprocess-worker-id", "Internal: index of the worker process (assigned automatically by parent)", -1); + command_line::arg_descriptor arg_run_root ("multiprocess-run-root", "Internal: directory used by parent and workers to store run artifacts and reports", ""); + command_line::arg_descriptor arg_shm_name ("multiprocess-shm-name", "Internal: shared memory name used to report failed tests", ""); +} diff --git a/tests/core_tests/chaingen_args.h b/tests/core_tests/chaingen_args.h new file mode 100644 index 000000000..d6a1c6947 --- /dev/null +++ b/tests/core_tests/chaingen_args.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include "common/command_line.h" + +namespace chaingen_args +{ + extern const char* WORKERS_REPORT_FILENAME; + + extern command_line::arg_descriptor arg_test_data_path; + extern command_line::arg_descriptor arg_generate_test_data; + extern command_line::arg_descriptor arg_play_test_data; + extern command_line::arg_descriptor arg_generate_and_play_test_data; + extern command_line::arg_descriptor arg_test_transactions; + extern command_line::arg_descriptor arg_run_single_test; + extern command_line::arg_descriptor arg_run_multiple_tests; + extern command_line::arg_descriptor arg_enable_debug_asserts; + extern command_line::arg_descriptor arg_stop_on_fail; + extern command_line::arg_descriptor arg_processes; + extern command_line::arg_descriptor arg_worker_id; + extern command_line::arg_descriptor arg_run_root; + extern command_line::arg_descriptor arg_shm_name; +} diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 9316feb03..d61f46feb 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -6,7 +6,6 @@ #include "chaingen.h" #include "chaingen_tests_list.h" -#include "common/command_line.h" #include "transaction_tests.h" #include "../../src/wallet/core_fast_rpc_proxy.h" #include "test_core_proxy.h" @@ -14,25 +13,25 @@ #include "random_helper.h" #include "core_state_helper.h" #include "common/db_backend_selector.h" +#include "parallel/parallel_test_runner.h" +#include "chaingen_args.h" +#include #include "common/callstack_helper.h" #define TX_BLOBSIZE_CHECKER_LOG_FILENAME "get_object_blobsize(tx).log" +using namespace chaingen_args; + namespace po = boost::program_options; +namespace pt = boost::property_tree; namespace { - const command_line::arg_descriptor arg_test_data_path ("test-data-path", "", ""); - const command_line::arg_descriptor arg_generate_test_data ("generate-test-data", ""); - const command_line::arg_descriptor arg_play_test_data ("play-test-data", ""); - const command_line::arg_descriptor arg_generate_and_play_test_data ("generate-and-play-test-data", ""); - const command_line::arg_descriptor arg_test_transactions ("test-transactions", ""); - const command_line::arg_descriptor arg_run_single_test ("run-single-test", " TEST_NAME -- name of a single test to run, HF -- specific hardfork id to run the test for" ); - const command_line::arg_descriptor arg_run_multiple_tests ("run-multiple-tests", "comma-separated list of tests to run, OR text file <@filename> containing list of tests"); - const command_line::arg_descriptor arg_enable_debug_asserts ("enable-debug-asserts", "" ); - const command_line::arg_descriptor arg_stop_on_fail ("stop-on-fail", ""); - boost::program_options::variables_map g_vm; + std::unique_ptr g_runner; + std::vector g_test_jobs; + coretests_shm::shared_state* g_shm_state = nullptr; + std::unique_ptr g_shm_region; } #define GENERATE(filename, genclass) \ @@ -57,7 +56,6 @@ namespace return 1; \ } - std::vector parse_hardfork_str_mask(std::string s /* intentionally passing by value */) { // "*" -> 0, 1, 2, ..., ZANO_HARDFORKS_TOTAL-1 @@ -387,73 +385,82 @@ bool gen_and_play_intermitted_by_blockchain_saveload(const char* const genclass_ return r; } - #define GENERATE_AND_PLAY(genclass) \ - if (is_test_eligible_to_run(#genclass)) \ - { \ - TIME_MEASURE_START_MS(t); \ - ++tests_count; \ - ++unique_tests_count; \ - if (!generate_and_play(#genclass)) \ - { \ - failed_tests.insert(#genclass); \ - LOCAL_ASSERT(false); \ - if (stop_on_first_fail) \ - skip_all_till_the_end = true; \ - } \ - TIME_MEASURE_FINISH_MS(t); \ - tests_running_time.push_back(std::make_pair(#genclass, t)); \ - } + do { \ + const std::string __test_name = #genclass; \ + auto __is_test_eligible_to_run = is_test_eligible_to_run; \ + g_test_jobs.push_back(test_job{ \ + __test_name, \ + [&, __test_name, __is_test_eligible_to_run]() -> bool { \ + if (!__is_test_eligible_to_run(__test_name)) \ + return true; \ + return run_one_test_job( \ + __test_name, \ + [&]() -> bool { return generate_and_play(__test_name.c_str()); }, \ + stop_on_first_fail, \ + skip_all_till_the_end, \ + tests_count, \ + unique_tests_count, \ + failed_tests, \ + tests_running_time); \ + } \ + }); \ + } while (0) #define GENERATE_AND_PLAY_INTERMITTED_BY_BLOCKCHAIN_SAVELOAD(genclass) \ - if (is_test_eligible_to_run(#genclass)) \ - { \ - const char* testname = #genclass " (BC saveload)"; \ - TIME_MEASURE_START_MS(t); \ - ++tests_count; \ - ++unique_tests_count; \ - if (!gen_and_play_intermitted_by_blockchain_saveload(testname)) \ - { \ - failed_tests.insert(testname); \ - LOCAL_ASSERT(false); \ - if (stop_on_first_fail) \ - skip_all_till_the_end = true; \ - } \ - TIME_MEASURE_FINISH_MS(t); \ - tests_running_time.push_back(std::make_pair(testname, t)); \ - } + do { \ + const std::string __test_name = std::string(#genclass) + " (BC saveload)"; \ + auto __is_test_eligible_to_run = is_test_eligible_to_run; \ + g_test_jobs.push_back(test_job{ \ + __test_name, \ + [&, __test_name, __is_test_eligible_to_run]() -> bool { \ + if (!__is_test_eligible_to_run(#genclass)) \ + return true; \ + return run_one_test_job( \ + __test_name, \ + [&]() -> bool { return gen_and_play_intermitted_by_blockchain_saveload(__test_name.c_str()); }, \ + stop_on_first_fail, \ + skip_all_till_the_end, \ + tests_count, \ + unique_tests_count, \ + failed_tests, \ + tests_running_time); \ + } \ + }); \ + } while (0) #define GENERATE_AND_PLAY_HF(genclass, hardfork_str_mask) \ - if (!skip_all_till_the_end) \ - { \ - std::vector hardforks = parse_hardfork_str_mask(hardfork_str_mask); \ - CHECK_AND_ASSERT_MES(!hardforks.empty(), false, "invalid hardforks mask: " << hardfork_str_mask); \ - for(size_t i = 0; i < hardforks.size() && !skip_all_till_the_end; ++i) \ + do { \ + const std::string __gen_name = #genclass; \ + std::vector __hardforks = parse_hardfork_str_mask(hardfork_str_mask); \ + CHECK_AND_ASSERT_MES_CUSTOM(!__hardforks.empty(), /*fail_ret_val=*/, \ + /*custom_code=*/skip_all_till_the_end = true, "invalid hardforks mask: " << hardfork_str_mask << " for test " << __gen_name); \ + auto __hf_filter = is_hf_test_eligible_to_run; \ + for (size_t __i = 0; __i < __hardforks.size(); ++__i) \ { \ - if (!is_hf_test_eligible_to_run(#genclass, hardforks[i])) \ - continue; \ - std::string tns = std::string(#genclass) + " @ HF " + epee::string_tools::num_to_string_fast(hardforks[i]); \ - const char* testname = tns.c_str(); \ - TIME_MEASURE_START_MS(t); \ - ++tests_count; \ - if (!generate_and_play(testname, hardforks[i])) \ - { \ - failed_tests.insert(testname); \ - LOCAL_ASSERT(false); \ - if (stop_on_first_fail) \ - skip_all_till_the_end = true; \ - } \ - TIME_MEASURE_FINISH_MS(t); \ - tests_running_time.push_back(std::make_pair(testname, t)); \ + const size_t __hf_id = __hardforks[__i]; \ + const std::string __test_name = __gen_name + " @ HF " + epee::string_tools::num_to_string_fast(__hf_id); \ + g_test_jobs.push_back(test_job{ \ + __test_name, \ + [&, __test_name, __gen_name, __hf_id, __hf_filter]() -> bool { \ + if (!__hf_filter(__gen_name, __hf_id)) \ + return true; \ + return run_one_test_job( \ + __test_name, \ + [&]() -> bool { return generate_and_play(__test_name.c_str(), __hf_id); }, \ + stop_on_first_fail, \ + skip_all_till_the_end, \ + tests_count, \ + unique_tests_count, \ + failed_tests, \ + tests_running_time); \ + } \ + }); \ } \ - ++unique_tests_count; \ - } - - + } while (0) //#define GENERATE_AND_PLAY(genclass) GENERATE_AND_PLAY_INTERMITTED_BY_BLOCKCHAIN_SAVELOAD(genclass) - #define CALL_TEST(test_name, function) \ { \ if(!function()) \ @@ -730,25 +737,6 @@ inline bool replay_events_through_core(currency::core& cr, const std::vector prepare_variables_map() -{ - boost::program_options::options_description desc("Allowed options"); - currency::core::init_options(desc); - command_line::add_arg(desc, command_line::arg_data_dir); - std::shared_ptr vm(new boost::program_options::variables_map); - bool r = command_line::handle_error_helper(desc, [&]() - { - boost::program_options::store(boost::program_options::basic_parsed_options(&desc), *vm.get()); - boost::program_options::notify(*vm.get()); - return true; - }); - - if (!r) - return nullptr; - - return vm; -} -//-------------------------------------------------------------------------- template inline bool do_replay_events(const std::vector& events, t_test_class& validator, size_t event_index_from = 0, size_t event_index_to = SIZE_MAX, bool deinit_core = true) @@ -900,6 +888,493 @@ bool parse_cmd_specific_tests_to_run(std::unordered_multimap& fn, bool& stop_on_first_fail, bool& skip_all_till_the_end, + size_t& tests_count, size_t& unique_tests_count, std::set& failed_tests, std::vector>& tests_running_time) +{ + if (skip_all_till_the_end) + return true; + + TIME_MEASURE_START_MS(t); + + ++tests_count; + ++unique_tests_count; + + if (g_runner) + g_runner->log_test_taken_by_this_process(test_name); + + bool ok = fn(); + if (!ok) + { + failed_tests.insert(test_name); + LOCAL_ASSERT(false); + + const int32_t wid = command_line::get_arg(g_vm, chaingen_args::arg_worker_id); + const uint32_t procs = command_line::get_arg(g_vm, chaingen_args::arg_processes); + if (procs > 1 && wid >= 0 && g_shm_state) + g_shm_state->record_fail(static_cast(wid), test_name.c_str()); + + if (stop_on_first_fail) + skip_all_till_the_end = true; + } + + TIME_MEASURE_FINISH_MS(t); + tests_running_time.push_back(std::make_pair(test_name, t)); + + return ok; +} + +static bool run_registered_tests(bool& stop_on_first_fail, bool& skip_all_till_the_end, size_t& tests_count, size_t& unique_tests_count, std::set& failed_tests, + std::vector>& tests_running_time, const std::function& /*is_test_eligible_to_run*/,const std::function& /*is_hf_test_eligible_to_run*/) +{ + bool all_ok = true; + + const int32_t worker_id = command_line::get_arg(g_vm, arg_worker_id); + const bool is_worker = (worker_id >= 0); + + if (!is_worker) + { + for (size_t i = 0; i < g_test_jobs.size() && !skip_all_till_the_end; ++i) + { + const bool ok = g_test_jobs[i].run(); + all_ok = all_ok && ok; + } + return all_ok; + } + + if (!g_shm_state) + return all_ok; + + while (!skip_all_till_the_end) + { + if (g_shm_state->stop_all.load(std::memory_order_acquire)) + break; + + uint32_t job_idx = 0; + if (!g_shm_state->try_take_next(job_idx)) + break; + + if (job_idx >= g_test_jobs.size()) + continue; + + const bool ok = g_test_jobs[job_idx].run(); + if (ok) + continue; + + all_ok = false; + + if (stop_on_first_fail) + { + skip_all_till_the_end = true; + g_shm_state->stop_all.store(true, std::memory_order_release); + break; + } + } + + return all_ok; +} +//-------------------------------------------------------------------------- +// parameters are used inside macros +static void register_all_tests(bool& stop_on_first_fail, bool& skip_all_till_the_end, size_t& tests_count, size_t& unique_tests_count, + std::set& failed_tests, std::vector>& tests_running_time, + std::function is_test_eligible_to_run, std::function is_hf_test_eligible_to_run) +{ + g_test_jobs.clear(); + + // TODO // GENERATE_AND_PLAY(wallet_spend_form_auditable_and_track); + GENERATE_AND_PLAY(gen_block_big_major_version); + + GENERATE_AND_PLAY(pos_minting_tx_packing); + + GENERATE_AND_PLAY(multisig_wallet_test); + GENERATE_AND_PLAY(multisig_wallet_test_many_dst); + GENERATE_AND_PLAY(multisig_wallet_heterogenous_dst); + GENERATE_AND_PLAY(multisig_wallet_same_dst_addr); + GENERATE_AND_PLAY(multisig_wallet_ms_to_ms); + GENERATE_AND_PLAY(multisig_minimum_sigs); + GENERATE_AND_PLAY(multisig_and_fake_outputs); + GENERATE_AND_PLAY(multisig_and_unlock_time); + GENERATE_AND_PLAY(multisig_and_coinbase); + GENERATE_AND_PLAY(multisig_with_same_id_in_pool); + GENERATE_AND_PLAY_HF(multisig_and_checkpoints, "0"); // TODO: fix for HF 1-3 (checkpoint hash check) + GENERATE_AND_PLAY(multisig_and_checkpoints_bad_txs); + GENERATE_AND_PLAY(multisig_and_altchains); + GENERATE_AND_PLAY(multisig_out_make_and_spent_in_altchain); + GENERATE_AND_PLAY(multisig_unconfirmed_transfer_and_multiple_scan_pool_calls); + GENERATE_AND_PLAY(multisig_out_spent_in_altchain_case_b4); + GENERATE_AND_PLAY(multisig_n_participants_seq_signing); + + GENERATE_AND_PLAY(ref_by_id_basics); + GENERATE_AND_PLAY(ref_by_id_mixed_inputs_types); + + GENERATE_AND_PLAY(escrow_wallet_test); + GENERATE_AND_PLAY(escrow_w_and_fake_outputs); + GENERATE_AND_PLAY(escrow_incorrect_proposal); + GENERATE_AND_PLAY(escrow_proposal_expiration); + GENERATE_AND_PLAY(escrow_proposal_and_accept_expiration); + GENERATE_AND_PLAY(escrow_incorrect_proposal_acceptance); + GENERATE_AND_PLAY(escrow_custom_test); + GENERATE_AND_PLAY(escrow_incorrect_cancel_proposal); + GENERATE_AND_PLAY(escrow_proposal_not_enough_money); + GENERATE_AND_PLAY(escrow_cancellation_and_tx_order); + GENERATE_AND_PLAY(escrow_cancellation_proposal_expiration); + GENERATE_AND_PLAY(escrow_cancellation_acceptance_expiration); + // GENERATE_AND_PLAY(escrow_proposal_acceptance_in_alt_chain); -- work in progress + GENERATE_AND_PLAY(escrow_zero_amounts); + GENERATE_AND_PLAY(escrow_balance); + + GENERATE_AND_PLAY(escrow_altchain_meta_test<0>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<1>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<2>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<3>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<4>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<5>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<6>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<7>); + GENERATE_AND_PLAY(escrow_altchain_meta_test<8>); + static_assert(escrow_altchain_meta_test_data<9>::empty_marker, ""); // make sure there are no sub-tests left + + GENERATE_AND_PLAY(offers_expiration_test); + GENERATE_AND_PLAY(offers_tests); + GENERATE_AND_PLAY(offers_filtering_1); + //GENERATE_AND_PLAY(offers_handling_on_chain_switching); + GENERATE_AND_PLAY(offer_removing_and_selected_output); + GENERATE_AND_PLAY(offers_multiple_update); + GENERATE_AND_PLAY(offer_sig_validity_in_update_and_cancel); + GENERATE_AND_PLAY(offer_lifecycle_via_tx_pool); + GENERATE_AND_PLAY(offers_updating_hack); + GENERATE_AND_PLAY(offer_cancellation_with_zero_fee); + + GENERATE_AND_PLAY(gen_crypted_attachments); + GENERATE_AND_PLAY(gen_checkpoints_attachments_basic); + GENERATE_AND_PLAY(gen_checkpoints_invalid_keyimage); + GENERATE_AND_PLAY(gen_checkpoints_altblock_before_and_after_cp); + GENERATE_AND_PLAY(gen_checkpoints_block_in_future); + GENERATE_AND_PLAY(gen_checkpoints_altchain_far_before_cp); + GENERATE_AND_PLAY(gen_checkpoints_block_in_future_after_cp); + GENERATE_AND_PLAY(gen_checkpoints_prun_txs_after_blockchain_load); + GENERATE_AND_PLAY(gen_checkpoints_reorganize); + GENERATE_AND_PLAY(gen_checkpoints_pos_validation_on_altchain); + GENERATE_AND_PLAY(gen_checkpoints_and_invalid_tx_to_pool); + GENERATE_AND_PLAY(gen_checkpoints_set_after_switching_to_altchain); + GENERATE_AND_PLAY_HF(gen_no_attchments_in_coinbase, "3"); + GENERATE_AND_PLAY(gen_no_attchments_in_coinbase_gentime); + + GENERATE_AND_PLAY_HF(gen_alias_tests, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_strange_data, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_concurrency_with_switch, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_same_alias_in_tx_pool, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_switch_and_tx_pool, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_update_after_addr_changed, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_blocking_reg_by_invalid_tx, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_blocking_update_by_invalid_tx, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_reg_with_locked_money, "*"); + GENERATE_AND_PLAY_HF(gen_alias_too_small_reward, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_too_much_reward, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_tx_no_outs, "*"); + GENERATE_AND_PLAY_HF(gen_alias_switch_and_check_block_template, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_too_many_regs_in_block_template, "3"); // disabled in HF4 due to tx outputs count limitation + GENERATE_AND_PLAY_HF(gen_alias_update_for_free, "3-*"); + GENERATE_AND_PLAY_HF(gen_alias_in_coinbase, "3-*"); + + GENERATE_AND_PLAY(gen_wallet_basic_transfer); + GENERATE_AND_PLAY(gen_wallet_refreshing_on_chain_switch); + GENERATE_AND_PLAY(gen_wallet_refreshing_on_chain_switch_2); + GENERATE_AND_PLAY(gen_wallet_unconfirmed_tx_from_tx_pool); + GENERATE_AND_PLAY_HF(gen_wallet_save_load_and_balance, "*"); + GENERATE_AND_PLAY_HF(gen_wallet_mine_pos_block, "3-*"); + GENERATE_AND_PLAY(gen_wallet_unconfirmed_outdated_tx); + GENERATE_AND_PLAY(gen_wallet_unlock_by_block_and_by_time); + GENERATE_AND_PLAY(gen_wallet_payment_id); + GENERATE_AND_PLAY(gen_wallet_oversized_payment_id); + GENERATE_AND_PLAY(gen_wallet_transfers_and_outdated_unconfirmed_txs); + GENERATE_AND_PLAY(gen_wallet_transfers_and_chain_switch); + GENERATE_AND_PLAY(gen_wallet_decrypted_payload_items); + GENERATE_AND_PLAY_HF(gen_wallet_alias_and_unconfirmed_txs, "3-*"); + GENERATE_AND_PLAY_HF(gen_wallet_alias_via_special_wallet_funcs, "3-*"); + GENERATE_AND_PLAY(gen_wallet_fake_outputs_randomness); + GENERATE_AND_PLAY(gen_wallet_fake_outputs_not_enough); + GENERATE_AND_PLAY(gen_wallet_offers_basic); + GENERATE_AND_PLAY(gen_wallet_offers_size_limit); + GENERATE_AND_PLAY(gen_wallet_dust_to_account); + GENERATE_AND_PLAY(gen_wallet_selecting_pos_entries); + GENERATE_AND_PLAY(gen_wallet_spending_coinstake_after_minting); + GENERATE_AND_PLAY(gen_wallet_fake_outs_while_having_too_little_own_outs); + // GENERATE_AND_PLAY(premine_wallet_test); // tests premine wallets; wallet files nedded; by demand only + GENERATE_AND_PLAY(mined_balance_wallet_test); + GENERATE_AND_PLAY(wallet_outputs_with_same_key_image); + GENERATE_AND_PLAY(wallet_unconfirmed_tx_expiration); + GENERATE_AND_PLAY(wallet_unconfimed_tx_balance); + GENERATE_AND_PLAY_HF(packing_outputs_on_pos_minting_wallet, "3"); + GENERATE_AND_PLAY_HF(wallet_watch_only_and_chain_switch, "3"); + GENERATE_AND_PLAY_HF(wallet_and_sweep_below, "3-*"); + + GENERATE_AND_PLAY(wallet_rpc_integrated_address); + GENERATE_AND_PLAY(wallet_rpc_integrated_address_transfer); + GENERATE_AND_PLAY(wallet_rpc_transfer); + GENERATE_AND_PLAY(wallet_rpc_alias_tests); + GENERATE_AND_PLAY_HF(wallet_rpc_exchange_suite, "3,4"); + GENERATE_AND_PLAY_HF(wallet_true_rpc_pos_mining, "4-*"); + GENERATE_AND_PLAY_HF(wallet_rpc_cold_signing, "3,5-*"); + // GENERATE_AND_PLAY_HF(wallet_rpc_multiple_receivers, "5-*"); work in progress -- sowle + GENERATE_AND_PLAY_HF(wallet_chain_switch_with_spending_the_same_ki, "3"); + GENERATE_AND_PLAY(wallet_sending_to_integrated_address); + GENERATE_AND_PLAY_HF(block_template_blacklist_test, "4-*"); + GENERATE_AND_PLAY_HF(wallet_rpc_hardfork_verification, "5"); + + // GENERATE_AND_PLAY(emission_test); // simulate 1 year of blockchain, too long run (1 y ~= 1 hr), by demand only + // LOG_ERROR2("print_reward_change_first_blocks.log", currency::print_reward_change_first_blocks(525601).str()); // outputs first 1 year of blocks' rewards (simplier) + + // pos tests + GENERATE_AND_PLAY_INTERMITTED_BY_BLOCKCHAIN_SAVELOAD(gen_pos_basic_tests); + // GENERATE_AND_PLAY(gen_pos_basic_tests); -- commented as this test is run intermittedly by previous line; uncomment if necessary (ex. for debugging) + GENERATE_AND_PLAY(gen_pos_coinstake_already_spent); + GENERATE_AND_PLAY(gen_pos_incorrect_timestamp); + GENERATE_AND_PLAY(gen_pos_too_early_pos_block); + GENERATE_AND_PLAY_HF(gen_pos_extra_nonce, "3-*"); + GENERATE_AND_PLAY(gen_pos_min_allowed_height); + GENERATE_AND_PLAY(gen_pos_invalid_coinbase); + // GENERATE_AND_PLAY(pos_wallet_minting_same_amount_diff_outs); // Long test! Takes ~10 hours to simulate 6000 blocks on 2015 middle-end computer + //GENERATE_AND_PLAY(pos_emission_test); // Long test! by demand only + GENERATE_AND_PLAY(pos_wallet_big_block_test); + //GENERATE_AND_PLAY(block_template_against_txs_size); // Long test! by demand only + GENERATE_AND_PLAY_HF(pos_altblocks_validation, "3-*"); + GENERATE_AND_PLAY_HF(pos_mining_with_decoys, "3"); + GENERATE_AND_PLAY_HF(pos_and_no_pow_blocks_between_output_and_stake, "4-*"); + + // alternative blocks and generic chain-switching tests + GENERATE_AND_PLAY(gen_chain_switch_pow_pos); + GENERATE_AND_PLAY(pow_pos_reorganize_specific_case); + GENERATE_AND_PLAY(gen_chain_switch_1); + GENERATE_AND_PLAY(bad_chain_switching_with_rollback); + GENERATE_AND_PLAY(chain_switching_and_tx_with_attachment_blobsize); + GENERATE_AND_PLAY_HF(chain_switching_when_gindex_spent_in_both_chains, "3-*"); + GENERATE_AND_PLAY(alt_chain_coins_pow_mined_then_spent); + GENERATE_AND_PLAY(gen_simple_chain_split_1); + GENERATE_AND_PLAY_HF(alt_blocks_validation_and_same_new_amount_in_two_txs, "3-*"); + GENERATE_AND_PLAY_HF(alt_blocks_with_the_same_txs, "3-*"); + GENERATE_AND_PLAY_HF(chain_switching_when_out_spent_in_alt_chain_mixin, "3-*"); + GENERATE_AND_PLAY_HF(chain_switching_when_out_spent_in_alt_chain_ref_id, "3-*"); + GENERATE_AND_PLAY_HF(alt_chain_and_block_tx_fee_median, "3-*"); + + // miscellaneous tests + GENERATE_AND_PLAY(test_blockchain_vs_spent_keyimges); + GENERATE_AND_PLAY(test_blockchain_vs_spent_multisig_outs); + GENERATE_AND_PLAY(block_template_vs_invalid_txs_from_pool); + GENERATE_AND_PLAY(cumulative_difficulty_adjustment_test); + GENERATE_AND_PLAY(cumulative_difficulty_adjustment_test_alt); + GENERATE_AND_PLAY(prun_ring_signatures); + GENERATE_AND_PLAY(gen_simple_chain_001); + GENERATE_AND_PLAY(one_block); + GENERATE_AND_PLAY(gen_ring_signature_1); + GENERATE_AND_PLAY(gen_ring_signature_2); + GENERATE_AND_PLAY(fill_tx_rpc_inputs); + //GENERATE_AND_PLAY(gen_ring_signature_big); // Takes up to XXX hours (if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10) + + // tests for outputs mixing in + GENERATE_AND_PLAY(get_random_outs_test); + GENERATE_AND_PLAY(mix_attr_tests); + GENERATE_AND_PLAY(mix_in_spent_outs); + GENERATE_AND_PLAY(random_outs_and_burnt_coins); + + // Block verification tests + GENERATE_AND_PLAY_HF(gen_block_big_major_version, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_big_minor_version, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_ts_not_checked, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_ts_in_past, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_ts_in_future, "0,3"); + //GENERATE_AND_PLAY(gen_block_invalid_prev_id); disabled because impossible to generate text chain with wrong prev_id - pow hash not works without chaining + GENERATE_AND_PLAY_HF(gen_block_invalid_nonce, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_no_miner_tx, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_low, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_high, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_timestamp_in_past, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_timestamp_in_future, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_height_is_low, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_height_is_high, "0,3"); + GENERATE_AND_PLAY_HF(block_with_correct_prev_id_on_wrong_height, "3-*"); + GENERATE_AND_PLAY_HF(block_reward_in_main_chain_basic, "3-*"); + GENERATE_AND_PLAY_HF(block_reward_in_alt_chain_basic, "3-*"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_2_tx_gen_in, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_2_in, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_with_txin_to_key, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_out_is_small, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_out_is_big, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_no_out, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_out_to_initiator, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_has_invalid_tx, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_is_too_big, "0,3"); + GENERATE_AND_PLAY_HF(gen_block_wrong_version_agains_hardfork, "0,3"); + GENERATE_AND_PLAY_HF(block_choice_rule_bigger_fee, "4-*"); + //GENERATE_AND_PLAY(gen_block_invalid_binary_format); // Takes up to 3 hours, if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 500, up to 30 minutes, if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10 + + // Transaction verification tests + GENERATE_AND_PLAY(gen_broken_attachments); + GENERATE_AND_PLAY(gen_tx_big_version); + GENERATE_AND_PLAY(gen_tx_unlock_time); + GENERATE_AND_PLAY(gen_tx_input_is_not_txin_to_key); + GENERATE_AND_PLAY(gen_tx_no_inputs_no_outputs); + GENERATE_AND_PLAY(gen_tx_no_inputs_has_outputs); + GENERATE_AND_PLAY(gen_tx_has_inputs_no_outputs); + GENERATE_AND_PLAY(gen_tx_invalid_input_amount); + GENERATE_AND_PLAY(gen_tx_input_wo_key_offsets); + GENERATE_AND_PLAY(gen_tx_sender_key_offest_not_exist); + GENERATE_AND_PLAY(gen_tx_key_offest_points_to_foreign_key); + GENERATE_AND_PLAY(gen_tx_mixed_key_offest_not_exist); + GENERATE_AND_PLAY(gen_tx_key_image_not_derive_from_tx_key); + GENERATE_AND_PLAY(gen_tx_key_image_is_invalid); + GENERATE_AND_PLAY(gen_tx_check_input_unlock_time); + GENERATE_AND_PLAY(gen_tx_txout_to_key_has_invalid_key); + GENERATE_AND_PLAY(gen_tx_output_with_zero_amount); + GENERATE_AND_PLAY(gen_tx_output_is_not_txout_to_key); + GENERATE_AND_PLAY(gen_tx_signatures_are_invalid); + GENERATE_AND_PLAY(gen_tx_extra_double_entry); + GENERATE_AND_PLAY(gen_tx_double_key_image); + GENERATE_AND_PLAY(tx_expiration_time); + GENERATE_AND_PLAY(tx_expiration_time_and_block_template); + GENERATE_AND_PLAY(tx_expiration_time_and_chain_switching); + GENERATE_AND_PLAY(tx_key_image_pool_conflict); + //GENERATE_AND_PLAY_HF(tx_version_against_hardfork, "4-*"); + /* To execute the check of bare balance (function "check_tx_bare_balance") we need to run the test "tx_pool_semantic_validation" on the HF 3. By default behaviour bare outputs are disallowed on + the heights >= 10. */ + GENERATE_AND_PLAY_HF(tx_pool_semantic_validation, "3"); + GENERATE_AND_PLAY(input_refers_to_incompatible_by_type_output); + GENERATE_AND_PLAY_HF(tx_pool_validation_and_chain_switch, "4-5"); + GENERATE_AND_PLAY_HF(tx_coinbase_separate_sig_flag, "4-*"); + GENERATE_AND_PLAY(tx_input_mixins); + + // Double spend + GENERATE_AND_PLAY(gen_double_spend_in_tx); + GENERATE_AND_PLAY(gen_double_spend_in_tx); + GENERATE_AND_PLAY(gen_double_spend_in_the_same_block); + GENERATE_AND_PLAY(gen_double_spend_in_the_same_block); + GENERATE_AND_PLAY(gen_double_spend_in_different_blocks); + GENERATE_AND_PLAY(gen_double_spend_in_different_blocks); + GENERATE_AND_PLAY(gen_double_spend_in_different_chains); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_the_same_block); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_the_same_block); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks); + + GENERATE_AND_PLAY(gen_uint_overflow_1); + GENERATE_AND_PLAY(gen_uint_overflow_2); + + // Hardfok 1 tests + GENERATE_AND_PLAY(before_hard_fork_1_cumulative_difficulty); + GENERATE_AND_PLAY(inthe_middle_hard_fork_1_cumulative_difficulty); + GENERATE_AND_PLAY(after_hard_fork_1_cumulative_difficulty); + GENERATE_AND_PLAY(hard_fork_1_locked_mining_test); + GENERATE_AND_PLAY(hard_fork_1_bad_pos_source); + GENERATE_AND_PLAY(hard_fork_1_unlock_time_2_in_normal_tx); + GENERATE_AND_PLAY(hard_fork_1_unlock_time_2_in_coinbase); + GENERATE_AND_PLAY(hard_fork_1_chain_switch_pow_only); + GENERATE_AND_PLAY(hard_fork_1_checkpoint_basic_test); + GENERATE_AND_PLAY(hard_fork_1_pos_locked_height_vs_time); + GENERATE_AND_PLAY(hard_fork_1_pos_and_locked_coins); + + // Hardfork 2 tests + //GENERATE_AND_PLAY(hard_fork_2_tx_payer_in_wallet); + //GENERATE_AND_PLAY(hard_fork_2_tx_receiver_in_wallet); + GENERATE_AND_PLAY(hard_fork_2_tx_extra_alias_entry_in_wallet); + GENERATE_AND_PLAY_HF(hard_fork_2_auditable_addresses_basics, "2-*"); + GENERATE_AND_PLAY(hard_fork_2_no_new_structures_before_hf); + GENERATE_AND_PLAY(hard_fork_2_awo_wallets_basic_test); + GENERATE_AND_PLAY(hard_fork_2_awo_wallets_basic_test); + GENERATE_AND_PLAY(hard_fork_2_alias_update_using_old_tx); + GENERATE_AND_PLAY(hard_fork_2_alias_update_using_old_tx); + GENERATE_AND_PLAY(hard_fork_2_incorrect_alias_update); + GENERATE_AND_PLAY(hard_fork_2_incorrect_alias_update); + + // HF4 + GENERATE_AND_PLAY_HF(hard_fork_4_consolidated_txs, "3-*"); + GENERATE_AND_PLAY_HF(hardfork_4_wallet_transfer_with_mandatory_mixins, "3-*"); + GENERATE_AND_PLAY(hardfork_4_wallet_sweep_bare_outs); + GENERATE_AND_PLAY_HF(hardfork_4_pop_tx_from_global_index, "4-*"); + + // HF5 + GENERATE_AND_PLAY_HF(hard_fork_5_tx_version, "5-*"); + + // HF6 + GENERATE_AND_PLAY(hard_fork_6_intrinsic_payment_id_basic_test); + GENERATE_AND_PLAY(hard_fork_6_intrinsic_payment_id_rpc_test); + + GENERATE_AND_PLAY_HF(isolate_auditable_and_proof, "2-*"); + + GENERATE_AND_PLAY(zarcanum_basic_test); + + GENERATE_AND_PLAY_HF(multiassets_basic_test, "4-*"); + GENERATE_AND_PLAY_HF(ionic_swap_basic_test, "4-*"); + GENERATE_AND_PLAY_HF(ionic_swap_exact_amounts_test, "4-*"); + GENERATE_AND_PLAY(zarcanum_test_n_inputs_validation); + GENERATE_AND_PLAY(zarcanum_gen_time_balance); + GENERATE_AND_PLAY(zarcanum_txs_with_big_shuffled_decoy_set_shuffled); + GENERATE_AND_PLAY(zarcanum_pos_block_math); + GENERATE_AND_PLAY(zarcanum_in_alt_chain); + GENERATE_AND_PLAY_HF(zarcanum_in_alt_chain_2, "4-*"); + GENERATE_AND_PLAY(assets_and_explicit_native_coins_in_outs); + GENERATE_AND_PLAY(zarcanum_block_with_txs); + GENERATE_AND_PLAY(asset_depoyment_and_few_zc_utxos); + GENERATE_AND_PLAY_HF(assets_and_pos_mining, "4-*"); + GENERATE_AND_PLAY_HF(asset_emission_and_unconfirmed_balance, "4-*"); + GENERATE_AND_PLAY_HF(asset_operation_in_consolidated_tx, "4-*"); + GENERATE_AND_PLAY_HF(asset_operation_and_hardfork_checks, "4-*"); + GENERATE_AND_PLAY_HF(eth_signed_asset_basics, "5-*"); // TODO: make HF4 version + GENERATE_AND_PLAY_HF(eth_signed_asset_via_rpc, "5-*"); // TODO: make HF4 version + //GENERATE_AND_PLAY_HF(asset_current_and_total_supplies_comparative_constraints, "4-*"); <-- temporary disabled, waiting for Stepan's fix -- sowle + GENERATE_AND_PLAY_HF(several_asset_emit_burn_txs_in_pool, "5-*"); + GENERATE_AND_PLAY_HF(assets_transfer_with_smallest_amount, "4-*"); + GENERATE_AND_PLAY_HF(asset_operations_and_chain_switching, "4-*"); + + GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*"); + GENERATE_AND_PLAY_HF(wallet_reorganize_and_trim_test, "4-*"); + GENERATE_AND_PLAY_HF(wallet_rpc_thirdparty_custody, "5-*"); + + GENERATE_AND_PLAY_HF(attachment_isolation_test, "4-*"); + + // GENERATE_AND_PLAY(gen_block_reward); + // END OF TESTS */ +} + +void fill_postponed_tests_set(std::set& postponed_tests) +{ + postponed_tests.clear(); + +#define MARK_TEST_AS_POSTPONED(genclass) postponed_tests.insert(#genclass) + MARK_TEST_AS_POSTPONED(gen_checkpoints_reorganize); + MARK_TEST_AS_POSTPONED(gen_alias_update_after_addr_changed); + MARK_TEST_AS_POSTPONED(gen_alias_blocking_reg_by_invalid_tx); + MARK_TEST_AS_POSTPONED(gen_alias_blocking_update_by_invalid_tx); + MARK_TEST_AS_POSTPONED(gen_wallet_fake_outputs_randomness); + MARK_TEST_AS_POSTPONED(gen_wallet_fake_outputs_not_enough); + MARK_TEST_AS_POSTPONED(gen_wallet_spending_coinstake_after_minting); + MARK_TEST_AS_POSTPONED(gen_wallet_fake_outs_while_having_too_little_own_outs); + MARK_TEST_AS_POSTPONED(gen_uint_overflow_1); + + MARK_TEST_AS_POSTPONED(after_hard_fork_1_cumulative_difficulty); + MARK_TEST_AS_POSTPONED(before_hard_fork_1_cumulative_difficulty); + MARK_TEST_AS_POSTPONED(inthe_middle_hard_fork_1_cumulative_difficulty); +#undef MARK_TEST_AS_POSTPONED +} +//-------------------------------------------------------------------------- +static void init_shared_fail_report_if_needed() +{ + const std::string shm_name = command_line::get_arg(g_vm, chaingen_args::arg_shm_name); + if (shm_name.empty()) + return; + + namespace bip = boost::interprocess; + try + { + bip::shared_memory_object shm(bip::open_only, shm_name.c_str(), bip::read_write); + g_shm_region.reset(new bip::mapped_region(shm, bip::read_write)); + g_shm_state = static_cast(g_shm_region->get_address()); + } + catch (...) + { + g_shm_state = nullptr; + g_shm_region.reset(); + } +} +//-------------------------------------------------------------------------- int main(int argc, char* argv[]) { TRY_ENTRY(); @@ -938,6 +1413,10 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_options, command_line::arg_data_dir, std::string(".")); command_line::add_arg(desc_options, command_line::arg_stop_after_height); command_line::add_arg(desc_options, command_line::arg_disable_ntp); + command_line::add_arg(desc_options, arg_processes); + command_line::add_arg(desc_options, arg_worker_id); + command_line::add_arg(desc_options, arg_run_root); + command_line::add_arg(desc_options, arg_shm_name); currency::core::init_options(desc_options); tools::db::db_backend_selector::init_options(desc_options); @@ -972,13 +1451,27 @@ int main(int argc, char* argv[]) stop_on_first_fail = command_line::get_arg(g_vm, arg_stop_on_fail); } - + const int32_t worker_id = command_line::get_arg(g_vm, arg_worker_id); + const bool is_worker = (worker_id >= 0); + + const uint32_t processes = command_line::get_arg(g_vm, arg_processes); + const bool multiprocess_enabled = + is_worker || (command_line::has_arg(g_vm, arg_processes) && processes > 1); + + if (multiprocess_enabled) + g_runner = std::make_unique(g_vm); + else + g_runner.reset(); + + if (is_worker && multiprocess_enabled) + init_shared_fail_report_if_needed(); + bool skip_all_till_the_end = false; size_t tests_count = 0; size_t unique_tests_count = 0; size_t serious_failures_count = 0; std::set failed_tests; - std::string tests_folder = command_line::get_arg(g_vm, arg_test_data_path); + typedef std::vector> tests_running_time_t; tests_running_time_t tests_running_time; if (command_line::get_arg(g_vm, arg_generate_test_data)) @@ -1040,385 +1533,36 @@ int main(int argc, char* argv[]) CALL_TEST("test_parse_hardfork_str_mask", test_parse_hardfork_str_mask); } - //CALL_TEST("check_hash_and_difficulty_monte_carlo_test", check_hash_and_difficulty_monte_carlo_test); // it's rather an experiment with unclean results than a solid test, for further research... - - // Postponed tests - tests that may fail for the time being (believed that it's a serious issue and should be fixed later for some reason). - // In a perfect world this list is empty. -#define MARK_TEST_AS_POSTPONED(genclass) postponed_tests.insert(#genclass) - MARK_TEST_AS_POSTPONED(gen_checkpoints_reorganize); - MARK_TEST_AS_POSTPONED(gen_alias_update_after_addr_changed); - MARK_TEST_AS_POSTPONED(gen_alias_blocking_reg_by_invalid_tx); - MARK_TEST_AS_POSTPONED(gen_alias_blocking_update_by_invalid_tx); - MARK_TEST_AS_POSTPONED(gen_wallet_fake_outputs_randomness); - MARK_TEST_AS_POSTPONED(gen_wallet_fake_outputs_not_enough); - MARK_TEST_AS_POSTPONED(gen_wallet_spending_coinstake_after_minting); - MARK_TEST_AS_POSTPONED(gen_wallet_fake_outs_while_having_too_little_own_outs); - MARK_TEST_AS_POSTPONED(gen_uint_overflow_1); - - MARK_TEST_AS_POSTPONED(after_hard_fork_1_cumulative_difficulty); // reason: set_pos_to_low_timestamp is not supported anymore - MARK_TEST_AS_POSTPONED(before_hard_fork_1_cumulative_difficulty); - MARK_TEST_AS_POSTPONED(inthe_middle_hard_fork_1_cumulative_difficulty); + // Postponed tests list. + fill_postponed_tests_set(postponed_tests); -#undef MARK_TEST_AS_POSTPONED - - - // TODO // GENERATE_AND_PLAY(wallet_spend_form_auditable_and_track); - GENERATE_AND_PLAY(gen_block_big_major_version); - - GENERATE_AND_PLAY(pos_minting_tx_packing); - - GENERATE_AND_PLAY(multisig_wallet_test); - GENERATE_AND_PLAY(multisig_wallet_test_many_dst); - GENERATE_AND_PLAY(multisig_wallet_heterogenous_dst); - GENERATE_AND_PLAY(multisig_wallet_same_dst_addr); - GENERATE_AND_PLAY(multisig_wallet_ms_to_ms); - GENERATE_AND_PLAY(multisig_minimum_sigs); - GENERATE_AND_PLAY(multisig_and_fake_outputs); - GENERATE_AND_PLAY(multisig_and_unlock_time); - GENERATE_AND_PLAY(multisig_and_coinbase); - GENERATE_AND_PLAY(multisig_with_same_id_in_pool); - GENERATE_AND_PLAY_HF(multisig_and_checkpoints, "0"); // TODO: fix for HF 1-3 (checkpoint hash check) - GENERATE_AND_PLAY(multisig_and_checkpoints_bad_txs); - GENERATE_AND_PLAY(multisig_and_altchains); - GENERATE_AND_PLAY(multisig_out_make_and_spent_in_altchain); - GENERATE_AND_PLAY(multisig_unconfirmed_transfer_and_multiple_scan_pool_calls); - GENERATE_AND_PLAY(multisig_out_spent_in_altchain_case_b4); - GENERATE_AND_PLAY(multisig_n_participants_seq_signing); - - GENERATE_AND_PLAY(ref_by_id_basics); - GENERATE_AND_PLAY(ref_by_id_mixed_inputs_types); - - GENERATE_AND_PLAY(escrow_wallet_test); - GENERATE_AND_PLAY(escrow_w_and_fake_outputs); - GENERATE_AND_PLAY(escrow_incorrect_proposal); - GENERATE_AND_PLAY(escrow_proposal_expiration); - GENERATE_AND_PLAY(escrow_proposal_and_accept_expiration); - GENERATE_AND_PLAY(escrow_incorrect_proposal_acceptance); - GENERATE_AND_PLAY(escrow_custom_test); - GENERATE_AND_PLAY(escrow_incorrect_cancel_proposal); - GENERATE_AND_PLAY(escrow_proposal_not_enough_money); - GENERATE_AND_PLAY(escrow_cancellation_and_tx_order); - GENERATE_AND_PLAY(escrow_cancellation_proposal_expiration); - GENERATE_AND_PLAY(escrow_cancellation_acceptance_expiration); - // GENERATE_AND_PLAY(escrow_proposal_acceptance_in_alt_chain); -- work in progress - GENERATE_AND_PLAY(escrow_zero_amounts); - GENERATE_AND_PLAY(escrow_balance); - - GENERATE_AND_PLAY(escrow_altchain_meta_test<0>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<1>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<2>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<3>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<4>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<5>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<6>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<7>); - GENERATE_AND_PLAY(escrow_altchain_meta_test<8>); - static_assert(escrow_altchain_meta_test_data<9>::empty_marker, ""); // make sure there are no sub-tests left - - GENERATE_AND_PLAY(offers_expiration_test); - GENERATE_AND_PLAY(offers_tests); - GENERATE_AND_PLAY(offers_filtering_1); - //GENERATE_AND_PLAY(offers_handling_on_chain_switching); - GENERATE_AND_PLAY(offer_removing_and_selected_output); - GENERATE_AND_PLAY(offers_multiple_update); - GENERATE_AND_PLAY(offer_sig_validity_in_update_and_cancel); - GENERATE_AND_PLAY(offer_lifecycle_via_tx_pool); - GENERATE_AND_PLAY(offers_updating_hack); - GENERATE_AND_PLAY(offer_cancellation_with_zero_fee); - - GENERATE_AND_PLAY(gen_crypted_attachments); - GENERATE_AND_PLAY(gen_checkpoints_attachments_basic); - GENERATE_AND_PLAY(gen_checkpoints_invalid_keyimage); - GENERATE_AND_PLAY(gen_checkpoints_altblock_before_and_after_cp); - GENERATE_AND_PLAY(gen_checkpoints_block_in_future); - GENERATE_AND_PLAY(gen_checkpoints_altchain_far_before_cp); - GENERATE_AND_PLAY(gen_checkpoints_block_in_future_after_cp); - GENERATE_AND_PLAY(gen_checkpoints_prun_txs_after_blockchain_load); - GENERATE_AND_PLAY(gen_checkpoints_reorganize); - GENERATE_AND_PLAY(gen_checkpoints_pos_validation_on_altchain); - GENERATE_AND_PLAY(gen_checkpoints_and_invalid_tx_to_pool); - GENERATE_AND_PLAY(gen_checkpoints_set_after_switching_to_altchain); - GENERATE_AND_PLAY_HF(gen_no_attchments_in_coinbase, "3"); - GENERATE_AND_PLAY(gen_no_attchments_in_coinbase_gentime); - - GENERATE_AND_PLAY_HF(gen_alias_tests, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_strange_data, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_concurrency_with_switch, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_same_alias_in_tx_pool, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_switch_and_tx_pool, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_update_after_addr_changed, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_blocking_reg_by_invalid_tx, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_blocking_update_by_invalid_tx, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_reg_with_locked_money, "*"); - GENERATE_AND_PLAY_HF(gen_alias_too_small_reward, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_too_much_reward, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_tx_no_outs, "*"); - GENERATE_AND_PLAY_HF(gen_alias_switch_and_check_block_template, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_too_many_regs_in_block_template, "3"); // disabled in HF4 due to tx outputs count limitation - GENERATE_AND_PLAY_HF(gen_alias_update_for_free, "3-*"); - GENERATE_AND_PLAY_HF(gen_alias_in_coinbase, "3-*"); - - GENERATE_AND_PLAY(gen_wallet_basic_transfer); - GENERATE_AND_PLAY(gen_wallet_refreshing_on_chain_switch); - GENERATE_AND_PLAY(gen_wallet_refreshing_on_chain_switch_2); - GENERATE_AND_PLAY(gen_wallet_unconfirmed_tx_from_tx_pool); - GENERATE_AND_PLAY_HF(gen_wallet_save_load_and_balance, "*"); - GENERATE_AND_PLAY_HF(gen_wallet_mine_pos_block, "3-*"); - GENERATE_AND_PLAY(gen_wallet_unconfirmed_outdated_tx); - GENERATE_AND_PLAY(gen_wallet_unlock_by_block_and_by_time); - GENERATE_AND_PLAY(gen_wallet_payment_id); - GENERATE_AND_PLAY(gen_wallet_oversized_payment_id); - GENERATE_AND_PLAY(gen_wallet_transfers_and_outdated_unconfirmed_txs); - GENERATE_AND_PLAY(gen_wallet_transfers_and_chain_switch); - GENERATE_AND_PLAY(gen_wallet_decrypted_payload_items); - GENERATE_AND_PLAY_HF(gen_wallet_alias_and_unconfirmed_txs, "3-*"); - GENERATE_AND_PLAY_HF(gen_wallet_alias_via_special_wallet_funcs, "3-*"); - GENERATE_AND_PLAY(gen_wallet_fake_outputs_randomness); - GENERATE_AND_PLAY(gen_wallet_fake_outputs_not_enough); - GENERATE_AND_PLAY(gen_wallet_offers_basic); - GENERATE_AND_PLAY(gen_wallet_offers_size_limit); - GENERATE_AND_PLAY(gen_wallet_dust_to_account); - GENERATE_AND_PLAY(gen_wallet_selecting_pos_entries); - GENERATE_AND_PLAY(gen_wallet_spending_coinstake_after_minting); - GENERATE_AND_PLAY(gen_wallet_fake_outs_while_having_too_little_own_outs); - // GENERATE_AND_PLAY(premine_wallet_test); // tests premine wallets; wallet files nedded; by demand only - GENERATE_AND_PLAY(mined_balance_wallet_test); - GENERATE_AND_PLAY(wallet_outputs_with_same_key_image); - GENERATE_AND_PLAY(wallet_unconfirmed_tx_expiration); - GENERATE_AND_PLAY(wallet_unconfimed_tx_balance); - GENERATE_AND_PLAY_HF(packing_outputs_on_pos_minting_wallet, "3"); - GENERATE_AND_PLAY_HF(wallet_watch_only_and_chain_switch, "3"); - GENERATE_AND_PLAY_HF(wallet_and_sweep_below, "3-*"); - - GENERATE_AND_PLAY(wallet_rpc_integrated_address); - GENERATE_AND_PLAY(wallet_rpc_integrated_address_transfer); - GENERATE_AND_PLAY(wallet_rpc_transfer); - GENERATE_AND_PLAY(wallet_rpc_alias_tests); - GENERATE_AND_PLAY_HF(wallet_rpc_exchange_suite, "3,4"); - GENERATE_AND_PLAY_HF(wallet_true_rpc_pos_mining, "4-*"); - GENERATE_AND_PLAY_HF(wallet_rpc_cold_signing, "3,5-*"); - // GENERATE_AND_PLAY_HF(wallet_rpc_multiple_receivers, "5-*"); work in progress -- sowle - GENERATE_AND_PLAY_HF(wallet_chain_switch_with_spending_the_same_ki, "3"); - GENERATE_AND_PLAY(wallet_sending_to_integrated_address); - GENERATE_AND_PLAY_HF(block_template_blacklist_test, "4-*"); - GENERATE_AND_PLAY_HF(wallet_rpc_hardfork_verification, "5"); - - - // GENERATE_AND_PLAY(emission_test); // simulate 1 year of blockchain, too long run (1 y ~= 1 hr), by demand only - // LOG_ERROR2("print_reward_change_first_blocks.log", currency::print_reward_change_first_blocks(525601).str()); // outputs first 1 year of blocks' rewards (simplier) - - // pos tests - GENERATE_AND_PLAY_INTERMITTED_BY_BLOCKCHAIN_SAVELOAD(gen_pos_basic_tests); - // GENERATE_AND_PLAY(gen_pos_basic_tests); -- commented as this test is run intermittedly by previous line; uncomment if necessary (ex. for debugging) - GENERATE_AND_PLAY(gen_pos_coinstake_already_spent); - GENERATE_AND_PLAY(gen_pos_incorrect_timestamp); - GENERATE_AND_PLAY(gen_pos_too_early_pos_block); - GENERATE_AND_PLAY_HF(gen_pos_extra_nonce, "3-*"); - GENERATE_AND_PLAY(gen_pos_min_allowed_height); - GENERATE_AND_PLAY(gen_pos_invalid_coinbase); - // GENERATE_AND_PLAY(pos_wallet_minting_same_amount_diff_outs); // Long test! Takes ~10 hours to simulate 6000 blocks on 2015 middle-end computer - //GENERATE_AND_PLAY(pos_emission_test); // Long test! by demand only - GENERATE_AND_PLAY(pos_wallet_big_block_test); - //GENERATE_AND_PLAY(block_template_against_txs_size); // Long test! by demand only - GENERATE_AND_PLAY_HF(pos_altblocks_validation, "3-*"); - GENERATE_AND_PLAY_HF(pos_mining_with_decoys, "3"); - GENERATE_AND_PLAY_HF(pos_and_no_pow_blocks_between_output_and_stake, "4-*"); - - // alternative blocks and generic chain-switching tests - GENERATE_AND_PLAY(gen_chain_switch_pow_pos); - GENERATE_AND_PLAY(pow_pos_reorganize_specific_case); - GENERATE_AND_PLAY(gen_chain_switch_1); - GENERATE_AND_PLAY(bad_chain_switching_with_rollback); - GENERATE_AND_PLAY(chain_switching_and_tx_with_attachment_blobsize); - GENERATE_AND_PLAY_HF(chain_switching_when_gindex_spent_in_both_chains, "3-*"); - GENERATE_AND_PLAY(alt_chain_coins_pow_mined_then_spent); - GENERATE_AND_PLAY(gen_simple_chain_split_1); - GENERATE_AND_PLAY_HF(alt_blocks_validation_and_same_new_amount_in_two_txs, "3-*"); - GENERATE_AND_PLAY_HF(alt_blocks_with_the_same_txs, "3-*"); - GENERATE_AND_PLAY_HF(chain_switching_when_out_spent_in_alt_chain_mixin, "3-*"); - GENERATE_AND_PLAY_HF(chain_switching_when_out_spent_in_alt_chain_ref_id, "3-*"); - GENERATE_AND_PLAY_HF(alt_chain_and_block_tx_fee_median, "3-*"); - - // miscellaneous tests - GENERATE_AND_PLAY(test_blockchain_vs_spent_keyimges); - GENERATE_AND_PLAY(test_blockchain_vs_spent_multisig_outs); - GENERATE_AND_PLAY(block_template_vs_invalid_txs_from_pool); - GENERATE_AND_PLAY(cumulative_difficulty_adjustment_test); - GENERATE_AND_PLAY(cumulative_difficulty_adjustment_test); - GENERATE_AND_PLAY(cumulative_difficulty_adjustment_test_alt); - GENERATE_AND_PLAY(prun_ring_signatures); - GENERATE_AND_PLAY(gen_simple_chain_001); - GENERATE_AND_PLAY(one_block); - GENERATE_AND_PLAY(gen_ring_signature_1); - GENERATE_AND_PLAY(gen_ring_signature_2); - GENERATE_AND_PLAY(fill_tx_rpc_inputs); - //GENERATE_AND_PLAY(gen_ring_signature_big); // Takes up to XXX hours (if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10) - - // tests for outputs mixing in - GENERATE_AND_PLAY(get_random_outs_test); - GENERATE_AND_PLAY(mix_attr_tests); - GENERATE_AND_PLAY(mix_in_spent_outs); - GENERATE_AND_PLAY(random_outs_and_burnt_coins); - - // Block verification tests - GENERATE_AND_PLAY_HF(gen_block_big_major_version, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_big_minor_version, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_ts_not_checked, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_ts_in_past, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_ts_in_future, "0,3"); - //GENERATE_AND_PLAY(gen_block_invalid_prev_id); disabled because impossible to generate text chain with wrong prev_id - pow hash not works without chaining - GENERATE_AND_PLAY_HF(gen_block_invalid_nonce, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_no_miner_tx, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_low, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_high, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_timestamp_in_past, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_unlock_time_is_timestamp_in_future, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_height_is_low, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_height_is_high, "0,3"); - GENERATE_AND_PLAY_HF(block_with_correct_prev_id_on_wrong_height, "3-*"); - GENERATE_AND_PLAY_HF(block_reward_in_main_chain_basic, "3-*"); - GENERATE_AND_PLAY_HF(block_reward_in_alt_chain_basic, "3-*"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_2_tx_gen_in, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_2_in, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_with_txin_to_key, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_out_is_small, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_out_is_big, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_no_out, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_miner_tx_has_out_to_initiator, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_has_invalid_tx, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_is_too_big, "0,3"); - GENERATE_AND_PLAY_HF(gen_block_wrong_version_agains_hardfork, "0,3"); - GENERATE_AND_PLAY_HF(block_choice_rule_bigger_fee, "4-*"); - //GENERATE_AND_PLAY(gen_block_invalid_binary_format); // Takes up to 3 hours, if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 500, up to 30 minutes, if CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10 + register_all_tests( + stop_on_first_fail, + skip_all_till_the_end, + tests_count, + unique_tests_count, + failed_tests, + tests_running_time, + is_test_eligible_to_run, + is_hf_test_eligible_to_run); + if (multiprocess_enabled) + { + const int parent_rc = g_runner->run_parent_if_needed(argc, argv, g_test_jobs); + if (parent_rc != parallel_test_runner::k_not_parent) + return parent_rc; + } - - // Transaction verification tests - GENERATE_AND_PLAY(gen_broken_attachments); - GENERATE_AND_PLAY(gen_tx_big_version); - GENERATE_AND_PLAY(gen_tx_unlock_time); - GENERATE_AND_PLAY(gen_tx_input_is_not_txin_to_key); - GENERATE_AND_PLAY(gen_tx_no_inputs_no_outputs); - GENERATE_AND_PLAY(gen_tx_no_inputs_has_outputs); - GENERATE_AND_PLAY(gen_tx_has_inputs_no_outputs); - GENERATE_AND_PLAY(gen_tx_invalid_input_amount); - GENERATE_AND_PLAY(gen_tx_input_wo_key_offsets); - GENERATE_AND_PLAY(gen_tx_sender_key_offest_not_exist); - GENERATE_AND_PLAY(gen_tx_key_offest_points_to_foreign_key); - GENERATE_AND_PLAY(gen_tx_mixed_key_offest_not_exist); - GENERATE_AND_PLAY(gen_tx_key_image_not_derive_from_tx_key); - GENERATE_AND_PLAY(gen_tx_key_image_is_invalid); - GENERATE_AND_PLAY(gen_tx_check_input_unlock_time); - GENERATE_AND_PLAY(gen_tx_txout_to_key_has_invalid_key); - GENERATE_AND_PLAY(gen_tx_output_with_zero_amount); - GENERATE_AND_PLAY(gen_tx_output_is_not_txout_to_key); - GENERATE_AND_PLAY(gen_tx_signatures_are_invalid); - GENERATE_AND_PLAY(gen_tx_extra_double_entry); - GENERATE_AND_PLAY(gen_tx_double_key_image); - GENERATE_AND_PLAY(tx_expiration_time); - GENERATE_AND_PLAY(tx_expiration_time_and_block_template); - GENERATE_AND_PLAY(tx_expiration_time_and_chain_switching); - GENERATE_AND_PLAY(tx_key_image_pool_conflict); - //GENERATE_AND_PLAY_HF(tx_version_against_hardfork, "4-*"); - /* To execute the check of bare balance (function "check_tx_bare_balance") we need to run the test "tx_pool_semantic_validation" on the HF 3. By default behaviour bare outputs are disallowed on - the heights >= 10. */ - GENERATE_AND_PLAY_HF(tx_pool_semantic_validation, "3"); - GENERATE_AND_PLAY(input_refers_to_incompatible_by_type_output); - GENERATE_AND_PLAY_HF(tx_pool_validation_and_chain_switch, "4-5"); - GENERATE_AND_PLAY_HF(tx_coinbase_separate_sig_flag, "4-*"); - GENERATE_AND_PLAY(tx_input_mixins); - - // Double spend - GENERATE_AND_PLAY(gen_double_spend_in_tx); - GENERATE_AND_PLAY(gen_double_spend_in_tx); - GENERATE_AND_PLAY(gen_double_spend_in_the_same_block); - GENERATE_AND_PLAY(gen_double_spend_in_the_same_block); - GENERATE_AND_PLAY(gen_double_spend_in_different_blocks); - GENERATE_AND_PLAY(gen_double_spend_in_different_blocks); - GENERATE_AND_PLAY(gen_double_spend_in_different_chains); - GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_the_same_block); - GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_the_same_block); - GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks); - GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks); - - GENERATE_AND_PLAY(gen_uint_overflow_1); - GENERATE_AND_PLAY(gen_uint_overflow_2); - - - // Hardfok 1 tests - GENERATE_AND_PLAY(before_hard_fork_1_cumulative_difficulty); - GENERATE_AND_PLAY(inthe_middle_hard_fork_1_cumulative_difficulty); - GENERATE_AND_PLAY(after_hard_fork_1_cumulative_difficulty); - GENERATE_AND_PLAY(hard_fork_1_locked_mining_test); - GENERATE_AND_PLAY(hard_fork_1_bad_pos_source); - GENERATE_AND_PLAY(hard_fork_1_unlock_time_2_in_normal_tx); - GENERATE_AND_PLAY(hard_fork_1_unlock_time_2_in_coinbase); - GENERATE_AND_PLAY(hard_fork_1_chain_switch_pow_only); - GENERATE_AND_PLAY(hard_fork_1_checkpoint_basic_test); - GENERATE_AND_PLAY(hard_fork_1_pos_locked_height_vs_time); - GENERATE_AND_PLAY(hard_fork_1_pos_and_locked_coins); - - // Hardfork 2 tests - //GENERATE_AND_PLAY(hard_fork_2_tx_payer_in_wallet); - //GENERATE_AND_PLAY(hard_fork_2_tx_receiver_in_wallet); - GENERATE_AND_PLAY(hard_fork_2_tx_extra_alias_entry_in_wallet); - GENERATE_AND_PLAY_HF(hard_fork_2_auditable_addresses_basics, "2-*"); - GENERATE_AND_PLAY(hard_fork_2_no_new_structures_before_hf); - GENERATE_AND_PLAY(hard_fork_2_awo_wallets_basic_test); - GENERATE_AND_PLAY(hard_fork_2_awo_wallets_basic_test); - GENERATE_AND_PLAY(hard_fork_2_alias_update_using_old_tx); - GENERATE_AND_PLAY(hard_fork_2_alias_update_using_old_tx); - GENERATE_AND_PLAY(hard_fork_2_incorrect_alias_update); - GENERATE_AND_PLAY(hard_fork_2_incorrect_alias_update); - - // HF4 - GENERATE_AND_PLAY_HF(hard_fork_4_consolidated_txs, "3-*"); - GENERATE_AND_PLAY_HF(hardfork_4_wallet_transfer_with_mandatory_mixins, "3-*"); - GENERATE_AND_PLAY(hardfork_4_wallet_sweep_bare_outs); - GENERATE_AND_PLAY_HF(hardfork_4_pop_tx_from_global_index, "4-*"); - - // HF5 - GENERATE_AND_PLAY_HF(hard_fork_5_tx_version, "5-*"); - - // HF6 - GENERATE_AND_PLAY(hard_fork_6_intrinsic_payment_id_basic_test); - GENERATE_AND_PLAY(hard_fork_6_intrinsic_payment_id_rpc_test); - - GENERATE_AND_PLAY_HF(isolate_auditable_and_proof, "2-*"); - - GENERATE_AND_PLAY(zarcanum_basic_test); - - GENERATE_AND_PLAY_HF(multiassets_basic_test, "4-*"); - GENERATE_AND_PLAY_HF(ionic_swap_basic_test, "4-*"); - GENERATE_AND_PLAY_HF(ionic_swap_exact_amounts_test, "4-*"); - GENERATE_AND_PLAY(zarcanum_test_n_inputs_validation); - GENERATE_AND_PLAY(zarcanum_gen_time_balance); - GENERATE_AND_PLAY(zarcanum_txs_with_big_shuffled_decoy_set_shuffled); - GENERATE_AND_PLAY(zarcanum_pos_block_math); - GENERATE_AND_PLAY(zarcanum_in_alt_chain); - GENERATE_AND_PLAY_HF(zarcanum_in_alt_chain_2, "4-*"); - GENERATE_AND_PLAY(assets_and_explicit_native_coins_in_outs); - GENERATE_AND_PLAY(zarcanum_block_with_txs); - GENERATE_AND_PLAY(asset_depoyment_and_few_zc_utxos); - GENERATE_AND_PLAY_HF(assets_and_pos_mining, "4-*"); - GENERATE_AND_PLAY_HF(asset_emission_and_unconfirmed_balance, "4-*"); - GENERATE_AND_PLAY_HF(asset_operation_in_consolidated_tx, "4-*"); - GENERATE_AND_PLAY_HF(asset_operation_and_hardfork_checks, "4-*"); - GENERATE_AND_PLAY_HF(eth_signed_asset_basics, "5-*"); // TODO: make HF4 version - GENERATE_AND_PLAY_HF(eth_signed_asset_via_rpc, "5-*"); // TODO: make HF4 version - //GENERATE_AND_PLAY_HF(asset_current_and_total_supplies_comparative_constraints, "4-*"); <-- temporary disabled, waiting for Stepan's fix -- sowle - GENERATE_AND_PLAY_HF(several_asset_emit_burn_txs_in_pool, "5-*"); - GENERATE_AND_PLAY_HF(assets_transfer_with_smallest_amount, "4-*"); - GENERATE_AND_PLAY_HF(asset_operations_and_chain_switching, "4-*"); - - GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*"); - GENERATE_AND_PLAY_HF(wallet_reorganize_and_trim_test, "4-*"); - GENERATE_AND_PLAY_HF(wallet_rpc_thirdparty_custody, "5-*"); - - GENERATE_AND_PLAY_HF(attachment_isolation_test, "4-*"); - - // GENERATE_AND_PLAY(gen_block_reward); - // END OF TESTS */ + // run + run_registered_tests( + stop_on_first_fail, + skip_all_till_the_end, + tests_count, + unique_tests_count, + failed_tests, + tests_running_time, + is_test_eligible_to_run, + is_hf_test_eligible_to_run); size_t failed_postponed_tests_count = 0; uint64_t total_time = 0; @@ -1427,48 +1571,83 @@ int main(int argc, char* argv[]) uint64_t max_time = std::max_element(tests_running_time.begin(), tests_running_time.end(), [](tests_running_time_t::value_type& lhs, tests_running_time_t::value_type& rhs)->bool { return lhs.second < rhs.second; })->second; uint64_t max_test_name_len = std::max_element(tests_running_time.begin(), tests_running_time.end(), [](tests_running_time_t::value_type& lhs, tests_running_time_t::value_type& rhs)->bool { return lhs.first.size() < rhs.first.size(); })->first.size(); size_t bar_width = 70; - for (auto i : tests_running_time) + + if (!is_worker) + { + for (auto& i : tests_running_time) + { + bool failed = failed_tests.count(i.first) != 0; + bool postponed = postponed_tests.count(i.first) != 0; + if (failed && postponed) + ++failed_postponed_tests_count; + std::string bar(bar_width * i.second / max_time, '#'); + std::cout << (failed ? (postponed ? concolor::yellow : concolor::magenta) : concolor::green) << std::left << std::setw(max_test_name_len + 1) << i.first << "\t" << std::setw(10) << i.second << " ms \t" << bar << std::endl; + total_time += i.second; + } + } + else { - bool failed = failed_tests.count(i.first) != 0; - bool postponed = postponed_tests.count(i.first) != 0; - if (failed && postponed) - ++failed_postponed_tests_count; - std::string bar(bar_width * i.second / max_time, '#'); - std::cout << (failed ? (postponed ? concolor::yellow : concolor::magenta) : concolor::green) << std::left << std::setw(max_test_name_len + 1) << i.first << "\t" << std::setw(10) << i.second << " ms \t" << bar << std::endl; - total_time += i.second; + for (const auto& i : tests_running_time) + total_time += i.second; + + for (const auto& name : failed_tests) + if (postponed_tests.count(name) != 0) + ++failed_postponed_tests_count; } } - if (skip_all_till_the_end) + if (skip_all_till_the_end && !is_worker) std::cout << ENDL << concolor::yellow << "(execution interrupted at the first failure; not all tests were run)" << ENDL; serious_failures_count = failed_tests.size() - failed_postponed_tests_count; - if (!postponed_tests.empty()) + if (is_worker) { - std::cout << concolor::yellow << std::endl << postponed_tests.size() << " POSTPONED TESTS:" << std::endl; - for(auto& el : postponed_tests) - std::cout << " " << el << std::endl; + parallel_test_runner::worker_report rep; + rep.worker_id = static_cast(worker_id); + rep.processes = command_line::get_arg(g_vm, chaingen_args::arg_processes); + rep.tests_count = tests_count; + rep.unique_tests_count = unique_tests_count; + rep.total_time_ms = total_time; + rep.tests_running_time = tests_running_time; + rep.failed_tests = failed_tests; + rep.skip_all_till_the_end = skip_all_till_the_end; + rep.exit_code = (serious_failures_count == 0 ? 0 : 1); + + if (g_runner) + (void)g_runner->write_worker_report(rep); } - - std::cout << (serious_failures_count == 0 ? concolor::green : concolor::magenta); - std::cout << "\nREPORT:\n"; - std::cout << " Unique tests run: " << unique_tests_count << std::endl; - std::cout << " Total tests run: " << tests_count << std::endl; - - std::cout << " Failures: " << serious_failures_count << " (postponed failures: " << failed_postponed_tests_count << ")" << std::endl; - std::cout << " Postponed: " << postponed_tests.size() << std::endl; - std::cout << " Total time: " << total_time / 1000 << " s. (" << (tests_count > 0 ? total_time / tests_count : 0) << " ms per test in average)" << std::endl; - if (!failed_tests.empty()) + + if (!is_worker) { - std::cout << "FAILED/POSTPONED TESTS:\n"; - for (auto test_name : failed_tests) + if (!postponed_tests.empty()) { - bool postponed = postponed_tests.count(test_name); - std::cout << " " << (postponed ? "POSTPONED: " : "FAILED: ") << test_name << '\n'; + std::cout << concolor::yellow << std::endl << postponed_tests.size() << " POSTPONED TESTS:" << std::endl; + for (const auto& el : postponed_tests) + std::cout << " " << el << std::endl; } + + std::cout << (serious_failures_count == 0 ? concolor::green : concolor::magenta); + std::cout << "\nREPORT:\n"; + std::cout << " Unique tests run: " << unique_tests_count << std::endl; + std::cout << " Total tests run: " << tests_count << std::endl; + + std::cout << " Failures: " << serious_failures_count << " (postponed failures: " << failed_postponed_tests_count << ")" << std::endl; + std::cout << " Postponed: " << postponed_tests.size() << std::endl; + std::cout << " Total time: " << total_time / 1000 << " s. (" << (tests_count > 0 ? total_time / tests_count : 0) << " ms per test in average)" << std::endl; + + if (!failed_tests.empty()) + { + std::cout << "FAILED/POSTPONED TESTS:\n"; + for (auto& test_name : failed_tests) + { + bool postponed = postponed_tests.count(test_name) != 0; + std::cout << " " << (postponed ? "POSTPONED: " : "FAILED: ") << test_name << '\n'; + } + } + + std::cout << concolor::normal << std::endl; } - std::cout << concolor::normal << std::endl; } /*{ diff --git a/tests/core_tests/parallel/parallel_test_runner.cpp b/tests/core_tests/parallel/parallel_test_runner.cpp new file mode 100644 index 000000000..d8e1e34a7 --- /dev/null +++ b/tests/core_tests/parallel/parallel_test_runner.cpp @@ -0,0 +1,764 @@ +#include "chaingen.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "parallel_test_runner.h" +#include "../chaingen_args.h" +#include "../../src/common/command_line.h" +#include "tests_distribution_entry.h" + +using namespace chaingen_args; +namespace bp = boost::process; + +namespace +{ + constexpr const int UNKNOWN_ERROR_EXIT_CODE = -999; + constexpr const int MS_PER_SECOND = 1000; + constexpr const int RESERVE_UNIQUE_TESTS_HINT = 8192; +} + +parallel_test_runner::parallel_test_runner( + const boost::program_options::variables_map& vm) + : m_vm(vm) +{} + +int parallel_test_runner::run_parent_if_needed(int argc, char* argv[], const std::vector& g_test_jobs) const +{ + const int rc = run_workers_and_wait(argc, argv, g_test_jobs); + return (rc < 0) ? k_not_parent : rc; +} + +bool parallel_test_runner::write_worker_report(const worker_report& rep) const +{ + return write_worker_report_file(rep); +} + +std::filesystem::path parallel_test_runner::get_run_root_path() const +{ + std::string run_root = command_line::get_arg(m_vm, arg_run_root); + if (run_root.empty()) + run_root = paths::default_run_root; + + std::filesystem::path run_root_path(run_root); + + if (run_root_path.is_relative()) + run_root_path = std::filesystem::absolute(run_root_path); + + return run_root_path; +} + +std::filesystem::path parallel_test_runner::get_worker_dir_path(uint32_t worker_id) const +{ + return get_run_root_path() / (paths::worker_dir_prefix + std::to_string(worker_id)); +} + +std::filesystem::path parallel_test_runner::get_worker_report_path(uint32_t worker_id) const +{ + return get_worker_dir_path(worker_id) / files::worker_report; +} + +std::string parallel_test_runner::make_worker_data_dir(const std::filesystem::path& run_root_abs, int worker_id) const +{ + std::filesystem::path worker_dir = run_root_abs / (std::string(paths::worker_dir_prefix) + std::to_string(worker_id)); + std::filesystem::create_directories(worker_dir); + return worker_dir.string(); +} + +// Removes worker-specific arguments from the command line +std::vector parallel_test_runner::build_base_args_without_worker_specific(int argc, char* argv[]) const +{ + static const std::set worker_specific_args = { + cli_args::multiprocess_worker_id, cli_args::multiprocess_run, cli_args::multiprocess_run_root, + cli_args::multiprocess_shm_name, cli_args::data_dir, + }; + + std::vector args; + for (int i = 1; i < argc; ++i) + { + std::string current_arg = argv[i]; + bool skip = false; + for (const auto& wa : worker_specific_args) + { + if (current_arg == wa || current_arg.find(wa + "=") == 0) + { + if (current_arg == wa && i + 1 < argc) ++i; + skip = true; + break; + } + } + if (!skip) + args.emplace_back(std::move(current_arg)); + } + return args; +} + +void fill_postponed_tests_set(std::set& postponed_tests); + +int parallel_test_runner::print_aggregated_report_and_return_rc(uint32_t processes, uint64_t wall_time_ms, const coretests_shm::shared_state* shm_state, const std::vector& jobs) const +{ + std::vector reps; + reps.reserve(processes); + + bool any_missing = false; + int failed_workers = 0; + + for (uint32_t i = 0; i < processes; ++i) + { + worker_report r; + if (!read_worker_report_file(i, r)) + { + any_missing = true; + continue; + } + reps.push_back(std::move(r)); + } + + size_t total_tests_count = 0; + size_t total_unique_tests_count = 0; + + std::set failed_tests_union; + std::unordered_set executed_tests_union; + for (const auto& r : reps) + { + if (r.exit_code != 0) + ++failed_workers; + + total_tests_count += r.tests_count; + total_unique_tests_count += r.unique_tests_count; + failed_tests_union.insert(r.failed_tests.begin(), r.failed_tests.end()); + for (const auto& it : r.tests_running_time) + executed_tests_union.insert(it.first); + } + + std::set postponed_tests; + fill_postponed_tests_set(postponed_tests); + + if (shm_state) + { + uint32_t n_fails = shm_state->write_idx.load(std::memory_order_relaxed); + if (n_fails > coretests_shm::k_max_fails) + n_fails = coretests_shm::k_max_fails; + + for (uint32_t j = 0; j < n_fails; ++j) + { + const coretests_shm::fail_entry& e = shm_state->entries[j]; + if (e.test_name[0] != '\0') + failed_tests_union.insert(std::string(e.test_name)); + if (e.test_name[0] != '\0') + executed_tests_union.insert(std::string(e.test_name)); + } + } + + if (!jobs.empty()) + { + for (const auto& j : jobs) + { + const bool failed = failed_tests_union.count(j.name) != 0; + const bool executed = executed_tests_union.count(j.name) != 0; + + if (failed) + std::cout << concolor::magenta << j.name << concolor::normal << '\n'; + else if (executed) + std::cout << concolor::green << j.name << concolor::normal << '\n'; + else + std::cout << concolor::yellow << j.name << concolor::normal << '\n'; + } + std::cout << '\n'; + } + + // 2) Postponed tests list + if (!postponed_tests.empty()) + { + std::cout << concolor::yellow << postponed_tests.size() << " POSTPONED TESTS:\n"; + for (const auto& el : postponed_tests) + std::cout << " " << el << '\n'; + std::cout << concolor::normal << '\n'; + } + + size_t failed_postponed_tests_count = 0; + for (const auto& t : failed_tests_union) + if (postponed_tests.count(t) != 0) + ++failed_postponed_tests_count; + + const size_t serious_failures_count = failed_tests_union.size() >= failed_postponed_tests_count ? (failed_tests_union.size() - failed_postponed_tests_count) : 0; + const bool ok = (!any_missing && failed_workers == 0 && serious_failures_count == 0); + + std::cout << (serious_failures_count == 0 ? concolor::green : concolor::magenta); + std::cout << "\nREPORT:\n"; + std::cout << " Unique tests run: " << total_unique_tests_count << '\n'; + std::cout << " Total tests run: " << total_tests_count << '\n'; + std::cout << " Failures: " << serious_failures_count << " (postponed failures: " << failed_postponed_tests_count << ")" << '\n'; + std::cout << " Postponed: " << postponed_tests.size() << '\n'; + std::cout << " Total time: " << (wall_time_ms / MS_PER_SECOND) << " s. (" << (total_tests_count > 0 ? (wall_time_ms / total_tests_count) : 0) << " ms per test in average)" << '\n'; + + if (!failed_tests_union.empty()) + { + std::cout << "FAILED/POSTPONED TESTS:\n"; + for (const auto& name : failed_tests_union) + { + const bool postponed = postponed_tests.count(name) != 0; + std::cout << " " << (postponed ? "POSTPONED: " : "FAILED: ") << name << "\n"; + } + } + + std::cout << concolor::normal << '\n'; + + if (!reps.empty()) + (void)write_workers_report_file(processes, reps); + + if (any_missing || failed_workers != 0) + return 1; + + return serious_failures_count == 0 ? 0 : 1; +} + +void parallel_test_runner::print_worker_failure_reasons(uint32_t processes, const std::vector& worker_exit_codes) const +{ + bool any = false; + + for (uint32_t i = 0; i < processes; ++i) + { + const int ec = (i < worker_exit_codes.size() ? worker_exit_codes[i] : UNKNOWN_ERROR_EXIT_CODE); + + worker_report rep; + const bool report_ok = read_worker_report_file(i, rep); + + if (report_ok && ec == 0 && rep.exit_code == 0) + continue; + + if (!any) + { + std::cout << "\nFAILURE REASONS (workers):\n"; + any = true; + } + + const std::filesystem::path worker_dir = get_worker_dir_path(i); + const std::filesystem::path report_path = get_worker_report_path(i); + const std::filesystem::path log_path = worker_dir / files::worker_log; + + std::cout << " report: " << (std::filesystem::exists(report_path) ? report_path.string() : ("missing (" + report_path.string() + ")")) << "\n"; + std::cout << " logs dir: " << worker_dir.string() << "\n"; + std::cout << " log file: " << (std::filesystem::exists(log_path) ? log_path.string() : ("missing (" + log_path.string() + ")")) << "\n"; + + if (!report_ok) + { + std::cout << " failed tests: (cannot read report)\n"; + continue; + } + + if (rep.failed_tests.empty()) + { + std::cout << " failed tests: (none in report)\n"; + continue; + } + + std::cout << " failed tests:\n"; + for (const auto& test_name : rep.failed_tests) + std::cout << " - " << test_name << "\n"; + } +} + +int parallel_test_runner::run_workers_and_wait(int argc, char* argv[], const std::vector& g_test_jobs) const +{ + const uint32_t processes = command_line::get_arg(m_vm, arg_processes); + const int32_t worker_id = command_line::get_arg(m_vm, arg_worker_id); + + if (worker_id >= 0) + return -1; + + if (command_line::get_arg(m_vm, command_line::arg_help)) return -1; + if (command_line::get_arg(m_vm, arg_generate_test_data)) return -1; + if (command_line::get_arg(m_vm, arg_play_test_data)) return -1; + if (command_line::get_arg(m_vm, arg_generate_and_play_test_data)) return -1; + if (command_line::get_arg(m_vm, arg_test_transactions)) return -1; + + const auto wall_start = std::chrono::steady_clock::now(); + const std::string run_root = command_line::get_arg(m_vm, arg_run_root); + std::vector base_args = build_base_args_without_worker_specific(argc, argv); + std::filesystem::path run_root_abs = run_root.empty() ? std::filesystem::path(paths::default_run_root) : std::filesystem::path(run_root); + + if (run_root_abs.is_relative()) + run_root_abs = std::filesystem::absolute(run_root_abs); + + base_args.emplace_back(cli_args::multiprocess_run_root); + base_args.emplace_back(run_root_abs.string()); + + std::string exe = (argv && argv[0]) ? std::string(argv[0]) : std::string(); + if (exe.empty()) + { + std::cout << concolor::magenta << "Cannot spawn workers: empty executable path (argv[0])" << concolor::normal << '\n'; + return 1; + } + + try + { + std::filesystem::path exe_path(exe); + + if (exe.find('/') == std::string::npos && exe.find('\\') == std::string::npos) + { + boost::filesystem::path resolved = bp::search_path(exe); + if (!resolved.empty()) + exe_path = std::filesystem::path(resolved.string()); + } + + if (exe_path.is_relative()) + exe_path = std::filesystem::absolute(exe_path); + + exe = exe_path.string(); + } + catch (const std::exception& e) + { + std::cout << concolor::magenta << "Failed to resolve executable path: " << e.what() << concolor::normal << '\n'; + } + catch (...) + { + std::cout << concolor::magenta << "Failed to resolve executable path: unknown error" << concolor::normal << '\n'; + } + + if (exe.empty() || !std::filesystem::exists(std::filesystem::path(exe))) + { + std::cout << concolor::magenta << "Cannot spawn workers: failed to resolve executable: " << exe << concolor::normal << '\n'; + return 1; + } + + const uint32_t parent_pid = +#if defined(_WIN32) || defined(_WIN64) + static_cast(::GetCurrentProcessId()); +#else + static_cast(::getpid()); +#endif + + const std::string shm_name = "zano_coretests_fail_" + std::to_string(parent_pid); + + namespace bip = boost::interprocess; + + bip::shared_memory_object::remove(shm_name.c_str()); + + int failed_workers = 0; + int rc = 1; + + try + { + bip::shared_memory_object shm(bip::create_only, shm_name.c_str(), bip::read_write); + shm.truncate(sizeof(coretests_shm::shared_state)); + bip::mapped_region region(shm, bip::read_write); + + coretests_shm::shared_state* st = new (region.get_address()) coretests_shm::shared_state(); + st->init(); + + base_args.emplace_back(cli_args::multiprocess_shm_name); + base_args.emplace_back(shm_name); + + struct worker_proc + { + bp::child child; + std::unique_ptr out; + std::unique_ptr file; + std::thread reader; + uint32_t wid = 0; + }; + + std::vector kids; + kids.reserve(processes); + std::mutex cout_mutex; + fill_shm_work_order(st, g_test_jobs); + + for (uint32_t i = 0; i < processes; ++i) + { + std::vector args = base_args; + + args.emplace_back(cli_args::multiprocess_worker_id); + args.emplace_back(std::to_string(i)); + + const std::string worker_data_dir = make_worker_data_dir(run_root_abs, static_cast(i)); + args.emplace_back(cli_args::data_dir); + args.emplace_back(worker_data_dir); + + const std::filesystem::path worker_dir = get_worker_dir_path(i); + std::filesystem::create_directories(worker_dir); + + try + { + const std::filesystem::path log_path = (worker_dir / files::worker_log).string(); + + worker_proc wp; + wp.wid = i; + wp.out = std::make_unique(); + wp.file = std::make_unique(log_path, std::ios::out | std::ios::app); + + if (!wp.file->is_open()) + { + std::cout << concolor::magenta << "Failed to open log file for worker " << i << ": " << log_path << concolor::normal << '\n'; + failed_workers = 1; + break; + } + + wp.child = bp::child( + exe, + bp::args(args), + bp::start_dir = worker_dir.string(), + bp::std_out > *wp.out + ); + + wp.reader = std::thread([&, out = wp.out.get(), f = wp.file.get()]() + { + std::string line; + while (std::getline(*out, line)) + { + (*f) << line << '\n'; + { + std::lock_guard lk(cout_mutex); + std::cout << line << '\n'; + } + } + }); + + kids.emplace_back(std::move(wp)); + } + catch (const std::exception& e) + { + std::cout << concolor::magenta << "Failed to spawn worker " << i << ": " << e.what() << concolor::normal << '\n'; + + for (auto& p : kids) + if (p.child.running()) + p.child.terminate(); + for (auto& p : kids) + p.child.wait(); + for (auto& p : kids) + if (p.reader.joinable()) + p.reader.join(); + + failed_workers = 1; + break; + } + } + + for (auto& p : kids) + p.child.wait(); + + for (auto& p : kids) + if (p.reader.joinable()) + p.reader.join(); + + for (const auto& p : kids) + if (p.child.exit_code() != 0) + ++failed_workers; + + const auto wall_end = std::chrono::steady_clock::now(); + const uint64_t wall_time_ms = static_cast(std::chrono::duration_cast(wall_end - wall_start).count()); + + rc = print_aggregated_report_and_return_rc(processes, wall_time_ms, st, g_test_jobs); + + if (failed_workers != 0 && rc == 0) + rc = 1; + + std::vector exit_codes; + exit_codes.reserve(kids.size()); + for (const auto& p : kids) + exit_codes.push_back(p.child.exit_code()); + + const uint32_t spawned = static_cast(kids.size()); + + if (failed_workers != 0 || rc != 0) + print_worker_failure_reasons(spawned, exit_codes); + } + catch (const std::exception& e) + { + std::cout << concolor::magenta << "Cannot spawn workers: " << e.what() << concolor::normal << std::endl; + rc = 1; + } + catch (...) + { + std::cout << concolor::magenta << "Cannot spawn workers: unknown error" << concolor::normal << std::endl; + rc = 1; + } + + bip::shared_memory_object::remove(shm_name.c_str()); + return rc; +} + +void parallel_test_runner::fill_worker_report_header(pt::ptree& root, const worker_report& rep) const +{ + root.put("worker_id", rep.worker_id); + root.put("processes", rep.processes); + root.put("tests_count", static_cast(rep.tests_count)); + root.put("unique_tests_count", static_cast(rep.unique_tests_count)); + root.put("total_time_ms", rep.total_time_ms); + root.put("skip_all_till_the_end", rep.skip_all_till_the_end); + root.put("exit_code", rep.exit_code); +} + +void parallel_test_runner::read_worker_report_header(const pt::ptree& root, uint32_t worker_id_fallback, worker_report& rep) const +{ + rep.worker_id = root.get("worker_id", worker_id_fallback); + rep.processes = root.get("processes", 1); + rep.tests_count = static_cast(root.get("tests_count", 0)); + rep.unique_tests_count = static_cast(root.get("unique_tests_count", 0)); + rep.total_time_ms = root.get("total_time_ms", 0); + rep.skip_all_till_the_end = root.get("skip_all_till_the_end", false); + rep.exit_code = root.get("exit_code", 1); +} + +bool parallel_test_runner::write_worker_report_file(const worker_report& rep) const +{ + try + { + pt::ptree root; + std::filesystem::create_directories(get_worker_dir_path(rep.worker_id)); + fill_worker_report_header(root, rep); + + pt::ptree failed_arr; + for (const auto& s : rep.failed_tests) + { + pt::ptree v; + v.put("", s); + failed_arr.push_back(std::make_pair("", v)); + } + root.add_child("failed_tests", failed_arr); + + pt::ptree times_arr; + for (const auto& it : rep.tests_running_time) + { + pt::ptree node; + node.put("name", it.first); + node.put("ms", it.second); + times_arr.push_back(std::make_pair("", node)); + } + root.add_child("tests_running_time", times_arr); + + std::ofstream out(get_worker_report_path(rep.worker_id)); + if (!out.is_open()) + return false; + + pt::write_json(out, root, /*pretty*/true); + return true; + } + catch (const std::exception& e) + { + std::lock_guard lk(cerr_mx); + std::cerr << "write_worker_report_file: exception: " << e.what() << ", worker_id=" << rep.worker_id << std::endl; + return false; + } + catch (...) + { + std::lock_guard lk(cerr_mx); + std::cerr << "write_worker_report_file: unknown exception, worker_id=" << rep.worker_id << std::endl; + return false; + } +} + +bool parallel_test_runner::read_worker_report_file(uint32_t worker_id, worker_report& rep) const +{ + try + { + const std::filesystem::path path = get_worker_report_path(worker_id); + if (!std::filesystem::exists(path)) + return false; + + pt::ptree root; + pt::read_json(path.string(), root); + read_worker_report_header(root, worker_id, rep); + + for (const auto& v : root.get_child("failed_tests", pt::ptree())) + { + const std::string name = v.second.get_value(); + if (!name.empty()) + rep.failed_tests.insert(name); + } + + for (const auto& v : root.get_child("tests_running_time", pt::ptree())) + { + const auto& node = v.second; + const std::string name = node.get("name", ""); + const uint64_t ms = node.get("ms", 0); + if (!name.empty()) + rep.tests_running_time.emplace_back(name, ms); + } + + return true; + } + catch (const std::exception& e) + { + std::lock_guard lk(cerr_mx); + std::cerr << "read_worker_report_file: exception: " << e.what() << ", worker_id=" << worker_id << ", path=" << get_worker_report_path(worker_id).string() << std::endl; + return false; + } + catch (...) + { + std::lock_guard lk(cerr_mx); + std::cerr << "read_worker_report_file: unknown exception" << ", worker_id=" << worker_id << ", path=" << get_worker_report_path(worker_id).string() << std::endl; + return false; + } +} + +std::filesystem::path parallel_test_runner::get_worker_log_path(uint32_t worker_id) const +{ + return get_worker_dir_path(worker_id) / files::worker_log; +} + +std::filesystem::path parallel_test_runner::get_taken_tests_log_path_for_this_process() const +{ + const uint32_t processes = command_line::get_arg(m_vm, arg_processes); + const int32_t worker_id = command_line::get_arg(m_vm, arg_worker_id); + + if (processes > 1 && worker_id >= 0) + { + const uint32_t wid = static_cast(worker_id); + std::filesystem::create_directories(get_worker_dir_path(wid)); + return get_worker_dir_path(wid) / files::taken_tests_log; + } + + const std::string data_dir = command_line::get_arg(m_vm, command_line::arg_data_dir); + std::filesystem::create_directories(data_dir); + return std::filesystem::path(data_dir) / files::taken_tests_log; +} + +void parallel_test_runner::log_test_taken_by_this_process(const std::string& test_name) const +{ + std::filesystem::path p; + + try + { + p = get_taken_tests_log_path_for_this_process(); + + std::ofstream f(p, std::ios::out | std::ios::binary | std::ios::app); + if (!f.is_open()) + return; + + f << test_name << '\n'; + } + catch (const std::exception& e) + { + std::lock_guard lk(cerr_mx); + std::cerr << "parallel_test_runner::log_test_taken_by_this_process: exception: " << e.what() << ", test=" << test_name << ", path=" << p.string() << std::endl; + } + catch (...) + { + std::lock_guard lk(cerr_mx); + std::cerr << "parallel_test_runner::log_test_taken_by_this_process: unknown exception" << ", test=" << test_name << ", path=" << p.string() << std::endl; + } +} + +std::filesystem::path parallel_test_runner::get_workers_report_path() const +{ + return get_run_root_path() / WORKERS_REPORT_FILENAME; +} + +bool parallel_test_runner::write_workers_report_file(uint32_t processes, const std::vector& reps) const +{ + try + { + std::unordered_map ms_by_test; + ms_by_test.reserve(RESERVE_UNIQUE_TESTS_HINT); + + for (const auto& r : reps) + { + for (const auto& it : r.tests_running_time) + { + const std::string& name = it.first; + const uint64_t ms = it.second; + auto ins = ms_by_test.emplace(name, ms); + if (!ins.second) + ins.first->second = std::max(ins.first->second, ms); + } + } + + std::vector> tests; + tests.reserve(ms_by_test.size()); + for (auto& kv : ms_by_test) + tests.emplace_back(std::move(kv.first), kv.second); + + std::sort(tests.begin(), tests.end(), [](const auto& a, const auto& b) + { + if (a.second != b.second) return a.second > b.second; + return a.first < b.first; + }); + + pt::ptree root; + root.put("format", 1); + root.put("processes", processes); + + pt::ptree arr; + for (const auto& t : tests) + { + pt::ptree node; + node.put("name", t.first); + node.put("ms", t.second); + arr.push_back(std::make_pair("", node)); + } + root.add_child("tests", arr); + + std::filesystem::path path = get_workers_report_path(); + std::ofstream out(path); + if (!out.is_open()) + { + std::lock_guard lk(cerr_mx); + std::cerr << "write_workers_report_file: cannot open file: " << path.string() << std::endl; + return false; + } + + pt::write_json(out, root, /*pretty*/true); + return true; + } + catch (const std::exception& e) + { + std::lock_guard lk(cerr_mx); + std::cerr << "write_workers_report_file: exception: " << e.what() << ", path=" << get_workers_report_path().string() << std::endl; + return false; + } + catch (...) + { + std::lock_guard lk(cerr_mx); + std::cerr << "write_workers_report_file: unknown exception, path=" << get_workers_report_path().string() << std::endl; + return false; + } +} + +bool parallel_test_runner::fill_shm_work_order(coretests_shm::shared_state* st, const std::vector& jobs) const +{ + if (!st) + return false; + + struct item_t { uint64_t ms; uint32_t idx; }; + + std::vector items; + items.reserve(jobs.size()); + + for (uint32_t i = 0; i < jobs.size(); ++i) + { + const uint64_t ms = get_test_estimated_ms(jobs[i].name); + items.push_back(item_t{ms, i}); + } + + std::sort(items.begin(), items.end(), [](const item_t& a, const item_t& b) + { + if (a.ms != b.ms) return a.ms > b.ms; + return a.idx < b.idx; + }); + + const uint32_t total = static_cast(items.size()); + if (total > coretests_shm::k_max_tests) + { + st->tests_total.store(coretests_shm::k_max_tests, std::memory_order_release); + st->next_pos.store(0, std::memory_order_release); + st->stop_all.store(false, std::memory_order_release); + for (uint32_t i = 0; i < coretests_shm::k_max_tests; ++i) + st->order[i] = i; + return false; + } + + st->tests_total.store(total, std::memory_order_release); + st->next_pos.store(0, std::memory_order_release); + st->stop_all.store(false, std::memory_order_release); + + for (uint32_t pos = 0; pos < total; ++pos) + st->order[pos] = items[pos].idx; + + return true; +} diff --git a/tests/core_tests/parallel/parallel_test_runner.h b/tests/core_tests/parallel/parallel_test_runner.h new file mode 100644 index 000000000..ae8cf30af --- /dev/null +++ b/tests/core_tests/parallel/parallel_test_runner.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pt = boost::property_tree; + +struct test_job +{ + std::string name; + std::function run; +}; + +namespace coretests_shm +{ + constexpr uint32_t k_max_fails = 4096; + constexpr uint32_t k_max_tests = 20000; + constexpr uint32_t k_name_max = 256; + + struct fail_entry + { + uint32_t worker_id; + char test_name[k_name_max]; + }; + + struct shared_state + { + // fail reporting + std::atomic write_idx{0}; + fail_entry entries[k_max_fails]; + + // work distribution + std::atomic next_pos{0}; + std::atomic tests_total{0}; + std::atomic stop_all{false}; + + uint32_t order[k_max_tests]; + + void record_fail(uint32_t wid, const char* name) + { + const uint32_t pos = write_idx.fetch_add(1, std::memory_order_acq_rel); + if (pos >= k_max_fails) + return; + + entries[pos].worker_id = wid; + std::strncpy(entries[pos].test_name, name ? name : "", k_name_max - 1); + entries[pos].test_name[k_name_max - 1] = '\0'; + } + + void init() + { + write_idx.store(0, std::memory_order_relaxed); + next_pos.store(0, std::memory_order_relaxed); + tests_total.store(0, std::memory_order_relaxed); + stop_all.store(false, std::memory_order_relaxed); + } + + bool try_take_next(uint32_t& out_test_idx) + { + const uint32_t total = tests_total.load(std::memory_order_acquire); + const uint32_t pos = next_pos.fetch_add(1, std::memory_order_acq_rel); + if (pos >= total) + return false; + + out_test_idx = order[pos]; + return true; + } + }; +} + +class parallel_test_runner +{ +public: + static constexpr int k_not_parent = -1; + + explicit parallel_test_runner(const boost::program_options::variables_map& vm); + + struct worker_report + { + uint32_t worker_id = 0; + uint32_t processes = 1; + + size_t tests_count = 0; + size_t unique_tests_count = 0; + + uint64_t total_time_ms = 0; + + std::vector> tests_running_time; + std::set failed_tests; + bool skip_all_till_the_end = false; + + int exit_code = 0; + }; + + int run_parent_if_needed(int argc, char* argv[], const std::vector& g_test_jobs) const; + bool write_worker_report(const worker_report& rep) const; + void log_test_taken_by_this_process(const std::string& test_name) const; + +private: + struct paths + { + static constexpr const char* default_run_root = "chaingen_runs"; + static constexpr const char* worker_dir_prefix = "w"; + }; + + struct files + { + static constexpr const char* taken_tests_log = "taken_tests.log"; + static constexpr const char* worker_report = "coretests_report.json"; + static constexpr const char* worker_log = "worker.log"; + }; + + struct cli_args + { + static constexpr const char* multiprocess_worker_id = "--multiprocess-worker-id"; + static constexpr const char* multiprocess_run = "--multiprocess-run"; + static constexpr const char* multiprocess_run_root = "--multiprocess-run-root"; + static constexpr const char* multiprocess_shm_name = "--multiprocess-shm-name"; + static constexpr const char* data_dir = "--data-dir"; + }; + + const boost::program_options::variables_map& m_vm; + mutable std::mutex cerr_mx; + + std::filesystem::path get_run_root_path() const; + std::filesystem::path get_worker_report_path(uint32_t worker_id) const; + std::filesystem::path get_worker_log_path(uint32_t worker_id) const; + std::string make_worker_data_dir(const std::filesystem::path& run_root_abs, int worker_id) const; + std::filesystem::path get_worker_dir_path(uint32_t worker_id) const; + std::filesystem::path get_taken_tests_log_path_for_this_process() const; + std::filesystem::path get_workers_report_path() const; + + std::vector build_base_args_without_worker_specific(int argc, char* argv[]) const; + + int run_workers_and_wait(int argc, char* argv[], const std::vector& g_test_jobs) const; + int print_aggregated_report_and_return_rc(uint32_t processes, uint64_t wall_time_ms, const coretests_shm::shared_state* shm_state, const std::vector& jobs) const; + void print_worker_failure_reasons(uint32_t processes, const std::vector& worker_exit_codes) const; + + bool fill_shm_work_order(coretests_shm::shared_state* st, const std::vector& jobs) const; + + bool write_workers_report_file(uint32_t processes, const std::vector& reps) const; + bool write_worker_report_file(const worker_report& rep) const; + bool read_worker_report_file(uint32_t worker_id, worker_report& rep) const; + void fill_worker_report_header(pt::ptree& root, const worker_report& rep) const; + void read_worker_report_header(const pt::ptree& root, uint32_t worker_id_fallback, worker_report& rep) const; +}; diff --git a/tests/core_tests/parallel/tests_distribution_entry.h b/tests/core_tests/parallel/tests_distribution_entry.h new file mode 100644 index 000000000..ab62fdb16 --- /dev/null +++ b/tests/core_tests/parallel/tests_distribution_entry.h @@ -0,0 +1,492 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct test_time_hint { + std::string_view name; + uint64_t ms; +}; + +// Aggregated per-test timings are written to: /workers_report.json (default run_root is "chaingen_runs") +inline const std::vector k_test_time_hints = { + {"gen_alias_tests @ HF 4", 102170}, + {"gen_alias_tests @ HF 5", 102154}, + {"gen_alias_tests @ HF 6", 102083}, + {"gen_alias_tests @ HF 3", 19867}, + {"gen_alias_too_many_regs_in_block_template @ HF 3", 19632}, + {"pos_and_no_pow_blocks_between_output_and_stake @ HF 4", 8235}, + {"pos_and_no_pow_blocks_between_output_and_stake @ HF 5", 8215}, + {"pos_and_no_pow_blocks_between_output_and_stake @ HF 6", 8152}, + {"gen_pos_basic_tests (BC saveload)", 5721}, + {"hard_fork_6_intrinsic_payment_id_rpc_test", 5057}, + {"multiassets_basic_test @ HF 4", 5029}, + {"multiassets_basic_test @ HF 5", 5026}, + {"multiassets_basic_test @ HF 6", 5015}, + {"gen_alias_too_small_reward @ HF 4", 4517}, + {"gen_alias_too_small_reward @ HF 5", 4508}, + {"gen_alias_too_small_reward @ HF 6", 4500}, + {"input_refers_to_incompatible_by_type_output", 4349}, + {"wallet_rpc_thirdparty_custody @ HF 5", 4026}, + {"wallet_rpc_thirdparty_custody @ HF 6", 4007}, + {"hard_fork_6_intrinsic_payment_id_basic_test", 3932}, + {"asset_operations_and_chain_switching @ HF 5", 3801}, + {"zarcanum_in_alt_chain", 3781}, + {"asset_operations_and_chain_switching @ HF 6", 3778}, + {"escrow_custom_test", 3707}, + {"asset_operations_and_chain_switching @ HF 4", 3672}, + {"pos_altblocks_validation @ HF 4", 3596}, + {"pos_altblocks_validation @ HF 6", 3575}, + {"pos_altblocks_validation @ HF 5", 3546}, + {"zarcanum_basic_test", 3530}, + {"wallet_true_rpc_pos_mining @ HF 4", 3521}, + {"zarcanum_gen_time_balance", 3504}, + {"wallet_rpc_cold_signing @ HF 6", 3450}, + {"wallet_rpc_cold_signing @ HF 5", 3436}, + {"gen_wallet_alias_and_unconfirmed_txs @ HF 5", 3433}, + {"gen_wallet_alias_and_unconfirmed_txs @ HF 6", 3424}, + {"gen_wallet_alias_and_unconfirmed_txs @ HF 4", 3407}, + {"tx_pool_validation_and_chain_switch @ HF 4", 3368}, + {"tx_pool_validation_and_chain_switch @ HF 5", 3348}, + {"gen_wallet_alias_via_special_wallet_funcs @ HF 4", 3063}, + {"gen_wallet_alias_via_special_wallet_funcs @ HF 5", 3048}, + {"ionic_swap_basic_test @ HF 4", 3035}, + {"ionic_swap_basic_test @ HF 6", 3029}, + {"block_reward_in_alt_chain_basic @ HF 5", 3026}, + {"gen_wallet_alias_via_special_wallet_funcs @ HF 6", 3022}, + {"ionic_swap_basic_test @ HF 5", 3021}, + {"block_reward_in_alt_chain_basic @ HF 6", 3005}, + {"assets_and_explicit_native_coins_in_outs", 3002}, + {"gen_alias_switch_and_check_block_template @ HF 4", 2995}, + {"gen_alias_switch_and_check_block_template @ HF 5", 2986}, + {"assets_transfer_with_smallest_amount @ HF 4", 2982}, + {"ionic_swap_exact_amounts_test @ HF 4", 2954}, + {"assets_transfer_with_smallest_amount @ HF 5", 2953}, + {"ionic_swap_exact_amounts_test @ HF 6", 2945}, + {"gen_alias_switch_and_check_block_template @ HF 6", 2942}, + {"ionic_swap_exact_amounts_test @ HF 5", 2924}, + {"gen_wallet_save_load_and_balance @ HF 6", 2840}, + {"wallet_reorganize_and_trim_test @ HF 5", 2835}, + {"wallet_reorganize_and_trim_test @ HF 6", 2832}, + {"hard_fork_2_auditable_addresses_basics @ HF 6", 2810}, + {"hard_fork_2_auditable_addresses_basics @ HF 4", 2795}, + {"hard_fork_2_auditable_addresses_basics @ HF 5", 2793}, + {"wallet_reorganize_and_trim_test @ HF 4", 2791}, + {"gen_wallet_save_load_and_balance @ HF 5", 2779}, + {"block_reward_in_main_chain_basic @ HF 4", 2766}, + {"gen_wallet_save_load_and_balance @ HF 4", 2757}, + {"block_reward_in_main_chain_basic @ HF 5", 2749}, + {"block_reward_in_main_chain_basic @ HF 6", 2728}, + {"asset_operation_in_consolidated_tx @ HF 5", 2667}, + {"asset_operation_in_consolidated_tx @ HF 6", 2665}, + {"asset_operation_in_consolidated_tx @ HF 4", 2659}, + {"zarcanum_pos_block_math", 2657}, + {"hard_fork_2_awo_wallets_basic_test", 2613}, + {"block_choice_rule_bigger_fee @ HF 5", 2558}, + {"several_asset_emit_burn_txs_in_pool @ HF 5", 2557}, + {"block_reward_in_alt_chain_basic @ HF 4", 2544}, + {"block_choice_rule_bigger_fee @ HF 4", 2542}, + {"eth_signed_asset_via_rpc @ HF 5", 2540}, + {"eth_signed_asset_via_rpc @ HF 6", 2532}, + {"block_choice_rule_bigger_fee @ HF 6", 2528}, + {"asset_operation_and_hardfork_checks @ HF 4", 2523}, + {"several_asset_emit_burn_txs_in_pool @ HF 6", 2517}, + {"asset_operation_and_hardfork_checks @ HF 6", 2516}, + {"wallet_rpc_exchange_suite @ HF 4", 2504}, + {"asset_operation_and_hardfork_checks @ HF 5", 2487}, + {"assets_transfer_with_smallest_amount @ HF 6", 2472}, + {"gen_alias_strange_data @ HF 5", 2450}, + {"gen_alias_strange_data @ HF 4", 2438}, + {"chain_switching_when_out_spent_in_alt_chain_ref_id @ HF 6", 2435}, + {"chain_switching_when_out_spent_in_alt_chain_mixin @ HF 4", 2430}, + {"chain_switching_when_out_spent_in_alt_chain_ref_id @ HF 4", 2427}, + {"chain_switching_when_out_spent_in_alt_chain_ref_id @ HF 5", 2426}, + {"chain_switching_when_out_spent_in_alt_chain_mixin @ HF 5", 2425}, + {"gen_alias_strange_data @ HF 6", 2424}, + {"chain_switching_when_out_spent_in_alt_chain_mixin @ HF 6", 2405}, + {"gen_alias_same_alias_in_tx_pool @ HF 5", 2388}, + {"gen_alias_same_alias_in_tx_pool @ HF 6", 2381}, + {"gen_alias_same_alias_in_tx_pool @ HF 4", 2351}, + {"block_template_blacklist_test @ HF 5", 2348}, + {"block_template_blacklist_test @ HF 4", 2318}, + {"wallet_and_sweep_below @ HF 5", 2297}, + {"block_template_blacklist_test @ HF 6", 2284}, + {"gen_chain_switch_pow_pos", 2278}, + {"hardfork_4_wallet_transfer_with_mandatory_mixins @ HF 4", 2249}, + {"wallet_and_sweep_below @ HF 6", 2247}, + {"zarcanum_in_alt_chain_2 @ HF 4", 2242}, + {"hardfork_4_wallet_transfer_with_mandatory_mixins @ HF 6", 2237}, + {"wallet_and_sweep_below @ HF 4", 2226}, + {"hardfork_4_wallet_transfer_with_mandatory_mixins @ HF 5", 2218}, + {"gen_wallet_mine_pos_block @ HF 6", 2196}, + {"zarcanum_in_alt_chain_2 @ HF 6", 2194}, + {"escrow_incorrect_cancel_proposal", 2183}, + {"zarcanum_in_alt_chain_2 @ HF 5", 2181}, + {"alt_chain_and_block_tx_fee_median @ HF 5", 2124}, + {"alt_chain_and_block_tx_fee_median @ HF 4", 2107}, + {"hard_fork_4_consolidated_txs @ HF 5", 2106}, + {"alt_chain_and_block_tx_fee_median @ HF 6", 2102}, + {"isolate_auditable_and_proof @ HF 5", 2080}, + {"isolate_auditable_and_proof @ HF 6", 2079}, + {"hard_fork_4_consolidated_txs @ HF 6", 2077}, + {"escrow_wallet_test", 2076}, + {"hard_fork_5_tx_version @ HF 5", 2074}, + {"hard_fork_5_tx_version @ HF 6", 2071}, + {"tx_input_mixins", 2071}, + {"hard_fork_4_consolidated_txs @ HF 4", 2069}, + {"isolate_auditable_and_proof @ HF 4", 2056}, + {"hard_fork_2_awo_wallets_basic_test", 2041}, + {"eth_signed_asset_basics @ HF 6", 2033}, + {"wallet_true_rpc_pos_mining @ HF 5", 2024}, + {"eth_signed_asset_basics @ HF 5", 2022}, + {"zarcanum_block_with_txs", 2019}, + {"gen_alias_switch_and_tx_pool @ HF 4", 2017}, + {"wallet_true_rpc_pos_mining @ HF 6", 2015}, + {"gen_alias_switch_and_tx_pool @ HF 5", 2014}, + {"gen_alias_switch_and_tx_pool @ HF 6", 2007}, + {"assets_and_pos_mining @ HF 6", 1937}, + {"assets_and_pos_mining @ HF 4", 1929}, + {"zarcanum_txs_with_big_shuffled_decoy_set_shuffled", 1892}, + {"pos_fuse_test @ HF 4", 1877}, + {"pos_fuse_test @ HF 5", 1827}, + {"pos_fuse_test @ HF 6", 1814}, + {"escrow_w_and_fake_outputs", 1730}, + {"gen_wallet_mine_pos_block @ HF 4", 1688}, + {"gen_alias_concurrency_with_switch @ HF 6", 1674}, + {"gen_wallet_mine_pos_block @ HF 5", 1672}, + {"gen_alias_concurrency_with_switch @ HF 5", 1668}, + {"escrow_incorrect_proposal", 1667}, + {"gen_alias_concurrency_with_switch @ HF 4", 1642}, + {"wallet_rpc_alias_tests", 1642}, + {"hardfork_4_wallet_sweep_bare_outs", 1595}, + {"asset_depoyment_and_few_zc_utxos", 1593}, + {"prun_ring_signatures", 1560}, + {"gen_block_is_too_big @ HF 3", 1544}, + {"gen_block_is_too_big @ HF 0", 1540}, + {"asset_emission_and_unconfirmed_balance @ HF 4", 1528}, + {"attachment_isolation_test @ HF 4", 1522}, + {"attachment_isolation_test @ HF 5", 1520}, + {"asset_emission_and_unconfirmed_balance @ HF 6", 1512}, + {"asset_emission_and_unconfirmed_balance @ HF 5", 1511}, + {"escrow_incorrect_proposal_acceptance", 1510}, + {"gen_block_ts_not_checked @ HF 3", 1494}, + {"gen_block_ts_in_past @ HF 0", 1455}, + {"assets_and_pos_mining @ HF 5", 1424}, + {"gen_block_ts_not_checked @ HF 0", 1387}, + {"pos_minting_tx_packing", 1387}, + {"gen_block_ts_in_past @ HF 3", 1363}, + {"multisig_wallet_test", 1351}, + {"multisig_wallet_test_many_dst", 1332}, + {"gen_alias_update_for_free @ HF 5", 1331}, + {"gen_alias_update_for_free @ HF 6", 1320}, + {"gen_alias_update_for_free @ HF 4", 1296}, + {"alt_blocks_validation_and_same_new_amount_in_two_txs @ HF 6", 1281}, + {"alt_blocks_validation_and_same_new_amount_in_two_txs @ HF 5", 1273}, + {"hard_fork_1_locked_mining_test", 1264}, + {"alt_blocks_validation_and_same_new_amount_in_two_txs @ HF 4", 1256}, + {"gen_simple_chain_001", 1256}, + {"block_with_correct_prev_id_on_wrong_height @ HF 6", 1254}, + {"alt_blocks_with_the_same_txs @ HF 6", 1240}, + {"alt_blocks_with_the_same_txs @ HF 5", 1239}, + {"block_with_correct_prev_id_on_wrong_height @ HF 5", 1233}, + {"block_with_correct_prev_id_on_wrong_height @ HF 4", 1231}, + {"gen_alias_too_much_reward @ HF 5", 1227}, + {"alt_blocks_with_the_same_txs @ HF 4", 1224}, + {"gen_alias_too_much_reward @ HF 6", 1223}, + {"gen_alias_too_much_reward @ HF 4", 1216}, + {"escrow_altchain_meta_test<0>", 1214}, + {"hard_fork_1_pos_and_locked_coins", 1201}, + {"chain_switching_when_gindex_spent_in_both_chains @ HF 4", 1198}, + {"escrow_altchain_meta_test<5>", 1198}, + {"chain_switching_when_gindex_spent_in_both_chains @ HF 5", 1197}, + {"escrow_altchain_meta_test<1>", 1196}, + {"chain_switching_when_gindex_spent_in_both_chains @ HF 6", 1192}, + {"escrow_proposal_expiration", 1190}, + {"escrow_altchain_meta_test<6>", 1164}, + {"escrow_altchain_meta_test<4>", 1156}, + {"offers_updating_hack", 1152}, + {"gen_ring_signature_1", 1151}, + {"gen_tx_signatures_are_invalid", 1147}, + {"wallet_and_sweep_below @ HF 3", 1140}, + {"mix_in_spent_outs", 1136}, + {"wallet_watch_only_and_chain_switch @ HF 3", 1127}, + {"packing_outputs_on_pos_minting_wallet @ HF 3", 1123}, + {"gen_broken_attachments", 1117}, + {"hard_fork_2_auditable_addresses_basics @ HF 2", 1114}, + {"escrow_altchain_meta_test<8>", 1112}, + {"hard_fork_2_alias_update_using_old_tx", 1110}, + {"escrow_altchain_meta_test<7>", 1105}, + {"escrow_altchain_meta_test<2>", 1103}, + {"hard_fork_2_auditable_addresses_basics @ HF 3", 1103}, + {"escrow_cancellation_proposal_expiration", 1097}, + {"tx_coinbase_separate_sig_flag @ HF 5", 1097}, + {"wallet_unconfirmed_tx_expiration", 1093}, + {"escrow_proposal_and_accept_expiration", 1090}, + {"hard_fork_2_tx_extra_alias_entry_in_wallet", 1081}, + {"tx_coinbase_separate_sig_flag @ HF 4", 1080}, + {"tx_coinbase_separate_sig_flag @ HF 6", 1080}, + {"hard_fork_2_incorrect_alias_update", 1079}, + {"hard_fork_2_alias_update_using_old_tx", 1071}, + {"random_outs_and_burnt_coins", 1071}, + {"escrow_proposal_not_enough_money", 1066}, + {"escrow_cancellation_acceptance_expiration", 1064}, + {"hardfork_4_pop_tx_from_global_index @ HF 5", 1036}, + {"multisig_wallet_heterogenous_dst", 1032}, + {"hardfork_4_pop_tx_from_global_index @ HF 6", 1029}, + {"gen_pos_extra_nonce @ HF 6", 1026}, + {"hardfork_4_pop_tx_from_global_index @ HF 4", 1026}, + {"wallet_rpc_cold_signing @ HF 3", 1025}, + {"attachment_isolation_test @ HF 6", 1008}, + {"gen_pos_extra_nonce @ HF 5", 1008}, + {"gen_pos_extra_nonce @ HF 4", 1006}, + {"wallet_rpc_hardfork_verification @ HF 5", 976}, + {"gen_alias_tx_no_outs @ HF 5", 902}, + {"gen_alias_tx_no_outs @ HF 6", 883}, + {"gen_alias_tx_no_outs @ HF 4", 873}, + {"gen_alias_too_small_reward @ HF 3", 849}, + {"cumulative_difficulty_adjustment_test_alt", 803}, + {"gen_block_big_major_version", 781}, + {"gen_pos_incorrect_timestamp", 771}, + {"zarcanum_test_n_inputs_validation", 760}, + {"gen_wallet_alias_via_special_wallet_funcs @ HF 3", 755}, + {"cumulative_difficulty_adjustment_test", 711}, + {"gen_alias_in_coinbase @ HF 4", 711}, + {"gen_alias_in_coinbase @ HF 5", 709}, + {"gen_simple_chain_split_1", 700}, + {"gen_alias_in_coinbase @ HF 6", 698}, + {"tx_key_image_pool_conflict", 690}, + {"pos_altblocks_validation @ HF 3", 685}, + {"escrow_altchain_meta_test<3>", 682}, + {"hard_fork_1_checkpoint_basic_test", 682}, + {"gen_tx_unlock_time", 657}, + {"gen_wallet_save_load_and_balance @ HF 0", 657}, + {"offer_sig_validity_in_update_and_cancel", 657}, + {"gen_chain_switch_1", 653}, + {"multisig_and_coinbase", 652}, + {"gen_wallet_decrypted_payload_items", 644}, + {"gen_double_spend_in_the_same_block", 643}, + {"hard_fork_1_bad_pos_source", 640}, + {"mix_attr_tests", 640}, + {"multisig_out_make_and_spent_in_altchain", 636}, + {"gen_double_spend_in_different_blocks", 633}, + {"offer_removing_and_selected_output", 632}, + {"gen_wallet_save_load_and_balance @ HF 2", 629}, + {"gen_wallet_save_load_and_balance @ HF 3", 627}, + {"chain_switching_when_out_spent_in_alt_chain_mixin @ HF 3", 626}, + {"block_reward_in_main_chain_basic @ HF 3", 625}, + {"gen_tx_check_input_unlock_time", 624}, + {"gen_tx_extra_double_entry", 623}, + {"chain_switching_when_out_spent_in_alt_chain_ref_id @ HF 3", 622}, + {"alt_blocks_validation_and_same_new_amount_in_two_txs @ HF 3", 621}, + {"gen_double_spend_in_tx", 616}, + {"gen_double_spend_in_different_chains", 614}, + {"alt_chain_and_block_tx_fee_median @ HF 3", 610}, + {"hard_fork_1_unlock_time_2_in_normal_tx", 608}, + {"hard_fork_2_no_new_structures_before_hf", 608}, + {"hard_fork_1_pos_locked_height_vs_time", 606}, + {"gen_double_spend_in_alt_chain_in_the_same_block", 605}, + {"gen_wallet_mine_pos_block @ HF 3", 604}, + {"multisig_and_altchains", 604}, + {"gen_alias_switch_and_check_block_template @ HF 3", 600}, + {"wallet_unconfimed_tx_balance", 599}, + {"pow_pos_reorganize_specific_case", 598}, + {"gen_double_spend_in_alt_chain_in_different_blocks", 597}, + {"gen_double_spend_in_alt_chain_in_different_blocks", 594}, + {"hard_fork_2_incorrect_alias_update", 594}, + {"offers_tests", 594}, + {"gen_wallet_unlock_by_block_and_by_time", 593}, + {"gen_alias_concurrency_with_switch @ HF 3", 592}, + {"gen_ring_signature_2", 592}, + {"gen_wallet_alias_and_unconfirmed_txs @ HF 3", 591}, + {"gen_wallet_save_load_and_balance @ HF 1", 591}, + {"offers_filtering_1", 590}, + {"offers_multiple_update", 590}, + {"isolate_auditable_and_proof @ HF 3", 588}, + {"gen_alias_strange_data @ HF 3", 587}, + {"gen_double_spend_in_alt_chain_in_the_same_block", 587}, + {"gen_wallet_transfers_and_chain_switch", 587}, + {"hard_fork_1_chain_switch_pow_only", 587}, + {"gen_double_spend_in_the_same_block", 586}, + {"gen_double_spend_in_tx", 585}, + {"gen_wallet_payment_id", 583}, + {"chain_switching_and_tx_with_attachment_blobsize", 580}, + {"multisig_and_checkpoints @ HF 0", 580}, + {"hard_fork_4_consolidated_txs @ HF 3", 579}, + {"multisig_with_same_id_in_pool", 579}, + {"offer_lifecycle_via_tx_pool", 579}, + {"gen_crypted_attachments", 577}, + {"gen_uint_overflow_2", 577}, + {"gen_wallet_selecting_pos_entries", 577}, + {"wallet_chain_switch_with_spending_the_same_ki @ HF 3", 577}, + {"gen_wallet_basic_transfer", 576}, + {"ref_by_id_mixed_inputs_types", 576}, + {"gen_pos_coinstake_already_spent", 575}, + {"gen_wallet_refreshing_on_chain_switch", 574}, + {"pos_mining_with_decoys @ HF 3", 574}, + {"escrow_cancellation_and_tx_order", 573}, + {"offers_expiration_test", 573}, + {"block_reward_in_alt_chain_basic @ HF 3", 572}, + {"escrow_balance", 572}, + {"get_random_outs_test", 572}, + {"multisig_and_unlock_time", 572}, + {"multisig_minimum_sigs", 572}, + {"tx_expiration_time_and_chain_switching", 571}, + {"gen_pos_too_early_pos_block", 570}, + {"gen_wallet_transfers_and_outdated_unconfirmed_txs", 570}, + {"isolate_auditable_and_proof @ HF 2", 570}, + {"gen_checkpoints_set_after_switching_to_altchain", 568}, + {"gen_block_miner_tx_has_2_in @ HF 0", 567}, + {"gen_checkpoints_altchain_far_before_cp", 567}, + {"gen_wallet_oversized_payment_id", 566}, + {"gen_double_spend_in_different_blocks", 565}, + {"block_template_vs_invalid_txs_from_pool", 564}, + {"escrow_zero_amounts", 564}, + {"gen_wallet_refreshing_on_chain_switch_2", 563}, + {"gen_alias_switch_and_tx_pool @ HF 3", 562}, + {"multisig_n_participants_seq_signing", 561}, + {"gen_block_invalid_nonce @ HF 0", 560}, + {"ref_by_id_basics", 560}, + {"wallet_outputs_with_same_key_image", 560}, + {"wallet_rpc_transfer", 560}, + {"tx_pool_semantic_validation @ HF 3", 559}, + {"bad_chain_switching_with_rollback", 557}, + {"gen_tx_mixed_key_offest_not_exist", 557}, + {"multisig_and_checkpoints_bad_txs", 556}, + {"offer_cancellation_with_zero_fee", 556}, + {"gen_checkpoints_invalid_keyimage", 554}, + {"multisig_out_spent_in_altchain_case_b4", 554}, + {"gen_alias_same_alias_in_tx_pool @ HF 3", 552}, + {"gen_wallet_unconfirmed_tx_from_tx_pool", 552}, + {"tx_expiration_time", 552}, + {"alt_blocks_with_the_same_txs @ HF 3", 551}, + {"alt_chain_coins_pow_mined_then_spent", 550}, + {"gen_checkpoints_block_in_future_after_cp", 550}, + {"gen_checkpoints_pos_validation_on_altchain", 549}, + {"gen_tx_invalid_input_amount", 548}, + {"multisig_and_fake_outputs", 547}, + {"gen_wallet_offers_basic", 546}, + {"chain_switching_when_gindex_spent_in_both_chains @ HF 3", 545}, + {"gen_checkpoints_prun_txs_after_blockchain_load", 545}, + {"gen_tx_input_is_not_txin_to_key", 545}, + {"multisig_wallet_ms_to_ms", 545}, + {"gen_tx_has_inputs_no_outputs", 543}, + {"test_blockchain_vs_spent_keyimges", 542}, + {"gen_checkpoints_attachments_basic", 541}, + {"block_with_correct_prev_id_on_wrong_height @ HF 3", 540}, + {"gen_tx_key_offest_points_to_foreign_key", 539}, + {"gen_tx_output_with_zero_amount", 538}, + {"pos_wallet_big_block_test", 538}, + {"gen_checkpoints_altblock_before_and_after_cp", 537}, + {"gen_wallet_offers_size_limit", 537}, + {"gen_alias_update_for_free @ HF 3", 536}, + {"gen_tx_double_key_image", 536}, + {"gen_tx_input_wo_key_offsets", 536}, + {"gen_tx_no_inputs_has_outputs", 536}, + {"fill_tx_rpc_inputs", 535}, + {"wallet_sending_to_integrated_address", 535}, + {"gen_alias_too_much_reward @ HF 3", 534}, + {"gen_tx_key_image_not_derive_from_tx_key", 534}, + {"gen_wallet_unconfirmed_outdated_tx", 534}, + {"gen_block_miner_tx_with_txin_to_key @ HF 0", 533}, + {"gen_block_miner_tx_with_txin_to_key @ HF 3", 533}, + {"tx_expiration_time_and_block_template", 533}, + {"wallet_rpc_integrated_address_transfer", 532}, + {"gen_checkpoints_block_in_future", 531}, + {"gen_tx_txout_to_key_has_invalid_key", 531}, + {"gen_wallet_dust_to_account", 531}, + {"gen_tx_big_version", 528}, + {"test_blockchain_vs_spent_multisig_outs", 528}, + {"gen_block_miner_tx_has_2_in @ HF 3", 526}, + {"gen_no_attchments_in_coinbase_gentime", 525}, + {"gen_alias_tx_no_outs @ HF 3", 524}, + {"gen_no_attchments_in_coinbase @ HF 3", 524}, + {"gen_pos_min_allowed_height", 524}, + {"hardfork_4_wallet_transfer_with_mandatory_mixins @ HF 3", 524}, + {"gen_checkpoints_and_invalid_tx_to_pool", 523}, + {"gen_alias_tx_no_outs @ HF 2", 522}, + {"gen_pos_invalid_coinbase", 522}, + {"gen_tx_key_image_is_invalid", 521}, + {"gen_tx_sender_key_offest_not_exist", 521}, + {"multisig_wallet_same_dst_addr", 521}, + {"gen_alias_tx_no_outs @ HF 0", 520}, + {"gen_alias_tx_no_outs @ HF 1", 520}, + {"hard_fork_1_unlock_time_2_in_coinbase", 520}, + {"multisig_unconfirmed_transfer_and_multiple_scan_pool_calls", 520}, + {"gen_block_invalid_nonce @ HF 3", 519}, + {"gen_pos_extra_nonce @ HF 3", 519}, + {"gen_alias_in_coinbase @ HF 3", 514}, + {"gen_alias_reg_with_locked_money @ HF 1", 505}, + {"gen_alias_reg_with_locked_money @ HF 2", 505}, + {"gen_alias_reg_with_locked_money @ HF 3", 505}, + {"gen_alias_reg_with_locked_money @ HF 5", 505}, + {"gen_alias_reg_with_locked_money @ HF 6", 505}, + {"gen_block_big_major_version @ HF 0", 505}, + {"gen_block_has_invalid_tx @ HF 0", 505}, + {"gen_block_has_invalid_tx @ HF 3", 505}, + {"gen_block_height_is_high @ HF 0", 505}, + {"gen_block_height_is_high @ HF 3", 505}, + {"gen_block_miner_tx_has_2_tx_gen_in @ HF 0", 505}, + {"gen_block_miner_tx_has_no_out @ HF 3", 505}, + {"gen_block_miner_tx_has_out_to_initiator @ HF 0", 505}, + {"gen_block_miner_tx_out_is_big @ HF 3", 505}, + {"gen_block_miner_tx_out_is_small @ HF 0", 505}, + {"gen_block_no_miner_tx @ HF 0", 505}, + {"gen_block_unlock_time_is_high @ HF 3", 505}, + {"gen_block_unlock_time_is_timestamp_in_past @ HF 0", 505}, + {"gen_alias_reg_with_locked_money @ HF 0", 504}, + {"gen_alias_reg_with_locked_money @ HF 4", 504}, + {"gen_block_big_major_version @ HF 3", 504}, + {"gen_block_big_minor_version @ HF 0", 504}, + {"gen_block_big_minor_version @ HF 3", 504}, + {"gen_block_height_is_low @ HF 0", 504}, + {"gen_block_height_is_low @ HF 3", 504}, + {"gen_block_miner_tx_has_2_tx_gen_in @ HF 3", 504}, + {"gen_block_miner_tx_has_no_out @ HF 0", 504}, + {"gen_block_miner_tx_has_out_to_initiator @ HF 3", 504}, + {"gen_block_miner_tx_out_is_big @ HF 0", 504}, + {"gen_block_miner_tx_out_is_small @ HF 3", 504}, + {"gen_block_no_miner_tx @ HF 3", 504}, + {"gen_block_ts_in_future @ HF 0", 504}, + {"gen_block_ts_in_future @ HF 3", 504}, + {"gen_block_unlock_time_is_high @ HF 0", 504}, + {"gen_block_unlock_time_is_low @ HF 0", 504}, + {"gen_block_unlock_time_is_low @ HF 3", 504}, + {"gen_block_unlock_time_is_timestamp_in_future @ HF 0", 504}, + {"gen_block_unlock_time_is_timestamp_in_future @ HF 3", 504}, + {"gen_block_unlock_time_is_timestamp_in_past @ HF 3", 504}, + {"mined_balance_wallet_test", 504}, + {"wallet_rpc_integrated_address", 504}, + {"gen_block_wrong_version_agains_hardfork @ HF 0", 503}, + {"gen_block_wrong_version_agains_hardfork @ HF 3", 503}, + {"gen_tx_no_inputs_no_outputs", 503}, + {"one_block", 503}, + {"wallet_rpc_exchange_suite @ HF 3", 503}, + {"gen_tx_output_is_not_txout_to_key", 502}, +}; + +inline uint64_t get_test_time_ms(std::string_view test_name, uint64_t unknown_ms) +{ + for (const auto& e : k_test_time_hints) + if (e.name == test_name) return e.ms; + return unknown_ms; +} + +// Unknown tests should be considered the slowest to run first +inline uint64_t get_test_estimated_ms(std::string_view test_name) +{ + static const std::unordered_map m = []() + { + std::unordered_map r; + r.reserve(k_test_time_hints.size()); + for (const auto& e : k_test_time_hints) + r.emplace(e.name, e.ms); + return r; + }(); + + constexpr uint64_t k_unknown_ms = (std::numeric_limits::max)() / 4; + auto it = m.find(test_name); + return it == m.end() ? k_unknown_ms : it->second; +} \ No newline at end of file