Skip to content

Commit 5441547

Browse files
committed
use fork for default tests
defaults changes global state, so using forks will isolate tests
1 parent a7d5b3f commit 5441547

File tree

4 files changed

+401
-262
lines changed

4 files changed

+401
-262
lines changed

src/provider/provider_ctl_stats_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static const umf_ctl_node_t CTL_NODE(peak_memory)[] = {CTL_LEAF_RUNNABLE(reset),
7474

7575
static const umf_ctl_node_t CTL_NODE(stats)[] = {
7676
CTL_LEAF_RO(allocated_memory), CTL_LEAF_RO(peak_memory),
77-
CTL_CHILD(peak_memory), CTL_LEAF_RUNNABLE(reset), CTL_NODE_END};
77+
CTL_CHILD(peak_memory), CTL_NODE_END};
7878

7979
static inline void provider_ctl_stats_alloc(CTL_PROVIDER_TYPE *provider,
8080
size_t size) {

test/common/fork_helpers.hpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (C) 2025 Intel Corporation
3+
*
4+
* Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
5+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
*/
7+
8+
#pragma once
9+
10+
#include <gtest/gtest.h>
11+
12+
#include <type_traits>
13+
#include <utility>
14+
15+
#ifndef _WIN32
16+
#include <sys/types.h>
17+
#include <sys/wait.h>
18+
#include <unistd.h>
19+
#endif
20+
21+
namespace umf_test {
22+
23+
constexpr int ForkedTestSuccess = 0;
24+
constexpr int ForkedTestFailure = 1;
25+
constexpr int ForkedTestSkip = 77;
26+
27+
template <typename Func> void run_in_fork(Func &&func) {
28+
#ifndef _WIN32
29+
static_assert(std::is_invocable_r_v<void, Func &&>,
30+
"run_in_fork requires a void-returning callable");
31+
32+
pid_t pid = fork();
33+
ASSERT_NE(pid, -1) << "fork failed";
34+
35+
if (pid == 0) {
36+
std::forward<Func>(func)();
37+
38+
auto *unit = ::testing::UnitTest::GetInstance();
39+
const ::testing::TestInfo *info =
40+
unit ? unit->current_test_info() : nullptr;
41+
const ::testing::TestResult *result = info ? info->result() : nullptr;
42+
43+
if (result != nullptr) {
44+
if (result->Skipped()) {
45+
_exit(ForkedTestSkip);
46+
}
47+
if (result->Failed()) {
48+
_exit(ForkedTestFailure);
49+
}
50+
}
51+
52+
_exit(ForkedTestSuccess);
53+
}
54+
55+
int status = 0;
56+
ASSERT_EQ(waitpid(pid, &status, 0), pid) << "waitpid failed";
57+
58+
if (!WIFEXITED(status)) {
59+
FAIL() << "Forked test terminated abnormally.";
60+
}
61+
62+
int exit_code = WEXITSTATUS(status);
63+
if (exit_code == ForkedTestSkip) {
64+
GTEST_SKIP() << "Forked test body requested skip.";
65+
}
66+
67+
ASSERT_EQ(exit_code, ForkedTestSuccess)
68+
<< "Forked test exited with code " << exit_code;
69+
#else
70+
(void)func;
71+
GTEST_SKIP() << "Fork-based tests are not supported on Windows.";
72+
#endif
73+
}
74+
75+
} // namespace umf_test
76+

test/ctl/ctl_api.cpp

Lines changed: 94 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <umf/providers/provider_os_memory.h>
2929

3030
#include "../common/base.hpp"
31+
#include "../common/fork_helpers.hpp"
3132
#include "gtest/gtest.h"
3233

3334
using namespace umf_test;
@@ -184,20 +185,21 @@ class CtlTest : public ::testing::Test {
184185
private:
185186
};
186187

187-
/* Case: default settings
188-
* This test sets a default value and then retrieves it */
188+
// setting default modyfies global state -
189+
// tests doing so should run in fork to ensure correct test isolation
189190
TEST_F(CtlTest, ctlDefault) {
190-
const char *arg = "default_name";
191-
192-
auto res = umfCtlSet("umf.pool.default.some_pool.some_path", (void *)arg,
193-
strlen(arg));
194-
ASSERT_EQ(res, UMF_RESULT_SUCCESS);
195-
196-
char output[64] = {1};
197-
res = umfCtlGet("umf.pool.default.some_pool.some_path", (void *)output,
198-
sizeof(output));
199-
ASSERT_EQ(res, UMF_RESULT_SUCCESS);
200-
ASSERT_STREQ(output, arg);
191+
umf_test::run_in_fork([] {
192+
const char *arg = "default_name";
193+
ASSERT_EQ(umfCtlSet("umf.pool.default.some_pool.some_path",
194+
(void *)arg, strlen(arg)),
195+
UMF_RESULT_SUCCESS);
196+
197+
char output[64] = {1};
198+
ASSERT_EQ(umfCtlGet("umf.pool.default.some_pool.some_path",
199+
(void *)output, sizeof(output)),
200+
UMF_RESULT_SUCCESS);
201+
ASSERT_STREQ(output, arg);
202+
});
201203
}
202204

203205
/* Case: umfCtlSet negative test */
@@ -234,58 +236,64 @@ TEST_F(CtlTest, ctlGetInvalid) {
234236
/* Case: multi-threaded test for pool defaults
235237
* This test sets a default value in multiple threads and then retrieves it */
236238
TEST_F(CtlTest, ctlDefaultPoolMultithreaded) {
237-
const size_t max_size = 10;
238-
const size_t num_threads = 8;
239-
std::vector<std::thread> threads;
240-
std::atomic<size_t> totalRecords = 0;
241-
const char *predefined_value = "xyzzyx";
242-
std::string name_prefix = "umf.pool.default.some_pool.";
243-
for (size_t i = 0; i < num_threads; i++) {
244-
threads.emplace_back([i, &totalRecords, &predefined_value, &name_prefix,
245-
max_size = max_size]() {
246-
for (size_t j = 0; j < max_size; j++) {
247-
std::string name = name_prefix + std::to_string(i * 10 + j);
248-
umfCtlSet(name.c_str(), (void *)predefined_value,
249-
strlen(predefined_value));
250-
std::atomic_fetch_add(&totalRecords, 1UL);
251-
}
252-
});
253-
}
254-
for (auto &thread : threads) {
255-
thread.join();
256-
}
239+
umf_test::run_in_fork([] {
240+
const size_t max_size = 10;
241+
const size_t num_threads = 8;
242+
std::vector<std::thread> threads;
243+
std::atomic<size_t> totalRecords = 0;
244+
const char *predefined_value = "xyzzyx";
245+
std::string name_prefix = "umf.pool.default.some_pool.";
246+
for (size_t i = 0; i < num_threads; i++) {
247+
threads.emplace_back([i, &totalRecords, &predefined_value,
248+
&name_prefix, max_size = max_size]() {
249+
for (size_t j = 0; j < max_size; j++) {
250+
std::string name =
251+
name_prefix + std::to_string(i * 10 + j);
252+
umfCtlSet(name.c_str(), (void *)predefined_value,
253+
strlen(predefined_value));
254+
std::atomic_fetch_add(&totalRecords, 1UL);
255+
}
256+
});
257+
}
258+
for (auto &thread : threads) {
259+
thread.join();
260+
}
257261

258-
// Check if all threads set the value correctly
259-
// and retrieve it
260-
ASSERT_EQ(totalRecords.load(), num_threads * max_size);
262+
ASSERT_EQ(totalRecords.load(), num_threads * max_size);
261263

262-
char output[100] = {0};
263-
for (size_t i = 0; i < totalRecords.load(); i++) {
264-
std::string name = name_prefix + std::to_string(i);
265-
auto status = umfCtlGet(name.c_str(), (void *)output, sizeof(output));
266-
ASSERT_EQ(status, UMF_RESULT_SUCCESS);
267-
ASSERT_EQ(std::string(output), std::string(predefined_value));
268-
}
264+
char output[100] = {0};
265+
for (size_t i = 0; i < totalRecords.load(); i++) {
266+
std::string name = name_prefix + std::to_string(i);
267+
umf_result_t status =
268+
umfCtlGet(name.c_str(), (void *)output, sizeof(output));
269+
ASSERT_EQ(status, UMF_RESULT_SUCCESS);
270+
ASSERT_STREQ(output, predefined_value);
271+
}
272+
});
269273
}
270274

271275
/* Case: overwriting an existing value for pool defaults
272276
* This test sets a default value and then overwrites it with a new value */
273277
TEST_F(CtlTest, ctlDefaultPoolOverwrite) {
274-
constexpr int max_size = 10;
275-
std::vector<std::string> values;
276-
const std::string name = "umf.pool.default.some_pool";
277-
278-
for (int i = 0; i < max_size; i++) {
279-
values.push_back("value_" + std::to_string(i));
280-
umfCtlSet(name.c_str(), (void *)values.back().c_str(),
281-
values.back().size());
282-
}
278+
umf_test::run_in_fork([] {
279+
constexpr int max_size = 10;
280+
std::vector<std::string> values;
281+
const std::string name = "umf.pool.default.some_pool";
282+
283+
for (int i = 0; i < max_size; i++) {
284+
values.push_back("value_" + std::to_string(i));
285+
umf_result_t set_status =
286+
umfCtlSet(name.c_str(), (void *)values.back().c_str(),
287+
values.back().size());
288+
ASSERT_EQ(set_status, UMF_RESULT_SUCCESS);
289+
}
283290

284-
char output[100] = {0};
285-
umf_result_t status =
286-
umfCtlGet(name.c_str(), (void *)output, sizeof(output));
287-
ASSERT_EQ(status, UMF_RESULT_SUCCESS);
288-
ASSERT_EQ(std::string(output), values.back());
291+
char output[100] = {0};
292+
umf_result_t status =
293+
umfCtlGet(name.c_str(), (void *)output, sizeof(output));
294+
ASSERT_EQ(status, UMF_RESULT_SUCCESS);
295+
ASSERT_STREQ(output, values.back().c_str());
296+
});
289297
}
290298

291299
TEST_F(CtlTest, DISABLED_ctlNameValidation) {
@@ -349,32 +357,36 @@ TEST_F(CtlTest, DISABLED_ctlExecInvalidSize) {
349357
}
350358

351359
TEST_F(CtlTest, ctlDefaultMultithreadedProvider) {
352-
std::vector<std::thread> threads;
353-
std::atomic<size_t> totalRecords = 0;
354-
const char *predefined_value = "xyzzyx";
355-
std::string name_prefix = "umf.provider.default.some_provider.";
356-
for (int i = 0; i < 8; i++) {
357-
threads.emplace_back(
358-
[i, &totalRecords, &predefined_value, &name_prefix]() {
359-
for (int j = 0; j < 10; j++) {
360-
std::string name = name_prefix + std::to_string(i * 10 + j);
361-
umfCtlSet(name.c_str(), (void *)predefined_value,
362-
strlen(predefined_value));
363-
std::atomic_fetch_add(&totalRecords, 1);
364-
}
365-
});
366-
}
367-
for (auto &thread : threads) {
368-
thread.join();
369-
}
360+
umf_test::run_in_fork([] {
361+
std::vector<std::thread> threads;
362+
std::atomic<size_t> totalRecords = 0;
363+
const char *predefined_value = "xyzzyx";
364+
std::string name_prefix = "umf.provider.default.some_provider.";
365+
for (int i = 0; i < 8; i++) {
366+
threads.emplace_back(
367+
[i, &totalRecords, &predefined_value, &name_prefix]() {
368+
for (int j = 0; j < 10; j++) {
369+
std::string name =
370+
name_prefix + std::to_string(i * 10 + j);
371+
umfCtlSet(name.c_str(), (void *)predefined_value,
372+
strlen(predefined_value));
373+
std::atomic_fetch_add(&totalRecords, 1);
374+
}
375+
});
376+
}
377+
for (auto &thread : threads) {
378+
thread.join();
379+
}
370380

371-
char output[100] = {0};
372-
for (size_t i = 0; i < totalRecords.load(); i++) {
373-
std::string name = name_prefix + std::to_string(i);
374-
auto status = umfCtlGet(name.c_str(), (void *)output, sizeof(output));
375-
ASSERT_EQ(status, UMF_RESULT_SUCCESS);
376-
ASSERT_EQ(std::string(output), std::string(predefined_value));
377-
}
381+
char output[100] = {0};
382+
for (size_t i = 0; i < totalRecords.load(); i++) {
383+
std::string name = name_prefix + std::to_string(i);
384+
umf_result_t status =
385+
umfCtlGet(name.c_str(), (void *)output, sizeof(output));
386+
ASSERT_EQ(status, UMF_RESULT_SUCCESS);
387+
ASSERT_STREQ(output, predefined_value);
388+
}
389+
});
378390
}
379391

380392
TEST_F(test, ctl_logger_basic_rw) {

0 commit comments

Comments
 (0)