Browse Source

test: refactor test suites of klotski cases

master
Dnomd343 3 months ago
parent
commit
afe8c595a1
  1. 1
      src/core_test/CMakeLists.txt
  2. 200
      src/core_test/cases/all_cases.cc
  3. 53
      src/core_test/cases/basic_ranges.cc
  4. 29
      src/core_test/cases/helper.h
  5. 45
      src/core_test/utility/concurrent.h

1
src/core_test/CMakeLists.txt

@ -16,6 +16,7 @@ include_directories(${KLOTSKI_ROOT_DIR}/src/core/ffi)
set(KLOTSKI_TEST_CASES_SRC set(KLOTSKI_TEST_CASES_SRC
cases/all_cases.cc cases/all_cases.cc
cases/basic_ranges.cc
) )
add_executable(test_klotski_cases ${KLOTSKI_TEST_CASES_SRC}) add_executable(test_klotski_cases ${KLOTSKI_TEST_CASES_SRC})

200
src/core_test/cases/all_cases.cc

@ -1,19 +1,5 @@
#include <vector>
#include "hash.h" #include "hash.h"
#include "exposer.h" #include "helper.h"
#include "all_cases.h"
#include "concurrent.h"
#include "gtest/gtest.h"
using klotski::cases::AllCases;
using klotski::cases::BasicRanges;
using klotski::cases::ALL_CASES_NUM;
using klotski::cases::ALL_CASES_NUM_;
using klotski::cases::BASIC_RANGES_NUM;
static constexpr uint64_t BASIC_RANGES_XXH3 = 0x82b040060044e336;
static constexpr std::array<uint64_t, 16> ALL_CASES_XXH3 = { static constexpr std::array<uint64_t, 16> ALL_CASES_XXH3 = {
0x71c8ff7a71c93da0, 0x2a5247ee8bfed666, 0xf4efc8fc692d58e2, 0x2d06800538d394c2, 0x71c8ff7a71c93da0, 0x2a5247ee8bfed666, 0xf4efc8fc692d58e2, 0x2d06800538d394c2,
@ -22,44 +8,25 @@ static constexpr std::array<uint64_t, 16> ALL_CASES_XXH3 = {
0x2cdf6c14a7ce3e9a, 0xb9dd04a315583f5c, 0x19046e49c44ae90d, 0x2d06800538d394c2, 0x2cdf6c14a7ce3e9a, 0xb9dd04a315583f5c, 0x19046e49c44ae90d, 0x2d06800538d394c2,
}; };
/// Forcibly modify private variables to reset state. class AllCasesTest : public testing::Test, public Concurrent {
FORCIBLY_ACCESS(AllCases, available_, bool) protected:
FORCIBLY_ACCESS(BasicRanges, available_, bool) void SetUp() override {
Reset();
class BR_Test final { EXPECT_FALSE(Available());
public:
[[nodiscard]] static bool available() {
return BasicRanges::instance().is_available();
} }
// TODO: expect member function /// Whether all cases are ready.
[[nodiscard]] static bool Available() {
/// Reset basic ranges build state, note it is thread-unsafe.
static void reset() {
exposer::BasicRanges_available_(BasicRanges::instance()) = false;
}
/// Verify that whether basic ranges data is correct.
static void verify() {
const auto &basic_ranges = BasicRanges::instance().fetch();
EXPECT_EQ(basic_ranges.size(), BASIC_RANGES_NUM); // verify basic ranges size
EXPECT_EQ(hash::xxh3(basic_ranges), BASIC_RANGES_XXH3); // verify basic ranges checksum
}
};
class AC_Test final {
public:
[[nodiscard]] static bool available() {
return AllCases::instance().is_available(); return AllCases::instance().is_available();
} }
/// Reset all cases build state, note it is thread-unsafe. /// Reset all cases build state, note it is thread-unsafe.
static void reset() { static void Reset() {
exposer::AllCases_available_(AllCases::instance()) = false; exposer::AllCases_available_(AllCases::instance()) = false;
} }
/// Verify that whether all cases data is correct. /// Verify that whether all cases data is correct.
static void verify() { static void Verify() {
const auto &all_cases = AllCases::instance().fetch(); const auto &all_cases = AllCases::instance().fetch();
for (int head = 0; head < 16; ++head) { for (int head = 0; head < 16; ++head) {
EXPECT_EQ(all_cases[head].size(), ALL_CASES_NUM[head]); // verify all cases size EXPECT_EQ(all_cases[head].size(), ALL_CASES_NUM[head]); // verify all cases size
@ -71,137 +38,112 @@ public:
}); });
EXPECT_EQ(all_cases_num, ALL_CASES_NUM_); // verify all cases global size EXPECT_EQ(all_cases_num, ALL_CASES_NUM_); // verify all cases global size
for (uint64_t head = 0; head < 16; ++head) { for (int head = 0; head < 16; ++head) {
EXPECT_EQ(hash::xxh3(all_cases[head]), ALL_CASES_XXH3[head]); // verify all cases checksum EXPECT_EQ(hash::xxh3(all_cases[head]), ALL_CASES_XXH3[head]); // verify all cases checksum
} }
} }
}; };
TEST(AllCases, basic_ranges) { TEST_FF(AllCases, constant) {
BR_Test::reset(); EXPECT_EQ(ALL_CASES_NUM[0], 2942906);
EXPECT_FALSE(BR_Test::available()); EXPECT_EQ(ALL_CASES_NUM[1], 2260392);
EXPECT_EQ(ALL_CASES_NUM[2], 2942906);
EXPECT_EQ(ALL_CASES_NUM[3], 0);
BasicRanges::instance().build(); EXPECT_EQ(ALL_CASES_NUM[4], 2322050);
EXPECT_TRUE(BR_Test::available()); EXPECT_EQ(ALL_CASES_NUM[5], 1876945);
EXPECT_EQ(ALL_CASES_NUM[6], 2322050);
EXPECT_EQ(ALL_CASES_NUM[7], 0);
BasicRanges::instance().build(); EXPECT_EQ(ALL_CASES_NUM[8], 2322050);
EXPECT_TRUE(BR_Test::available()); EXPECT_EQ(ALL_CASES_NUM[9], 1876945);
BR_Test::verify(); EXPECT_EQ(ALL_CASES_NUM[10], 2322050);
} EXPECT_EQ(ALL_CASES_NUM[11], 0);
TEST(AllCases, basic_ranges_race) { EXPECT_EQ(ALL_CASES_NUM[12], 2942906);
BR_Test::reset(); EXPECT_EQ(ALL_CASES_NUM[13], 2260392);
EXPECT_FALSE(BR_Test::available()); EXPECT_EQ(ALL_CASES_NUM[14], 2942906);
EXPECT_EQ(ALL_CASES_NUM[15], 0);
auto racer = co::Racer([] { EXPECT_EQ(ALL_CASES_NUM_, 29334498);
BasicRanges::instance().build();
});
EXPECT_FALSE(BR_Test::available());
racer.Join();
EXPECT_TRUE(BR_Test::available());
BR_Test::verify();
} }
TEST(AllCases, all_cases) { TEST_FF(AllCases, all_cases) {
AC_Test::reset();
EXPECT_FALSE(AC_Test::available());
AllCases::instance().build(); AllCases::instance().build();
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
Verify();
AllCases::instance().build(); AllCases::instance().build();
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
AC_Test::verify(); Verify();
} }
TEST(AllCases, all_cases_race) { TEST_FF(AllCases, all_cases_race) {
AC_Test::reset(); racer_.Begin([] {
EXPECT_FALSE(AC_Test::available());
auto racer = co::Racer([] {
AllCases::instance().build(); AllCases::instance().build();
}); });
EXPECT_FALSE(AC_Test::available()); EXPECT_FALSE(Available());
racer_.Join();
racer.Join(); EXPECT_TRUE(Available());
EXPECT_TRUE(AC_Test::available()); Verify();
AC_Test::verify();
} }
TEST(AllCases, all_cases_parallel) { TEST_FF(AllCases, all_cases_parallel) {
AC_Test::reset(); AllCases::instance().build_parallel(executor_.Entry());
EXPECT_FALSE(AC_Test::available()); EXPECT_TRUE(Available());
Verify();
co::Executor executor;
AllCases::instance().build_parallel(executor.Entry());
EXPECT_TRUE(AC_Test::available());
AllCases::instance().build_parallel(executor.Entry()); AllCases::instance().build_parallel(executor_.Entry());
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
AC_Test::verify(); Verify();
} }
TEST(AllCases, all_cases_parallel_race) { TEST_FF(AllCases, all_cases_parallel_race) {
AC_Test::reset(); racer_.Begin([this] {
EXPECT_FALSE(AC_Test::available()); AllCases::instance().build_parallel(executor_.Entry());
co::Executor executor;
auto racer = co::Racer([&executor] {
AllCases::instance().build_parallel(executor.Entry());
}); });
EXPECT_FALSE(AC_Test::available()); EXPECT_FALSE(Available());
racer_.Join();
racer.Join(); EXPECT_TRUE(Available());
EXPECT_TRUE(AC_Test::available()); Verify();
AC_Test::verify();
} }
TEST(AllCases, all_cases_async) { TEST_FF(AllCases, all_cases_async) {
AC_Test::reset();
EXPECT_FALSE(AC_Test::available());
co::Executor executor;
std::atomic_flag flag; std::atomic_flag flag;
flag.clear(); flag.clear();
AllCases::instance().build_parallel_async(executor.Entry(), [&flag]() { AllCases::instance().build_parallel_async(executor_.Entry(), [&flag]() {
flag.test_and_set(); flag.test_and_set();
flag.notify_all(); flag.notify_all();
}); });
EXPECT_FALSE(AC_Test::available()); EXPECT_FALSE(Available());
flag.wait(false); flag.wait(false);
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
Verify();
flag.clear(); flag.clear();
AllCases::instance().build_parallel_async(executor.Entry(), [&flag]() { AllCases::instance().build_parallel_async(executor_.Entry(), [&flag]() {
flag.test_and_set(); flag.test_and_set();
flag.notify_all(); flag.notify_all();
}); });
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
flag.wait(false); flag.wait(false);
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
AC_Test::verify(); Verify();
} }
TEST(AllCases, all_cases_async_race) { TEST_FF(AllCases, all_cases_async_race) {
AC_Test::reset();
EXPECT_FALSE(AC_Test::available());
co::Executor executor;
std::atomic<int> callback_num(0); std::atomic<int> callback_num(0);
auto racer = co::Racer([&executor, &callback_num] { racer_.Begin([this, &callback_num] {
AllCases::instance().build_parallel_async(executor.Entry(), [&callback_num]() { AllCases::instance().build_parallel_async(executor_.Entry(), [&callback_num]() {
callback_num.fetch_add(1); callback_num.fetch_add(1);
}); });
}); });
EXPECT_FALSE(AC_Test::available()); EXPECT_FALSE(Available());
racer.Join(); racer_.Join();
EXPECT_TRUE(AC_Test::available()); EXPECT_TRUE(Available());
EXPECT_EQ(callback_num.load(), co::Racer::Times); EXPECT_EQ(callback_num.load(), co::Racer::Num);
AC_Test::verify(); Verify();
} }

53
src/core_test/cases/basic_ranges.cc

@ -0,0 +1,53 @@
#include "hash.h"
#include "helper.h"
static constexpr uint64_t BASIC_RANGES_XXH3 = 0x82b040060044e336;
class BasicRangesTest : public testing::Test, public Concurrent {
protected:
void SetUp() override {
Reset();
EXPECT_FALSE(Available());
}
/// Whether basic ranges are ready.
[[nodiscard]] static bool Available() {
return BasicRanges::instance().is_available();
}
/// Reset basic ranges build state, note it is thread-unsafe.
static void Reset() {
exposer::BasicRanges_available_(BasicRanges::instance()) = false;
}
/// Verify that whether basic ranges data is correct.
static void Verify() {
const auto &basic_ranges = BasicRanges::instance().fetch();
EXPECT_EQ(basic_ranges.size(), BASIC_RANGES_NUM); // verify basic ranges size
EXPECT_EQ(hash::xxh3(basic_ranges), BASIC_RANGES_XXH3); // verify basic ranges checksum
}
};
TEST_FF(BasicRanges, constant) {
EXPECT_EQ(BASIC_RANGES_NUM, 7311921);
}
TEST_FF(BasicRanges, basic_ranges) {
BasicRanges::instance().build();
EXPECT_TRUE(Available());
Verify();
BasicRanges::instance().build();
EXPECT_TRUE(Available());
Verify();
}
TEST_FF(BasicRanges, basic_ranges_race) {
racer_.Begin([] {
BasicRanges::instance().build();
});
EXPECT_FALSE(Available());
racer_.Join();
EXPECT_TRUE(Available());
Verify();
}

29
src/core_test/cases/helper.h

@ -0,0 +1,29 @@
#pragma once
#include "exposer.h"
#include "all_cases.h"
#include "concurrent.h"
#include "gtest/gtest.h"
using klotski::cases::AllCases;
using klotski::cases::BasicRanges;
using klotski::cases::ALL_CASES_NUM;
using klotski::cases::ALL_CASES_NUM_;
using klotski::cases::BASIC_RANGES_NUM;
/// Test fixture wrapper with Racer and Executor.
class Concurrent {
protected:
co::Racer racer_;
co::Executor executor_;
};
/// Forcibly modify private variables to reset state.
FORCIBLY_ACCESS(AllCases, available_, bool)
FORCIBLY_ACCESS(BasicRanges, available_, bool)
/// Test fixture macro with custom test suite name.
#define TEST_FF(test_suite_name, test_name) \
GTEST_TEST_(test_suite_name, test_name, test_suite_name##Test, \
::testing::internal::GetTypeId<test_suite_name##Test>())

45
src/core_test/utility/concurrent.h

@ -1,5 +1,10 @@
#pragma once #pragma once
/// The concurrency test helper class implements the Executor and Racer based
/// on the BS::thread_pool. The Executor allows you to submit multiple tasks
/// and dispatch them to different threads. The Racer schedules the specified
/// function to a certain number of threads at one time.
#include <memory> #include <memory>
#include <functional> #include <functional>
@ -7,39 +12,41 @@
namespace co { namespace co {
class Racer final { class Executor final {
public: public:
static constexpr int Times = 256; Executor() = default;
~Executor() { pool_.wait(); }
explicit Racer(std::function<void()> &&item) : pool_(Times), item_(item) { std::function<void(std::function<void()> &&)> Entry() {
pool_.detach_sequence(0, Times, [this](const int) { return [this](auto &&func) {
item_(); // execute race function pool_.detach_task(func);
}); };
} }
~Racer() { Join(); }
void Join() { pool_.wait(); }
private: private:
BS::thread_pool pool_; BS::thread_pool pool_;
std::function<void()> item_;
}; };
class Executor final { class Racer final {
public: public:
Executor() = default; Racer() = default;
~Racer() { Join(); }
~Executor() { pool_.wait(); } static constexpr int Num = 256; // number of racing threads
std::function<void(std::function<void()> &&)> Entry() { void Begin(std::function<void()> &&item) {
return [this](auto &&func) { item_ = std::move(item);
pool_.detach_task(func); pool_.detach_sequence(0, Num, [this](const int) {
}; item_(); // execute racing function
});
} }
void Join() { pool_.wait(); }
private: private:
BS::thread_pool pool_; std::function<void()> item_;
BS::thread_pool pool_ { Num };
}; };
} // namespace co } // namespace co

Loading…
Cancel
Save