/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the LICENSE file, which can be found at the root of the source code       *
 * distribution tree, or in https://www.hdfgroup.org/licenses.               *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "H5_api_async_test_parallel.h"

static void print_async_test_header(const void *params);

static void
print_async_test_header(const void *params)
{
    bool coll_md_read = true;

    if (params)
        coll_md_read = *(const bool *)params;

    if (coll_md_read) {
        if (MAINPROCESS) {
            printf("\n");
            printf("**********************************************\n");
            printf("*                                            *\n");
            printf("*          API Parallel Async Tests          *\n");
            printf("*                                            *\n");
            printf("**********************************************\n\n");

#ifndef H5_API_TEST_HAVE_ASYNC
            printf("SKIPPED due to no async support\n");
#endif
        }
    }
#ifdef H5_API_TEST_HAVE_ASYNC
    else if (MAINPROCESS) {
        printf("\n");
        printf("****************************************************\n");
        printf("*                                                  *\n");
        printf("* Re-running tests with independent metadata reads *\n");
        printf("*                                                  *\n");
        printf("****************************************************\n\n");
    }
#endif
}

#ifdef H5_API_TEST_HAVE_ASYNC

static void test_async_one_dataset_io(const void *params);
static void test_async_multi_dataset_io(const void *params);
static void test_async_multi_file_dataset_io(const void *params);
static void test_async_multi_file_grp_dset_io(const void *params);
static void test_async_set_extent(const void *params);
static void test_async_attribute_exists(const void *params);
static void test_async_attribute_io(const void *params);
static void test_async_attribute_io_tconv(const void *params);
static void test_async_attribute_io_compound(const void *params);
static void test_async_group(const void *params);
static void test_async_link(const void *params);
static void test_async_ocopy_orefresh(const void *params);
static void test_async_file_reopen(const void *params);

/* Highest "printf" file created (starting at 0) */
int max_printf_file = -1;

/*
 * Create file and dataset. Each rank writes to a portion
 * of the dataset.
 */
#define ONE_DATASET_IO_TEST_SPACE_RANK 2
static void
test_async_one_dataset_io(const void *params)
{
    hsize_t *dims = NULL;
    hsize_t  start[ONE_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  stride[ONE_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  count[ONE_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  block[ONE_DATASET_IO_TEST_SPACE_RANK];
    bool     op_failed     = false;
    bool     is_native_vol = false;
    size_t   i, data_size, num_in_progress;
    hid_t    file_id   = H5I_INVALID_HID;
    hid_t    fapl_id   = H5I_INVALID_HID;
    hid_t    dset_id   = H5I_INVALID_HID;
    hid_t    space_id  = H5I_INVALID_HID;
    hid_t    mspace_id = H5I_INVALID_HID;
    hid_t    es_id     = H5I_INVALID_HID;
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING_MULTIPART("single dataset I/O");

    TESTING_2("test setup");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf(
                "    API functions for basic file, dataset, or flush aren't supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(ONE_DATASET_IO_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    if ((space_id = H5Screate_simple(ONE_DATASET_IO_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Create file asynchronously */
    if ((file_id = H5Fcreate_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Find out if the native connector is used */
    if (H5VLobject_is_native(file_id, &is_native_vol) < 0)
        TEST_ERROR;

    /* Create the dataset asynchronously */
    if ((dset_id = H5Dcreate_async(file_id, "dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT, H5P_DEFAULT,
                                   H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers - first dimension is skipped in calculation */
    for (i = 1, data_size = 1; i < ONE_DATASET_IO_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset read\n");
        TEST_ERROR;
    }

    /* Select this rank's portion of the dataspace */
    for (i = 0; i < ONE_DATASET_IO_TEST_SPACE_RANK; i++) {
        if (i == 0) {
            start[i] = (hsize_t)mpi_rank;
            block[i] = 1;
        }
        else {
            start[i] = 0;
            block[i] = dims[i];
        }

        stride[i] = 1;
        count[i]  = 1;
    }

    if (H5Sselect_hyperslab(space_id, H5S_SELECT_SET, start, stride, count, block) < 0) {
        H5_FAILED();
        printf("    couldn't select hyperslab for dataset write\n");
        goto error;
    }

    /* Setup memory space for write_buf */
    {
        hsize_t mdims[] = {data_size / sizeof(int)};

        if ((mspace_id = H5Screate_simple(1, mdims, NULL)) < 0) {
            H5_FAILED();
            printf("    couldn't create memory dataspace\n");
            goto error;
        }
    }

    PASSED();

    BEGIN_MULTIPART
    {
        PART_BEGIN(single_dset_eswait)
        {
            TESTING_2("synchronization using H5ESwait()");

            /* Initialize write_buf */
            for (i = 0; i < data_size / sizeof(int); i++)
                ((int *)write_buf)[i] = mpi_rank;

            /* Write the dataset asynchronously */
            if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, write_buf, es_id) <
                0)
                PART_TEST_ERROR(single_dset_eswait);

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(single_dset_eswait);
            if (op_failed)
                PART_TEST_ERROR(single_dset_eswait);

            /* Read the dataset asynchronously */
            if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, read_buf, es_id) < 0)
                PART_TEST_ERROR(single_dset_eswait);

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(single_dset_eswait);
            if (op_failed)
                PART_TEST_ERROR(single_dset_eswait);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(single_dset_eswait);
                } /* end if */

            PASSED();
        }
        PART_END(single_dset_eswait);

        PART_BEGIN(single_dset_dclose)
        {
            TESTING_2("synchronization using H5Dclose()");

            /* Initialize write_buf */
            for (i = 0; i < data_size / sizeof(int); i++)
                ((int *)write_buf)[i] = (int)i;

            /* Write the dataset asynchronously */
            if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, write_buf, es_id) <
                0)
                PART_TEST_ERROR(single_dset_dclose);

            /* Close the dataset synchronously */
            if (H5Dclose(dset_id) < 0)
                PART_TEST_ERROR(single_dset_dclose);

            /* Re-open the dataset asynchronously */
            if ((dset_id = H5Dopen_async(file_id, "dset", H5P_DEFAULT, es_id)) < 0)
                PART_TEST_ERROR(single_dset_dclose);

            /* Read the dataset asynchronously */
            if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, read_buf, es_id) < 0)
                PART_TEST_ERROR(single_dset_dclose);

            /* Close the dataset synchronously */
            if (H5Dclose(dset_id) < 0)
                PART_TEST_ERROR(single_dset_dclose);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(single_dset_dclose);
                } /* end if */

            /* Re-open the dataset asynchronously */
            if ((dset_id = H5Dopen_async(file_id, "dset", H5P_DEFAULT, es_id)) < 0)
                PART_TEST_ERROR(single_dset_dclose);

            PASSED();
        }
        PART_END(single_dset_dclose);

        PART_BEGIN(single_dset_dflush)
        {
            TESTING_2("synchronization using H5Oflush_async()");

            /* Initialize write_buf */
            for (i = 0; i < data_size / sizeof(int); i++)
                ((int *)write_buf)[i] = 10 * (int)i;

            /* Write the dataset asynchronously */
            if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, write_buf, es_id) <
                0)
                PART_TEST_ERROR(single_dset_dflush);

            /* Flush the dataset asynchronously.  This will effectively work as a
             * barrier, guaranteeing the read takes place after the write. Skip this
             * function because it isn't supported for the native vol in parallel. */
            if (!is_native_vol && H5Oflush_async(dset_id, es_id) < 0)
                PART_TEST_ERROR(single_dset_dflush);

            /* Read the dataset asynchronously */
            if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, read_buf, es_id) < 0)
                PART_TEST_ERROR(single_dset_dflush);

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(single_dset_dflush);
            if (op_failed)
                PART_TEST_ERROR(single_dset_dflush);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(single_dset_dflush);
                } /* end if */

            PASSED();
        }
        PART_END(single_dset_dflush);

        PART_BEGIN(single_dset_fclose)
        {
            TESTING_2("synchronization using H5Fclose()");

            /* Initialize write_buf */
            for (i = 0; i < data_size / sizeof(int); i++)
                ((int *)write_buf)[i] = (int)i + 5;

            /* Write the dataset asynchronously */
            if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, write_buf, es_id) <
                0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Close the dataset asynchronously */
            if (H5Dclose_async(dset_id, es_id) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Close the file synchronously */
            if (H5Fclose(file_id) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Reopen the file asynchronously. */
            if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDONLY, fapl_id, es_id)) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Re-open the dataset asynchronously */
            if ((dset_id = H5Dopen_async(file_id, "dset", H5P_DEFAULT, es_id)) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Read the dataset asynchronously */
            if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, read_buf, es_id) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Close the dataset asynchronously */
            if (H5Dclose_async(dset_id, es_id) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Close the file synchronously */
            if (H5Fclose(file_id) < 0)
                PART_TEST_ERROR(single_dset_fclose);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(single_dset_fclose);
                } /* end if */

            PASSED();
        }
        PART_END(single_dset_fclose);
    }
    END_MULTIPART;

    TESTING_2("test cleanup");

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Sclose(mspace_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Sclose(mspace_id);
        H5Dclose(dset_id);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef ONE_DATASET_IO_TEST_SPACE_RANK

/*
 * Create file and multiple datasets. Each rank writes to a
 * portion of each dataset and reads back their portion of
 * each dataset.
 */
#define MULTI_DATASET_IO_TEST_SPACE_RANK 2
#define MULTI_DATASET_IO_TEST_NDSETS     5
static void
test_async_multi_dataset_io(const void *params)
{
    hsize_t *dims = NULL;
    hsize_t  start[MULTI_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  stride[MULTI_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  count[MULTI_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  block[MULTI_DATASET_IO_TEST_SPACE_RANK];
    bool     op_failed;
    size_t   i, j, data_size, num_in_progress;
    hid_t    file_id                               = H5I_INVALID_HID;
    hid_t    fapl_id                               = H5I_INVALID_HID;
    hid_t    dset_id[MULTI_DATASET_IO_TEST_NDSETS] = {H5I_INVALID_HID, H5I_INVALID_HID, H5I_INVALID_HID,
                                                      H5I_INVALID_HID, H5I_INVALID_HID};
    hid_t    space_id                              = H5I_INVALID_HID;
    hid_t    mspace_id                             = H5I_INVALID_HID;
    hid_t    es_id                                 = H5I_INVALID_HID;
    char     dset_name[32];
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING_MULTIPART("multi dataset I/O");

    TESTING_2("test setup");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf(
                "    API functions for basic file, dataset, or flush aren't supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(MULTI_DATASET_IO_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(MULTI_DATASET_IO_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Create file asynchronously */
    if ((file_id = H5Fcreate_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers - first dimension is skipped in calculation */
    for (i = 1, data_size = 1; i < MULTI_DATASET_IO_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);
    data_size *= MULTI_DATASET_IO_TEST_NDSETS;

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset read\n");
        TEST_ERROR;
    }

    /* Select this rank's portion of the dataspace */
    for (i = 0; i < MULTI_DATASET_IO_TEST_SPACE_RANK; i++) {
        if (i == 0) {
            start[i] = (hsize_t)mpi_rank;
            block[i] = 1;
        }
        else {
            start[i] = 0;
            block[i] = dims[i];
        }

        stride[i] = 1;
        count[i]  = 1;
    }

    if (H5Sselect_hyperslab(space_id, H5S_SELECT_SET, start, stride, count, block) < 0) {
        H5_FAILED();
        printf("    couldn't select hyperslab for dataset write\n");
        goto error;
    }

    /* Setup memory space for write_buf */
    {
        hsize_t mdims[] = {data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int)};

        if ((mspace_id = H5Screate_simple(1, mdims, NULL)) < 0) {
            H5_FAILED();
            printf("    couldn't create memory dataspace\n");
            goto error;
        }
    }

    PASSED();

    BEGIN_MULTIPART
    {
        PART_BEGIN(multi_dset_open)
        {
            size_t buf_start_idx;

            TESTING_2("keeping datasets open");

            /* Loop over datasets */
            for (i = 0; i < MULTI_DATASET_IO_TEST_NDSETS; i++) {
                size_t buf_end_idx;

                /* Set dataset name */
                snprintf(dset_name, sizeof(dset_name), "dset%d", (int)i);

                /* Create the dataset asynchronously */
                if ((dset_id[i] = H5Dcreate_async(file_id, dset_name, H5T_NATIVE_INT, space_id, H5P_DEFAULT,
                                                  H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_dset_open);

                /* Initialize write_buf.  Must use a new slice of write_buf for
                 * each dset since we can't overwrite the buffers until I/O is done. */
                buf_start_idx = i * (data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int));
                buf_end_idx   = buf_start_idx + (data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id[i], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_dset_open);
            } /* end for */

            /* Flush the file asynchronously.  This will effectively work as a
             * barrier, guaranteeing the read takes place after the write. */
            if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
                PART_TEST_ERROR(multi_dset_open);

            /* Loop over datasets */
            for (i = 0; i < MULTI_DATASET_IO_TEST_NDSETS; i++) {
                buf_start_idx = i * (data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int));

                /* Read the dataset asynchronously */
                if (H5Dread_async(dset_id[i], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_dset_open);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_dset_open);
            if (op_failed)
                PART_TEST_ERROR(multi_dset_open);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_dset_open);
                } /* end if */

            /* Close the datasets */
            for (i = 0; i < MULTI_DATASET_IO_TEST_NDSETS; i++)
                if (H5Dclose(dset_id[i]) < 0)
                    PART_TEST_ERROR(multi_dset_open);

            PASSED();
        }
        PART_END(multi_dset_open);

        PART_BEGIN(multi_dset_close)
        {
            size_t buf_start_idx;

            TESTING_2("closing datasets between I/O");

            /* Loop over datasets */
            for (i = 0; i < MULTI_DATASET_IO_TEST_NDSETS; i++) {
                size_t buf_end_idx;

                /* Set dataset name */
                snprintf(dset_name, sizeof(dset_name), "dset%d", (int)i);

                /* Open the dataset asynchronously */
                if ((dset_id[0] = H5Dopen_async(file_id, dset_name, H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_dset_close);

                /* Initialize write_buf. */
                buf_start_idx = i * (data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int));
                buf_end_idx   = buf_start_idx + (data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank * 10;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id[0], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_dset_close);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_dset_close);
            } /* end for */

            /* Flush the file asynchronously.  This will effectively work as a
             * barrier, guaranteeing the read takes place after the write. */
            if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
                PART_TEST_ERROR(multi_dset_close);

            /* Loop over datasets */
            for (i = 0; i < MULTI_DATASET_IO_TEST_NDSETS; i++) {
                /* Set dataset name */
                snprintf(dset_name, sizeof(dset_name), "dset%d", (int)i);

                /* Open the dataset asynchronously */
                if ((dset_id[0] = H5Dopen_async(file_id, dset_name, H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_dset_close);

                /* Read the dataset asynchronously */
                buf_start_idx = i * (data_size / MULTI_DATASET_IO_TEST_NDSETS / sizeof(int));
                if (H5Dread_async(dset_id[0], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_dset_close);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_dset_close);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_dset_close);
            if (op_failed)
                PART_TEST_ERROR(multi_dset_close);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_dset_close);
                } /* end if */

            PASSED();
        }
        PART_END(multi_dset_close);
    }
    END_MULTIPART;

    TESTING_2("test cleanup");

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Sclose(mspace_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Sclose(mspace_id);
        for (i = 0; i < MULTI_DATASET_IO_TEST_NDSETS; i++)
            H5Dclose(dset_id[i]);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef MULTI_DATASET_IO_TEST_SPACE_RANK
#undef MULTI_DATASET_IO_TEST_NDSETS

/*
 * Create multiple files, each with a single dataset. Each rank writes
 * to a portion of each dataset and reads from a portion of each dataset.
 */
#define MULTI_FILE_DATASET_IO_TEST_SPACE_RANK 2
#define MULTI_FILE_DATASET_IO_TEST_NFILES     5
static void
test_async_multi_file_dataset_io(const void *params)
{
    hsize_t *dims = NULL;
    hsize_t  start[MULTI_FILE_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  stride[MULTI_FILE_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  count[MULTI_FILE_DATASET_IO_TEST_SPACE_RANK];
    hsize_t  block[MULTI_FILE_DATASET_IO_TEST_SPACE_RANK];
    bool     op_failed     = false;
    bool     is_native_vol = false;
    size_t   i, j, data_size, num_in_progress;
    hid_t    fapl_id                                    = H5I_INVALID_HID;
    hid_t    file_id[MULTI_FILE_DATASET_IO_TEST_NFILES] = {H5I_INVALID_HID, H5I_INVALID_HID, H5I_INVALID_HID,
                                                           H5I_INVALID_HID, H5I_INVALID_HID};
    hid_t    dset_id[MULTI_FILE_DATASET_IO_TEST_NFILES] = {H5I_INVALID_HID, H5I_INVALID_HID, H5I_INVALID_HID,
                                                           H5I_INVALID_HID, H5I_INVALID_HID};
    hid_t    space_id                                   = H5I_INVALID_HID;
    hid_t    mspace_id                                  = H5I_INVALID_HID;
    hid_t    es_id                                      = H5I_INVALID_HID;
    char     file_name[32];
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING_MULTIPART("multi file dataset I/O");

    TESTING_2("test setup");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf(
                "    API functions for basic file, dataset, or flush aren't supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(MULTI_FILE_DATASET_IO_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(MULTI_FILE_DATASET_IO_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers - first dimension is skipped in calculation */
    for (i = 1, data_size = 1; i < MULTI_FILE_DATASET_IO_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);
    data_size *= MULTI_FILE_DATASET_IO_TEST_NFILES;

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset read\n");
        TEST_ERROR;
    }

    /* Select this rank's portion of the dataspace */
    for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_SPACE_RANK; i++) {
        if (i == 0) {
            start[i] = (hsize_t)mpi_rank;
            block[i] = 1;
        }
        else {
            start[i] = 0;
            block[i] = dims[i];
        }

        stride[i] = 1;
        count[i]  = 1;
    }

    if (H5Sselect_hyperslab(space_id, H5S_SELECT_SET, start, stride, count, block) < 0) {
        H5_FAILED();
        printf("    couldn't select hyperslab for dataset write\n");
        goto error;
    }

    /* Setup memory space for write_buf */
    {
        hsize_t mdims[] = {data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int)};

        if ((mspace_id = H5Screate_simple(1, mdims, NULL)) < 0) {
            H5_FAILED();
            printf("    couldn't create memory dataspace\n");
            goto error;
        }
    }

    PASSED();

    BEGIN_MULTIPART
    {
        PART_BEGIN(multi_file_dset_open)
        {
            size_t buf_start_idx;

            TESTING_2("keeping files and datasets open");

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
                size_t buf_end_idx;

                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Create file asynchronously */
                if ((file_id[i] = H5Fcreate_async(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);
                if ((int)i > max_printf_file)
                    max_printf_file = (int)i;

                /* Create the dataset asynchronously */
                if ((dset_id[i] = H5Dcreate_async(file_id[i], "dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT,
                                                  H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);

                /* Initialize write_buf.  Must use a new slice of write_buf for
                 * each dset since we can't overwrite the buffers until I/O is done. */
                buf_start_idx = i * (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                buf_end_idx   = buf_start_idx + (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id[i], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);
            } /* end for */

            /* Find out if the native connector is used */
            if (H5VLobject_is_native(file_id[0], &is_native_vol) < 0)
                PART_TEST_ERROR(multi_file_dset_open);

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
                /* Flush the dataset asynchronously.  This will effectively work as a
                 * barrier, guaranteeing the read takes place after the write. Skip this
                 * function because it isn't supported for the native vol in parallel. */
                if (!is_native_vol && H5Oflush_async(dset_id[i], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);

                /* Read the dataset asynchronously */
                buf_start_idx = i * (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                if (H5Dread_async(dset_id[i], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_dset_open);
            if (op_failed)
                PART_TEST_ERROR(multi_file_dset_open);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_file_dset_open);
                } /* end if */

            /* Close the datasets */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++)
                if (H5Dclose(dset_id[i]) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);

            PASSED();
        }
        PART_END(multi_file_dset_open);

        PART_BEGIN(multi_file_dset_dclose)
        {
            size_t buf_start_idx;

            TESTING_2("closing datasets between I/O");

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
                size_t buf_end_idx;

                /* Open the dataset asynchronously */
                if ((dset_id[0] = H5Dopen_async(file_id[i], "dset", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);

                /* Initialize write_buf. */
                buf_start_idx = i * (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                buf_end_idx   = buf_start_idx + (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank * 10;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id[0], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);
            } /* end for */

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
                /* Flush the file asynchronously.  This will effectively work as a
                 * barrier, guaranteeing the read takes place after the write. */
                if (H5Fflush_async(file_id[i], H5F_SCOPE_LOCAL, es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_open);

                /* Open the dataset asynchronously */
                if ((dset_id[0] = H5Dopen_async(file_id[i], "dset", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);

                /* Read the dataset asynchronously */
                buf_start_idx = i * (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                if (H5Dread_async(dset_id[0], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_dset_dclose);
            if (op_failed)
                PART_TEST_ERROR(multi_file_dset_dclose);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_file_dset_dclose);
                } /* end if */

            /* Close the files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++)
                if (H5Fclose(file_id[i]) < 0)
                    PART_TEST_ERROR(multi_file_dset_dclose);

            PASSED();
        }
        PART_END(multi_file_dset_dclose);

        PART_BEGIN(multi_file_dset_fclose)
        {
            size_t buf_start_idx;

            TESTING_2("closing files between I/O");

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
                size_t buf_end_idx;

                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Open the file asynchronously */
                if ((file_id[0] = H5Fopen_async(file_name, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Open the dataset asynchronously */
                if ((dset_id[0] = H5Dopen_async(file_id[0], "dset", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Initialize write_buf. */
                buf_start_idx = i * (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                buf_end_idx   = buf_start_idx + (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank + 5;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id[0], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Close the file asynchronously */
                if (H5Fclose_async(file_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_dset_fclose);
            if (op_failed)
                PART_TEST_ERROR(multi_file_dset_fclose);

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Open the file asynchronously */
                if ((file_id[0] = H5Fopen_async(file_name, H5F_ACC_RDONLY, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Open the dataset asynchronously */
                if ((dset_id[0] = H5Dopen_async(file_id[0], "dset", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Read the dataset asynchronously */
                buf_start_idx = i * (data_size / MULTI_FILE_DATASET_IO_TEST_NFILES / sizeof(int));
                if (H5Dread_async(dset_id[0], H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);

                /* Close the file asynchronously */
                if (H5Fclose_async(file_id[0], es_id) < 0)
                    PART_TEST_ERROR(multi_file_dset_fclose);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_dset_fclose);
            if (op_failed)
                PART_TEST_ERROR(multi_file_dset_fclose);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_file_dset_fclose);
                } /* end if */

            PASSED();
        }
        PART_END(multi_file_dset_fclose);
    }
    END_MULTIPART;

    TESTING_2("test cleanup");

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Sclose(mspace_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Sclose(mspace_id);
        for (i = 0; i < MULTI_FILE_DATASET_IO_TEST_NFILES; i++) {
            H5Dclose(dset_id[i]);
            H5Fclose(file_id[i]);
        }
        H5Pclose(fapl_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef MULTI_FILE_DATASET_IO_TEST_SPACE_RANK
#undef MULTI_FILE_DATASET_IO_TEST_NFILES

/*
 * Create multiple files, each with a single group and dataset. Each rank
 * writes to a portion of each dataset and reads from a portion of each dataset.
 */
#define MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK 2
#define MULTI_FILE_GRP_DSET_IO_TEST_NFILES     5
static void
test_async_multi_file_grp_dset_io(const void *params)
{
    hsize_t *dims = NULL;
    hsize_t  start[MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK];
    hsize_t  stride[MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK];
    hsize_t  count[MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK];
    hsize_t  block[MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK];
    bool     op_failed;
    size_t   i, j, data_size, num_in_progress;
    hid_t    file_id   = H5I_INVALID_HID;
    hid_t    fapl_id   = H5I_INVALID_HID;
    hid_t    grp_id    = H5I_INVALID_HID;
    hid_t    dset_id   = H5I_INVALID_HID;
    hid_t    space_id  = H5I_INVALID_HID;
    hid_t    mspace_id = H5I_INVALID_HID;
    hid_t    es_id     = H5I_INVALID_HID;
    char     file_name[32];
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING_MULTIPART("multi file dataset I/O with groups");

    TESTING_2("test setup");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_GROUP_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf(
                "    API functions for basic file, group, or dataset aren't supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers - first dimension is skipped in calculation */
    for (i = 1, data_size = 1; i < MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);
    data_size *= MULTI_FILE_GRP_DSET_IO_TEST_NFILES;

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset read\n");
        TEST_ERROR;
    }

    /* Select this rank's portion of the dataspace */
    for (i = 0; i < MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK; i++) {
        if (i == 0) {
            start[i] = (hsize_t)mpi_rank;
            block[i] = 1;
        }
        else {
            start[i] = 0;
            block[i] = dims[i];
        }

        stride[i] = 1;
        count[i]  = 1;
    }

    if (H5Sselect_hyperslab(space_id, H5S_SELECT_SET, start, stride, count, block) < 0) {
        H5_FAILED();
        printf("    couldn't select hyperslab for dataset write\n");
        goto error;
    }

    /* Setup memory space for write_buf */
    {
        hsize_t mdims[] = {data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int)};

        if ((mspace_id = H5Screate_simple(1, mdims, NULL)) < 0) {
            H5_FAILED();
            printf("    couldn't create memory dataspace\n");
            goto error;
        }
    }

    PASSED();

    BEGIN_MULTIPART
    {
        PART_BEGIN(multi_file_grp_dset_no_kick)
        {
            size_t buf_start_idx;

            TESTING_2("without intermediate calls to H5ESwait()");

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_GRP_DSET_IO_TEST_NFILES; i++) {
                size_t buf_end_idx;

                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Create file asynchronously */
                if ((file_id = H5Fcreate_async(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);
                if ((int)i > max_printf_file)
                    max_printf_file = (int)i;

                /* Create the group asynchronously */
                if ((grp_id = H5Gcreate_async(file_id, "grp", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) <
                    0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Create the dataset asynchronously */
                if ((dset_id = H5Dcreate_async(grp_id, "dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT,
                                               H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Initialize write_buf.  Must use a new slice of write_buf for
                 * each dset since we can't overwrite the buffers until I/O is done. */
                buf_start_idx = i * (data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int));
                buf_end_idx = buf_start_idx + (data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Close the group asynchronously */
                if (H5Gclose_async(grp_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Close the file asynchronously */
                if (H5Fclose_async(file_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_grp_dset_no_kick);
            if (op_failed)
                PART_TEST_ERROR(multi_file_grp_dset_no_kick);

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_GRP_DSET_IO_TEST_NFILES; i++) {
                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Open the file asynchronously */
                if ((file_id = H5Fopen_async(file_name, H5F_ACC_RDONLY, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Open the group asynchronously */
                if ((grp_id = H5Gopen_async(file_id, "grp", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Open the dataset asynchronously */
                if ((dset_id = H5Dopen_async(grp_id, "dset", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Read the dataset asynchronously */
                buf_start_idx = i * (data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int));
                if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Close the group asynchronously */
                if (H5Gclose_async(grp_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);

                /* Close the file asynchronously */
                if (H5Fclose_async(file_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_no_kick);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_grp_dset_no_kick);
            if (op_failed)
                PART_TEST_ERROR(multi_file_grp_dset_no_kick);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_file_grp_dset_no_kick);
                } /* end if */

            PASSED();
        }
        PART_END(multi_file_grp_dset_no_kick);

        PART_BEGIN(multi_file_grp_dset_kick)
        {
            size_t buf_start_idx;

            TESTING_2("with intermediate calls to H5ESwait() (0 timeout)");

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_GRP_DSET_IO_TEST_NFILES; i++) {
                size_t buf_end_idx;

                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Create file asynchronously */
                if ((file_id = H5Fcreate_async(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);
                if ((int)i > max_printf_file)
                    max_printf_file = (int)i;

                /* Create the group asynchronously */
                if ((grp_id = H5Gcreate_async(file_id, "grp", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) <
                    0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Create the dataset asynchronously */
                if ((dset_id = H5Dcreate_async(grp_id, "dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT,
                                               H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Initialize write_buf.  Must use a new slice of write_buf for
                 * each dset since we can't overwrite the buffers until I/O is done. */
                buf_start_idx = i * (data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int));
                buf_end_idx = buf_start_idx + (data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int));
                for (j = buf_start_idx; j < buf_end_idx; j++)
                    ((int *)write_buf)[j] = mpi_rank;

                /* Write the dataset asynchronously */
                if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                   &write_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Close the group asynchronously */
                if (H5Gclose_async(grp_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Close the file asynchronously */
                if (H5Fclose_async(file_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Kick the event stack to make progress */
                if (H5ESwait(es_id, 0, &num_in_progress, &op_failed) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);
                if (op_failed)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_grp_dset_kick);
            if (op_failed)
                PART_TEST_ERROR(multi_file_grp_dset_kick);

            /* Loop over files */
            for (i = 0; i < MULTI_FILE_GRP_DSET_IO_TEST_NFILES; i++) {
                /* Set file name */
                snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, (int)i);

                /* Open the file asynchronously */
                if ((file_id = H5Fopen_async(file_name, H5F_ACC_RDONLY, fapl_id, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Open the group asynchronously */
                if ((grp_id = H5Gopen_async(file_id, "grp", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Open the dataset asynchronously */
                if ((dset_id = H5Dopen_async(grp_id, "dset", H5P_DEFAULT, es_id)) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Read the dataset asynchronously */
                buf_start_idx = i * (data_size / MULTI_FILE_GRP_DSET_IO_TEST_NFILES / sizeof(int));
                if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT,
                                  &read_buf[buf_start_idx], es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Close the dataset asynchronously */
                if (H5Dclose_async(dset_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Close the group asynchronously */
                if (H5Gclose_async(grp_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Close the file asynchronously */
                if (H5Fclose_async(file_id, es_id) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);

                /* Kick the event stack to make progress */
                if (H5ESwait(es_id, 0, &num_in_progress, &op_failed) < 0)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);
                if (op_failed)
                    PART_TEST_ERROR(multi_file_grp_dset_kick);
            } /* end for */

            /* Wait for the event stack to complete */
            if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
                PART_TEST_ERROR(multi_file_grp_dset_kick);
            if (op_failed)
                PART_TEST_ERROR(multi_file_grp_dset_kick);

            /* Verify the read data */
            for (i = 0; i < data_size / sizeof(int); i++)
                if (write_buf[i] != read_buf[i]) {
                    H5_FAILED();
                    printf("    data verification failed\n");
                    PART_ERROR(multi_file_grp_dset_kick);
                } /* end if */

            PASSED();
        }
        PART_END(multi_file_grp_dset_kick);
    }
    END_MULTIPART;

    TESTING_2("test cleanup");

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Sclose(mspace_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Sclose(mspace_id);
        H5Dclose(dset_id);
        H5Gclose(grp_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef MULTI_FILE_GRP_DSET_IO_TEST_SPACE_RANK
#undef MULTI_FILE_GRP_DSET_IO_TEST_NFILES

/*
 * Creates a single file and dataset, then each rank writes to a portion
 * of the dataset. Next, the dataset is continually extended in the first
 * dimension by 1 "row" per mpi rank and partially written to by each rank.
 * Finally, each rank reads from a portion of the dataset.
 */
#define SET_EXTENT_TEST_SPACE_RANK  2
#define SET_EXTENT_TEST_NUM_EXTENDS 6
static void
test_async_set_extent(const void *params)
{
    hsize_t *dims    = NULL;
    hsize_t *maxdims = NULL;
    hsize_t *cdims   = NULL;
    hsize_t  start[SET_EXTENT_TEST_SPACE_RANK];
    hsize_t  stride[SET_EXTENT_TEST_SPACE_RANK];
    hsize_t  count[SET_EXTENT_TEST_SPACE_RANK];
    hsize_t  block[SET_EXTENT_TEST_SPACE_RANK];
    bool     op_failed     = false;
    bool     is_native_vol = false;
    size_t   i, j, data_size, num_in_progress;
    hid_t    file_id      = H5I_INVALID_HID;
    hid_t    fapl_id      = H5I_INVALID_HID;
    hid_t    dset_id      = H5I_INVALID_HID;
    hid_t    dcpl_id      = H5I_INVALID_HID;
    hid_t    space_id     = H5I_INVALID_HID;
    hid_t    space_id_out = H5I_INVALID_HID;
    hid_t    mspace_id    = H5I_INVALID_HID;
    hid_t    es_id        = H5I_INVALID_HID;
    htri_t   tri_ret;
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING("extending dataset");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_MORE)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, dataset, dataset more, or flush aren't supported "
                   "with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(SET_EXTENT_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    if (NULL == (maxdims = malloc(SET_EXTENT_TEST_SPACE_RANK * sizeof(hsize_t)))) {
        H5_FAILED();
        printf("    couldn't allocate max dataspace dimension buffer\n");
        TEST_ERROR;
    }

    if (NULL == (cdims = malloc(SET_EXTENT_TEST_SPACE_RANK * sizeof(hsize_t)))) {
        H5_FAILED();
        printf("    couldn't allocate chunk dimension buffer\n");
        TEST_ERROR;
    }

    for (i = 0; i < SET_EXTENT_TEST_SPACE_RANK; i++) {
        maxdims[i] = (i == 0) ? dims[i] + (hsize_t)(SET_EXTENT_TEST_NUM_EXTENDS * mpi_size) : dims[i];
        cdims[i]   = (dims[i] == 1) ? 1 : dims[i] / 2;
    }

    /* Create file dataspace */
    if ((space_id = H5Screate_simple(SET_EXTENT_TEST_SPACE_RANK, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create DCPL */
    if ((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;

    /* Set chunking */
    if (H5Pset_chunk(dcpl_id, SET_EXTENT_TEST_SPACE_RANK, cdims) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Create file asynchronously */
    if ((file_id = H5Fcreate_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Find out if the native connector is used */
    if (H5VLobject_is_native(file_id, &is_native_vol) < 0)
        TEST_ERROR;

    /* Create the dataset asynchronously */
    if ((dset_id = H5Dcreate_async(file_id, "dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT, dcpl_id,
                                   H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers - first dimension is skipped in calculation */
    for (i = 1, data_size = 1; i < SET_EXTENT_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);
    data_size *= SET_EXTENT_TEST_NUM_EXTENDS;

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for dataset read\n");
        TEST_ERROR;
    }

    /* Select this rank's portion of the dataspace */
    for (i = 0; i < SET_EXTENT_TEST_SPACE_RANK; i++) {
        if (i == 0) {
            start[i] = (hsize_t)mpi_rank;
            block[i] = 1;
        }
        else {
            start[i] = 0;
            block[i] = dims[i];
        }

        stride[i] = 1;
        count[i]  = 1;
    }

    if (H5Sselect_hyperslab(space_id, H5S_SELECT_SET, start, stride, count, block) < 0) {
        H5_FAILED();
        printf("    couldn't select hyperslab for dataset write\n");
        goto error;
    }

    /* Setup memory space for write_buf */
    {
        hsize_t mdims[] = {data_size / SET_EXTENT_TEST_NUM_EXTENDS / sizeof(int)};

        if ((mspace_id = H5Screate_simple(1, mdims, NULL)) < 0) {
            H5_FAILED();
            printf("    couldn't create memory dataspace\n");
            goto error;
        }
    }

    /* Initialize write_buf */
    for (i = 0; i < data_size / sizeof(int); i++)
        ((int *)write_buf)[i] = mpi_rank;

    /* Extend the dataset in the first dimension n times, extending by 1 "row" per
     * mpi rank involved on each iteration. Each rank will claim one of the new
     * "rows" for I/O in an interleaved fashion. */
    for (i = 0; i < SET_EXTENT_TEST_NUM_EXTENDS; i++) {
        /* No need to extend on the first iteration */
        if (i) {
            /* Extend datapace */
            dims[0] += (hsize_t)mpi_size;
            if (H5Sset_extent_simple(space_id, SET_EXTENT_TEST_SPACE_RANK, dims, maxdims) < 0)
                TEST_ERROR;

            /* Extend dataset asynchronously */
            if (H5Dset_extent_async(dset_id, dims, es_id) < 0)
                TEST_ERROR;

            /* Select hyperslab in file space to match new region */
            for (j = 0; j < SET_EXTENT_TEST_SPACE_RANK; j++) {
                if (j == 0) {
                    start[j]  = (hsize_t)mpi_rank;
                    block[j]  = 1;
                    stride[j] = (hsize_t)mpi_size;
                    count[j]  = i + 1;
                }
                else {
                    start[j]  = 0;
                    block[j]  = dims[j];
                    stride[j] = 1;
                    count[j]  = 1;
                }
            }

            if (H5Sselect_hyperslab(space_id, H5S_SELECT_SET, start, stride, count, block) < 0) {
                H5_FAILED();
                printf("    couldn't select hyperslab for dataset write\n");
                goto error;
            }

            /* Adjust memory dataspace to match as well */
            {
                hsize_t mdims[] = {(i + 1) * (data_size / SET_EXTENT_TEST_NUM_EXTENDS / sizeof(int))};

                if (H5Sset_extent_simple(mspace_id, 1, mdims, NULL) < 0)
                    TEST_ERROR;

                if (H5Sselect_all(mspace_id) < 0)
                    TEST_ERROR;
            }
        } /* end if */

        /* Get dataset dataspace */
        if ((space_id_out = H5Dget_space_async(dset_id, es_id)) < 0)
            TEST_ERROR;

        /* Verify extent is correct */
        if ((tri_ret = H5Sextent_equal(space_id, space_id_out)) < 0)
            TEST_ERROR;
        if (!tri_ret)
            FAIL_PUTS_ERROR("    dataspaces are not equal\n");

        /* Close output dataspace */
        if (H5Sclose(space_id_out) < 0)
            TEST_ERROR;

        /* Write the dataset slice asynchronously */
        if (H5Dwrite_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, write_buf, es_id) < 0)
            TEST_ERROR;
    }

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the write. Skip this
     * function because it isn't supported for the native vol in parallel. */
    if (!is_native_vol && H5Oflush_async(dset_id, es_id) < 0)
        TEST_ERROR;

    /* Read the entire dataset asynchronously */
    if (H5Dread_async(dset_id, H5T_NATIVE_INT, mspace_id, space_id, H5P_DEFAULT, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(int); i++)
        if (write_buf[i] != read_buf[i]) {
            H5_FAILED();
            printf("    data verification failed, expected %d but got %d\n", write_buf[i], read_buf[i]);
            goto error;
        } /* end if */

    /* Close dataset asynchronously */
    if (H5Dclose_async(dset_id, es_id) < 0)
        TEST_ERROR;

    /* Open dataset asynchronously */
    if ((dset_id = H5Dopen_async(file_id, "dset", H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Get dataset dataspace asynchronously */
    if ((space_id_out = H5Dget_space_async(dset_id, es_id)) < 0)
        TEST_ERROR;

    /* Verify the extents match */
    if ((tri_ret = H5Sextent_equal(space_id, space_id_out)) < 0)
        TEST_ERROR;
    if (!tri_ret)
        FAIL_PUTS_ERROR("    dataspaces are not equal\n");

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (cdims) {
        free(cdims);
        cdims = NULL;
    }

    if (maxdims) {
        free(maxdims);
        maxdims = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (H5Dclose(dset_id) < 0)
        TEST_ERROR;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Sclose(mspace_id) < 0)
        TEST_ERROR;
    if (H5Pclose(dcpl_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (cdims)
            free(cdims);
        if (maxdims)
            free(maxdims);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Sclose(mspace_id);
        H5Sclose(space_id_out);
        H5Dclose(dset_id);
        H5Pclose(dcpl_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef SET_EXTENT_TEST_SPACE_RANK
#undef SET_EXTENT_TEST_NUM_EXTENDS

/*
 * Creates an attribute on a dataset. All ranks check to see
 * if the attribute exists before and after creating the
 * attribute on the dataset.
 */
#define ATTRIBUTE_EXISTS_TEST_SPACE_RANK 2
static void
test_async_attribute_exists(const void *params)
{
    hsize_t *dims          = NULL;
    bool     op_failed     = false;
    bool     is_native_vol = false;
    size_t   num_in_progress;
    bool     exists1  = false;
    bool     exists2  = false;
    hid_t    file_id  = H5I_INVALID_HID;
    hid_t    fapl_id  = H5I_INVALID_HID;
    hid_t    dset_id  = H5I_INVALID_HID;
    hid_t    attr_id  = H5I_INVALID_HID;
    hid_t    space_id = H5I_INVALID_HID;
    hid_t    es_id    = H5I_INVALID_HID;

    TESTING("H5Aexists()");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_ATTR_BASIC)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, dataset, dataset more, attribute, or flush aren't "
                   "supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(ATTRIBUTE_EXISTS_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(ATTRIBUTE_EXISTS_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Find out if the native connector is used */
    if (H5VLobject_is_native(file_id, &is_native_vol) < 0)
        TEST_ERROR;

    /* Create the dataset asynchronously */
    if ((dset_id = H5Dcreate_async(file_id, "attr_exists_dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT,
                                   H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Check if the attribute exists asynchronously */
    if (H5Aexists_async(dset_id, "attr", &exists1, es_id) < 0)
        TEST_ERROR;

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the create takes place after the existence check.
     * Skip this function because it isn't supported for the native vol in parallel.
     */
    if (!is_native_vol && H5Oflush_async(dset_id, es_id) < 0)
        TEST_ERROR;

    /* Create the attribute asynchronously */
    if ((attr_id =
             H5Acreate_async(dset_id, "attr", H5T_NATIVE_INT, space_id, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the existence check takes place after the create.
     * Skip this function because it isn't supported for the native vol in parallel.
     */
    if (!is_native_vol && H5Oflush_async(dset_id, es_id) < 0)
        TEST_ERROR;

    /* Check if the attribute exists asynchronously */
    if (H5Aexists_async(dset_id, "attr", &exists2, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Check if H5Aexists returned the correct values */
    if (exists1)
        FAIL_PUTS_ERROR("    H5Aexists returned true for an attribute that should not exist")
    if (!exists2)
        FAIL_PUTS_ERROR("    H5Aexists returned false for an attribute that should exist");

    /* Close */
    if (H5Aclose_async(attr_id, es_id) < 0)
        TEST_ERROR;
    if (H5Dclose_async(dset_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Aclose(attr_id);
        H5Dclose(dset_id);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef ATTRIBUTE_EXISTS_TEST_SPACE_RANK

/*
 * Creates a file, dataset and attribute. Each rank writes to
 * the attribute. Then, each rank reads the attribute and
 * verifies the data is correct.
 */
#define ATTRIBUTE_IO_TEST_SPACE_RANK 2
static void
test_async_attribute_io(const void *params)
{
    hsize_t *dims          = NULL;
    bool     op_failed     = false;
    bool     is_native_vol = false;
    size_t   num_in_progress;
    size_t   i, data_size;
    hid_t    file_id   = H5I_INVALID_HID;
    hid_t    fapl_id   = H5I_INVALID_HID;
    hid_t    dset_id   = H5I_INVALID_HID;
    hid_t    attr_id   = H5I_INVALID_HID;
    hid_t    space_id  = H5I_INVALID_HID;
    hid_t    es_id     = H5I_INVALID_HID;
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING("attribute I/O");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_ATTR_BASIC)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, dataset, dataset more, attribute, or flush aren't "
                   "supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(ATTRIBUTE_IO_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(ATTRIBUTE_IO_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Find out if the native connector is used */
    if (H5VLobject_is_native(file_id, &is_native_vol) < 0)
        TEST_ERROR;

    /* Create the dataset asynchronously */
    if ((dset_id = H5Dcreate_async(file_id, "attr_dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT, H5P_DEFAULT,
                                   H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Create the attribute asynchronously */
    if ((attr_id =
             H5Acreate_async(dset_id, "attr", H5T_NATIVE_INT, space_id, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers */
    for (i = 0, data_size = 1; i < ATTRIBUTE_IO_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute read\n");
        TEST_ERROR;
    }

    /* Initialize write_buf. */
    for (i = 0; i < data_size / sizeof(int); i++)
        write_buf[i] = 10 * (int)i;

    /* Write the attribute asynchronously */
    if (H5Awrite_async(attr_id, H5T_NATIVE_INT, write_buf, es_id) < 0)
        TEST_ERROR;

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the write.
     * Skip this function because it isn't supported for the native vol in parallel.
     */
    if (!is_native_vol && H5Oflush_async(dset_id, es_id) < 0)
        TEST_ERROR;

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, H5T_NATIVE_INT, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(int); i++)
        if (write_buf[i] != read_buf[i]) {
            H5_FAILED();
            printf("    data verification failed\n");
            goto error;
        } /* end if */

    /* Close the attribute asynchronously */
    if (H5Aclose_async(attr_id, es_id) < 0)
        TEST_ERROR;

    /* Open the attribute asynchronously */
    if ((attr_id = H5Aopen_async(dset_id, "attr", H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, H5T_NATIVE_INT, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(int); i++)
        if (write_buf[i] != read_buf[i]) {
            H5_FAILED();
            printf("    data verification failed\n");
            goto error;
        } /* end if */

    /* Close out of order to see if it trips things up */
    if (H5Dclose_async(dset_id, es_id) < 0)
        TEST_ERROR;
    if (H5Aclose_async(attr_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Aclose(attr_id);
        H5Dclose(dset_id);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}

/*
 * Creates a file, dataset and attribute in parallel. Each rank writes to
 * the attribute with datatype conversion involved, then reads back the
 * attribute and verifies the data is correct.
 */
#define ATTRIBUTE_IO_TCONV_TEST_SPACE_RANK 2
static void
test_async_attribute_io_tconv(const void *params)
{
    hsize_t *dims = NULL;
    bool     op_failed;
    size_t   num_in_progress;
    size_t   i, data_size;
    hid_t    file_id   = H5I_INVALID_HID;
    hid_t    fapl_id   = H5I_INVALID_HID;
    hid_t    dset_id   = H5I_INVALID_HID;
    hid_t    attr_id   = H5I_INVALID_HID;
    hid_t    space_id  = H5I_INVALID_HID;
    hid_t    es_id     = H5I_INVALID_HID;
    int     *write_buf = NULL;
    int     *read_buf  = NULL;

    TESTING("attribute I/O with type conversion");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH) || !(vol_cap_flags_g & H5VL_CAP_FLAG_ATTR_BASIC)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, attribute, or flush aren't supported with this "
                   "connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(ATTRIBUTE_IO_TCONV_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(ATTRIBUTE_IO_TCONV_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Create the attribute asynchronously by name */
    if ((attr_id = H5Acreate_by_name_async(file_id, "attr_dset", "attr_tconv", H5T_STD_U16BE, space_id,
                                           H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers */
    for (i = 0, data_size = 1; i < ATTRIBUTE_IO_TCONV_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(int);

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute read\n");
        TEST_ERROR;
    }

    /* Initialize write_buf. */
    for (i = 0; i < data_size / sizeof(int); i++)
        write_buf[i] = 10 * (int)i;

    /* Write the attribute asynchronously */
    if (H5Awrite_async(attr_id, H5T_NATIVE_INT, write_buf, es_id) < 0)
        TEST_ERROR;

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the write. */
    if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
        TEST_ERROR;

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, H5T_NATIVE_INT, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(int); i++)
        if (write_buf[i] != read_buf[i]) {
            H5_FAILED();
            printf("    data verification failed\n");
            goto error;
        } /* end if */

    /* Close the attribute asynchronously */
    if (H5Aclose_async(attr_id, es_id) < 0)
        TEST_ERROR;

    /* Open the attribute asynchronously */
    if ((attr_id =
             H5Aopen_by_name_async(file_id, "attr_dset", "attr_tconv", H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, H5T_NATIVE_INT, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(int); i++)
        if (write_buf[i] != read_buf[i]) {
            H5_FAILED();
            printf("    data verification failed\n");
            goto error;
        } /* end if */

    /* Close */
    if (H5Aclose_async(attr_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Aclose(attr_id);
        H5Dclose(dset_id);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}

/*
 * Creates a file, dataset and attribute in parallel. Each rank writes to
 * the attribute with a compound datatype, then reads back the attribute
 * and verifies the data is correct.
 */
typedef struct tattr_cmpd_t {
    int a;
    int b;
} tattr_cmpd_t;

#define ATTRIBUTE_IO_COMPOUND_TEST_SPACE_RANK 2
static void
test_async_attribute_io_compound(const void *params)
{
    hsize_t      *dims = NULL;
    bool          op_failed;
    size_t        num_in_progress;
    size_t        i, data_size;
    hid_t         file_id   = H5I_INVALID_HID;
    hid_t         fapl_id   = H5I_INVALID_HID;
    hid_t         attr_id   = H5I_INVALID_HID;
    hid_t         space_id  = H5I_INVALID_HID;
    hid_t         mtype_id  = H5I_INVALID_HID;
    hid_t         ftype_id  = H5I_INVALID_HID;
    hid_t         mtypea_id = H5I_INVALID_HID;
    hid_t         mtypeb_id = H5I_INVALID_HID;
    hid_t         es_id     = H5I_INVALID_HID;
    tattr_cmpd_t *write_buf = NULL;
    tattr_cmpd_t *read_buf  = NULL;
    tattr_cmpd_t *fbuf      = NULL;

    TESTING("attribute I/O with compound type conversion");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH) || !(vol_cap_flags_g & H5VL_CAP_FLAG_ATTR_BASIC)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, dataset, dataset more, attribute, or flush aren't "
                   "supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(ATTRIBUTE_IO_COMPOUND_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create datatype */
    if ((mtype_id = H5Tcreate(H5T_COMPOUND, sizeof(tattr_cmpd_t))) < 0)
        TEST_ERROR;
    if (H5Tinsert(mtype_id, "a_name", HOFFSET(tattr_cmpd_t, a), H5T_NATIVE_INT) < 0)
        TEST_ERROR;
    if (H5Tinsert(mtype_id, "b_name", HOFFSET(tattr_cmpd_t, b), H5T_NATIVE_INT) < 0)
        TEST_ERROR;

    if ((mtypea_id = H5Tcreate(H5T_COMPOUND, sizeof(tattr_cmpd_t))) < 0)
        TEST_ERROR;
    if (H5Tinsert(mtypea_id, "a_name", HOFFSET(tattr_cmpd_t, a), H5T_NATIVE_INT) < 0)
        TEST_ERROR;

    if ((mtypeb_id = H5Tcreate(H5T_COMPOUND, sizeof(tattr_cmpd_t))) < 0)
        TEST_ERROR;
    if (H5Tinsert(mtypeb_id, "b_name", HOFFSET(tattr_cmpd_t, b), H5T_NATIVE_INT) < 0)
        TEST_ERROR;

    if ((ftype_id = H5Tcreate(H5T_COMPOUND, 2 + 8)) < 0)
        TEST_ERROR;
    if (H5Tinsert(ftype_id, "a_name", 0, H5T_STD_U16BE) < 0)
        TEST_ERROR;
    if (H5Tinsert(ftype_id, "b_name", 2, H5T_STD_I64LE) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(ATTRIBUTE_IO_COMPOUND_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Create the attribute asynchronously by name */
    if ((attr_id = H5Acreate_by_name_async(file_id, "attr_dset", "attr_cmpd", ftype_id, space_id, H5P_DEFAULT,
                                           H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Calculate size of data buffers */
    for (i = 0, data_size = 1; i < ATTRIBUTE_IO_COMPOUND_TEST_SPACE_RANK; i++)
        data_size *= dims[i];
    data_size *= sizeof(tattr_cmpd_t);

    if (NULL == (write_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute write\n");
        TEST_ERROR;
    }

    if (NULL == (read_buf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute read\n");
        TEST_ERROR;
    }

    if (NULL == (fbuf = malloc(data_size))) {
        H5_FAILED();
        printf("    couldn't allocate buffer for attribute read verification\n");
        TEST_ERROR;
    }

    /* Initialize write_buf. */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        write_buf[i].a = 10 * (int)i;
        write_buf[i].b = (10 * (int)i) + 1;
    }

    /* Write the attribute asynchronously */
    if (H5Awrite_async(attr_id, mtype_id, write_buf, es_id) < 0)
        TEST_ERROR;

    /* Update fbuf */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        fbuf[i].a = write_buf[i].a;
        fbuf[i].b = write_buf[i].b;
    }

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the write. */
    if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
        TEST_ERROR;

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, mtype_id, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        if (read_buf[i].a != fbuf[i].a) {
            H5_FAILED();
            printf("    data verification failed for field 'a'\n");
            goto error;
        } /* end if */
        if (read_buf[i].b != fbuf[i].b) {
            H5_FAILED();
            printf("    data verification failed for field 'b'\n");
            goto error;
        } /* end if */
    }

    /* Clear the read buffer */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        read_buf[i].a = -2;
        read_buf[i].b = -2;
    }

    /* Read the attribute asynchronously (element a only) */
    if (H5Aread_async(attr_id, mtypea_id, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        if (read_buf[i].a != fbuf[i].a) {
            H5_FAILED();
            printf("    data verification failed for field 'a'\n");
            goto error;
        } /* end if */
        if (read_buf[i].b != -2) {
            H5_FAILED();
            printf("    data verification failed for field 'b'\n");
            goto error;
        } /* end if */
    }

    /* Clear the read buffer */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        read_buf[i].a = -2;
        read_buf[i].b = -2;
    }

    /* Read the attribute asynchronously (element b only) */
    if (H5Aread_async(attr_id, mtypeb_id, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        if (read_buf[i].a != -2) {
            H5_FAILED();
            printf("    data verification failed for field 'a'\n");
            goto error;
        } /* end if */
        if (read_buf[i].b != fbuf[i].b) {
            H5_FAILED();
            printf("    data verification failed for field 'b'\n");
            goto error;
        } /* end if */
    }

    if (MPI_SUCCESS != MPI_Barrier(MPI_COMM_WORLD)) {
        H5_FAILED();
        printf("    MPI_Barrier failed\n");
        goto error;
    }

    /* Update write_buf */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        write_buf[i].a += 2 * 6 * 10;
        write_buf[i].b += 2 * 6 * 10;
    }

    /* Write the attribute asynchronously (element a only) */
    if (H5Awrite_async(attr_id, mtypea_id, write_buf, es_id) < 0)
        TEST_ERROR;

    /* Update fbuf */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        fbuf[i].a = write_buf[i].a;
    }

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the write. */
    if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
        TEST_ERROR;

    /* Clear the read buffer */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        read_buf[i].a = -2;
        read_buf[i].b = -2;
    }

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, mtype_id, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        if (read_buf[i].a != fbuf[i].a) {
            H5_FAILED();
            printf("    data verification failed for field 'a'\n");
            goto error;
        } /* end if */
        if (read_buf[i].b != fbuf[i].b) {
            H5_FAILED();
            printf("    data verification failed for field 'b'\n");
            goto error;
        } /* end if */
    }

    if (MPI_SUCCESS != MPI_Barrier(MPI_COMM_WORLD)) {
        H5_FAILED();
        printf("    MPI_Barrier failed\n");
        goto error;
    }

    /* Update write_buf */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        write_buf[i].a += 2 * 6 * 10;
        write_buf[i].b += 2 * 6 * 10;
    }

    /* Write the attribute asynchronously (element b only) */
    if (H5Awrite_async(attr_id, mtypeb_id, write_buf, es_id) < 0)
        TEST_ERROR;

    /* Update fbuf */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        fbuf[i].b = write_buf[i].b;
    }

    /* Flush the dataset asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the write. */
    if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
        TEST_ERROR;

    /* Clear the read buffer */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        read_buf[i].a = -2;
        read_buf[i].b = -2;
    }

    /* Read the attribute asynchronously */
    if (H5Aread_async(attr_id, mtype_id, read_buf, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify the read data */
    for (i = 0; i < data_size / sizeof(tattr_cmpd_t); i++) {
        if (read_buf[i].a != fbuf[i].a) {
            H5_FAILED();
            printf("    data verification failed for field 'a'\n");
            goto error;
        } /* end if */
        if (read_buf[i].b != fbuf[i].b) {
            H5_FAILED();
            printf("    data verification failed for field 'b'\n");
            goto error;
        } /* end if */
    }

    /* Close */
    if (H5Aclose_async(attr_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space_id) < 0)
        TEST_ERROR;
    if (H5Tclose(mtype_id) < 0)
        TEST_ERROR;
    if (H5Tclose(ftype_id) < 0)
        TEST_ERROR;
    if (H5Tclose(mtypea_id) < 0)
        TEST_ERROR;
    if (H5Tclose(mtypeb_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (read_buf) {
        free(read_buf);
        read_buf = NULL;
    }

    if (write_buf) {
        free(write_buf);
        write_buf = NULL;
    }

    if (fbuf) {
        free(fbuf);
        fbuf = NULL;
    }

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (read_buf)
            free(read_buf);
        if (write_buf)
            free(write_buf);
        if (fbuf)
            free(fbuf);
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Tclose(mtype_id);
        H5Tclose(ftype_id);
        H5Tclose(mtypea_id);
        H5Tclose(mtypeb_id);
        H5Aclose(attr_id);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}

/*
 * Tests async group interfaces in parallel
 */
static void
test_async_group(const void *params)
{
    hid_t      file_id         = H5I_INVALID_HID;
    hid_t      fapl_id         = H5I_INVALID_HID;
    hid_t      parent_group_id = H5I_INVALID_HID;
    hid_t      group_id        = H5I_INVALID_HID;
    hid_t      subgroup_id     = H5I_INVALID_HID;
    hid_t      gcpl_id         = H5I_INVALID_HID;
    hid_t      es_id           = H5I_INVALID_HID;
    H5G_info_t info1;
    H5G_info_t info2;
    H5G_info_t info3;
    size_t     num_in_progress;
    bool       op_failed;

    TESTING("group operations");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_GROUP_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_GROUP_MORE) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, group, group more or flush aren't "
                   "supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create GCPL */
    if ((gcpl_id = H5Pcreate(H5P_GROUP_CREATE)) < 0)
        TEST_ERROR;

    if (vol_cap_flags_g & H5VL_CAP_FLAG_CREATION_ORDER) {
        /* Track creation order */
        if (H5Pset_link_creation_order(gcpl_id, H5P_CRT_ORDER_TRACKED | H5P_CRT_ORDER_INDEXED) < 0)
            TEST_ERROR;
    }

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Create the parent group asynchronously */
    if ((parent_group_id =
             H5Gcreate_async(file_id, "group_parent", H5P_DEFAULT, gcpl_id, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Create 3 subgroups asynchronously, the first with no sub-subgroups, the
     * second with 1, and the third with 2 */
    if ((group_id =
             H5Gcreate_async(parent_group_id, "group1", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;
    if (H5Gclose_async(group_id, es_id) < 0)
        TEST_ERROR;

    if ((group_id =
             H5Gcreate_async(parent_group_id, "group2", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;
    if ((subgroup_id = H5Gcreate_async(group_id, "subgroup1", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) <
        0)
        TEST_ERROR;
    if (H5Gclose_async(subgroup_id, es_id) < 0)
        TEST_ERROR;
    if (H5Gclose_async(group_id, es_id) < 0)
        TEST_ERROR;

    if ((group_id =
             H5Gcreate_async(parent_group_id, "group3", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;
    if ((subgroup_id = H5Gcreate_async(group_id, "subgroup1", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) <
        0)
        TEST_ERROR;
    if (H5Gclose_async(subgroup_id, es_id) < 0)
        TEST_ERROR;
    if ((subgroup_id = H5Gcreate_async(group_id, "subgroup2", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) <
        0)
        TEST_ERROR;
    if (H5Gclose_async(subgroup_id, es_id) < 0)
        TEST_ERROR;
    if (H5Gclose_async(group_id, es_id) < 0)
        TEST_ERROR;

    /* Flush the file asynchronously.  This will effectively work as a barrier,
     * guaranteeing the read takes place after the write. */
    if (H5Fflush_async(file_id, H5F_SCOPE_LOCAL, es_id) < 0)
        TEST_ERROR;

    /* Test H5Gget_info_async */
    /* Open group1 asynchronously */
    if ((group_id = H5Gopen_async(parent_group_id, "group1", H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Get info */
    if (H5Gget_info_async(group_id, &info1, es_id) < 0)
        TEST_ERROR;

    if (vol_cap_flags_g & H5VL_CAP_FLAG_CREATION_ORDER) {
        /* Test H5Gget_info_by_idx_async */
        if (H5Gget_info_by_idx_async(parent_group_id, ".", H5_INDEX_CRT_ORDER, H5_ITER_INC, 1, &info2,
                                     H5P_DEFAULT, es_id) < 0)
            TEST_ERROR;
    }

    /* Test H5Gget_info_by_name_async */
    if (H5Gget_info_by_name_async(parent_group_id, "group3", &info3, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Verify group infos */
    if (info1.nlinks != 0)
        FAIL_PUTS_ERROR("    incorrect number of links");
    if (vol_cap_flags_g & H5VL_CAP_FLAG_CREATION_ORDER) {
        if (info2.nlinks != 1)
            FAIL_PUTS_ERROR("    incorrect number of links");
    }
    if (info3.nlinks != 2)
        FAIL_PUTS_ERROR("    incorrect number of links");

    /* Close */
    if (H5Gclose_async(group_id, es_id) < 0)
        TEST_ERROR;
    if (H5Gclose_async(parent_group_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(gcpl_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        H5Gclose(subgroup_id);
        H5Gclose(group_id);
        H5Gclose(parent_group_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5Pclose(gcpl_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}

/*
 * Tests async link interfaces in parallel
 */
static void
test_async_link(const void *params)
{
    hid_t  file_id         = H5I_INVALID_HID;
    hid_t  fapl_id         = H5I_INVALID_HID;
    hid_t  parent_group_id = H5I_INVALID_HID;
    hid_t  group_id        = H5I_INVALID_HID;
    hid_t  gcpl_id         = H5I_INVALID_HID;
    hid_t  es_id           = H5I_INVALID_HID;
    bool   existsh1;
    bool   existsh2;
    bool   existsh3;
    bool   existss1;
    bool   existss2;
    bool   existss3;
    size_t num_in_progress;
    bool   op_failed     = false;
    bool   is_native_vol = false;

    TESTING("link operations");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_LINK_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_HARD_LINKS) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_SOFT_LINKS) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_CREATION_ORDER)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, link, hard link, soft link, flush, or creation order "
                   "aren't supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create GCPL */
    if ((gcpl_id = H5Pcreate(H5P_GROUP_CREATE)) < 0)
        TEST_ERROR;

    /* Track creation order */
    if (H5Pset_link_creation_order(gcpl_id, H5P_CRT_ORDER_TRACKED | H5P_CRT_ORDER_INDEXED) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Find out if the native connector is used */
    if (H5VLobject_is_native(file_id, &is_native_vol) < 0)
        TEST_ERROR;

    /* Create the parent group asynchronously */
    if ((parent_group_id =
             H5Gcreate_async(file_id, "link_parent", H5P_DEFAULT, gcpl_id, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Create subgroup asynchronously. */
    if ((group_id = H5Gcreate_async(parent_group_id, "group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) <
        0)
        TEST_ERROR;
    if (H5Gclose_async(group_id, es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the link to the subgroup is visible to later tasks.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Create hard link asynchronously */
    if (H5Lcreate_hard_async(parent_group_id, "group", parent_group_id, "hard_link", H5P_DEFAULT, H5P_DEFAULT,
                             es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the soft link create takes place after the hard
     * link create. Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Create soft link asynchronously */
    if (H5Lcreate_soft_async("/link_parent/group", parent_group_id, "soft_link", H5P_DEFAULT, H5P_DEFAULT,
                             es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the writes.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (MPI_SUCCESS != MPI_Barrier(MPI_COMM_WORLD)) {
        H5_FAILED();
        printf("    MPI_Barrier failed\n");
        goto error;
    }

    /* Check if hard link exists */
    if (H5Lexists_async(parent_group_id, "hard_link", &existsh1, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Check if soft link exists */
    if (H5Lexists_async(parent_group_id, "soft_link", &existss1, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the delete takes place after the reads.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Delete soft link by index */
    if (H5Ldelete_by_idx_async(parent_group_id, ".", H5_INDEX_CRT_ORDER, H5_ITER_INC, 2, H5P_DEFAULT, es_id) <
        0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the delete.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (MPI_SUCCESS != MPI_Barrier(MPI_COMM_WORLD)) {
        H5_FAILED();
        printf("    MPI_Barrier failed\n");
        goto error;
    }

    /* Check if hard link exists */
    if (H5Lexists_async(parent_group_id, "hard_link", &existsh2, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Check if soft link exists */
    if (H5Lexists_async(parent_group_id, "soft_link", &existss2, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the delete takes place after the reads.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Delete hard link */
    if (H5Ldelete_async(parent_group_id, "hard_link", H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the read takes place after the delete.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (MPI_SUCCESS != MPI_Barrier(MPI_COMM_WORLD)) {
        H5_FAILED();
        printf("    MPI_Barrier failed\n");
        goto error;
    }

    /* Check if hard link exists */
    if (H5Lexists_async(parent_group_id, "hard_link", &existsh3, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Check if soft link exists */
    if (H5Lexists_async(parent_group_id, "soft_link", &existss3, H5P_DEFAULT, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Check if existence returns were correct */
    if (!existsh1)
        FAIL_PUTS_ERROR("    link exists returned false for link that should exist");
    if (!existss1)
        FAIL_PUTS_ERROR("    link exists returned false for link that should exist");
    if (!existsh2)
        FAIL_PUTS_ERROR("    link exists returned false for link that should exist");
    if (existss2)
        FAIL_PUTS_ERROR("    link exists returned true for link that should not exist");
    if (existsh3)
        FAIL_PUTS_ERROR("    link exists returned true for link that should not exist");
    if (existsh3)
        FAIL_PUTS_ERROR("    link exists returned true for link that should not exist");

    /* Close */
    if (H5Gclose_async(parent_group_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(gcpl_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        H5Gclose(group_id);
        H5Gclose(parent_group_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5Pclose(gcpl_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}

/*
 * Tests H5Ocopy_async and H5Orefresh_async in parallel
 */
#define OCOPY_REFRESH_TEST_SPACE_RANK 2
static void
test_async_ocopy_orefresh(const void *params)
{
    hsize_t *dims            = NULL;
    hid_t    file_id         = H5I_INVALID_HID;
    hid_t    fapl_id         = H5I_INVALID_HID;
    hid_t    parent_group_id = H5I_INVALID_HID;
    hid_t    dset_id         = H5I_INVALID_HID;
    hid_t    space_id        = H5I_INVALID_HID;
    hid_t    es_id           = H5I_INVALID_HID;
    size_t   num_in_progress;
    bool     coll_metadata_read = false;
    bool     op_failed          = false;
    bool     is_native_vol      = false;

    TESTING("H5Ocopy() and H5Orefresh()");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_GROUP_BASIC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_DATASET_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_OBJECT_MORE) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FLUSH_REFRESH)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file, group, dataset, object more, flush, or refresh "
                   "aren't supported with this connector\n");
        }

        return;
    }

    coll_metadata_read = *(const bool *)params;

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, coll_metadata_read)) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if (generate_random_parallel_dimensions(OCOPY_REFRESH_TEST_SPACE_RANK, &dims) < 0)
        TEST_ERROR;

    /* Create dataspace */
    if ((space_id = H5Screate_simple(OCOPY_REFRESH_TEST_SPACE_RANK, dims, NULL)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Find out if the native connector is used */
    if (H5VLobject_is_native(file_id, &is_native_vol) < 0)
        TEST_ERROR;

    /* Create the parent group asynchronously */
    if ((parent_group_id =
             H5Gcreate_async(file_id, "ocopy_parent", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Create dataset asynchronously. */
    if ((dset_id = H5Dcreate_async(parent_group_id, "dset", H5T_NATIVE_INT, space_id, H5P_DEFAULT,
                                   H5P_DEFAULT, H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;
    if (H5Dclose_async(dset_id, es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the copy takes place after dataset create.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    /* Copy dataset */
    if (H5Ocopy_async(parent_group_id, "dset", parent_group_id, "copied_dset", H5P_DEFAULT, H5P_DEFAULT,
                      es_id) < 0)
        TEST_ERROR;

    /* Flush the parent group asynchronously.  This will effectively work as a
     * barrier, guaranteeing the dataset open takes place copy.
     * Skip this function for the native vol because it isn't supported in parallel.
     */
    if (!is_native_vol && H5Oflush_async(parent_group_id, es_id) < 0)
        TEST_ERROR;

    if (!coll_metadata_read) {
        /* Wait for the event stack to complete */
        if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
            TEST_ERROR;
        if (op_failed)
            TEST_ERROR;

        if (MPI_SUCCESS != MPI_Barrier(MPI_COMM_WORLD)) {
            H5_FAILED();
            printf("    MPI_Barrier failed\n");
            goto error;
        }
    }

    /* Open the copied dataset asynchronously */
    if ((dset_id = H5Dopen_async(parent_group_id, "copied_dset", H5P_DEFAULT, es_id)) < 0)
        TEST_ERROR;

    /* Refresh the copied dataset asynchronously */
    if (H5Orefresh(dset_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Close */
    if (H5Dclose_async(dset_id, es_id) < 0)
        TEST_ERROR;
    if (H5Gclose_async(parent_group_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (dims) {
        free(dims);
        dims = NULL;
    }

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        if (dims)
            free(dims);
        H5Sclose(space_id);
        H5Dclose(dset_id);
        H5Gclose(parent_group_id);
        H5Pclose(fapl_id);
        H5Fclose(file_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}
#undef OCOPY_REFRESH_TEST_SPACE_RANK

/*
 * Tests H5Freopen_async in parallel
 */
static void
test_async_file_reopen(const void *params)
{
    hid_t  file_id          = H5I_INVALID_HID;
    hid_t  fapl_id          = H5I_INVALID_HID;
    hid_t  reopened_file_id = H5I_INVALID_HID;
    hid_t  es_id            = H5I_INVALID_HID;
    size_t num_in_progress;
    bool   op_failed;

    TESTING("H5Freopen()");

    /* Make sure the connector supports the API functions being tested */
    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) || !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC) ||
        !(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_MORE)) {
        if (MAINPROCESS) {
            SKIPPED();
            printf("    API functions for basic file or file more aren't supported with this connector\n");
        }

        return;
    }

    if ((fapl_id = create_mpi_fapl(MPI_COMM_WORLD, MPI_INFO_NULL, *(const bool *)params)) < 0)
        TEST_ERROR;

    /* Create event stack */
    if ((es_id = H5EScreate()) < 0)
        TEST_ERROR;

    /* Open file asynchronously */
    if ((file_id = H5Fopen_async(PAR_ASYNC_API_TEST_FILE, H5F_ACC_RDWR, fapl_id, es_id)) < 0)
        TEST_ERROR;

    /* Reopen file asynchronously */
    if ((reopened_file_id = H5Freopen_async(file_id, es_id)) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    /* Close */
    if (H5Fclose_async(reopened_file_id, es_id) < 0)
        TEST_ERROR;
    if (H5Fclose_async(file_id, es_id) < 0)
        TEST_ERROR;

    /* Wait for the event stack to complete */
    if (H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed) < 0)
        TEST_ERROR;
    if (op_failed)
        TEST_ERROR;

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5ESclose(es_id) < 0)
        TEST_ERROR;

    PASSED();

    return;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(reopened_file_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5ESwait(es_id, H5_API_TEST_WAIT_FOREVER, &num_in_progress, &op_failed);
        H5ESclose(es_id);
    }
    H5E_END_TRY

    return;
}

/*
 * Cleanup temporary test files
 */
static void
test_async_file_cleanup(const void H5_ATTR_UNUSED *params)
{
    char file_name[64];
    int  i;

    if (MAINPROCESS) {
        remove_test_file(NULL, PAR_ASYNC_API_TEST_FILE);

        for (i = 0; i <= max_printf_file; i++) {
            snprintf(file_name, sizeof(file_name), PAR_ASYNC_API_TEST_FILE_PRINTF, i);
            remove_test_file(NULL, file_name);
        }
    }
}

void
H5_api_async_test_parallel_add(void)
{
    bool coll_metadata_read = true;

    /* Add a fake test to print out a header to distinguish different test interfaces */
    AddTest("print_async_test_header (coll)", print_async_test_header, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "Prints header for async tests");

    /* Add tests using collective metadata reads */
    AddTest("test_async_one_dataset_io (coll)", test_async_one_dataset_io, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async single dataset I/O (collective metadata reads)");
    AddTest("test_async_multi_dataset_io (coll)", test_async_multi_dataset_io, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async multi dataset I/O (collective metadata reads)");
    AddTest("test_async_multi_file_dataset_io (coll)", test_async_multi_file_dataset_io, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async multi file dataset I/O (collective metadata reads)");
    AddTest("test_async_multi_file_grp_dset_io (coll)", test_async_multi_file_grp_dset_io, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async multi file dataset I/O with groups (collective metadata reads)");
    AddTest("test_async_set_extent (coll)", test_async_set_extent, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async extending dataset (collective metadata reads)");
    AddTest("test_async_attribute_exists (coll)", test_async_attribute_exists, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read), "async H5Aexists() (collective metadata reads)");
    AddTest("test_async_attribute_io (coll)", test_async_attribute_io, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async attribute I/O (collective metadata reads)");
    AddTest("test_async_attribute_io_tconv (coll)", test_async_attribute_io_tconv, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async attribute I/O with type conversion (collective metadata reads)");
    AddTest("test_async_attribute_io_compound (coll)", test_async_attribute_io_compound, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async attribute I/O with compound type conversion (collective metadata reads)");
    AddTest("test_async_group (coll)", test_async_group, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async group operations (collective metadata reads)");
    AddTest("test_async_link (coll)", test_async_link, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async link operations (collective metadata reads)");
    AddTest("test_async_ocopy_orefresh (coll)", test_async_ocopy_orefresh, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async H5Ocopy() and H5Orefresh() (collective metadata reads)");
    AddTest("test_async_file_reopen (coll)", test_async_file_reopen, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async H5Freopen() (collective metadata reads)");

    /* Add a fake test to cleanup test files due to current test interdependencies */
    AddTest("test_async_file_cleanup (coll)", test_async_file_cleanup, NULL, NULL, NULL, 0,
            "cleanup async test files");

    /* Re-run tests with independent metadata reads */
    coll_metadata_read = false;

    /* Add a fake test to print out that tests are being re-run with independent metadata reads */
    AddTest("print_async_test_header (ind)", print_async_test_header, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "Prints header for async tests");

    /* Add tests using independent metadata reads */
    AddTest("test_async_one_dataset_io (ind)", test_async_one_dataset_io, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async single dataset I/O (independent metadata reads)");
    AddTest("test_async_multi_dataset_io (ind)", test_async_multi_dataset_io, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async multi dataset I/O (independent metadata reads)");
    AddTest("test_async_multi_file_dataset_io (ind)", test_async_multi_file_dataset_io, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async multi file dataset I/O (independent metadata reads)");
    AddTest("test_async_multi_file_grp_dset_io (ind)", test_async_multi_file_grp_dset_io, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async multi file dataset I/O with groups (independent metadata reads)");
    AddTest("test_async_set_extent (ind)", test_async_set_extent, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async extending dataset (independent metadata reads)");
    AddTest("test_async_attribute_exists (ind)", test_async_attribute_exists, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async H5Aexists() (independent metadata reads)");
    AddTest("test_async_attribute_io (ind)", test_async_attribute_io, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async attribute I/O (independent metadata reads)");
    AddTest("test_async_attribute_io_tconv (ind)", test_async_attribute_io_tconv, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async attribute I/O with type conversion (independent metadata reads)");
    AddTest("test_async_attribute_io_compound (ind)", test_async_attribute_io_compound, NULL, NULL,
            &coll_metadata_read, sizeof(coll_metadata_read),
            "async attribute I/O with compound type conversion (independent metadata reads)");
    AddTest("test_async_group (ind)", test_async_group, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async group operations (independent metadata reads)");
    AddTest("test_async_link (ind)", test_async_link, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async link operations (independent metadata reads)");
    AddTest("test_async_ocopy_orefresh (ind)", test_async_ocopy_orefresh, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async H5Ocopy() and H5Orefresh() (independent metadata reads)");
    AddTest("test_async_file_reopen (ind)", test_async_file_reopen, NULL, NULL, &coll_metadata_read,
            sizeof(coll_metadata_read), "async H5Freopen() (independent metadata reads)");

    /* Add a fake test to cleanup test files due to current test interdependencies */
    AddTest("test_async_file_cleanup (ind)", test_async_file_cleanup, NULL, NULL, NULL, 0,
            "cleanup async test files");
}

#else /* H5_API_TEST_HAVE_ASYNC */

void
H5_api_async_test_parallel_add(void)
{
    /* Add a fake test to print out a header to distinguish different test interfaces */
    AddTest("print_async_test_header", print_async_test_header, NULL, NULL, NULL, 0,
            "Prints header for async tests");
}

#endif
