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 7ea133a0181..d26a5154a09 100644 --- a/test/API/H5_api_dataset_test.c +++ b/test/API/H5_api_dataset_test.c @@ -3129,9 +3129,11 @@ test_create_many_dataset(void H5_ATTR_UNUSED *params) 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); snprintf(dset_name, sizeof(dset_name), "dset_%02u", i); data = i % 256; @@ -5258,7 +5260,7 @@ test_dataset_io_point_selections(void H5_ATTR_UNUSED *params) /* 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, @@ -5339,9 +5341,9 @@ test_dataset_io_point_selections(void H5_ATTR_UNUSED *params) 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); @@ -5405,9 +5407,9 @@ test_dataset_io_point_selections(void H5_ATTR_UNUSED *params) 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); @@ -5478,9 +5480,9 @@ test_dataset_io_point_selections(void H5_ATTR_UNUSED *params) 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); @@ -5551,9 +5553,9 @@ test_dataset_io_point_selections(void H5_ATTR_UNUSED *params) 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); @@ -8342,7 +8344,6 @@ test_dataset_string_encodings(void H5_ATTR_UNUSED *params) } PART_END(UTF8_cset); - PASSED(); } END_MULTIPART; @@ -12855,11 +12856,13 @@ test_write_multi_chunk_dataset_diff_shape_read(void H5_ATTR_UNUSED *params) /* * 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]) @@ -13081,7 +13084,8 @@ test_overwrite_multi_chunk_dataset_same_shape_read(void H5_ATTR_UNUSED *params) 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); @@ -13190,7 +13194,8 @@ test_overwrite_multi_chunk_dataset_same_shape_read(void H5_ATTR_UNUSED *params) 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]) @@ -13425,7 +13430,8 @@ test_overwrite_multi_chunk_dataset_diff_shape_read(void H5_ATTR_UNUSED *params) 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); @@ -13534,7 +13540,8 @@ test_overwrite_multi_chunk_dataset_diff_shape_read(void H5_ATTR_UNUSED *params) 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]) @@ -13945,7 +13952,8 @@ test_read_partial_chunk_hyperslab_selection(void H5_ATTR_UNUSED *params) /* * 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]; @@ -14009,7 +14017,8 @@ test_read_partial_chunk_hyperslab_selection(void H5_ATTR_UNUSED *params) 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) { @@ -14044,7 +14053,8 @@ test_read_partial_chunk_hyperslab_selection(void H5_ATTR_UNUSED *params) 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_datatype_test.c b/test/API/H5_api_datatype_test.c index b96de1a0344..1fa474460db 100644 --- a/test/API/H5_api_datatype_test.c +++ b/test/API/H5_api_datatype_test.c @@ -755,6 +755,8 @@ test_create_committed_datatype_empty_types(void H5_ATTR_UNUSED *params) } END_MULTIPART; + TESTING_2("test cleanup"); + if (H5Gclose(group_id) < 0) TEST_ERROR; if (H5Gclose(container_group) < 0) @@ -762,6 +764,10 @@ test_create_committed_datatype_empty_types(void H5_ATTR_UNUSED *params) if (H5Fclose(file_id) < 0) TEST_ERROR; + PASSED(); + + return; + error: H5E_BEGIN_TRY { diff --git a/test/API/H5_api_file_test.c b/test/API/H5_api_file_test.c index aabf2ccc1be..a9d5d73ec1b 100644 --- a/test/API/H5_api_file_test.c +++ b/test/API/H5_api_file_test.c @@ -230,9 +230,13 @@ test_create_file_invalid_params(void H5_ATTR_UNUSED *params) } END_MULTIPART; + TESTING_2("test cleanup"); + free(prefixed_filename); prefixed_filename = NULL; + PASSED(); + return; error: @@ -404,7 +408,7 @@ test_open_file(void H5_ATTR_UNUSED *params) * XXX: SWMR open flags */ } - END_MULTIPART; + END_MULTIPART_NO_CLEANUP; return; @@ -506,7 +510,7 @@ test_open_file_invalid_params(void H5_ATTR_UNUSED *params) } PART_END(H5Fopen_invalid_flags); } - END_MULTIPART; + END_MULTIPART_NO_CLEANUP; return; @@ -1107,9 +1111,13 @@ test_file_is_accessible(void H5_ATTR_UNUSED *params) } END_MULTIPART; + TESTING_2("test cleanup"); + free(prefixed_filename); prefixed_filename = NULL; + PASSED(); + return; error: @@ -1572,12 +1580,16 @@ test_get_file_intent(void H5_ATTR_UNUSED *params) } END_MULTIPART; + TESTING_2("test cleanup"); + if (GetTestCleanup() && H5Fdelete(prefixed_filename, H5P_DEFAULT) < 0) TEST_ERROR; free(prefixed_filename); prefixed_filename = NULL; + PASSED(); + return; error: @@ -1627,11 +1639,6 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) return; } - if (GetTestMaxNumThreads() == 0) { - printf(" Active thread count is invalid\n"); - TEST_ERROR; - } - TESTING_2("test setup"); if (prefix_filename(test_path_prefix, GET_OBJ_COUNT_TEST_FILENAME1, &prefixed_filename1) < 0) { @@ -1720,7 +1727,7 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) if (check_open_obj_count(obj_count, 2) < 0) { H5_FAILED(); printf(" number of open files (%ld) did not match %s expected number (2)\n", obj_count, - GetTestMaxNumThreads() > 1 ? "or exceed" : ""); + TEST_EXECUTION_CONCURRENT ? "or exceed" : ""); PART_ERROR(H5Fget_obj_count_files); } @@ -1764,7 +1771,7 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) if (check_open_obj_count(obj_count, 1) < 0) { H5_FAILED(); printf(" number of open groups (%ld) did not match %s expected number (1)\n", obj_count, - GetTestMaxNumThreads() > 1 ? "or exceed" : ""); + TEST_EXECUTION_CONCURRENT ? "or exceed" : ""); PART_ERROR(H5Fget_obj_count_types); } @@ -1786,7 +1793,7 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) if (check_open_obj_count(obj_count, 1) < 0) { H5_FAILED(); printf(" number of open named datatypes (%ld) did not match %s expected number (1)\n", obj_count, - GetTestMaxNumThreads() > 1 ? "or exceed" : ""); + TEST_EXECUTION_CONCURRENT ? "or exceed" : ""); PART_ERROR(H5Fget_obj_count_types); } @@ -1808,7 +1815,7 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) if (check_open_obj_count(obj_count, 1) < 0) { H5_FAILED(); printf(" number of open attributes (%ld) did not match %s expected number (1)\n", obj_count, - GetTestMaxNumThreads() > 1 ? "or exceed" : ""); + TEST_EXECUTION_CONCURRENT ? "or exceed" : ""); PART_ERROR(H5Fget_obj_count_attrs); } @@ -1830,7 +1837,7 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) if (check_open_obj_count(obj_count, 1) < 0) { H5_FAILED(); printf(" number of open datasets (%ld) did not match %s expected number (1)\n", obj_count, - GetTestMaxNumThreads() > 1 ? "or exceed" : ""); + TEST_EXECUTION_CONCURRENT ? "or exceed" : ""); PART_ERROR(H5Fget_obj_count_dsets); } @@ -1874,7 +1881,7 @@ test_get_file_obj_count(void H5_ATTR_UNUSED *params) if (check_open_obj_count(obj_count, 6) < 0) { H5_FAILED(); printf(" number of open objects (%ld) did not match %s expected number (6)\n", obj_count, - GetTestMaxNumThreads() > 1 ? "or exceed" : ""); + TEST_EXECUTION_CONCURRENT ? "or exceed" : ""); PART_ERROR(H5Fget_obj_count_all); } @@ -2559,9 +2566,9 @@ herr_t check_open_obj_count(ssize_t obj_count, int expected) { herr_t ret_value = SUCCEED; /* If multiple threads are concurrently executing tests, then more objects than expected may be open in the library */ - if (GetTestMaxNumThreads() > 1 && obj_count < expected) { + if (TEST_EXECUTION_CONCURRENT && obj_count < expected) { ret_value = FAIL; - } else if (GetTestMaxNumThreads() == 1 && obj_count != expected) { /* Single thread, expect exact count */ + } else if (!TEST_EXECUTION_CONCURRENT && obj_count != expected) { /* Single thread, expect exact count */ ret_value = FAIL; } diff --git a/test/API/H5_api_group_test.c b/test/API/H5_api_group_test.c index df199573702..01a54d84183 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 H5_ATTR_UNUSED *params) } /* 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); snprintf(group_name, sizeof(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 H5_ATTR_UNUSED *params) 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) snprintf(gname, sizeof(gname), "2nd_child_group"); else if (counter == 2) diff --git a/test/API/H5_api_link_test.c b/test/API/H5_api_link_test.c index 4eaf78c2d12..f52a44d095b 100644 --- a/test/API/H5_api_link_test.c +++ b/test/API/H5_api_link_test.c @@ -461,9 +461,9 @@ test_create_hard_link_many(void H5_ATTR_UNUSED *params) } for (size_t i = 1; (i < HARD_LINK_TEST_GROUP_MANY_NUM_HARD_LINKS + 1 && !valid_name_matched); i++) { - char name_possibility[H5_API_TEST_FILENAME_MAX_LENGTH]; + char name_possibility[H5_TEST_FILENAME_MAX_LENGTH]; - snprintf(name_possibility, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%zu", + snprintf(name_possibility, H5_TEST_FILENAME_MAX_LENGTH, "%s%zu", "/" LINK_TEST_GROUP_NAME "/" HARD_LINK_TEST_GROUP_MANY_NAME "/hard", i); valid_name_matched |= !strcmp(objname, name_possibility); @@ -1678,9 +1678,9 @@ test_create_soft_link_many(void H5_ATTR_UNUSED *params) } for (size_t i = 1; (i < SOFT_LINK_TEST_GROUP_MANY_NAME_SOFT_LINK_COUNT + 1 && !valid_name_matched); i++) { - char name_possibility[H5_API_TEST_FILENAME_MAX_LENGTH]; + char name_possibility[H5_TEST_FILENAME_MAX_LENGTH]; - snprintf(name_possibility, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%zu", + snprintf(name_possibility, H5_TEST_FILENAME_MAX_LENGTH, "%s%zu", "/" LINK_TEST_GROUP_NAME "/" SOFT_LINK_TEST_GROUP_MANY_NAME "/soft", i); valid_name_matched |= !strcmp(objname, name_possibility); @@ -2570,6 +2570,8 @@ test_create_external_link_multi(void H5_ATTR_UNUSED *params) } END_MULTIPART; + TESTING_2("test cleanup"); + if (remove_test_file(NULL, ext_link_filename1) < 0) TEST_ERROR; if (remove_test_file(NULL, ext_link_filename2) < 0) @@ -2581,6 +2583,8 @@ test_create_external_link_multi(void H5_ATTR_UNUSED *params) free(ext_link_filename2); free(ext_link_filename3); + PASSED(); + return; error: @@ -2777,9 +2781,9 @@ test_create_external_link_ping_pong(void H5_ATTR_UNUSED *params) } for (size_t i = 1; i < EXTERNAL_LINK_TEST_PING_PONG_NUM_LINKS + 1 && !valid_name_matched; i++) { - char name_possibility[H5_API_TEST_FILENAME_MAX_LENGTH]; + char name_possibility[H5_TEST_FILENAME_MAX_LENGTH]; - snprintf(name_possibility, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%zu", "/link", i); + snprintf(name_possibility, H5_TEST_FILENAME_MAX_LENGTH, "%s%zu", "/link", i); valid_name_matched |= !strcmp(name_possibility, objname); } @@ -2847,9 +2851,9 @@ test_create_external_link_ping_pong(void H5_ATTR_UNUSED *params) } for (size_t i = 1; i < EXTERNAL_LINK_TEST_PING_PONG_NUM_LINKS + 1 && !valid_name_matched; i++) { - char name_possibility[H5_API_TEST_FILENAME_MAX_LENGTH]; + char name_possibility[H5_TEST_FILENAME_MAX_LENGTH]; - snprintf(name_possibility, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%zu%s", "/link", i, + snprintf(name_possibility, H5_TEST_FILENAME_MAX_LENGTH, "%s%zu%s", "/link", i, "/new_group"); valid_name_matched |= !strcmp(objname, name_possibility); @@ -2882,6 +2886,8 @@ test_create_external_link_ping_pong(void H5_ATTR_UNUSED *params) } END_MULTIPART; + TESTING_2("test cleanup"); + if (remove_test_file(NULL, ext_link_filename1) < 0) TEST_ERROR; if (remove_test_file(NULL, ext_link_filename2) < 0) @@ -2890,6 +2896,8 @@ test_create_external_link_ping_pong(void H5_ATTR_UNUSED *params) free(ext_link_filename1); free(ext_link_filename2); + PASSED(); + return; error: diff --git a/test/API/H5_api_object_test.c b/test/API/H5_api_object_test.c index c89e36a5737..7eee91e8af0 100644 --- a/test/API/H5_api_object_test.c +++ b/test/API/H5_api_object_test.c @@ -1933,7 +1933,7 @@ test_incr_decr_object_refcount_invalid_params(void H5_ATTR_UNUSED *params) } PART_END(H5Odecr_refcount_invalid_param); } - END_MULTIPART; + END_MULTIPART_NO_CLEANUP; return; diff --git a/test/API/H5_api_test.c b/test/API/H5_api_test.c index aef0e6ed2e4..c609a8a9ba2 100644 --- a/test/API/H5_api_test.c +++ b/test/API/H5_api_test.c @@ -44,7 +44,7 @@ #include #endif -char H5_api_test_filename_g[H5_API_TEST_FILENAME_MAX_LENGTH]; +char H5_api_test_filename_g[H5_TEST_FILENAME_MAX_LENGTH]; static int H5_api_test_create_containers(const char *filename, uint64_t vol_cap_flags); static int H5_api_test_create_single_container(const char *filename, uint64_t vol_cap_flags); @@ -214,27 +214,23 @@ main(int argc, char **argv) test_path_prefix = ""; #ifndef H5_HAVE_MULTITHREAD - if (GetTestMaxNumThreads() > 1) { - fprintf(stderr, "HDF5 must be built with multi-thread support to run multi-threaded API tests\n"); + if (TEST_EXECUTION_THREADED) { + fprintf(stderr, "HDF5 must be built with multi-thread support to run threaded API tests\n"); err_occurred = TRUE; goto done; } #endif - if (GetTestMaxNumThreads() <= 0) { - SetTestMaxNumThreads(API_TESTS_DEFAULT_NUM_THREADS); - } - - if (GetTestMaxNumThreads() == 1) { + if (!TEST_EXECUTION_THREADED) { /* Populate global test filename */ - if ((chars_written = HDsnprintf(H5_api_test_filename_g, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%s",test_path_prefix, + if ((chars_written = HDsnprintf(H5_api_test_filename_g, H5_TEST_FILENAME_MAX_LENGTH, "%s%s",test_path_prefix, TEST_FILE_NAME)) < 0) { fprintf(stderr, "Error while creating test file name\n"); err_occurred = TRUE; goto done; } - if ((size_t)chars_written >= H5_API_TEST_FILENAME_MAX_LENGTH) { + if ((size_t)chars_written >= H5_TEST_FILENAME_MAX_LENGTH) { fprintf(stderr, "Test file name exceeded expected size\n"); err_occurred = TRUE; goto done; @@ -414,7 +410,6 @@ main(int argc, char **argv) static int H5_api_test_create_containers(const char *filename, uint64_t vol_cap_flags) { - int max_threads = GetTestMaxNumThreads(); char *tl_filename = NULL; if (!(vol_cap_flags & H5VL_CAP_FLAG_FILE_BASIC)) { @@ -422,9 +417,9 @@ H5_api_test_create_containers(const char *filename, uint64_t vol_cap_flags) goto error; } - if (max_threads > 1) { + if (TEST_EXECUTION_THREADED) { #ifdef H5_HAVE_MULTITHREAD - for (int i = 0; i < max_threads; i++) { + for (int i = 0; i < GetTestMaxNumThreads(); i++) { if ((tl_filename = generate_threadlocal_filename(test_path_prefix, i, filename)) == NULL) { printf(" failed to generate thread-local API test filename\n"); goto error; @@ -534,7 +529,6 @@ H5_api_test_create_single_container(const char *filename, uint64_t vol_cap_flags static int H5_api_test_destroy_container_files(void) { - int max_threads = GetTestMaxNumThreads(); char *filename = NULL; if (!(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC)) { @@ -542,13 +536,13 @@ H5_api_test_destroy_container_files(void) { goto error; } - if (max_threads > 1) { + if (TEST_EXECUTION_THREADED) { #ifndef H5_HAVE_MULTITHREAD printf(" thread-specific cleanup requested, but multithread support not enabled\n"); goto error; #endif - for (int i = 0; i < max_threads; i++) { + for (int i = 0; i < GetTestMaxNumThreads(); i++) { if ((filename = generate_threadlocal_filename(test_path_prefix, i, TEST_FILE_NAME)) == NULL) { printf(" failed to generate thread-local API test filename\n"); goto error; diff --git a/test/API/H5_api_test.h b/test/API/H5_api_test.h index d421c98acec..49f12d6121f 100644 --- a/test/API/H5_api_test.h +++ b/test/API/H5_api_test.h @@ -38,7 +38,7 @@ /* Final name of the API test container file (for this thread) */ #ifdef H5_HAVE_MULTITHREAD -#define H5_API_TEST_FILENAME (GetTestMaxNumThreads() > 1 ? ((thread_info_t*) pthread_getspecific(test_thread_info_key_g))->test_thread_filename : H5_api_test_filename_g) +#define H5_API_TEST_FILENAME (TEST_EXECUTION_THREADED ? ((thread_info_t*) pthread_getspecific(test_thread_info_key_g))->test_thread_filename : H5_api_test_filename_g) #else #define H5_API_TEST_FILENAME H5_api_test_filename_g #endif diff --git a/test/API/H5_api_test_util.c b/test/API/H5_api_test_util.c index 6736a47fac0..80a312c71bd 100644 --- a/test/API/H5_api_test_util.c +++ b/test/API/H5_api_test_util.c @@ -649,7 +649,7 @@ generate_random_dataspace(int rank, const hsize_t *max_dims, hsize_t *dims_out, * is responsible for freeing the returned filename * pointer with free(). * - * If the API tests are being run multi-threaded, + * If the API tests are being run in separate thread(s) * then the framework-assigned thread index will be inserted as well. */ herr_t @@ -678,7 +678,7 @@ prefix_filename(const char *prefix, const char *filename, char **filename_out) goto done; } - if (GetTestMaxNumThreads() > 1) { + if (TEST_EXECUTION_THREADED) { #ifdef H5_HAVE_MULTITHREAD if ((tinfo = (thread_info_t *)pthread_getspecific(test_thread_info_key_g)) == NULL) { @@ -699,13 +699,13 @@ prefix_filename(const char *prefix, const char *filename, char **filename_out) goto done; #endif } else { - if (NULL == (out_buf = malloc(H5_API_TEST_FILENAME_MAX_LENGTH))) { + if (NULL == (out_buf = malloc(H5_TEST_FILENAME_MAX_LENGTH))) { printf(" couldn't allocated filename buffer\n"); ret_value = FAIL; goto done; } - if ((chars_written = HDsnprintf(out_buf, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%s", prefix, filename)) < + if ((chars_written = HDsnprintf(out_buf, H5_TEST_FILENAME_MAX_LENGTH, "%s%s", prefix, filename)) < 0) { printf(" couldn't prefix filename\n"); ret_value = FAIL; @@ -713,7 +713,7 @@ prefix_filename(const char *prefix, const char *filename, char **filename_out) } } - if ((size_t)chars_written >= H5_API_TEST_FILENAME_MAX_LENGTH) { + if ((size_t)chars_written >= H5_TEST_FILENAME_MAX_LENGTH) { printf(" filename buffer too small\n"); ret_value = FAIL; goto done; diff --git a/test/API/H5_api_test_util.h b/test/API/H5_api_test_util.h index 798b7d0d7fb..01154cffe14 100644 --- a/test/API/H5_api_test_util.h +++ b/test/API/H5_api_test_util.h @@ -13,8 +13,6 @@ #ifndef H5_API_TEST_UTIL_H_ #define H5_API_TEST_UTIL_H_ -#define API_TESTS_DEFAULT_NUM_THREADS 1 - #include "hdf5.h" hid_t generate_random_datatype(H5T_class_t parent_class, bool is_compact); diff --git a/test/h5test.c b/test/h5test.c index 58a053c24bc..aac7f77e5f6 100644 --- a/test/h5test.c +++ b/test/h5test.c @@ -2194,19 +2194,19 @@ char *generate_threadlocal_filename(const char *prefix, int thread_idx, const ch goto error; } - if (MAX_THREAD_IDX_LEN + strlen(filename) >= H5_API_TEST_FILENAME_MAX_LENGTH) { + if (MAX_THREAD_IDX_LEN + strlen(filename) >= H5_TEST_FILENAME_MAX_LENGTH) { fprintf(stderr, " test file name exceeded expected size\n"); goto error; } - if (NULL == (test_filename = (char *)calloc(1, H5_API_TEST_FILENAME_MAX_LENGTH))) { + if (NULL == (test_filename = (char *)calloc(1, H5_TEST_FILENAME_MAX_LENGTH))) { fprintf(stderr, " couldn't allocate memory for test file name\n"); goto error; } /* Write prefix, thread index, and filename into buffer */ if ((chars_written = snprintf(test_filename, - H5_API_TEST_FILENAME_MAX_LENGTH, "%s%d%s", + H5_TEST_FILENAME_MAX_LENGTH, "%s%d%s", prefix, thread_idx, filename)) < 0) { fprintf(stderr, " couldn't create test file name\n"); goto error; diff --git a/test/h5test.h b/test/h5test.h index 9ab3c74410f..e9d03999d11 100644 --- a/test/h5test.h +++ b/test/h5test.h @@ -43,18 +43,42 @@ H5TEST_DLLVAR char *paraprefix; H5TEST_DLLVAR MPI_Info h5_io_info_g; /* MPI INFO object for IO */ #endif -#define H5_API_TEST_FILENAME_MAX_LENGTH 1024 +#define H5_TEST_FILENAME_MAX_LENGTH 1024 +#define H5_MAX_NUM_SUBTESTS 64 + +/* 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; +/* Whether or not the tests are configured to execute using threaded infrastructure. + * Note that if GetTestMaxNumThreads() == 1, then the tests are still only run in a single thread, + * but that thread is a new thread spawned by the main thread. */ +#define TEST_EXECUTION_THREADED (GetTestMaxNumThreads() >= 1) + +/* Whether the tests are configured to concurrently execute in more than one thread */ +#define TEST_EXECUTION_CONCURRENT (GetTestMaxNumThreads() > 1) + #ifdef H5_HAVE_MULTITHREAD extern pthread_key_t test_thread_info_key_g; -#define IS_MAIN_TEST_THREAD ((GetTestMaxNumThreads() <= 1) ||\ +#define IS_MAIN_TEST_THREAD (!TEST_EXECUTION_CONCURRENT ||\ ((pthread_getspecific(test_thread_info_key_g)) && (((thread_info_t*)pthread_getspecific(test_thread_info_key_g))->thread_idx == 0))) #else @@ -62,7 +86,7 @@ extern pthread_key_t test_thread_info_key_g; #endif /* H5_HAVE_MULTITHREAD */ /* Flag values for TestFrameworkFlags */ -#define ALLOW_MULTITHREAD 0x00000001 /* Run the test in a multi-threaded environment */ +#define ALLOW_MULTITHREAD 0x00000001 /* Allow test to be run in spawned thread(s) based on runtime configuration */ /* * Print the current location on the standard output stream. @@ -77,12 +101,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 (TEST_EXECUTION_THREADED && 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 (TEST_EXECUTION_THREADED && 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 (TEST_EXECUTION_THREADED && 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 (TEST_EXECUTION_THREADED && 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 H5_ATOMIC_ADD(n_tests_run_g, 1); +#define INCR_FAILED_COUNT H5_ATOMIC_ADD(n_tests_failed_g, 1); +#define INCR_PASSED_COUNT H5_ATOMIC_ADD(n_tests_passed_g, 1); +#define INCR_SKIPPED_COUNT H5_ATOMIC_ADD(n_tests_skipped_g, 1); #endif /* @@ -96,35 +161,67 @@ 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); \ + printf("Testing %-62s", WHAT); \ + fflush(stdout); \ + } \ } while (0) +#define TESTING_2_DISPLAY(WHAT) \ + do { \ + printf(" Testing %-60s", WHAT); \ + fflush(stdout); \ + } while (0) + +#ifdef H5_HAVE_MULTITHREAD #define TESTING_2(WHAT) \ do { \ - if (IS_MAIN_TEST_THREAD) { \ - printf(" Testing %-60s", WHAT); \ - fflush(stdout); \ - } \ - INCR_TEST_STAT(n_tests_run_g); \ + INCR_RUN_COUNT; \ + if (!TEST_EXECUTION_THREADED) { \ + 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); \ + assert(_tinfo); \ + /* TBD - Only need to store this for 1 thread */\ + _tinfo->test_descriptions[_tinfo->num_tests - 1] = WHAT; \ + } \ + } while (0) +#else +#define TESTING_2(WHAT) \ + do { \ + INCR_RUN_COUNT; \ + if (TEST_EXECUTION_THREADED) { \ + printf(" Test run with multiple threads, but library not built with multi-thread support!\n");\ + goto error; \ + } \ + TESTING_2_DISPLAY(WHAT); \ + } while (0) +#endif /* H5_HAVE_MULTITHREAD */ + +#define PASSED_DISPLAY() \ + do { \ + HDputs(" PASSED"); \ + fflush(stdout); \ } while (0) #define PASSED() \ do { \ - if (IS_MAIN_TEST_THREAD) { \ - HDputs(" PASSED"); \ - fflush(stdout); \ - } \ - INCR_TEST_STAT(n_tests_passed_g); \ + if (!TEST_EXECUTION_THREADED) { \ + PASSED_DISPLAY(); \ + } \ + 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); \ - } \ - INCR_TEST_STAT(n_tests_failed_g); \ + if (!TEST_EXECUTION_THREADED) { \ + H5_FAILED_DISPLAY(); \ + } \ + INCR_FAILED_COUNT; \ } while (0) #define H5_WARNING() \ do { \ @@ -133,13 +230,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); \ - } \ - INCR_TEST_STAT(n_tests_skipped_g); \ + if (!TEST_EXECUTION_THREADED) { \ + SKIPPED_DISPLAY(); \ + } \ + INCR_SKIPPED_COUNT; \ + } while (0) +#define ERROR_DISPLAY() \ + do { \ + HDputs(" *ERROR*"); \ + fflush(stdout); \ } while (0) #define PUTS_ERROR(s) \ do { \ @@ -203,8 +309,17 @@ extern pthread_key_t test_thread_info_key_g; int part_nerrors = 0; #define END_MULTIPART \ - if (part_nerrors > 0) \ - goto error; \ + if (part_nerrors > 0) { \ + TESTING_2("test cleanup"); \ + SKIPPED(); \ + goto error; \ + } \ + } + +#define END_MULTIPART_NO_CLEANUP \ + if (part_nerrors) { \ + goto error; \ + } \ } /* @@ -221,7 +336,6 @@ 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); \ part_nerrors++; \ goto part_##part_name##_end; \ } while (0) diff --git a/test/testframe.c b/test/testframe.c index 8b6952d25a7..8b69ecb7cc0 100644 --- a/test/testframe.c +++ b/test/testframe.c @@ -35,6 +35,9 @@ typedef struct TestStruct { typedef struct TestThreadArgs { int ThreadIndex; TestStruct *Test; + size_t num_tests; + const char **test_descriptions; + test_outcome_t *test_outcomes; } TestThreadArgs; /* @@ -68,6 +71,8 @@ static void *ThreadTestWrapper(void *test); static int H5_mt_test_thread_setup(int thread_idx); static int H5_mt_test_global_setup(void); static void H5_test_thread_info_key_destructor(void *value); +static void PerformThreadedTest(TestStruct Test); +static void UpdateTestStats(TestThreadArgs *test_args); #endif /* @@ -444,22 +449,22 @@ TestParseCmdLine(int argc, char *argv[]) herr_t PerformTests(void) { - bool mt_initialized = false; - int test_num_errs = 0; - int max_num_threads = GetTestMaxNumThreads(); + int test_num_errs = 0; + int max_num_threads = GetTestMaxNumThreads(); for (unsigned Loop = 0; Loop < TestCount; Loop++) { - bool is_test_mt = (TestArray[Loop].TestFrameworkFlags & ALLOW_MULTITHREAD) && (max_num_threads > 1); - + bool is_test_mt = (TestArray[Loop].TestFrameworkFlags & ALLOW_MULTITHREAD) && TEST_EXECUTION_THREADED; + if (TestArray[Loop].TestSkipFlag) { MESSAGE(2, ("Skipping -- %s (%s) \n", TestArray[Loop].Description, TestArray[Loop].Name)); continue; } MESSAGE(2, ("Testing %s -- %s (%s) \n", (is_test_mt ? "(Multi-threaded)" : ""), - TestArray[Loop].Description, TestArray[Loop].Name)); + TestArray[Loop].Description, TestArray[Loop].Name)); MESSAGE(5, ("===============================================\n")); + test_num_errs = H5_ATOMIC_LOAD(TestArray[Loop].TestNumErrors); H5_ATOMIC_STORE(TestArray[Loop].TestNumErrors, TestNumErrs_g); if (TestAlarmOn() < 0) @@ -484,75 +489,13 @@ PerformTests(void) MESSAGE(5, ("There were %d errors detected.\n\n", H5_ATOMIC_LOAD(TestArray[Loop].TestNumErrors))); } else { -#ifndef H5_HAVE_MULTITHREAD - if (TestArray[Loop].TestFrameworkFlags & ALLOW_MULTITHREAD) { - if (TestFrameworkProcessID_g == 0) - 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; - - if (max_num_threads <= 0) { - fprintf(stderr, "Invalid number of threads specified\n"); - return FAIL; - } - - if (NULL == (threads = calloc((size_t) max_num_threads, sizeof(pthread_t)))) { - fprintf(stderr, "Couldn't allocate array of threads to run test\n"); - return FAIL; - } - if (NULL == (thread_args = calloc((size_t) max_num_threads, sizeof(TestThreadArgs)))) { - fprintf(stderr, "Couldn't allocate test thread argument array\n"); - return FAIL; - } - - if (!mt_initialized) { - if (H5_mt_test_global_setup() < 0) { - fprintf(stderr, "Error setting up global MT test info\n"); - return FAIL; - } - - mt_initialized = true; - } - - for (int i = 0; i < max_num_threads; i++) { - thread_args[i].ThreadIndex = i; - thread_args[i].Test = &TestArray[Loop]; - - ret = pthread_create(&threads[i], NULL, ThreadTestWrapper, (void*) &thread_args[i]); - if (ret != 0) { - fprintf(stderr, "Error creating thread %d\n", i); - return FAIL; - } - } - - for (int i = 0; i < max_num_threads; i++) { - ret = pthread_join(threads[i], NULL); - - if (ret != 0) { - fprintf(stderr, "Error joining thread %d\n", i); - return FAIL; - } - } - - free(threads); - threads = NULL; - free(thread_args); - thread_args = NULL; + PerformThreadedTest(TestArray[Loop]); TestAlarmOff(); - - test_num_errs = H5_ATOMIC_LOAD(TestArray[Loop].TestNumErrors); H5_ATOMIC_STORE(TestArray[Loop].TestNumErrors, TestNumErrs_g - test_num_errs); - MESSAGE(5, ("===============================================\n")); - MESSAGE(5, ("There were %d errors detected.\n\n", H5_ATOMIC_LOAD(TestArray[Loop].TestNumErrors))); -#endif /* H5_HAVE_MULTITHREAD */ + MESSAGE(5, ("There were %d errors detected.\n\n", (int)H5_ATOMIC_LOAD(TestArray[Loop].TestNumErrors))); } } @@ -566,8 +509,147 @@ PerformTests(void) } #ifdef H5_HAVE_MULTITHREAD + +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); + } + + 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 = (TestThreadArgs *)calloc((size_t) GetTestMaxNumThreads(), sizeof(TestThreadArgs))) == NULL) { + fprintf(stderr, "Error allocating memory for thread arguments\n"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < GetTestMaxNumThreads(); i++) { + thread_args[i].ThreadIndex = i; + thread_args[i].Test = &threaded_test; + 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]); + + if (ret != 0) { + fprintf(stderr, "Error creating thread %d\n", i); + exit(EXIT_FAILURE); + } + } + + for (int i = 0; i < GetTestMaxNumThreads(); i++) { + ret = pthread_join(threads[i], 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; + + return; +} + +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); + } + } + + /* 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; + } + } + + return; +} + /* - * Set up and execute a test flagged for multi-threaded + * Set up and execute a test flagged for threaded * execution within a single thread. */ static void * @@ -575,23 +657,46 @@ ThreadTestWrapper(void *test) { TestStruct *test_struct; int thread_idx; - + thread_info_t *tinfo = NULL; assert(test); + TestThreadArgs *test_args = (TestThreadArgs *)test; + thread_idx = ((TestThreadArgs *)test)->ThreadIndex; test_struct = ((TestThreadArgs *)test)->Test; - + if (H5_mt_test_thread_setup(thread_idx) < 0) { fprintf(stderr, "Error setting up thread-local test info"); return (void*)-1; } - /* TODO: Test setup and cleanup functions should be - * accounted for in the multithread case + /* This setup/cleanup pattern requires that each + * thread that delegates threading to the test framework + * must not have any form of "shared" setup or cleanup. + * + * This is usually accomplished by having thread-specific filenames. + * + * If a test requires shared setup/cleanup, then the test must + * handle its own threading internally */ + if (test_struct->TestSetupFunc) + test_struct->TestSetupFunc(test_struct->TestParameters); test_struct->TestFunc(test_struct->TestParameters); + if (test_struct->TestCleanupFunc) + test_struct->TestCleanupFunc(test_struct->TestParameters); + + 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; } @@ -607,6 +712,7 @@ 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 */ @@ -615,6 +721,16 @@ 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; @@ -624,6 +740,8 @@ 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; } @@ -635,6 +753,8 @@ 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); @@ -642,7 +762,15 @@ 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 a summary of running tests */ @@ -859,7 +987,7 @@ GetTestNumErrs(void) void IncTestNumErrs(void) { - TestNumErrs_g++; + H5_ATOMIC_ADD(TestNumErrs_g, 1); } /* @@ -1014,23 +1142,14 @@ TestAlarmOff(void) #ifdef H5_HAVE_MULTITHREAD /* Set up global variables used for API tests */ -static int -H5_mt_test_global_setup(void) { - int max_threads = 0; - - /* Set up thread count, used for some file tests */ - max_threads = GetTestMaxNumThreads(); +int H5_mt_test_global_setup(void) { - if (max_threads <= 0) { - printf(" invalid max thread count\n"); - goto error; - } - - /* 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: diff --git a/testpar/API/H5_api_test_parallel.c b/testpar/API/H5_api_test_parallel.c index a9ab1e3d2ac..f46caff4427 100644 --- a/testpar/API/H5_api_test_parallel.c +++ b/testpar/API/H5_api_test_parallel.c @@ -25,7 +25,7 @@ #include "H5_api_async_test_parallel.h" #endif -char H5_api_test_parallel_filename[H5_API_TEST_FILENAME_MAX_LENGTH]; +char H5_api_test_parallel_filename[H5_TEST_FILENAME_MAX_LENGTH]; const char *test_path_prefix; @@ -383,8 +383,8 @@ main(int argc, char **argv) if (NULL == (test_path_prefix = getenv(HDF5_API_TEST_PATH_PREFIX))) test_path_prefix = ""; - snprintf(H5_api_test_parallel_filename, H5_API_TEST_FILENAME_MAX_LENGTH, "%s%s", test_path_prefix, - PARALLEL_TEST_FILE_NAME); + snprintf(H5_api_test_parallel_filename, H5_TEST_FILENAME_MAX_LENGTH, "%s%s", test_path_prefix, + PARALLEL_TEST_FILE_NAME); if (NULL == (vol_connector_string = getenv(HDF5_VOL_CONNECTOR))) { if (MAINPROCESS)