Skip to content

Commit

Permalink
Isolate threaded test stat handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mattjala committed Nov 26, 2024
1 parent cc9369b commit 0c2fa79
Showing 1 changed file with 157 additions and 145 deletions.
302 changes: 157 additions & 145 deletions test/testframe.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ static int (*TestPrivateParser)(int ac, char *av[]) = NULL;
static int TestMaxNumThreads_g = -1; /* Max number of threads that can be spawned */
const char *test_path_prefix = NULL;

static void PerformThreadedTest(TestStruct Test);

#ifdef H5_HAVE_MULTITHREAD
static void UpdateTestStats(TestThreadArgs *test_args);
#endif

/*
* Setup a test function and add it to the list of tests.
* It must have no parameters and returns void.
Expand Down Expand Up @@ -350,11 +356,8 @@ PerformTests(void)
{
unsigned Loop;
bool is_test_threaded = false;
bool mt_initialized = false;
int old_num_errs = 0;

/* Silence compiler warnings */
(void) mt_initialized;

for (Loop = 0; Loop < Index; Loop++) {
is_test_threaded = (Test[Loop].TestFrameworkFlags & ALLOW_MULTITHREAD) && (TEST_EXECUTION_THREADED);
Expand All @@ -364,173 +367,173 @@ PerformTests(void)
MESSAGE(2, ("Skipping -- %s (%s) \n", Test[Loop].Description, Test[Loop].Name));
}
else {
MESSAGE(2, ("Testing %s -- %s (%s) \n", (is_test_threaded ? "(Threaded)" : ""),
Test[Loop].Description, Test[Loop].Name));
MESSAGE(2, ("Testing -- %s (%s) \n", Test[Loop].Description, Test[Loop].Name));
MESSAGE(5, ("===============================================\n"));
H5_ATOMIC_STORE(Test[Loop].NumErrors, num_errs_g);
Test_parameters = Test[Loop].TestParameters;
TestAlarmOn();

if (!is_test_threaded) {
Test[Loop].Call();
TestAlarmOff();
H5_ATOMIC_STORE(Test[Loop].NumErrors, num_errs_g - old_num_errs);
MESSAGE(5, ("===============================================\n"));
MESSAGE(5, ("There were %d errors detected.\n\n", (int)H5_ATOMIC_LOAD(Test[Loop].NumErrors)));
} else {
#ifndef H5_HAVE_MULTITHREAD
if (Test[Loop].TestFrameworkFlags & ALLOW_MULTITHREAD) {
MESSAGE(2, ("HDF5 was not built with multi-threaded support; Skipping test\n"));
TestAlarmOff();
continue;
}
#else
pthread_t *threads;
TestThreadArgs *thread_args;
int ret = 0;
test_outcome_t final_results[H5_MAX_NUM_SUBTESTS];

memset(final_results, (int) TEST_UNINIT, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t));

if ((threads = (pthread_t *)calloc((size_t) GetTestMaxNumThreads(), sizeof(pthread_t))) == NULL) {
fprintf(stderr, "Error allocating memory for threads\n");
exit(EXIT_FAILURE);
}
PerformThreadedTest(Test[Loop]);
}

if ((thread_args = (TestThreadArgs *)calloc((size_t) GetTestMaxNumThreads(), sizeof(TestThreadArgs))) == NULL) {
fprintf(stderr, "Error allocating memory for thread arguments\n");
exit(EXIT_FAILURE);
}
TestAlarmOff();
H5_ATOMIC_STORE(Test[Loop].NumErrors, num_errs_g - old_num_errs);
MESSAGE(5, ("===============================================\n"));
MESSAGE(5, ("There were %d errors detected.\n\n", (int)H5_ATOMIC_LOAD(Test[Loop].NumErrors)));
}
}

if (!mt_initialized) {
if (H5_mt_test_global_setup() < 0) {
fprintf(stderr, "Error setting up global MT test info\n");
exit(EXIT_FAILURE);
}
Test_parameters = NULL; /* clear it. */

mt_initialized = true;
}
if (num_errs_g)
print_func("!!! %d Error(s) were detected !!!\n\n", (int)num_errs_g);
else
MESSAGE(VERBO_NONE, ("All tests were successful. \n\n"));
}

for (int i = 0; i < GetTestMaxNumThreads(); i++) {
thread_args[i].ThreadIndex = i;
thread_args[i].Call = Test[Loop].Call;
thread_args[i].num_tests = 0;
#ifdef H5_HAVE_MULTITHREAD

if ((thread_args[i].test_outcomes = calloc(H5_MAX_NUM_SUBTESTS, sizeof(test_outcome_t))) == NULL) {
fprintf(stderr, "Error allocating memory for thread outcomes\n");
exit(EXIT_FAILURE);
}
static void
PerformThreadedTest(TestStruct threaded_test) {
pthread_t *threads;
TestThreadArgs *thread_args;
int ret = 0;

if (H5_mt_test_global_setup() < 0) {
fprintf(stderr, "Error setting up global MT test info\n");
exit(EXIT_FAILURE);
}

memset(thread_args[i].test_outcomes, (int) TEST_UNINIT, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t));
if ((threads = (pthread_t *)calloc((size_t) GetTestMaxNumThreads(), sizeof(pthread_t))) == NULL) {
fprintf(stderr, "Error allocating memory for threads\n");
exit(EXIT_FAILURE);
}

if ((thread_args[i].test_descriptions = calloc(H5_MAX_NUM_SUBTESTS, sizeof(char*))) == NULL) {
fprintf(stderr, "Error allocating memory for thread test descriptions\n");
exit(EXIT_FAILURE);
}
if ((thread_args = (TestThreadArgs *)calloc((size_t) GetTestMaxNumThreads(), sizeof(TestThreadArgs))) == NULL) {
fprintf(stderr, "Error allocating memory for thread arguments\n");
exit(EXIT_FAILURE);
}

memset(thread_args[i].test_descriptions, 0, H5_MAX_NUM_SUBTESTS * sizeof(char*));
for (int i = 0; i < GetTestMaxNumThreads(); i++) {
thread_args[i].ThreadIndex = i;
thread_args[i].Call = threaded_test.Call;
thread_args[i].num_tests = 0;

ret = pthread_create(&threads[i], NULL, ThreadTestWrapper, (void*) &thread_args[i]);
if ((thread_args[i].test_outcomes = calloc(H5_MAX_NUM_SUBTESTS, sizeof(test_outcome_t))) == NULL) {
fprintf(stderr, "Error allocating memory for thread outcomes\n");
exit(EXIT_FAILURE);
}

if (ret != 0) {
fprintf(stderr, "Error creating thread %d\n", i);
exit(EXIT_FAILURE);
}
}
memset(thread_args[i].test_outcomes, (int) TEST_UNINIT, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t));

for (int i = 0; i < GetTestMaxNumThreads(); i++) {
ret = pthread_join(threads[i], NULL);
if ((thread_args[i].test_descriptions = calloc(H5_MAX_NUM_SUBTESTS, sizeof(char*))) == NULL) {
fprintf(stderr, "Error allocating memory for thread test descriptions\n");
exit(EXIT_FAILURE);
}

if (ret != 0) {
fprintf(stderr, "Error joining thread %d\n", i);
exit(EXIT_FAILURE);
}
memset(thread_args[i].test_descriptions, 0, H5_MAX_NUM_SUBTESTS * sizeof(char*));

ret = pthread_create(&threads[i], NULL, ThreadTestWrapper, (void*) &thread_args[i]);

if (thread_args[i].num_tests == 0) {
fprintf(stderr, "Empty test found for thread %d\n", i);
exit(EXIT_FAILURE);
}
}

/* Verify that each thread reported the same number of subtests */
for (int i = 0; i < GetTestMaxNumThreads(); i++) {
if (thread_args[i].num_tests != thread_args[0].num_tests) {
fprintf(stderr, "Thread %d reported %ld subtests, but thread 0 reported %ld\n", i, thread_args[i].num_tests, thread_args[0].num_tests);
exit(EXIT_FAILURE);
}
}
if (ret != 0) {
fprintf(stderr, "Error creating thread %d\n", i);
exit(EXIT_FAILURE);
}
}

/* Aggregate results - priority order is invalid > fail > pass > skip */
H5_ATOMIC_ADD(n_tests_run_g, thread_args[0].num_tests);

for (size_t j = 0; j < thread_args[0].num_tests; j++) {
for (int i = 0; i < GetTestMaxNumThreads(); i++)
final_results[j] = ((final_results[j] > thread_args[i].test_outcomes[j]) ? final_results[j] : thread_args[i].test_outcomes[j]);

/* Display subtest description, if result is from subtest */
if (thread_args[0].test_descriptions[j] != NULL)
TESTING_2_DISPLAY(thread_args[0].test_descriptions[j]);

switch (final_results[j]) {
case TEST_PASS:
PASSED_DISPLAY();
H5_ATOMIC_ADD(n_tests_passed_g, 1);
break;
case TEST_FAIL:
H5_FAILED_DISPLAY();
H5_ATOMIC_ADD(n_tests_failed_g, 1);
/* TBD - Neither multi-threaded nor single-threaded API tests increment the testframe error count.
* This would deal with the multi-threaded case, but the single-threaded case is trickier. */
/* H5_ATOMIC_ADD(num_errs_g, 1); */
break;
case TEST_SKIP:
SKIPPED_DISPLAY();
H5_ATOMIC_ADD(n_tests_skipped_g, 1);
break;
case TEST_UNINIT:
ERROR_DISPLAY();
exit(EXIT_FAILURE);
break;
case TEST_INVALID:
default:
ERROR_DISPLAY();
exit(EXIT_FAILURE);
break;
}
}
for (int i = 0; i < GetTestMaxNumThreads(); i++) {
ret = pthread_join(threads[i], NULL);

for (int i = 0; i < GetTestMaxNumThreads(); i++) {
free(thread_args[i].test_outcomes);
free(thread_args[i].test_descriptions);
thread_args[i].test_outcomes = NULL;
thread_args[i].test_descriptions = NULL;
}
if (ret != 0) {
fprintf(stderr, "Error joining thread %d\n", i);
exit(EXIT_FAILURE);
}
}

UpdateTestStats(thread_args);

/* Clean up */
for (int i = 0; i < GetTestMaxNumThreads(); i++) {
free(thread_args[i].test_outcomes);
thread_args[i].test_outcomes = NULL;

free(thread_args[i].test_descriptions);
thread_args[i].test_descriptions = NULL;
}

free(threads);
free(thread_args);
threads = NULL;
thread_args = NULL;
free(threads);
free(thread_args);

threads = NULL;
thread_args = NULL;

TestAlarmOff();
return;
}

H5_ATOMIC_STORE(Test[Loop].NumErrors, num_errs_g - old_num_errs);
MESSAGE(5, ("===============================================\n"));
MESSAGE(5, ("There were %d errors detected.\n\n", (int)H5_ATOMIC_LOAD(Test[Loop].NumErrors)));
#endif /* H5_HAVE_MULTITHREAD */
}
static void
UpdateTestStats(TestThreadArgs *thread_args) {
test_outcome_t final_results[H5_MAX_NUM_SUBTESTS];
memset(final_results, (int) TEST_UNINIT, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t));

/* If test does not publish its results information to a threadlocal variable,
* do not track statistics */
if (thread_args[0].num_tests == 0) {
return;
}

/* Verify that each thread reported the same number of subtests */
for (int i = 0; i < GetTestMaxNumThreads(); i++) {
if (thread_args[i].num_tests != thread_args[0].num_tests) {
fprintf(stderr, "Thread %d reported %ld subtests, but thread 0 reported %ld\n", i, thread_args[i].num_tests, thread_args[0].num_tests);
exit(EXIT_FAILURE);
}
}

Test_parameters = NULL; /* clear it. */
/* Aggregate results - priority order is invalid > fail > pass > skip */
H5_ATOMIC_ADD(n_tests_run_g, thread_args[0].num_tests);

for (size_t j = 0; j < thread_args[0].num_tests; j++) {
for (int i = 0; i < GetTestMaxNumThreads(); i++)
final_results[j] = ((final_results[j] > thread_args[i].test_outcomes[j]) ? final_results[j] : thread_args[i].test_outcomes[j]);

/* Display subtest description, if result is from subtest */
if (thread_args[0].test_descriptions[j] != NULL)
TESTING_2_DISPLAY(thread_args[0].test_descriptions[j]);

switch (final_results[j]) {
case TEST_PASS:
PASSED_DISPLAY();
H5_ATOMIC_ADD(n_tests_passed_g, 1);
break;
case TEST_FAIL:
H5_FAILED_DISPLAY();
H5_ATOMIC_ADD(n_tests_failed_g, 1);
/* TBD - Neither multi-threaded nor single-threaded API tests increment the testframe error count.
* This would deal with the multi-threaded case, but the single-threaded case is trickier. */
/* H5_ATOMIC_ADD(num_errs_g, 1); */
break;
case TEST_SKIP:
SKIPPED_DISPLAY();
H5_ATOMIC_ADD(n_tests_skipped_g, 1);
break;
case TEST_UNINIT:
ERROR_DISPLAY();
exit(EXIT_FAILURE);
break;
case TEST_INVALID:
default:
ERROR_DISPLAY();
exit(EXIT_FAILURE);
break;
}
}

if (num_errs_g)
print_func("!!! %d Error(s) were detected !!!\n\n", (int)num_errs_g);
else
MESSAGE(VERBO_NONE, ("All tests were successful. \n\n"));
return;
}

#ifdef H5_HAVE_MULTITHREAD
/*
* Set up and execute a test flagged for threaded
* execution within a single thread.
Expand Down Expand Up @@ -627,7 +630,15 @@ void H5_test_thread_info_key_destructor(void *value) {
return;
}

#endif
#else /* H5_HAVE_MULTITHREAD */
static void
PerformThreadedTest(TestStruct threaded_test) {
(void) threaded_test;
MESSAGE(2, ("HDF5 was not built with multi-threaded support; Skipping test\n"));
return;
}

#endif /* H5_HAVE_MULTITHREAD */
/*
* Display test summary.
*/
Expand Down Expand Up @@ -928,11 +939,12 @@ SetTest(const char *testname, int action)
/* Set up global variables used for API tests */
int H5_mt_test_global_setup(void) {

/* Set up pthread key */
if (pthread_key_create(&test_thread_info_key_g, H5_test_thread_info_key_destructor) != 0) {
fprintf(stderr, "Error creating threadlocal key\n");
goto error;
}
/* Set up pthread key if it doesn't exist */
if (pthread_getspecific(test_thread_info_key_g) == NULL)
if (pthread_key_create(&test_thread_info_key_g, H5_test_thread_info_key_destructor) != 0) {
fprintf(stderr, "Error creating threadlocal key\n");
goto error;
}

return 0;
error:
Expand Down Expand Up @@ -990,4 +1002,4 @@ SetTestMaxNumThreads(int max_num_threads)
{
TestMaxNumThreads_g = max_num_threads;
return;
}
}

0 comments on commit 0c2fa79

Please sign in to comment.