diff --git a/modules/core/performance/tests/perf_tests.cpp b/modules/core/performance/tests/perf_tests.cpp index 69d8b3e6c..b90d6537c 100644 --- a/modules/core/performance/tests/perf_tests.cpp +++ b/modules/core/performance/tests/perf_tests.cpp @@ -141,7 +141,7 @@ class GetStringTaskTypeTest : public ::testing::TestWithParam (*j)["tasks"]["tbb"] = "TBB"; (*j)["tasks"]["seq"] = "SEQ"; - std::ofstream(temp_path) << (*j).dump(); + std::ofstream(temp_path) << j->dump(); } void TearDown() override { std::filesystem::remove(temp_path); } @@ -238,13 +238,8 @@ TEST(TaskTest, GetDynamicTypeReturnsCorrectEnum) { } TEST(TaskTest, DestructorTerminatesIfWrongOrder) { - testing::FLAGS_gtest_death_test_style = "threadsafe"; - ASSERT_DEATH_IF_SUPPORTED( - { - DummyTask task; - task.Run(); - }, - ""); + DummyTask task; + EXPECT_THROW(task.Run(), std::runtime_error); } namespace my { @@ -263,15 +258,69 @@ using TestTypes = ::testing::Types; TYPED_TEST_SUITE(GetNamespaceTest, TestTypes); TYPED_TEST(GetNamespaceTest, ExtractsNamespaceCorrectly) { - constexpr std::string_view kNs = ppc::util::GetNamespace(); + std::string k_ns = ppc::util::GetNamespace(); if constexpr (std::is_same_v) { - EXPECT_EQ(kNs, "my::nested"); + EXPECT_EQ(k_ns, "my::nested"); } else if constexpr (std::is_same_v) { - EXPECT_EQ(kNs, "my"); + EXPECT_EQ(k_ns, "my"); } else if constexpr (std::is_same_v) { - EXPECT_EQ(kNs, ""); + EXPECT_EQ(k_ns, ""); } else { FAIL() << "Unhandled type in test"; } } + +TEST(PerfTest, PipelineRunAndTaskRun) { + auto task_ptr = std::make_shared(); + ppc::core::Perf perf(task_ptr); + + ppc::core::PerfAttr attr; + double time = 0.0; + attr.num_running = 2; + attr.current_timer = [&time]() { + double t = time; + time += 1.0; + return t; + }; + + EXPECT_NO_THROW(perf.PipelineRun(attr)); + auto res_pipeline = perf.GetPerfResults(); + EXPECT_EQ(res_pipeline.type_of_running, ppc::core::PerfResults::kPipeline); + EXPECT_GT(res_pipeline.time_sec, 0.0); + + EXPECT_NO_THROW(perf.TaskRun(attr)); + auto res_taskrun = perf.GetPerfResults(); + EXPECT_EQ(res_taskrun.type_of_running, ppc::core::PerfResults::kTaskRun); + EXPECT_GT(res_taskrun.time_sec, 0.0); +} + +TEST(PerfTest, PrintPerfStatisticThrowsOnNone) { + { + auto task_ptr = std::make_shared(); + ppc::core::Perf perf(task_ptr); + EXPECT_THROW(perf.PrintPerfStatistic("test"), std::runtime_error); + } + EXPECT_TRUE(ppc::util::DestructorFailureFlag::Get()); + ppc::util::DestructorFailureFlag::Unset(); +} + +TEST(PerfTest, GetStringParamNameTest) { + EXPECT_EQ(GetStringParamName(ppc::core::PerfResults::kTaskRun), "task_run"); + EXPECT_EQ(GetStringParamName(ppc::core::PerfResults::kPipeline), "pipeline"); + EXPECT_EQ(GetStringParamName(ppc::core::PerfResults::kNone), "none"); +} + +TEST(TaskTest, Destructor_InvalidPipelineOrderTerminates_PartialPipeline) { + { + struct BadTask : ppc::core::Task { + bool ValidationImpl() override { return true; } + bool PreProcessingImpl() override { return true; } + bool RunImpl() override { return true; } + bool PostProcessingImpl() override { return true; } + } task; + task.Validation(); + } + EXPECT_TRUE(ppc::util::DestructorFailureFlag::Get()); + ppc::util::DestructorFailureFlag::Unset(); +} diff --git a/modules/core/runners/include/runners.hpp b/modules/core/runners/include/runners.hpp index 1a09c302b..15d50941d 100644 --- a/modules/core/runners/include/runners.hpp +++ b/modules/core/runners/include/runners.hpp @@ -43,4 +43,10 @@ class WorkerTestFailurePrinter : public ::testing::EmptyTestEventListener { /// finalization fails. int Init(int argc, char** argv); +/// @brief Initializes the testing environment only for gtest. +/// @param argc Argument count. +/// @param argv Argument vector. +/// @return Exit code from RUN_ALL_TESTS. +int SimpleInit(int argc, char** argv); + } // namespace ppc::core diff --git a/modules/core/runners/src/runners.cpp b/modules/core/runners/src/runners.cpp index b0db49dea..6a2bc1bea 100644 --- a/modules/core/runners/src/runners.cpp +++ b/modules/core/runners/src/runners.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "core/util/include/util.hpp" @@ -82,6 +83,11 @@ int Init(int argc, char** argv) { listeners.Append(new ppc::core::UnreadMessagesDetector()); auto status = RUN_ALL_TESTS(); + if (ppc::util::DestructorFailureFlag::Get()) { + throw std::runtime_error( + std::format("[ ERROR ] Destructor failed with code {}", ppc::util::DestructorFailureFlag::Get())); + } + const int finalize_res = MPI_Finalize(); if (finalize_res != MPI_SUCCESS) { std::cerr << std::format("[ ERROR ] MPI_Finalize failed with code {}", finalize_res) << '\n'; @@ -91,4 +97,17 @@ int Init(int argc, char** argv) { return status; } +int SimpleInit(int argc, char** argv) { + // Limit the number of threads in TBB + tbb::global_control control(tbb::global_control::max_allowed_parallelism, ppc::util::GetNumThreads()); + + testing::InitGoogleTest(&argc, argv); + auto status = RUN_ALL_TESTS(); + if (ppc::util::DestructorFailureFlag::Get()) { + throw std::runtime_error( + std::format("[ ERROR ] Destructor failed with code {}", ppc::util::DestructorFailureFlag::Get())); + } + return status; +} + } // namespace ppc::core diff --git a/modules/core/task/include/task.hpp b/modules/core/task/include/task.hpp index 338b6f69a..05fcd0b26 100644 --- a/modules/core/task/include/task.hpp +++ b/modules/core/task/include/task.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include namespace ppc::core { @@ -39,6 +41,25 @@ enum TypeOfTask : uint8_t { kUnknown }; +using TaskMapping = std::pair; +using TaskMappingArray = std::array; + +const TaskMappingArray kTaskTypeMappings = {{{TypeOfTask::kALL, "all"}, + {TypeOfTask::kMPI, "mpi"}, + {TypeOfTask::kOMP, "omp"}, + {TypeOfTask::kSEQ, "seq"}, + {TypeOfTask::kSTL, "stl"}, + {TypeOfTask::kTBB, "tbb"}}}; + +inline std::string TypeOfTaskToString(TypeOfTask type) { + for (const auto &[key, value] : kTaskTypeMappings) { + if (key == type) { + return value; + } + } + return "unknown"; +} + /// @brief Indicates whether a task is enabled or disabled. enum StatusOfTask : uint8_t { /// Task is enabled and should be executed @@ -71,29 +92,12 @@ inline std::string GetStringTaskType(TypeOfTask type_of_task, const std::string auto list_settings = ppc::util::InitJSONPtr(); file >> *list_settings; - auto to_type_str = [&](const std::string &type) -> std::string { - return type + "_" + std::string((*list_settings)["tasks"][type]); - }; - - if (type_of_task == TypeOfTask::kALL) { - return to_type_str("all"); - } - if (type_of_task == TypeOfTask::kSTL) { - return to_type_str("stl"); + std::string type_str = TypeOfTaskToString(type_of_task); + if (type_str == "unknown") { + return type_str; } - if (type_of_task == TypeOfTask::kOMP) { - return to_type_str("omp"); - } - if (type_of_task == TypeOfTask::kMPI) { - return to_type_str("mpi"); - } - if (type_of_task == TypeOfTask::kTBB) { - return to_type_str("tbb"); - } - if (type_of_task == TypeOfTask::kSEQ) { - return to_type_str("seq"); - } - return "unknown"; + + return type_str + "_" + std::string((*list_settings)["tasks"][type_str]); } enum StateOfTesting : uint8_t { kFunc, kPerf }; @@ -104,22 +108,29 @@ template /// @tparam OutType Output data type. class Task { public: - /// @brief Constructs a new Task object. - explicit Task(StateOfTesting /*state_of_testing*/ = StateOfTesting::kFunc) { functions_order_.clear(); } - /// @brief Validates input data and task attributes before execution. /// @return True if validation is successful. virtual bool Validation() final { - InternalOrderTest(ppc::util::FuncName()); + if (stage_ == PipelineStage::kNone || stage_ == PipelineStage::kDone) { + stage_ = PipelineStage::kValidation; + } else { + stage_ = PipelineStage::kException; + throw std::runtime_error("Validation should be called before preprocessing"); + } return ValidationImpl(); } /// @brief Performs preprocessing on the input data. /// @return True if preprocessing is successful. virtual bool PreProcessing() final { - InternalOrderTest(ppc::util::FuncName()); + if (stage_ == PipelineStage::kValidation) { + stage_ = PipelineStage::kPreProcessing; + } else { + stage_ = PipelineStage::kException; + throw std::runtime_error("Preprocessing should be called after validation"); + } if (state_of_testing_ == StateOfTesting::kFunc) { - InternalTimeTest(ppc::util::FuncName()); + InternalTimeTest(); } return PreProcessingImpl(); } @@ -127,16 +138,26 @@ class Task { /// @brief Executes the main logic of the task. /// @return True if execution is successful. virtual bool Run() final { - InternalOrderTest(ppc::util::FuncName()); + if (stage_ == PipelineStage::kPreProcessing || stage_ == PipelineStage::kRun) { + stage_ = PipelineStage::kRun; + } else { + stage_ = PipelineStage::kException; + throw std::runtime_error("Run should be called after preprocessing"); + } return RunImpl(); } /// @brief Performs postprocessing on the output data. /// @return True if postprocessing is successful. virtual bool PostProcessing() final { - InternalOrderTest(ppc::util::FuncName()); + if (stage_ == PipelineStage::kRun) { + stage_ = PipelineStage::kDone; + } else { + stage_ = PipelineStage::kException; + throw std::runtime_error("Postprocessing should be called after run"); + } if (state_of_testing_ == StateOfTesting::kFunc) { - InternalTimeTest(ppc::util::FuncName()); + InternalTimeTest(); } return PostProcessingImpl(); } @@ -170,14 +191,10 @@ class Task { OutType &GetOutput() { return output_; } /// @brief Destructor. Verifies that the pipeline was executed in the correct order. - /// @note Terminates the program if pipeline order is incorrect or incomplete. + /// @note Terminates the program if the pipeline order is incorrect or incomplete. virtual ~Task() { - if (!functions_order_.empty() || !was_worked_) { - std::cerr << "ORDER OF FUNCTIONS IS NOT RIGHT! \n Expected - \"Validation\", \"PreProcessing\", \"Run\", " - "\"PostProcessing\" \n"; - std::terminate(); - } else { - functions_order_.clear(); + if (stage_ != PipelineStage::kDone && stage_ != PipelineStage::kException) { + ppc::util::DestructorFailureFlag::Set(); } #if _OPENMP >= 201811 omp_pause_resource_all(omp_pause_soft); @@ -185,26 +202,14 @@ class Task { } protected: - /// @brief Verifies the correct order of pipeline method calls. - /// @param str Name of the method being called. - virtual void InternalOrderTest(const std::string &str) final { - functions_order_.push_back(str); - if (str == "PostProcessing" && IsFullPipelineStage()) { - functions_order_.clear(); - } else { - was_worked_ = true; - } - } - /// @brief Measures execution time between preprocessing and postprocessing steps. - /// @param str Name of the method being timed. /// @throws std::runtime_error If execution exceeds the allowed time limit. - virtual void InternalTimeTest(const std::string &str) final { - if (str == "PreProcessing") { + virtual void InternalTimeTest() final { + if (stage_ == PipelineStage::kPreProcessing) { tmp_time_point_ = std::chrono::high_resolution_clock::now(); } - if (str == "PostProcessing") { + if (stage_ == PipelineStage::kDone) { auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - tmp_time_point_) .count(); @@ -244,26 +249,16 @@ class Task { StateOfTesting state_of_testing_ = kFunc; TypeOfTask type_of_task_ = kUnknown; StatusOfTask status_of_task_ = kEnabled; - std::vector functions_order_; - std::vector right_functions_order_ = {"Validation", "PreProcessing", "Run", "PostProcessing"}; static constexpr double kMaxTestTime = 1.0; std::chrono::high_resolution_clock::time_point tmp_time_point_; - bool was_worked_ = false; - - bool IsFullPipelineStage() { - if (functions_order_.size() < 4) { - return false; - } - - auto it = std::adjacent_find(functions_order_.begin() + 2, - functions_order_.begin() + static_cast(functions_order_.size() - 2), - std::not_equal_to<>()); - - return (functions_order_[0] == "Validation" && functions_order_[1] == "PreProcessing" && - functions_order_[2] == "Run" && - it == (functions_order_.begin() + static_cast(functions_order_.size() - 2)) && - functions_order_[functions_order_.size() - 1] == "PostProcessing"); - } + enum class PipelineStage : uint8_t { + kNone, + kValidation, + kPreProcessing, + kRun, + kDone, + kException + } stage_ = PipelineStage::kNone; }; /// @brief Smart pointer alias for Task. @@ -276,7 +271,7 @@ using TaskPtr = std::shared_ptr>; /// @tparam TaskType Type of the task to create. /// @tparam InType Type of the input. /// @param in Input to pass to the task constructor. -/// @return Shared pointer to the newly created task. +/// @return Shared a pointer to the newly created task. template std::shared_ptr TaskGetter(InType in) { return std::make_shared(in); diff --git a/modules/core/task/tests/task_tests.cpp b/modules/core/task/tests/task_tests.cpp index 0b31ad425..17f5485d7 100644 --- a/modules/core/task/tests/task_tests.cpp +++ b/modules/core/task/tests/task_tests.cpp @@ -1,178 +1,234 @@ #include +#include #include #include +#include +#include +#include +#include +#include #include +#include "core/runners/include/runners.hpp" +#include "core/task/include/task.hpp" #include "core/task/tests/test_task.hpp" +#include "core/util/include/util.hpp" TEST(task_tests, check_int32_t) { - // Create data std::vector in(20, 1); - - // Create and check Task ppc::test::task::TestTask, int32_t> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); - - // Run Task + ASSERT_EQ(test_task.Validation(), true); test_task.PreProcessing(); test_task.Run(); test_task.PostProcessing(); - - // Check Result ASSERT_EQ(static_cast(test_task.GetOutput()), in.size()); } TEST(task_tests, check_int32_t_slow) { - // Create data std::vector in(20, 1); - - // Create and check Task ppc::test::task::FakeSlowTask, int32_t> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); - - // Run Task + ASSERT_EQ(test_task.Validation(), true); test_task.PreProcessing(); test_task.Run(); ASSERT_ANY_THROW(test_task.PostProcessing()); } TEST(task_tests, check_validate_func) { - // Create data std::vector in; - - // Create and check Task ppc::test::task::TestTask, int32_t> test_task(in); - bool is_valid = test_task.Validation(); - - // Check Result - ASSERT_EQ(is_valid, false); - + ASSERT_EQ(test_task.Validation(), false); test_task.PreProcessing(); test_task.Run(); test_task.PostProcessing(); } TEST(task_tests, check_double) { - // Create data std::vector in(20, 1); - - // Create and check Task ppc::test::task::TestTask, double> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); - - // Run Task + ASSERT_EQ(test_task.Validation(), true); test_task.PreProcessing(); test_task.Run(); test_task.PostProcessing(); - - // Check Result EXPECT_NEAR(test_task.GetOutput(), static_cast(in.size()), 1e-6); } -TEST(task_tests, check_uint8_t) { - // Create data - std::vector in(20, 1); - - // Create Task - ppc::test::task::TestTask, uint8_t> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); - - // Run Task +TEST(task_tests, check_float) { + std::vector in(20, 1); + ppc::test::task::TestTask, float> test_task(in); + ASSERT_EQ(test_task.Validation(), true); test_task.PreProcessing(); test_task.Run(); test_task.PostProcessing(); + EXPECT_NEAR(test_task.GetOutput(), in.size(), 1e-3); +} - // Check Result - ASSERT_EQ(static_cast(test_task.GetOutput()), in.size()); +TEST(task_tests, check_wrong_order_disabled_valgrind) { + std::vector in(20, 1); + ppc::test::task::TestTask, float> test_task(in); + ASSERT_EQ(test_task.Validation(), true); + test_task.PreProcessing(); + EXPECT_THROW(test_task.PostProcessing(), std::runtime_error); } -TEST(task_tests, check_int64_t) { - // Create data - std::vector in(20, 1); +TEST(task_tests, premature_postprocessing_no_steps) { + std::vector in(20, 1); + ppc::test::task::TestTask, float> test_task(in); + EXPECT_THROW(test_task.PostProcessing(), std::runtime_error); +} - // Create Task - ppc::test::task::TestTask, int64_t> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); +TEST(task_tests, premature_postprocessing_after_preprocessing) { + std::vector in(20, 1); + ppc::test::task::TestTask, float> test_task(in); + EXPECT_THROW(test_task.PreProcessing(), std::runtime_error); + EXPECT_THROW(test_task.PostProcessing(), std::runtime_error); +} - // Run Task - test_task.PreProcessing(); - test_task.Run(); - test_task.PostProcessing(); +TEST(TaskTest, GetStringTaskStatus_Disabled) { + EXPECT_EQ(GetStringTaskStatus(ppc::core::StatusOfTask::kDisabled), "disabled"); +} - // Check Result - ASSERT_EQ(static_cast(test_task.GetOutput()), in.size()); +TEST(TaskTest, GetStringTaskStatus_Enabled) { + EXPECT_EQ(GetStringTaskStatus(ppc::core::StatusOfTask::kEnabled), "enabled"); } -TEST(task_tests, check_float) { - // Create data - std::vector in(20, 1); +TEST(TaskTest, GetStringTaskType_InvalidFileThrows) { + EXPECT_THROW({ GetStringTaskType(ppc::core::TypeOfTask::kALL, "non_existing_file.json"); }, std::runtime_error); +} - // Create Task - ppc::test::task::TestTask, float> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); +TEST(TaskTest, GetStringTaskType_UnknownType_WithValidFile) { + std::string path = "settings_valid.json"; + std::ofstream file(path); + file + << R"({"tasks": {"all": "enabled", "stl": "enabled", "omp": "enabled", "mpi": "enabled", "tbb": "enabled", "seq": "enabled"}})"; + file.close(); + EXPECT_NO_THROW({ GetStringTaskType(ppc::core::TypeOfTask::kUnknown, path); }); +} - // Run Task - test_task.PreProcessing(); - test_task.Run(); - test_task.PostProcessing(); +TEST(TaskTest, GetStringTaskType_ThrowsOnBadJSON) { + std::string path = "bad_settings.json"; + std::ofstream file(path); + file << "{"; + file.close(); + EXPECT_THROW({ GetStringTaskType(ppc::core::TypeOfTask::kALL, path); }, std::exception); +} - // Check Result - EXPECT_NEAR(test_task.GetOutput(), in.size(), 1e-3); +TEST(TaskTest, GetStringTaskType_EachType_WithValidFile) { + std::string path = "settings_valid_all.json"; + std::ofstream file(path); + file + << R"({"tasks": {"all": "enabled", "stl": "enabled", "omp": "enabled", "mpi": "enabled", "tbb": "enabled", "seq": "enabled"}})"; + file.close(); + + EXPECT_NO_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kALL, path)); + EXPECT_NO_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kSTL, path)); + EXPECT_NO_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kOMP, path)); + EXPECT_NO_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kMPI, path)); + EXPECT_NO_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kTBB, path)); + EXPECT_NO_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kSEQ, path)); } -TEST(task_tests, check_wrong_order_disabled_valgrind) { - auto destroy_function = [] { - // Create data - std::vector in(20, 1); - - // Create Task - ppc::test::task::TestTask, float> test_task(in); - bool is_valid = test_task.Validation(); - ASSERT_EQ(is_valid, true); - test_task.PreProcessing(); - test_task.PostProcessing(); - }; - EXPECT_DEATH_IF_SUPPORTED(destroy_function(), ".*ORDER OF FUNCTIONS IS NOT RIGHT.*"); +TEST(TaskTest, GetStringTaskType_ReturnsUnknown_OnDefault) { + std::string path = "settings_valid_unknown.json"; + std::ofstream file(path); + file << R"({"tasks": {"all": "enabled"}})"; + file.close(); + + auto result = ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kUnknown, path); + EXPECT_EQ(result, "unknown"); } -TEST(task_tests, check_empty_order_disabled_valgrind) { - auto destroy_function = [] { - // Create data - std::vector in(20, 1); +TEST(TaskTest, GetStringTaskType_ThrowsIfKeyMissing) { + std::string path = "settings_partial.json"; + std::ofstream file(path); + file << R"({"tasks": {"all": "enabled"}})"; + file.close(); - // Create Task - ppc::test::task::TestTask, float> test_task(in); - }; - EXPECT_DEATH_IF_SUPPORTED(destroy_function(), ".*ORDER OF FUNCTIONS IS NOT RIGHT.*"); + EXPECT_ANY_THROW(ppc::core::GetStringTaskType(ppc::core::TypeOfTask::kSTL, path)); } -TEST(task_tests, premature_postprocessing_no_steps) { - auto destroy_function = [] { - std::vector in(20, 1); - ppc::test::task::TestTask, float> test_task(in); - ASSERT_NO_THROW(test_task.PostProcessing()); - }; - EXPECT_DEATH_IF_SUPPORTED(destroy_function(), ".*ORDER OF FUNCTIONS IS NOT RIGHT.*"); +TEST(TaskTest, TaskDestructor_ThrowsIfStageIncomplete) { + { + std::vector in(20, 1); + struct LocalTask : ppc::core::Task, int32_t> { + explicit LocalTask(const std::vector& in) { this->GetInput() = in; } + bool ValidationImpl() override { return true; } + bool PreProcessingImpl() override { return true; } + bool RunImpl() override { return true; } + bool PostProcessingImpl() override { return true; } + } task(in); + task.Validation(); + } + EXPECT_TRUE(ppc::util::DestructorFailureFlag::Get()); + ppc::util::DestructorFailureFlag::Unset(); } -TEST(task_tests, premature_postprocessing_after_preprocessing) { - auto destroy_function = [] { - std::vector in(20, 1); - ppc::test::task::TestTask, float> test_task(in); - ASSERT_NO_THROW(test_task.PreProcessing()); - ASSERT_NO_THROW(test_task.PostProcessing()); +TEST(TaskTest, TaskDestructor_ThrowsIfEmpty) { + { + std::vector in(20, 1); + struct LocalTask : ppc::core::Task, int32_t> { + explicit LocalTask(const std::vector& in) { this->GetInput() = in; } + bool ValidationImpl() override { return true; } + bool PreProcessingImpl() override { return true; } + bool RunImpl() override { return true; } + bool PostProcessingImpl() override { return true; } + } task(in); + } + EXPECT_TRUE(ppc::util::DestructorFailureFlag::Get()); + ppc::util::DestructorFailureFlag::Unset(); +} + +TEST(TaskTest, InternalTimeTest_ThrowsIfTimeoutExceeded) { + struct SlowTask : ppc::core::Task, int32_t> { + explicit SlowTask(const std::vector& in) { this->GetInput() = in; } + bool ValidationImpl() override { return true; } + bool PreProcessingImpl() override { + std::this_thread::sleep_for(std::chrono::seconds(2)); + return true; + } + bool RunImpl() override { return true; } + bool PostProcessingImpl() override { return true; } }; - EXPECT_DEATH_IF_SUPPORTED(destroy_function(), ".*ORDER OF FUNCTIONS IS NOT RIGHT.*"); + + std::vector in(20, 1); + SlowTask task(in); + task.GetStateOfTesting() = ppc::core::StateOfTesting::kFunc; + task.Validation(); + EXPECT_NO_THROW(task.PreProcessing()); + task.Run(); + EXPECT_THROW(task.PostProcessing(), std::runtime_error); +} + +class DummyTask : public ppc::core::Task { + public: + using Task::Task; + bool ValidationImpl() override { return true; } + bool PreProcessingImpl() override { return true; } + bool RunImpl() override { return true; } + bool PostProcessingImpl() override { return true; } +}; + +TEST(TaskTest, ValidationThrowsIfCalledTwice) { + auto task = std::make_shared(); + task->Validation(); + EXPECT_THROW(task->Validation(), std::runtime_error); +} + +TEST(TaskTest, PreProcessingThrowsIfCalledBeforeValidation) { + auto task = std::make_shared(); + EXPECT_THROW(task->PreProcessing(), std::runtime_error); +} + +TEST(TaskTest, RunThrowsIfCalledBeforePreProcessing) { + auto task = std::make_shared(); + EXPECT_THROW(task->Run(), std::runtime_error); } -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); +TEST(TaskTest, PostProcessingThrowsIfCalledBeforeRun) { + auto task = std::make_shared(); + task->Validation(); + task->PreProcessing(); + EXPECT_THROW(task->PostProcessing(), std::runtime_error); } + +int main(int argc, char** argv) { return ppc::core::SimpleInit(argc, argv); } diff --git a/modules/core/util/include/util.hpp b/modules/core/util/include/util.hpp index 9d48d5b0a..de86faa90 100644 --- a/modules/core/util/include/util.hpp +++ b/modules/core/util/include/util.hpp @@ -1,11 +1,15 @@ #pragma once +#include #include #include #include #include #include -#include +#include +#ifdef __GNUG__ +#include +#endif #include "nlohmann/json_fwd.hpp" @@ -26,22 +30,23 @@ using NlohmannJsonTypeError = nlohmann::json::type_error; namespace ppc::util { -/** - * @brief Returns the unqualified name of the current function. - * - * @param loc Source location, defaults to the current function. - * @return Function name without namespaces or parameters. - */ -inline std::string FuncName(const std::source_location& loc = std::source_location::current()) { - std::string s{loc.function_name()}; - if (auto p = s.find('('); p != std::string::npos) { - s.resize(p); // drop “(…)” - } - if (auto p = s.rfind("::"); p != std::string::npos) { - s.erase(0, p + 2); // drop namespaces - } - return s; -} +/// @brief Utility class for tracking destructor failure across tests. +/// @details Provides thread-safe methods to set, unset, and check the failure flag. +class DestructorFailureFlag { + public: + /// @brief Marks that a destructor failure has occurred. + static void Set() { failure_flag.store(true); } + + /// @brief Clears the destructor failure flag. + static void Unset() { failure_flag.store(false); } + + /// @brief Checks if a destructor failure was recorded. + /// @return True if failure occurred, false otherwise. + static bool Get() { return failure_flag.load(); } + + private: + inline static std::atomic failure_flag{false}; +}; enum GTestParamIndex : uint8_t { kTaskGetter, kNameTest, kTestParams }; @@ -49,61 +54,26 @@ std::string GetAbsoluteTaskPath(const std::string& id_path, const std::string& r int GetNumThreads(); template -constexpr std::string_view GetNamespace() { -#if defined(__clang__) || defined(__GNUC__) - constexpr std::string_view kFunc = __PRETTY_FUNCTION__; - constexpr std::string_view kKey = "T = "; - - auto start = kFunc.find(kKey); - if (start == std::string_view::npos) { - return {}; - } - start += kKey.size(); - - auto end = kFunc.find_first_of(";]> ,", start); - if (end == std::string_view::npos) { - return {}; - } - - auto full_type = kFunc.substr(start, end - start); - - auto ns_end = full_type.rfind("::"); - if (ns_end == std::string_view::npos) { - return {}; - } - - return full_type.substr(0, ns_end); - -#elif defined(_MSC_VER) - constexpr std::string_view kFunc = __FUNCSIG__; - constexpr std::string_view kKey = "GetNamespace<"; - - auto start = kFunc.find(kKey); - if (start == std::string_view::npos) return {}; - start += kKey.size(); - - constexpr std::string_view prefixes[] = {"class ", "struct ", "enum ", "union "}; - for (auto prefix : prefixes) { - if (kFunc.substr(start, prefix.size()) == prefix) { - start += prefix.size(); +std::string GetNamespace() { + std::string name = typeid(T).name(); +#ifdef __GNUC__ + int status = 0; + std::unique_ptr demangled{abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), + std::free}; + name = (status == 0) ? demangled.get() : name; +#endif +#if defined(_MSC_VER) + const std::string prefixes[] = {"class ", "struct ", "enum ", "union "}; + for (const auto& prefix : prefixes) { + if (name.starts_with(prefix)) { + name = name.substr(prefix.size()); break; } } - - auto end = kFunc.find('>', start); - if (end == std::string_view::npos) return {}; - - auto full_type = kFunc.substr(start, end - start); - - auto ns_end = full_type.rfind("::"); - if (ns_end == std::string_view::npos) return {}; - - return full_type.substr(0, ns_end); - -#else - static_assert([] { return false; }(), "Unsupported compiler"); - return {}; + name.erase(0, name.find_first_not_of(' ')); #endif + auto pos = name.rfind("::"); + return (pos != std::string::npos) ? name.substr(0, pos) : std::string{}; } inline std::shared_ptr InitJSONPtr() { return std::make_shared(); } diff --git a/modules/core/util/tests/util.cpp b/modules/core/util/tests/util.cpp index 56a0c5da3..913773b0d 100644 --- a/modules/core/util/tests/util.cpp +++ b/modules/core/util/tests/util.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include "omp.h" @@ -12,8 +12,8 @@ struct Type {}; } // namespace my::nested TEST(util_tests, extracts_correct_namespace) { - constexpr std::string_view kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, "my::nested"); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, "my::nested"); } TEST(util_tests, threads_control_check_openmp_disabled_valgrind) { @@ -29,18 +29,18 @@ struct TypeInNamespace {}; struct PlainType {}; TEST(GetNamespaceTest, ReturnsExpectedNamespace) { - constexpr auto kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, "test_ns"); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, "test_ns"); } TEST(GetNamespaceTest, ReturnsEmptyIfNoNamespace_PrimitiveType) { - constexpr auto kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, ""); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, ""); } TEST(GetNamespaceTest, ReturnsEmptyIfNoNamespace_PlainStruct) { - constexpr auto kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, ""); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, ""); } namespace test_ns { @@ -48,23 +48,23 @@ struct Nested {}; } // namespace test_ns TEST(GetNamespaceTest, ReturnsNamespaceCorrectly) { - constexpr auto kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, "test_ns"); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, "test_ns"); } struct NoNamespaceType {}; TEST(GetNamespaceTest, NoNamespaceInType) { - constexpr auto kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, ""); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, ""); } template struct NotATemplate {}; TEST(GetNamespaceTest, NoKeyInPrettyFunction) { - constexpr auto kNs = ppc::util::GetNamespace>(); - EXPECT_EQ(kNs, ""); + std::string k_ns = ppc::util::GetNamespace>(); + EXPECT_EQ(k_ns, ""); } namespace crazy { @@ -72,6 +72,6 @@ struct VeryLongTypeNameWithOnlyLettersAndUnderscores {}; } // namespace crazy TEST(GetNamespaceTest, NoTerminatorCharactersInPrettyFunction) { - constexpr auto kNs = ppc::util::GetNamespace(); - EXPECT_EQ(kNs, "crazy"); + std::string k_ns = ppc::util::GetNamespace(); + EXPECT_EQ(k_ns, "crazy"); } diff --git a/tasks/common/runners/functional.cpp b/tasks/common/runners/functional.cpp index 4a8b0e286..443721579 100644 --- a/tasks/common/runners/functional.cpp +++ b/tasks/common/runners/functional.cpp @@ -8,10 +8,5 @@ int main(int argc, char** argv) { if (ppc::util::IsUnderMpirun()) { return ppc::core::Init(argc, argv); } - - // Limit the number of threads in TBB - tbb::global_control control(tbb::global_control::max_allowed_parallelism, ppc::util::GetNumThreads()); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + return ppc::core::SimpleInit(argc, argv); }