From 6ef874f3b48ca8761f40ef050b76d9c7cb290d6f Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Mon, 25 Nov 2024 13:09:48 -0600 Subject: [PATCH] Synchronize multi-threaded error reporting --- src/H5public.h | 2 + test/API/H5_api_dataset_test.c | 51 ++++++----- test/API/H5_api_group_test.c | 13 ++- test/h5test.h | 131 ++++++++++++++++++++++------ test/testframe.c | 150 +++++++++++++++++++++++++++++---- 5 files changed, 280 insertions(+), 67 deletions(-) diff --git a/src/H5public.h b/src/H5public.h index e9790484cca..7722bda2ca3 100644 --- a/src/H5public.h +++ b/src/H5public.h @@ -419,6 +419,7 @@ extern "C" { #define H5_ATOMIC_STORE(dst, value) atomic_store(&dst, value) #define H5_ATOMIC_LOAD(src) atomic_load(&src) +#define H5_ATOMIC_ADD(dst, value) atomic_fetch_add(&dst, value) #else #define H5_ATOMIC(type) type @@ -427,6 +428,7 @@ extern "C" { #define H5_ATOMIC_STORE(dst, value) (dst = value) #define H5_ATOMIC_LOAD(src) (src) +#define H5_ATOMIC_ADD(dst, value) (dst += value) #endif diff --git a/test/API/H5_api_dataset_test.c b/test/API/H5_api_dataset_test.c index 1d6fa21f817..75f59f214ea 100644 --- a/test/API/H5_api_dataset_test.c +++ b/test/API/H5_api_dataset_test.c @@ -2704,9 +2704,11 @@ test_create_many_dataset(void) goto error; } - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); for (i = 0; i < DATASET_NUMB; i++) { - printf("\r %u/%u", i + 1, DATASET_NUMB); + if (IS_MAIN_TEST_THREAD) + printf("\r %u/%u", i + 1, DATASET_NUMB); sprintf(dset_name, "dset_%02u", i); data = i % 256; @@ -4400,7 +4402,7 @@ test_dataset_io_point_selections(void) /* Perform with and without chunking */ for (do_chunk = FALSE;; do_chunk = TRUE) { if (do_chunk) { - TESTING("point selection I/O with all selection in memory and points in file with chunking"); + TESTING_2("point selection I/O with all selection in memory and points in file with chunking"); /* Create chunked dataset */ if ((dset_id = H5Dcreate2(group_id, DATASET_IO_POINT_DSET_NAME_CHUNK, H5T_NATIVE_INT, fspace_id, @@ -4481,9 +4483,9 @@ test_dataset_io_point_selections(void) PASSED(); if (do_chunk) - TESTING("point selection I/O with points in memory and file (same shape) with chunking"); + TESTING_2("point selection I/O with points in memory and file (same shape) with chunking"); else - TESTING("point selection I/O with points in memory and file (same shape)"); + TESTING_2("point selection I/O with points in memory and file (same shape)"); /* Generate points to read */ DATASET_IO_POINT_GEN_POINTS(points, i, j); @@ -4547,9 +4549,9 @@ test_dataset_io_point_selections(void) PASSED(); if (do_chunk) - TESTING("point selection I/O with points in memory and file (different shape) with chunking"); + TESTING_2("point selection I/O with points in memory and file (different shape) with chunking"); else - TESTING("point selection I/O with points in memory and file (different shape)"); + TESTING_2("point selection I/O with points in memory and file (different shape)"); /* Generate points to read */ DATASET_IO_POINT_GEN_POINTS(points, i, j); @@ -4620,9 +4622,9 @@ test_dataset_io_point_selections(void) PASSED(); if (do_chunk) - TESTING("point selection I/O with hyperslab in memory and points in file with chunking"); + TESTING_2("point selection I/O with hyperslab in memory and points in file with chunking"); else - TESTING("point selection I/O with hyperslab in memory and points in file"); + TESTING_2("point selection I/O with hyperslab in memory and points in file"); /* Generate points to read */ DATASET_IO_POINT_GEN_POINTS(points, i, j); @@ -4693,9 +4695,9 @@ test_dataset_io_point_selections(void) PASSED(); if (do_chunk) - TESTING("point selection I/O with points in memory and hyperslab in file with chunking"); + TESTING_2("point selection I/O with points in memory and hyperslab in file with chunking"); else - TESTING("point selection I/O with points in memory and hyperslab in file"); + TESTING_2("point selection I/O with points in memory and hyperslab in file"); /* Generate points to read */ DATASET_IO_POINT_GEN_POINTS(points, i, j); @@ -9460,11 +9462,13 @@ test_write_multi_chunk_dataset_diff_shape_read(void) /* * Read every chunk in the dataset, checking the data for each one. */ - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); for (i = 0; i < data_size / chunk_size; i++) { size_t j; - printf("\r Reading chunk %zu", i); + if (IS_MAIN_TEST_THREAD) + printf("\r Reading chunk %zu", i); for (j = 0; j < DATASET_MULTI_CHUNK_WRITE_DIFF_SPACE_READ_TEST_DSET_SPACE_RANK; j++) { if (dims[j] == chunk_dims[j]) @@ -9686,7 +9690,8 @@ test_overwrite_multi_chunk_dataset_same_shape_read(void) count[i] = chunk_dims[i]; } - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); for (niter = 0; niter < DATASET_MULTI_CHUNK_OVERWRITE_SAME_SPACE_READ_TEST_NITERS; niter++) { memset(write_buf, 0, data_size); @@ -9795,7 +9800,8 @@ test_overwrite_multi_chunk_dataset_same_shape_read(void) for (i = 0; i < data_size / chunk_size; i++) { size_t j, k; - printf("\r Reading chunk %zu", i); + if (IS_MAIN_TEST_THREAD) + printf("\r Reading chunk %zu", i); for (j = 0; j < DATASET_MULTI_CHUNK_OVERWRITE_SAME_SPACE_READ_TEST_DSET_SPACE_RANK; j++) { if (dims[j] == chunk_dims[j]) @@ -10030,7 +10036,8 @@ test_overwrite_multi_chunk_dataset_diff_shape_read(void) count[i] = chunk_dims[i]; } - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); for (niter = 0; niter < DATASET_MULTI_CHUNK_OVERWRITE_DIFF_SPACE_READ_TEST_NITERS; niter++) { memset(write_buf, 0, data_size); @@ -10139,7 +10146,8 @@ test_overwrite_multi_chunk_dataset_diff_shape_read(void) for (i = 0; i < data_size / chunk_size; i++) { size_t j; - printf("\r Reading chunk %zu", i); + if (IS_MAIN_TEST_THREAD) + printf("\r Reading chunk %zu", i); for (j = 0; j < DATASET_MULTI_CHUNK_OVERWRITE_DIFF_SPACE_READ_TEST_DSET_SPACE_RANK; j++) { if (dims[j] == chunk_dims[j]) @@ -10550,7 +10558,8 @@ test_read_partial_chunk_hyperslab_selection(void) /* * Write and read each chunk in the dataset. */ - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); for (i = 0; i < FIXED_NCHUNKS; i++) { hsize_t start[DATASET_PARTIAL_CHUNK_READ_HYPER_SEL_TEST_DSET_SPACE_RANK]; hsize_t count[DATASET_PARTIAL_CHUNK_READ_HYPER_SEL_TEST_DSET_SPACE_RANK]; @@ -10614,7 +10623,8 @@ test_read_partial_chunk_hyperslab_selection(void) goto error; } - printf("\r Writing chunk %zu", i); + if (IS_MAIN_TEST_THREAD) + printf("\r Writing chunk %zu", i); if (H5Dwrite(dset_id, DATASET_PARTIAL_CHUNK_READ_HYPER_SEL_TEST_DSET_DTYPE, mspace_id, fspace_id, H5P_DEFAULT, write_buf) < 0) { @@ -10649,7 +10659,8 @@ test_read_partial_chunk_hyperslab_selection(void) goto error; } - printf("\r Reading chunk %zu", i); + if (IS_MAIN_TEST_THREAD) + printf("\r Reading chunk %zu", i); if (H5Dread(dset_id, DATASET_PARTIAL_CHUNK_READ_HYPER_SEL_TEST_DSET_DTYPE, mspace_id, fspace_id, H5P_DEFAULT, read_buf) < 0) { diff --git a/test/API/H5_api_group_test.c b/test/API/H5_api_group_test.c index 24832baf0cd..3e875ddfb44 100644 --- a/test/API/H5_api_group_test.c +++ b/test/API/H5_api_group_test.c @@ -214,10 +214,13 @@ test_create_many_groups(void) } /* Create multiple groups under the parent group */ - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); for (i = 0; i < GROUP_NUMB_MANY; i++) { - printf("\r %u/%u", i + 1, GROUP_NUMB_MANY); + if (IS_MAIN_TEST_THREAD) + printf("\r %u/%u", i + 1, GROUP_NUMB_MANY); sprintf(group_name, "group %02u", i); + if ((child_group_id = H5Gcreate2(parent_group_id, group_name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0) { H5_FAILED(); @@ -292,7 +295,8 @@ test_create_deep_groups(void) goto error; } - printf("\n"); + if (IS_MAIN_TEST_THREAD) + printf("\n"); if (create_group_recursive(group_id, 1) < 0) TEST_ERROR; @@ -328,7 +332,8 @@ create_group_recursive(hid_t parent_gid, unsigned counter) hid_t child_gid = H5I_INVALID_HID; char gname[NAME_BUF_SIZE]; - printf("\r %u/%u", counter, GROUP_DEPTH); + if (IS_MAIN_TEST_THREAD) + printf("\r %u/%u", counter, GROUP_DEPTH); if (counter == 1) sprintf(gname, "2nd_child_group"); else if (counter == 2) diff --git a/test/h5test.h b/test/h5test.h index 6f9f3466449..18a2ae2e096 100644 --- a/test/h5test.h +++ b/test/h5test.h @@ -87,14 +87,26 @@ H5TEST_DLLVAR MPI_Info h5_io_info_g; /* MPI INFO object for IO */ #endif #define H5_TEST_FILENAME_MAX_LENGTH 1024 +#define H5_MAX_NUM_SUBTESTS 64 -#define API_TEST_PASS 1 -#define API_TEST_FAIL 0 -#define API_TEST_ERROR -1 +/* The results are defined like this to make it simple for + * a fail in one thread to supersede passes/skips in other threads + * by using greater-than comparisons + */ +typedef uint8_t test_outcome_t; + +#define TEST_UNINIT ((uint8_t) 0x00) +#define TEST_PASS ((uint8_t) 0x01) +#define TEST_SKIP ((uint8_t) 0x02) +#define TEST_FAIL ((uint8_t) 0x03) +#define TEST_INVALID ((uint8_t) 0x04) /* Information for an individual thread running the API tests */ typedef struct thread_info_t { - int thread_idx; /* The test-assign index of the thread */ + int thread_idx; /* The test-framework-assigned index of the thread */ + size_t num_tests; /* Number of individual tests contained within a top-level test */ + test_outcome_t *test_outcomes; + const char **test_descriptions; char* test_thread_filename; /* The name of the test container file */ } thread_info_t; @@ -124,12 +136,53 @@ extern pthread_key_t test_thread_info_key_g; */ #ifdef H5_HAVE_MULTITHREAD -/* Increment global atomic testing variable. Used for MT testing by - * tests that don't define threadlocal test information */ -#define INCR_TEST_STAT(field_name) atomic_fetch_add(&field_name, 1); +#define INCR_RUN_COUNT \ + if (GetTestMaxNumThreads() > 1 && pthread_getspecific(test_thread_info_key_g)) { \ + ((thread_info_t*)pthread_getspecific(test_thread_info_key_g))->num_tests++; \ + assert(((thread_info_t*)pthread_getspecific(test_thread_info_key_g))->num_tests <= H5_MAX_NUM_SUBTESTS); \ + } else { \ + H5_ATOMIC_ADD(n_tests_run_g, 1); \ + } + +/* If running multi-threaded tests, store outcomes on threadlocal variable for later aggregation. */ +/* The global variables are atomic based on build configuration, not runtime thread count, + * and so the ATOMIC_ADD macros must be used even in the single-thread runtime. */ +#define INCR_FAILED_COUNT \ + if (GetTestMaxNumThreads() > 1 && pthread_getspecific(test_thread_info_key_g)) { \ + thread_info_t *_tinfo = (thread_info_t*)pthread_getspecific(test_thread_info_key_g); \ + assert(_tinfo->num_tests > 0); \ + assert(_tinfo->test_outcomes[_tinfo->num_tests - 1] == TEST_UNINIT); \ + _tinfo->test_outcomes[_tinfo->num_tests - 1] = TEST_FAIL; \ + } else { \ + H5_ATOMIC_ADD(n_tests_failed_g, 1); \ + } + +#define INCR_PASSED_COUNT \ + if (GetTestMaxNumThreads() > 1 && pthread_getspecific(test_thread_info_key_g)) { \ + thread_info_t *_tinfo = (thread_info_t*)pthread_getspecific(test_thread_info_key_g); \ + assert(_tinfo->num_tests > 0); \ + assert(_tinfo->test_outcomes[_tinfo->num_tests - 1] == TEST_UNINIT); \ + _tinfo->test_outcomes[_tinfo->num_tests - 1] = TEST_PASS; \ + } else { \ + H5_ATOMIC_ADD(n_tests_passed_g, 1); \ + } + +#define INCR_SKIPPED_COUNT \ + if (GetTestMaxNumThreads() > 1 && pthread_getspecific(test_thread_info_key_g)) { \ + thread_info_t *_tinfo = (thread_info_t*)pthread_getspecific(test_thread_info_key_g); \ + assert(_tinfo->num_tests > 0); \ + assert(_tinfo->test_outcomes[_tinfo->num_tests - 1] == TEST_UNINIT); \ + _tinfo->test_outcomes[_tinfo->num_tests - 1] = TEST_SKIP; \ + } else { \ + H5_ATOMIC_ADD(n_tests_skipped_g, 1); \ + } #else -#define INCR_TEST_STAT(field_name) field_name++ + +#define INCR_RUN_COUNT n_tests_run_g++; +#define INCR_FAILED_COUNT n_tests_failed_g++; +#define INCR_PASSED_COUNT n_tests_passed_g++; +#define INCR_SKIPPED_COUNT n_tests_skipped_g++; #endif /* @@ -143,35 +196,52 @@ extern pthread_key_t test_thread_info_key_g; */ #define TESTING(WHAT) \ do { \ + INCR_RUN_COUNT; \ if (IS_MAIN_TEST_THREAD) { \ printf("Testing %-62s", WHAT); \ fflush(stdout); \ } \ - INCR_TEST_STAT(n_tests_run_g); \ + } while (0) +#define TESTING_2_DISPLAY(WHAT) \ + do { \ + printf(" Testing %-60s", WHAT); \ + fflush(stdout); \ } while (0) #define TESTING_2(WHAT) \ do { \ - if (IS_MAIN_TEST_THREAD) { \ - printf(" Testing %-60s", WHAT); \ - fflush(stdout); \ + INCR_RUN_COUNT; \ + if (GetTestMaxNumThreads() == 1) { \ + TESTING_2_DISPLAY(WHAT); \ + } else { \ + /* Store test desc for display after test completion */ \ + thread_info_t *_tinfo = (thread_info_t*)pthread_getspecific(test_thread_info_key_g); \ + /* TBD - Only need to store this for 1 thread */\ + _tinfo->test_descriptions[_tinfo->num_tests - 1] = WHAT; \ } \ - INCR_TEST_STAT(n_tests_run_g); \ + } while (0) +#define PASSED_DISPLAY() \ + do { \ + HDputs(" PASSED"); \ + fflush(stdout); \ } while (0) #define PASSED() \ do { \ - if (IS_MAIN_TEST_THREAD) { \ - HDputs(" PASSED"); \ - fflush(stdout); \ + if (GetTestMaxNumThreads() == 1) { \ + PASSED_DISPLAY(); \ } \ - INCR_TEST_STAT(n_tests_passed_g); \ + INCR_PASSED_COUNT; \ + } while (0) +#define H5_FAILED_DISPLAY() \ + do { \ + HDputs("*FAILED*"); \ + fflush(stdout); \ } while (0) #define H5_FAILED() \ do { \ - if (IS_MAIN_TEST_THREAD) { \ - HDputs("*FAILED*"); \ - fflush(stdout); \ + if (GetTestMaxNumThreads() == 1) { \ + H5_FAILED_DISPLAY(); \ } \ - INCR_TEST_STAT(n_tests_failed_g); \ + INCR_FAILED_COUNT; \ } while (0) #define H5_WARNING() \ do { \ @@ -180,13 +250,22 @@ extern pthread_key_t test_thread_info_key_g; fflush(stdout); \ } \ } while (0) +#define SKIPPED_DISPLAY() \ + do { \ + HDputs(" -SKIP-"); \ + fflush(stdout); \ + } while (0) #define SKIPPED() \ do { \ - if (IS_MAIN_TEST_THREAD) { \ - HDputs(" -SKIP-"); \ - fflush(stdout); \ + if (GetTestMaxNumThreads() == 1) { \ + SKIPPED_DISPLAY(); \ } \ - INCR_TEST_STAT(n_tests_skipped_g); \ + INCR_SKIPPED_COUNT; \ + } while (0) +#define ERROR_DISPLAY() \ + do { \ + HDputs(" *ERROR*"); \ + fflush(stdout); \ } while (0) #define PUTS_ERROR(s) \ do { \ @@ -268,7 +347,7 @@ extern pthread_key_t test_thread_info_key_g; part_##part_name##_end: #define PART_ERROR(part_name) \ do { \ - INCR_TEST_STAT(n_tests_failed_g); \ + INCR_FAILED_COUNT; \ part_nerrors++; \ goto part_##part_name##_end; \ } while (0) diff --git a/test/testframe.c b/test/testframe.c index db547f510e8..784a015fa84 100644 --- a/test/testframe.c +++ b/test/testframe.c @@ -38,6 +38,9 @@ typedef struct TestStruct { typedef struct TestThreadArgs { int ThreadIndex; + size_t num_tests; + const char **test_descriptions; + test_outcome_t *test_outcomes; TestCall Call; } TestThreadArgs; @@ -348,7 +351,7 @@ PerformTests(void) unsigned Loop; bool is_test_mt = false; bool mt_initialized = false; - int test_num_errs = 0; + int old_num_errs = 0; int max_num_threads = GetTestMaxNumThreads(); /* Silence compiler warnings */ @@ -356,7 +359,8 @@ PerformTests(void) for (Loop = 0; Loop < Index; Loop++) { is_test_mt = (Test[Loop].TestFrameworkFlags & ALLOW_MULTITHREAD) && (max_num_threads > 1); - + old_num_errs = H5_ATOMIC_LOAD(Test[Loop].NumErrors); + if (Test[Loop].SkipFlag) { MESSAGE(2, ("Skipping -- %s (%s) \n", Test[Loop].Description, Test[Loop].Name)); } @@ -371,8 +375,7 @@ PerformTests(void) if (!is_test_mt) { Test[Loop].Call(); TestAlarmOff(); - test_num_errs = H5_ATOMIC_LOAD(Test[Loop].NumErrors); - H5_ATOMIC_STORE(Test[Loop].NumErrors, num_errs_g - test_num_errs); + 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 { @@ -386,14 +389,24 @@ PerformTests(void) 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 (max_num_threads <= 0) { fprintf(stderr, "Invalid number of threads specified\n"); exit(EXIT_FAILURE); } - threads = (pthread_t *)calloc((size_t) max_num_threads, sizeof(pthread_t)); - thread_args = (TestThreadArgs *)calloc((size_t) max_num_threads, sizeof(TestThreadArgs)); + if ((threads = (pthread_t *)calloc((size_t) max_num_threads, sizeof(pthread_t))) == NULL) { + fprintf(stderr, "Error allocating memory for threads\n"); + exit(EXIT_FAILURE); + } + + if ((thread_args = (TestThreadArgs *)calloc((size_t) max_num_threads, sizeof(TestThreadArgs))) == NULL) { + fprintf(stderr, "Error allocating memory for thread arguments\n"); + exit(EXIT_FAILURE); + } if (!mt_initialized) { if (H5_mt_test_global_setup() < 0) { @@ -407,6 +420,21 @@ PerformTests(void) for (int i = 0; i < max_num_threads; i++) { thread_args[i].ThreadIndex = i; thread_args[i].Call = Test[Loop].Call; + thread_args[i].num_tests = 0; + + 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); + } + + memset(thread_args[i].test_outcomes, (int) TEST_UNINIT, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t)); + + 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); + } + + memset(thread_args[i].test_descriptions, 0, H5_MAX_NUM_SUBTESTS * sizeof(char*)); ret = pthread_create(&threads[i], NULL, ThreadTestWrapper, (void*) &thread_args[i]); @@ -417,22 +445,82 @@ PerformTests(void) } for (int i = 0; i < max_num_threads; i++) { - ret = pthread_join(threads[i], NULL); + ret = pthread_join(threads[i], NULL); - if (ret != 0) { - fprintf(stderr, "Error joining thread %d\n", i); - exit(EXIT_FAILURE); - } + if (ret != 0) { + fprintf(stderr, "Error joining thread %d\n", i); + exit(EXIT_FAILURE); } + + 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 < max_num_threads; 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); + } + } + + /* 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 < max_num_threads; 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 < max_num_threads; 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; + } free(threads); free(thread_args); + threads = NULL; + thread_args = NULL; + TestAlarmOff(); - test_num_errs = H5_ATOMIC_LOAD(Test[Loop].NumErrors); - H5_ATOMIC_STORE(Test[Loop].NumErrors, num_errs_g - test_num_errs); + 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 */ @@ -457,11 +545,13 @@ void *ThreadTestWrapper(void *test) { TestCall test_call; int thread_idx; + thread_info_t *tinfo = NULL; + TestThreadArgs *test_args = (TestThreadArgs *)test; assert(test); - thread_idx = ((TestThreadArgs *)test)->ThreadIndex; - test_call = ((TestThreadArgs *)test)->Call; + thread_idx = test_args->ThreadIndex; + test_call = test_args->Call; if (H5_mt_test_thread_setup((int)thread_idx) < 0) { fprintf(stderr, "Error setting up thread-local test info"); @@ -469,6 +559,17 @@ void *ThreadTestWrapper(void *test) } test_call(); + + if ((tinfo = pthread_getspecific(test_thread_info_key_g)) == NULL) { + memset(test_args->test_outcomes, (int) TEST_INVALID, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t)); + memset(test_args->test_descriptions, 0, H5_MAX_NUM_SUBTESTS * sizeof(char*)); + test_args->num_tests = 0; + } else { + memcpy(test_args->test_outcomes, tinfo->test_outcomes, H5_MAX_NUM_SUBTESTS * sizeof(test_outcome_t)); + memcpy(test_args->test_descriptions, tinfo->test_descriptions, H5_MAX_NUM_SUBTESTS * sizeof(char*)); + test_args->num_tests = tinfo->num_tests; + } + return NULL; } @@ -483,6 +584,7 @@ int H5_mt_test_thread_setup(int thread_idx) { } tinfo->thread_idx = thread_idx; + tinfo->num_tests = 0; /* TBD: This is currently only useful for API tests. Modification of existing testframe tests would be necessary * for them to use thread-local filenames to avoid conflicts during multi-threaded execution */ @@ -491,6 +593,16 @@ int H5_mt_test_thread_setup(int thread_idx) { goto error; } + if ((tinfo->test_outcomes = (test_outcome_t *)calloc(H5_MAX_NUM_SUBTESTS, sizeof(test_outcome_t))) == NULL) { + TestErrPrintf(" couldn't allocate memory for test outcomes\n"); + goto error; + } + + if ((tinfo->test_descriptions = (const char **)calloc(H5_MAX_NUM_SUBTESTS, sizeof(char*))) == NULL) { + TestErrPrintf(" couldn't allocate memory for test descriptions\n"); + goto error; + } + if (pthread_setspecific(test_thread_info_key_g, (void *) tinfo) != 0) { TestErrPrintf(" couldn't set thread-specific data\n"); goto error; @@ -500,6 +612,8 @@ int H5_mt_test_thread_setup(int thread_idx) { error: free(tinfo->test_thread_filename); + free(tinfo->test_outcomes); + free(tinfo->test_descriptions); free(tinfo); return -1; } @@ -510,6 +624,8 @@ void H5_test_thread_info_key_destructor(void *value) { if (tinfo) { free(tinfo->test_thread_filename); + free(tinfo->test_outcomes); + free(tinfo->test_descriptions); } free(tinfo); @@ -733,7 +849,7 @@ GetTestNumErrs(void) void IncTestNumErrs(void) { - num_errs_g++; + H5_ATOMIC_ADD(num_errs_g, 1); } /* @@ -756,7 +872,7 @@ TestErrPrintf(const char *format, ...) int ret_value; /* Increment the error count */ - num_errs_g++; + H5_ATOMIC_ADD(num_errs_g, 1); /* Print the requested information */ va_start(arglist, format);