Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f14017c
refactor GetNamespace function implementation
allnes Jun 29, 2025
7cf5d51
comment out unused dependencies in ubuntu.yml
allnes Jun 29, 2025
9abeb45
refactor GetNamespace to simplify logic and improve readability
allnes Jun 29, 2025
397dcb0
refactor GetNamespace to enhance readability and handle missing keys …
allnes Jun 29, 2025
6424cd7
refactor GetNamespace to standardize variable naming and streamline k…
allnes Jun 29, 2025
4375000
refactor GetNamespace to replace string_view with string and use type…
allnes Jun 29, 2025
4537e9e
refactor GetNamespace tests and implementation to standardize variabl…
allnes Jun 29, 2025
ba1eb5e
refactor util module: replace `string_view` with `string`, remove unu…
allnes Jun 29, 2025
e0b2a65
add performance tests for PipelineRun, TaskRun, PrintPerfStatistic, a…
allnes Jun 29, 2025
a7329e5
add unit tests for Task status strings, type handling, and invalid pi…
allnes Jun 29, 2025
7cbb1f6
add test case for GetStringTaskType to handle unknown type with valid…
allnes Jun 29, 2025
c097cec
add tests for GetStringTaskType to validate behavior with valid and i…
allnes Jun 29, 2025
873c7dd
add test for Task destructor to validate termination on invalid pipel…
allnes Jun 29, 2025
9958b4b
refactor Task class: replace manual pipeline stage tracking with enum…
allnes Jun 29, 2025
c6d74f8
refactor task tests: remove redundant comments, consolidate assertion…
allnes Jun 29, 2025
5093418
refactor Task module: replace switch-case in `GetStringTaskType` with…
allnes Jun 29, 2025
b9efa05
refactor Task module: replace `kTypeOfTaskStrings` with inline `TypeO…
allnes Jun 29, 2025
4c5032b
refactor Task module: replace runtime error for unknown task type wit…
allnes Jun 29, 2025
ff28fc4
refactor Task module and tests: enforce explicit constructors, replac…
allnes Jun 29, 2025
d8d96f8
update Task module and tests: add missing includes, adjust test utili…
allnes Jun 29, 2025
e1e2960
refactor Task module: replace `constexpr` with `const` for `kTaskType…
allnes Jun 29, 2025
9d3430c
add tests for Task pipeline stage order validation and update Task me…
allnes Jun 29, 2025
99a8a83
update Task tests and implementation: replace `EXPECT_DEATH_IF_SUPPOR…
allnes Jun 29, 2025
1a12d05
add test for Task destructor to validate behavior when called with em…
allnes Jun 29, 2025
a7eaf77
add SimpleInit utility for gtest, refactor tests to replace `EXPECT_D…
allnes Jun 30, 2025
5acbda2
Merge branch 'master' into an/increase-coverage
allnes Jun 30, 2025
010d1b1
add missing include for `util.hpp` in Task tests
allnes Jun 30, 2025
d747766
Merge remote-tracking branch 'origin/an/increase-coverage' into an/in…
allnes Jun 30, 2025
44246e4
uncomment dependencies for `gcc-build-codecov` in Ubuntu workflow
allnes Jun 30, 2025
d390223
reduce cognitive complexity threshold in `.clang-tidy` and simplify J…
allnes Jun 30, 2025
4e76848
Update modules/core/util/include/util.hpp
aobolensk Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 61 additions & 12 deletions modules/core/performance/tests/perf_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class GetStringTaskTypeTest : public ::testing::TestWithParam<TaskTypeTestCase>
(*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); }
Expand Down Expand Up @@ -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 {
Expand All @@ -263,15 +258,69 @@ using TestTypes = ::testing::Types<my::nested::Type, my::Another, int>;
TYPED_TEST_SUITE(GetNamespaceTest, TestTypes);

TYPED_TEST(GetNamespaceTest, ExtractsNamespaceCorrectly) {
constexpr std::string_view kNs = ppc::util::GetNamespace<TypeParam>();
std::string k_ns = ppc::util::GetNamespace<TypeParam>();

if constexpr (std::is_same_v<TypeParam, my::nested::Type>) {
EXPECT_EQ(kNs, "my::nested");
EXPECT_EQ(k_ns, "my::nested");
} else if constexpr (std::is_same_v<TypeParam, my::Another>) {
EXPECT_EQ(kNs, "my");
EXPECT_EQ(k_ns, "my");
} else if constexpr (std::is_same_v<TypeParam, int>) {
EXPECT_EQ(kNs, "");
EXPECT_EQ(k_ns, "");
} else {
FAIL() << "Unhandled type in test";
}
}

TEST(PerfTest, PipelineRunAndTaskRun) {
auto task_ptr = std::make_shared<DummyTask>();
ppc::core::Perf<int, int> 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<DummyTask>();
ppc::core::Perf<int, int> 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<int, int> {
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();
}
6 changes: 6 additions & 0 deletions modules/core/runners/include/runners.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 19 additions & 0 deletions modules/core/runners/src/runners.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <format>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>

#include "core/util/include/util.hpp"
Expand Down Expand Up @@ -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';
Expand All @@ -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
139 changes: 67 additions & 72 deletions modules/core/task/include/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <omp.h>

#include <algorithm>
#include <array>
#include <chrono>
#include <core/util/include/util.hpp>
#include <cstdint>
Expand All @@ -16,6 +17,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

namespace ppc::core {
Expand All @@ -39,6 +41,25 @@ enum TypeOfTask : uint8_t {
kUnknown
};

using TaskMapping = std::pair<TypeOfTask, std::string>;
using TaskMappingArray = std::array<TaskMapping, 6>;

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
Expand Down Expand Up @@ -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 };
Expand All @@ -104,39 +108,56 @@ template <typename InType, typename OutType>
/// @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();
}

/// @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();
}
Expand Down Expand Up @@ -170,41 +191,25 @@ 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);
#endif
}

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::nanoseconds>(std::chrono::high_resolution_clock::now() -
tmp_time_point_)
.count();
Expand Down Expand Up @@ -244,26 +249,16 @@ class Task {
StateOfTesting state_of_testing_ = kFunc;
TypeOfTask type_of_task_ = kUnknown;
StatusOfTask status_of_task_ = kEnabled;
std::vector<std::string> functions_order_;
std::vector<std::string> 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<long>(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<long>(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.
Expand All @@ -276,7 +271,7 @@ using TaskPtr = std::shared_ptr<Task<InType, OutType>>;
/// @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 <typename TaskType, typename InType>
std::shared_ptr<TaskType> TaskGetter(InType in) {
return std::make_shared<TaskType>(in);
Expand Down
Loading
Loading