diff --git a/src/core_test/codec/helper/codec.cc b/src/core_test/codec/helper/codec.cc index ea5617a..3396393 100644 --- a/src/core_test/codec/helper/codec.cc +++ b/src/core_test/codec/helper/codec.cc @@ -68,3 +68,21 @@ void raw_code_parallel(std::function)> &&func) { pool.wait(); } + +void short_code_parallel(std::function)> &&func) { + static auto codes = []() { + std::vector v (klotski::codec::SHORT_CODE_LIMIT); + std::iota(v.begin(), v.end(), 0); + return v; + }(); + + BS::thread_pool pool; + pool.detach_blocks((uint64_t)0, codes.size(), [func = std::move(func)](auto start, auto end) { + + auto span = std::span {codes.data() + start, end - start}; + func(std::bit_cast>(span)); + + }, 16); + + pool.wait(); +} diff --git a/src/core_test/codec/short_code.cc b/src/core_test/codec/short_code.cc index 203b386..2fbfc51 100644 --- a/src/core_test/codec/short_code.cc +++ b/src/core_test/codec/short_code.cc @@ -5,12 +5,15 @@ #include "helper/codec.h" #include "helper/sample.h" #include "utility/exposer.h" +#include "utility/concurrent.h" #include "all_cases/all_cases.h" #include "short_code/short_code.h" #include "common_code/common_code.h" +using klotski::cases::Ranges; using klotski::cases::AllCases; using klotski::cases::BasicRanges; +using klotski::cases::RangesUnion; using klotski::codec::ShortCode; using klotski::codec::CommonCode; @@ -19,11 +22,14 @@ using klotski::cases::ALL_CASES_NUM; using klotski::cases::ALL_CASES_NUM_; using klotski::codec::SHORT_CODE_LIMIT; -static const auto TEST_THREAD_NUM = 256; +constexpr auto TEST_THREAD_NUM = 256; /// Forcibly modify private variables to reset state. EXPOSE_VAR(AllCases, bool, available_) EXPOSE_VAR(BasicRanges, bool, available_) +EXPOSE_STATIC_VAR(ShortCode, bool, fast_) +EXPOSE_STATIC_VAR(ShortCode, const RangesUnion*, cases_) +EXPOSE_STATIC_VAR(ShortCode, std::atomic, ranges_) /// Reset basic ranges build state, note it is thread-unsafe. void basic_ranges_reset() { @@ -35,20 +41,25 @@ void all_cases_reset() { exposer::AllCases_available_(AllCases::instance()) = false; } -TEST(ShortCode, limit) { - auto all_cases_num = std::accumulate(ALL_CASES_NUM.begin(), ALL_CASES_NUM.end(), 0); - EXPECT_EQ(all_cases_num, SHORT_CODE_LIMIT); +void speed_up_reset() { + exposer::ShortCode_fast_() = false; + exposer::ShortCode_cases_() = nullptr; + exposer::ShortCode_ranges_() = nullptr; + exposer::AllCases_available_(AllCases::instance()) = false; + exposer::BasicRanges_available_(BasicRanges::instance()) = false; } -TEST(ShortCode, validity) { +TEST(ShortCode, basic) { EXPECT_FALSE(ShortCode::check(-1)); // out of short code range EXPECT_FALSE(ShortCode::check(29670987)); // out of short code range - EXPECT_FALSE(ShortCode::create(SHORT_CODE_LIMIT).has_value()); // invalid code EXPECT_FALSE(ShortCode::from_string("R50EH").has_value()); // with invalid `0` EXPECT_FALSE(ShortCode::from_string("123456").has_value()); // length != 5 EXPECT_FALSE(ShortCode::from_string("Z9EFV").has_value()); // out of short code range + const auto sum = std::accumulate(ALL_CASES_NUM.begin(), ALL_CASES_NUM.end(), 0); + EXPECT_EQ(sum, SHORT_CODE_LIMIT); + #ifndef KLSK_NDEBUG std::ostringstream out; out << ShortCode::unsafe_create(TEST_S_CODE); // ostream capture @@ -97,14 +108,14 @@ TEST(ShortCode, exporter) { auto short_code = ShortCode::unsafe_create(TEST_S_CODE); EXPECT_EQ(short_code.unwrap(), TEST_S_CODE); EXPECT_EQ(short_code.to_string(), TEST_S_CODE_STR); - EXPECT_EQ(short_code.to_common_code(), TEST_C_CODE); - // TODO: test fast mode of `to_common_code` + speed_up_reset(); + EXPECT_EQ(short_code.to_common_code(), TEST_C_CODE); + ShortCode::speed_up(true); + EXPECT_EQ(short_code.to_common_code(), TEST_C_CODE); } -// TODO: maybe add `speed_up` test suite - -TEST(ShortCode, initializate) { +TEST(ShortCode, initialize) { auto short_code = ShortCode::unsafe_create(TEST_S_CODE); auto common_code = CommonCode::unsafe_create(TEST_C_CODE); @@ -114,14 +125,17 @@ TEST(ShortCode, initializate) { EXPECT_EQ(s1, TEST_S_CODE); // l-value EXPECT_EQ(s2, TEST_S_CODE); // r-value - // TODO: test fast mode of `ShortCode(CommonCode)` - // ShortCode(...) EXPECT_EQ(ShortCode(common_code), TEST_S_CODE); EXPECT_EQ(ShortCode(short_code), TEST_S_CODE); // l-value EXPECT_EQ(ShortCode(ShortCode(short_code)), TEST_S_CODE); // r-value // ShortCode::create(uint32_t) + speed_up_reset(); + EXPECT_TRUE(ShortCode::create(TEST_S_CODE).has_value()); + EXPECT_FALSE(ShortCode::create(TEST_S_CODE_ERR).has_value()); + EXPECT_EQ(ShortCode::create(TEST_S_CODE), TEST_S_CODE); + ShortCode::speed_up(true); EXPECT_TRUE(ShortCode::create(TEST_S_CODE).has_value()); EXPECT_FALSE(ShortCode::create(TEST_S_CODE_ERR).has_value()); EXPECT_EQ(ShortCode::create(TEST_S_CODE), TEST_S_CODE); @@ -148,95 +162,89 @@ TEST(ShortCode, initializate) { EXPECT_EQ(ShortCode::from_common_code(TEST_C_CODE_STR), TEST_S_CODE); } -// TODO: global verify function -// -> check -// -> fast_decode / fast_encode -// -> tiny_decode / tiny_encode -// -> string_encode / string_decode - TEST(ShortCode, speed_up) { - all_cases_reset(); - basic_ranges_reset(); - BS::thread_pool pool(TEST_THREAD_NUM); - - for (auto i = 0; i < TEST_THREAD_NUM; ++i) { - pool.detach_task([]() { - ShortCode::speed_up(false); - }); - } - EXPECT_FALSE(BasicRanges::instance().is_available()); - EXPECT_FALSE(AllCases::instance().is_available()); - pool.wait(); - EXPECT_TRUE(BasicRanges::instance().is_available()); - EXPECT_FALSE(AllCases::instance().is_available()); - - for (auto i = 0; i < TEST_THREAD_NUM; ++i) { - pool.detach_task([]() { - ShortCode::speed_up(true); - }); - } - EXPECT_TRUE(BasicRanges::instance().is_available()); - EXPECT_FALSE(AllCases::instance().is_available()); - pool.wait(); - EXPECT_TRUE(BasicRanges::instance().is_available()); - EXPECT_TRUE(AllCases::instance().is_available()); + co::Racer racer {TEST_THREAD_NUM}; + + static auto EXPECT_STAGE_0 = +[]() { + EXPECT_FALSE(exposer::ShortCode_fast_()); + EXPECT_EQ(exposer::ShortCode_cases_(), nullptr); + EXPECT_EQ(exposer::ShortCode_ranges_(), nullptr); + EXPECT_FALSE(BasicRanges::instance().is_available()); + EXPECT_FALSE(AllCases::instance().is_available()); + }; + + static auto EXPECT_STAGE_1 = +[]() { + EXPECT_FALSE(exposer::ShortCode_fast_()); + EXPECT_EQ(exposer::ShortCode_cases_(), nullptr); + EXPECT_EQ(exposer::ShortCode_ranges_(), &BasicRanges::instance().fetch()); + EXPECT_TRUE(BasicRanges::instance().is_available()); + EXPECT_FALSE(AllCases::instance().is_available()); + }; + + static auto EXPECT_STAGE_2 = +[]() { + EXPECT_TRUE(exposer::ShortCode_fast_()); + EXPECT_EQ(exposer::ShortCode_cases_(), &AllCases::instance().fetch()); + EXPECT_EQ(exposer::ShortCode_ranges_(), &BasicRanges::instance().fetch()); + EXPECT_TRUE(BasicRanges::instance().is_available()); + EXPECT_TRUE(AllCases::instance().is_available()); + }; + + speed_up_reset(); + EXPECT_STAGE_0(); + racer.Race([] { ShortCode::speed_up(false); }); + EXPECT_STAGE_1(); + racer.Race([] { ShortCode::speed_up(true); }); + EXPECT_STAGE_2(); + racer.Race([] { ShortCode::speed_up(true); }); + EXPECT_STAGE_2(); + racer.Race([] { ShortCode::speed_up(false); }); + EXPECT_STAGE_2(); + + speed_up_reset(); + EXPECT_STAGE_0(); + racer.Race([] { ShortCode::speed_up(true); }); + EXPECT_STAGE_2(); } TEST(ShortCode, code_verify) { - BS::thread_pool pool; - ShortCode::speed_up(true); - pool.detach_sequence(0, 16, [](const uint64_t head) { - std::vector archive; - for (auto range : AllCases::instance().fetch()[head]) { - auto code = ShortCode::from_common_code(head << 32 | range); - EXPECT_TRUE(code.has_value()); - EXPECT_TRUE(ShortCode::check(code->unwrap())); - EXPECT_EQ(code->to_common_code(), head << 32 | range); - archive.emplace_back(code->unwrap()); - } - if (!archive.empty()) { - EXPECT_TRUE(std::is_sorted(archive.begin(), archive.end())); // increasingly one by one - EXPECT_EQ(archive[archive.size() - 1] - archive[0], archive.size() - 1); - EXPECT_EQ(std::accumulate(ALL_CASES_NUM.begin(), ALL_CASES_NUM.begin() + head, 0), archive[0]); + ShortCode::speed_up(true); // enter fast mode + short_code_parallel([](std::span codes) { + for (auto code : codes) { + EXPECT_TRUE(ShortCode::check(code.unwrap())); + auto common_code = code.to_common_code(); // ShortCode::fast_decode + EXPECT_EQ(ShortCode::from_common_code(common_code), code); // ShortCode::fast_encode } }); - pool.wait(); } TEST(ShortCode, code_string) { - auto test_func = [](ShortCode code) { - auto code_str = code.to_string(); - EXPECT_EQ(code_str.size(), 5); // length = 5 - for (auto c : code_str) { - EXPECT_TRUE((c >= '1' && c <= '9') || (c >= 'A' && c <= 'Z')); - EXPECT_TRUE(c != 'I' && c != 'L' && c != 'O'); - } - EXPECT_EQ(ShortCode::from_string(code_str), code); // test upper cases - std::transform(code_str.begin(), code_str.end(), code_str.begin(), ::tolower); - EXPECT_EQ(ShortCode::from_string(code_str), code); // test lower cases - }; - - BS::thread_pool pool; - pool.detach_blocks((uint32_t)0, SHORT_CODE_LIMIT, [&test_func](uint32_t start, uint32_t end) { - for (uint32_t short_code = start; short_code < end; ++short_code) { - test_func(ShortCode::unsafe_create(short_code)); + short_code_parallel([](std::span codes) { + for (auto code : codes) { + auto code_str = code.to_string(); + EXPECT_EQ(code_str.size(), 5); // length = 5 + for (auto c : code_str) { + EXPECT_TRUE((c >= '1' && c <= '9') || (c >= 'A' && c <= 'Z')); + EXPECT_TRUE(c != 'I' && c != 'L' && c != 'O'); + } + EXPECT_EQ(ShortCode::from_string(code_str), code); // test upper cases + std::transform(code_str.begin(), code_str.end(), code_str.begin(), ::tolower); + EXPECT_EQ(ShortCode::from_string(code_str), code); // test lower cases } }); - pool.wait(); } TEST(ShortCode, DISABLED_global_verify) { - all_cases_reset(); + speed_up_reset(); BS::thread_pool pool; - auto futures = pool.submit_blocks((uint32_t)0, SHORT_CODE_LIMIT, [](uint32_t start, uint32_t end) { - std::vector archive; - archive.reserve(end - start); + auto futures = pool.submit_blocks(0U, SHORT_CODE_LIMIT, [](auto start, auto end) { + std::vector codes; + codes.reserve(end - start); for (uint32_t short_code = start; short_code < end; ++short_code) { - auto common_code = CommonCode::from_short_code(short_code).value(); - EXPECT_EQ(common_code.to_short_code(), short_code); - archive.emplace_back(common_code.unwrap()); + auto common_code = CommonCode::from_short_code(short_code).value(); // ShortCode::tiny_decode + EXPECT_EQ(common_code.to_short_code(), short_code); // ShortCode::tiny_encode + codes.emplace_back(common_code.unwrap()); } - return archive; + return codes; }, 0x1000); // split as 4096 pieces std::vector result; @@ -245,6 +253,5 @@ TEST(ShortCode, DISABLED_global_verify) { const auto data = future.get(); result.insert(result.end(), data.begin(), data.end()); // combine sections } - pool.wait(); EXPECT_EQ(result, all_common_codes()); } diff --git a/src/core_test/utility/concurrent.h b/src/core_test/utility/concurrent.h index 4086a77..46e4a2d 100644 --- a/src/core_test/utility/concurrent.h +++ b/src/core_test/utility/concurrent.h @@ -37,6 +37,11 @@ public: return race_num_; // number of racing threads } + void Race(std::function &&item) { + Start(std::move(item)); + Join(); + } + void Start(std::function &&item) { auto wrapper = [item = std::move(item)](const int) { item(); // execute racing function