/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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 COPYING 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.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Purpose: Tests the virtual object layer (H5VL)
 *
 *          This is a minimal test to ensure VOL usage (setting a VOL, etc.)
 *          works as expected. Actual VOL functionality is tested using
 *          other mechanisms.
 */

/* Headers needed */
#include "h5test.h"
#include "H5Iprivate.h" /* IDs                                  */
#define H5T_FRIEND      /* Suppress error about including H5Tpkg    */
#include "H5Tpkg.h"     /* Datatypes                            */
#define H5VL_FRIEND     /* Suppress error about including H5VLpkg    */
#define H5VL_TESTING
#include "H5VLpkg.h" /* Virtual Object Layer                 */

/* Filename */
static const char *FILENAME[] = {"vol_test_file", NULL};

#define NATIVE_VOL_TEST_GROUP_NAME     "test_group"
#define NATIVE_VOL_TEST_DATASET_NAME   "test_dataset"
#define NATIVE_VOL_TEST_ATTRIBUTE_NAME "test_dataset"
#define NATIVE_VOL_TEST_HARD_LINK_NAME "test_hard_link"
#define NATIVE_VOL_TEST_SOFT_LINK_NAME "test_soft_link"
#define NATIVE_VOL_TEST_MOVE_LINK_NAME "test_move_link"
#define NATIVE_VOL_TEST_COPY_LINK_NAME "test_copy_link"
#define NATIVE_VOL_TEST_DATATYPE_NAME  "test_datatype"

#define N_ELEMENTS 10
#define NAME_LEN   512

/* A VOL class struct to verify registering optional operations */
static int    reg_opt_curr_op_val;
static herr_t reg_opt_op_optional(void *obj, H5VL_optional_args_t *args, hid_t dxpl_id, void **req);
static herr_t reg_opt_link_optional(void *obj, const H5VL_loc_params_t *loc_params,
                                    H5VL_optional_args_t *args, hid_t dxpl_id, void **req);
static herr_t reg_opt_datatype_get(void *obj, H5VL_datatype_get_args_t *args, hid_t dxpl_id, void **req);

#define REG_OPT_VOL_NAME  "reg_opt"
#define REG_OPT_VOL_VALUE ((H5VL_class_value_t)502)
static const H5VL_class_t reg_opt_vol_g = {
    H5VL_VERSION,       /* VOL class struct version */
    REG_OPT_VOL_VALUE,  /* value        */
    REG_OPT_VOL_NAME,   /* name         */
    0,                  /* version      */
    H5VL_CAP_FLAG_NONE, /* capability flags */
    NULL,               /* initialize   */
    NULL,               /* terminate    */
    {
        /* info_cls */
        (size_t)0, /* size    */
        NULL,      /* copy    */
        NULL,      /* compare */
        NULL,      /* free    */
        NULL,      /* to_str  */
        NULL,      /* from_str */
    },
    {
        /* wrap_cls */
        NULL, /* get_object   */
        NULL, /* get_wrap_ctx */
        NULL, /* wrap_object  */
        NULL, /* unwrap_object */
        NULL, /* free_wrap_ctx */
    },
    {
        /* attribute_cls */
        NULL,                /* create       */
        NULL,                /* open         */
        NULL,                /* read         */
        NULL,                /* write        */
        NULL,                /* get          */
        NULL,                /* specific     */
        reg_opt_op_optional, /* optional     */
        NULL                 /* close        */
    },
    {
        /* dataset_cls */
        NULL,                /* create       */
        NULL,                /* open         */
        NULL,                /* read         */
        NULL,                /* write        */
        NULL,                /* get          */
        NULL,                /* specific     */
        reg_opt_op_optional, /* optional     */
        NULL                 /* close        */
    },
    {
        /* datatype_cls */
        NULL,                 /* commit       */
        NULL,                 /* open         */
        reg_opt_datatype_get, /* get          */
        NULL,                 /* specific     */
        reg_opt_op_optional,  /* optional     */
        NULL                  /* close        */
    },
    {
        /* file_cls */
        NULL,                /* create       */
        NULL,                /* open         */
        NULL,                /* get          */
        NULL,                /* specific     */
        reg_opt_op_optional, /* optional     */
        NULL                 /* close        */
    },
    {
        /* group_cls */
        NULL,                /* create       */
        NULL,                /* open         */
        NULL,                /* get          */
        NULL,                /* specific     */
        reg_opt_op_optional, /* optional     */
        NULL                 /* close        */
    },
    {
        /* link_cls */
        NULL,                 /* create       */
        NULL,                 /* copy         */
        NULL,                 /* move         */
        NULL,                 /* get          */
        NULL,                 /* specific     */
        reg_opt_link_optional /* optional     */
    },
    {
        /* object_cls */
        NULL,                 /* open         */
        NULL,                 /* copy         */
        NULL,                 /* get          */
        NULL,                 /* specific     */
        reg_opt_link_optional /* optional     */
    },
    {
        /* introspect_cls */
        NULL, /* get_conn_cls */
        NULL, /* get_cap_flags */
        NULL, /* opt_query    */
    },
    {
        /* request_cls */
        NULL, /* wait         */
        NULL, /* notify       */
        NULL, /* cancel       */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* free         */
    },
    {
        /* blob_cls */
        NULL, /* put          */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* token_cls */
        NULL, /* cmp              */
        NULL, /* to_str           */
        NULL  /* from_str         */
    },
    NULL /* optional     */
};

static herr_t fake_get_cap_flags(const void *info, uint64_t *cap_flags);
static herr_t fake_vol_info_to_str(const void *info, char **str);
static herr_t fake_vol_str_to_info(const char *str, void **info);
static herr_t fake_vol_free_info(void *info);

#define FAKE_VOL_NAME  "fake"
#define FAKE_VOL_VALUE ((H5VL_class_value_t)501)
#define H5VL_FAKE_CAP_FLAGS                                                                                  \
    (H5VL_CAP_FLAG_DATASET_BASIC | H5VL_CAP_FLAG_GROUP_BASIC | H5VL_CAP_FLAG_FILE_BASIC)

/* A VOL class struct that describes a VOL class with no
 * functionality.
 */
static const H5VL_class_t fake_vol_g = {
    H5VL_VERSION,        /* VOL class struct version */
    FAKE_VOL_VALUE,      /* value        */
    FAKE_VOL_NAME,       /* name         */
    0,                   /* connector version */
    H5VL_FAKE_CAP_FLAGS, /* capability flags */
    NULL,                /* initialize   */
    NULL,                /* terminate    */
    {
        /* info_cls */
        (size_t)0,            /* size    */
        NULL,                 /* copy    */
        NULL,                 /* compare */
        fake_vol_free_info,   /* free    */
        fake_vol_info_to_str, /* to_str  */
        fake_vol_str_to_info, /* from_str */
    },
    {
        /* wrap_cls */
        NULL, /* get_object   */
        NULL, /* get_wrap_ctx */
        NULL, /* wrap_object  */
        NULL, /* unwrap_object */
        NULL, /* free_wrap_ctx */
    },
    {
        /* attribute_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* read         */
        NULL, /* write        */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* dataset_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* read         */
        NULL, /* write        */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* datatype_cls */
        NULL,                 /* commit       */
        NULL,                 /* open         */
        reg_opt_datatype_get, /* get          */
        NULL,                 /* specific     */
        NULL,                 /* optional     */
        NULL                  /* close        */
    },
    {
        /* file_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* group_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* link_cls */
        NULL, /* create       */
        NULL, /* copy         */
        NULL, /* move         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* object_cls */
        NULL, /* open         */
        NULL, /* copy         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* introspect_cls */
        NULL,               /* get_conn_cls */
        fake_get_cap_flags, /* get_cap_flags */
        NULL,               /* opt_query    */
    },
    {
        /* request_cls */
        NULL, /* wait         */
        NULL, /* notify       */
        NULL, /* cancel       */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* free         */
    },
    {
        /* blob_cls */
        NULL, /* put          */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* token_cls */
        NULL, /* cmp              */
        NULL, /* to_str           */
        NULL  /* from_str         */
    },
    NULL /* optional     */
};

static herr_t fake_async_get_cap_flags(const void *info, uint64_t *cap_flags);

#define FAKE_ASYNC_VOL_NAME  "fake_async"
#define FAKE_ASYNC_VOL_VALUE ((H5VL_class_value_t)503)

/* A VOL class struct that describes a VOL class with no
 * functionality except to set the async capability flag.
 */
static const H5VL_class_t fake_async_vol_g = {
    H5VL_VERSION,         /* VOL class struct version */
    FAKE_ASYNC_VOL_VALUE, /* value        */
    FAKE_ASYNC_VOL_NAME,  /* name         */
    0,                    /* connector version */
    H5VL_CAP_FLAG_ASYNC,  /* capability flags */
    NULL,                 /* initialize   */
    NULL,                 /* terminate    */
    {
        /* info_cls */
        (size_t)0, /* size    */
        NULL,      /* copy    */
        NULL,      /* compare */
        NULL,      /* free    */
        NULL,      /* to_str  */
        NULL,      /* from_str */
    },
    {
        /* wrap_cls */
        NULL, /* get_object   */
        NULL, /* get_wrap_ctx */
        NULL, /* wrap_object  */
        NULL, /* unwrap_object */
        NULL, /* free_wrap_ctx */
    },
    {
        /* attribute_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* read         */
        NULL, /* write        */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* dataset_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* read         */
        NULL, /* write        */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* datatype_cls */
        NULL, /* commit       */
        NULL, /* open         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* file_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* group_cls */
        NULL, /* create       */
        NULL, /* open         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* close        */
    },
    {
        /* link_cls */
        NULL, /* create       */
        NULL, /* copy         */
        NULL, /* move         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* object_cls */
        NULL, /* open         */
        NULL, /* copy         */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* introspect_cls */
        NULL,                     /* get_conn_cls */
        fake_async_get_cap_flags, /* get_cap_flags */
        NULL,                     /* opt_query    */
    },
    {
        /* request_cls */
        NULL, /* wait         */
        NULL, /* notify       */
        NULL, /* cancel       */
        NULL, /* specific     */
        NULL, /* optional     */
        NULL  /* free         */
    },
    {
        /* blob_cls */
        NULL, /* put          */
        NULL, /* get          */
        NULL, /* specific     */
        NULL  /* optional     */
    },
    {
        /* token_cls */
        NULL, /* cmp              */
        NULL, /* to_str           */
        NULL  /* from_str         */
    },
    NULL /* optional     */
};

/*-------------------------------------------------------------------------
 * Function:    reg_opt_op_optional_verify
 *
 * Purpose:     Common verification routine for dynamic optional operations
 *
 * Return:      Success:    0
 *              Failure:    -1
 *
 *-------------------------------------------------------------------------
 */
static herr_t
reg_opt_op_optional_verify(void *obj, H5VL_optional_args_t *args)
{
    int *o = (int *)obj;
    int *op_args;

    /* Check for receiving correct operation value */
    if (args->op_type != reg_opt_curr_op_val)
        return -1;

    /* Check that the object is correct */
    if ((-1) != *o)
        return -1;

    /* Update the object, with the operation value */
    *o = args->op_type;

    /* Check that the argument is correct */
    op_args = args->args;
    if (NULL == op_args)
        return -1;
    if ((-1) != *op_args)
        return -1;

    /* Update the argument return parameter */
    *op_args = args->op_type;

    return 0;
} /* end reg_opt_op_optional_verify() */

/*-------------------------------------------------------------------------
 * Function:    reg_opt_op_optional
 *
 * Purpose:     Common callback to perform a connector-specific operation
 *              on an object
 *
 * Return:      Success:    0
 *              Failure:    -1
 *
 *-------------------------------------------------------------------------
 */
static herr_t
reg_opt_op_optional(void *obj, H5VL_optional_args_t *args, hid_t H5_ATTR_UNUSED dxpl_id,
                    void H5_ATTR_UNUSED **req)
{
    /* Invoke the common value verification routine */
    return reg_opt_op_optional_verify(obj, args);
} /* end reg_opt_op_optional() */

/*-------------------------------------------------------------------------
 * Function:    reg_opt_link_optional
 *
 * Purpose:     Callback to perform a connector-specific operation
 *              on a link
 *
 * Return:      Success:    0
 *              Failure:    -1
 *
 *-------------------------------------------------------------------------
 */
static herr_t
reg_opt_link_optional(void *obj, const H5VL_loc_params_t *loc_params, H5VL_optional_args_t *args,
                      hid_t H5_ATTR_UNUSED dxpl_id, void H5_ATTR_UNUSED **req)
{
    /* Check for receiving correct loc_params info */
    if (loc_params->type != H5VL_OBJECT_BY_NAME)
        return -1;
    if (loc_params->obj_type != H5I_GROUP)
        return -1;
    if (strcmp(loc_params->loc_data.loc_by_name.name, ".") != 0)
        return -1;
    if (loc_params->loc_data.loc_by_name.lapl_id != H5P_LINK_ACCESS_DEFAULT)
        return -1;

    /* Invoke the common value verification routine */
    return reg_opt_op_optional_verify(obj, args);
} /* end reg_opt_link_optional() */

/*-------------------------------------------------------------------------
 * Function:    reg_opt_datatype_get
 *
 * Purpose:     Handles the datatype get callback
 *
 * Note:        This is _strictly_ a testing fixture to support the
 *              exercise_reg_opt_oper() testing routine.  It fakes just
 *              enough of the named datatype VOL callback for the
 *              H5VL_register_using_vol_id() call in that test routine to
 *              succeed.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
reg_opt_datatype_get(void H5_ATTR_UNUSED *obj, H5VL_datatype_get_args_t *args, hid_t H5_ATTR_UNUSED dxpl_id,
                     void H5_ATTR_UNUSED **req)
{
    herr_t ret_value = SUCCEED; /* Return value */

    if (H5VL_DATATYPE_GET_BINARY_SIZE == args->op_type) {
        if (H5Tencode(H5T_NATIVE_INT, NULL, args->args.get_binary_size.size) < 0)
            ret_value = FAIL;
    } /* end if */
    else if (H5VL_DATATYPE_GET_BINARY == args->op_type) {
        if (H5Tencode(H5T_NATIVE_INT, args->args.get_binary.buf, &args->args.get_binary.buf_size) < 0)
            ret_value = FAIL;
    } /* end if */
    else
        ret_value = FAIL;

    return ret_value;
} /* end reg_opt_datatype_get() */

/*-------------------------------------------------------------------------
 * Function:    fake_vol_info_to_str
 *
 * Purpose:     Convert the fake VOL info to a string
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
fake_vol_info_to_str(const void *info, char **str)
{
    const int    val       = *(const int *)info;
    const size_t str_size  = 16; /* The size of the string */
    herr_t       ret_value = SUCCEED;

    /* Verify the info is correct before continuing */
    if (val != INT_MAX) {
        printf("The value of info (%d) is incorrect\n", val);
        return FAIL;
    }

    /* Allocate the string long enough for the info */
    if (NULL == (*str = (char *)calloc(1, str_size)))
        return FAIL;

    snprintf(*str, str_size, "%d", val);

    return ret_value;
} /* end fake_vol_info_to_str() */

/*-------------------------------------------------------------------------
 * Function:    fake_vol_str_to_info
 *
 * Purpose:     Convert a string to a VOL info
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
fake_vol_str_to_info(const char *str, void **info /*out*/)
{
    herr_t ret_value = SUCCEED; /* Return value */

    *((int **)info) = (int *)malloc(sizeof(int));

    **((int **)info) = atoi(str);

    return ret_value;
} /* end fake_vol_str_to_info() */

/*-------------------------------------------------------------------------
 * Function:    fake_vol_free_info
 *
 * Purpose:     Free the memory of a VOL info
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
fake_vol_free_info(void *info)
{
    herr_t ret_value = SUCCEED; /* Return value */

    if (info)
        free(info);

    return ret_value;
} /* end fake_vol_free_info() */

/*-------------------------------------------------------------------------
 * Function:    fake_get_cap_flags
 *
 * Purpose:     Return the capability flags for the 'fake' connector
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
fake_get_cap_flags(const void H5_ATTR_UNUSED *info, uint64_t *cap_flags)
{
    *cap_flags = fake_vol_g.cap_flags;

    return SUCCEED;
} /* end fake_get_cap_flags() */

/*-------------------------------------------------------------------------
 * Function:    fake_async_get_cap_flags
 *
 * Purpose:     Return the capability flags for the 'fake async' connector
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
fake_async_get_cap_flags(const void H5_ATTR_UNUSED *info, uint64_t *cap_flags)
{
    *cap_flags = fake_async_vol_g.cap_flags;

    return SUCCEED;
} /* end fake_async_get_cap_flags() */

/*-------------------------------------------------------------------------
 * Function:    test_vol_registration()
 *
 * Purpose:     Tests if we can load, register, and close a simple
 *              VOL connector.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_vol_registration(void)
{
    hid_t         native_id          = H5I_INVALID_HID;
    hid_t         lapl_id            = H5I_INVALID_HID;
    hid_t         vipl_id            = H5I_INVALID_HID;
    herr_t        ret                = SUCCEED;
    htri_t        is_registered      = FAIL;
    hid_t         vol_id             = H5I_INVALID_HID;
    hid_t         vol_id2            = H5I_INVALID_HID;
    H5VL_class_t *bad_fake_vol_class = NULL;

    TESTING("VOL registration");

    /* The test/fake VOL connector should not be registered at the start of the test */
    if ((is_registered = H5VLis_connector_registered_by_name(FAKE_VOL_NAME)) < 0)
        TEST_ERROR;
    if (is_registered > 0)
        FAIL_PUTS_ERROR("VOL connector is inappropriately registered");
    if ((is_registered = H5VLis_connector_registered_by_value(FAKE_VOL_VALUE)) < 0)
        TEST_ERROR;
    if (is_registered > 0)
        FAIL_PUTS_ERROR("VOL connector is inappropriately registered");

    /* Test registering a connector with an incorrect property list (SHOULD FAIL) */
    if ((lapl_id = H5Pcreate(H5P_LINK_ACCESS)) < 0)
        TEST_ERROR;
    H5E_BEGIN_TRY
    {
        vol_id = H5VLregister_connector(&fake_vol_g, lapl_id);
    }
    H5E_END_TRY
    if (H5I_INVALID_HID != vol_id)
        FAIL_PUTS_ERROR("should not be able to register a connector with an incorrect property list");
    if (H5Pclose(lapl_id) < 0)
        TEST_ERROR;

    /* Test registering a VOL connector with an incompatible version # */
    if (NULL == (bad_fake_vol_class = malloc(sizeof(H5VL_class_t))))
        TEST_ERROR;
    memcpy(bad_fake_vol_class, &fake_vol_g, sizeof(H5VL_class_t));
    bad_fake_vol_class->version = H5VL_VERSION + 1;
    H5E_BEGIN_TRY
    {
        vol_id = H5VLregister_connector(bad_fake_vol_class, H5P_DEFAULT);
    }
    H5E_END_TRY
    if (H5I_INVALID_HID != vol_id)
        FAIL_PUTS_ERROR("should not be able to register a connector with an incompatible version #");
    free(bad_fake_vol_class);
    bad_fake_vol_class = NULL;

    /* Load a VOL interface
     * The vipl_id does nothing without a VOL that needs it, but we do need to
     * test creating a property list of that class and passing it along as a
     * smoke check.
     */
    if ((vipl_id = H5Pcreate(H5P_VOL_INITIALIZE)) < 0)
        TEST_ERROR;
    if ((vol_id = H5VLregister_connector(&fake_vol_g, vipl_id)) < 0)
        TEST_ERROR;
    if (H5Pclose(vipl_id) < 0)
        TEST_ERROR;

    /* The test/fake VOL connector should be registered now */
    if ((is_registered = H5VLis_connector_registered_by_name(FAKE_VOL_NAME)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("VOL connector is un-registered");
    if ((is_registered = H5VLis_connector_registered_by_value(FAKE_VOL_VALUE)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("VOL connector is un-registered");

    /* Re-register a VOL connector */
    if ((vol_id2 = H5VLregister_connector(&fake_vol_g, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* The test/fake VOL connector should still be registered now */
    if ((is_registered = H5VLis_connector_registered_by_name(FAKE_VOL_NAME)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("VOL connector is un-registered");
    if ((is_registered = H5VLis_connector_registered_by_value(FAKE_VOL_VALUE)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("VOL connector is un-registered");

    /* Unregister the second test/fake VOL ID */
    if (H5VLunregister_connector(vol_id2) < 0)
        TEST_ERROR;

    /* The test/fake VOL connector should still be registered now */
    if ((is_registered = H5VLis_connector_registered_by_name(FAKE_VOL_NAME)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("VOL connector is un-registered");
    if ((is_registered = H5VLis_connector_registered_by_value(FAKE_VOL_VALUE)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("VOL connector is un-registered");

    /* Unregister the original test/fake VOL ID */
    if (H5VLunregister_connector(vol_id) < 0)
        TEST_ERROR;

    /* Try to unregister the native VOL connector (should fail) */
    if (H5I_INVALID_HID == (native_id = H5VLget_connector_id_by_name(H5VL_NATIVE_NAME)))
        TEST_ERROR;
    H5E_BEGIN_TRY
    {
        ret = H5VLunregister_connector(native_id);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to unregister the native VOL connector");

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5VLunregister_connector(vol_id);
        H5Pclose(lapl_id);
        H5Pclose(vipl_id);
    }
    H5E_END_TRY

    if (bad_fake_vol_class)
        free(bad_fake_vol_class);

    return FAIL;
} /* end test_vol_registration() */

/*-------------------------------------------------------------------------
 * Function:    test_native_vol_init()
 *
 * Purpose:     Tests if the native VOL connector gets initialized.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_native_vol_init(void)
{
    htri_t is_registered;

    TESTING("Native VOL connector initialization");

    /* The native VOL connector should always be registered */
    if ((is_registered = H5VLis_connector_registered_by_name(H5VL_NATIVE_NAME)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("native VOL connector is un-registered");

    if ((is_registered = H5VLis_connector_registered_by_value(H5VL_NATIVE_VALUE)) < 0)
        TEST_ERROR;
    if (0 == is_registered)
        FAIL_PUTS_ERROR("native VOL connector is un-registered");

    PASSED();
    return SUCCEED;

error:
    return FAIL;

} /* end test_native_vol_init() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_file_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL file operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_file_operation(const char *env_h5_drvr)
{
    hid_t fid        = H5I_INVALID_HID;
    hid_t fid_reopen = H5I_INVALID_HID;
    hid_t fapl_id    = H5I_INVALID_HID;
    hid_t fapl_id2   = H5I_INVALID_HID;
    hid_t fcpl_id    = H5I_INVALID_HID;

    char        filename[1024];
    ssize_t     obj_count;
    hid_t       obj_id_list[1];
    hsize_t     file_size;
    unsigned    intent;
    void       *os_file_handle = NULL;
    H5F_info2_t finfo;
    char        name[32];

    TESTING("Basic VOL file operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    /* Set the file close degree to a non-default value, to make the H5Pequal
     *  work out.  This is kinda odd, but the library's current behavior with
     *  a default value is to return the value chosen (H5F_CLOSE_SEMI) instead
     *  of the default value (H5F_CLOSE_DEFAULT) from the property and then
     *  the H5Pequal doesn't detect that the property lists are the same.  Since
     *  this is the documented behavior for file close degree for many years,
     *  I'm not fighting it, just getting the testing to verify that the VOL
     *  connector property is returned correctly.  -QAK, 2018/11/17
     */
    if (H5Pset_fclose_degree(fapl_id, H5F_CLOSE_SEMI) < 0)
        TEST_ERROR;
    if (H5Pset_metadata_read_attempts(fapl_id, 9) < 0)
        TEST_ERROR;

    /* H5Fcreate */
    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;

    /* H5Fget_obj_count */
    if ((obj_count = H5Fget_obj_count(fid, H5F_OBJ_FILE)) < 0)
        TEST_ERROR;
    if ((obj_count = H5Fget_obj_count(fid, H5F_OBJ_ALL)) < 0)
        TEST_ERROR;
    if ((obj_count = H5Fget_obj_count((hid_t)H5F_OBJ_ALL, H5F_OBJ_DATASET)) < 0)
        TEST_ERROR;

    /* H5Fget_obj_ids */
    if ((obj_count = H5Fget_obj_ids(fid, H5F_OBJ_ALL, 2, obj_id_list)) < 0)
        TEST_ERROR;
    if ((obj_count = H5Fget_obj_ids((hid_t)H5F_OBJ_ALL, H5F_OBJ_DATASET, 2, obj_id_list)) < 0)
        TEST_ERROR;

    /* Can't compare VFD properties for several VFDs */
    if ((bool)(strcmp(env_h5_drvr, "split") != 0 && strcmp(env_h5_drvr, "multi") != 0 &&
               strcmp(env_h5_drvr, "family") != 0 && strcmp(env_h5_drvr, "direct") != 0 &&
               strcmp(env_h5_drvr, "core") != 0 && strcmp(env_h5_drvr, "core_paged") != 0 &&
               strcmp(env_h5_drvr, "mpio") != 0 && strcmp(env_h5_drvr, "splitter") != 0)) {
        /* H5Fget_access_plist */
        if ((fapl_id2 = H5Fget_access_plist(fid)) < 0)
            TEST_ERROR;
        if (H5Pequal(fapl_id, fapl_id2) != true)
            TEST_ERROR;
        if (H5Pclose(fapl_id2) < 0)
            TEST_ERROR;
    } /* end if */

    /* H5Fget_create_plist */
    if ((fcpl_id = H5Fget_create_plist(fid)) < 0)
        TEST_ERROR;
    if (H5Pclose(fcpl_id) < 0)
        TEST_ERROR;

    /* H5Fget_filesize */
    if (H5Fget_filesize(fid, &file_size) < 0)
        TEST_ERROR;

    /* Can't retrieve VFD handle for split / multi / family VFDs */
    if ((bool)(strcmp(env_h5_drvr, "split") != 0 && strcmp(env_h5_drvr, "multi") != 0 &&
               strcmp(env_h5_drvr, "family") != 0)) {
        /* H5Fget_vfd_handle */
        if (H5Fget_vfd_handle(fid, H5P_DEFAULT, &os_file_handle) < 0)
            TEST_ERROR;
    } /* end if */

    /* H5Fget_intent */
    if (H5Fget_intent(fid, &intent) < 0)
        TEST_ERROR;

    /* H5Fget_info2 */
    if (H5Fget_info2(fid, &finfo) < 0)
        TEST_ERROR;

    /* H5Fget_name */
    if (H5Fget_name(fid, name, 32) < 0)
        TEST_ERROR;

    /* H5Fclear_elink_file_cache */
    if (H5Fclear_elink_file_cache(fid) < 0)
        TEST_ERROR;

    /* H5Fflush */
    if (H5Fflush(fid, H5F_SCOPE_GLOBAL) < 0)
        TEST_ERROR;

    /* H5Fclose */
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    /* H5Fis_accessible */
    if (H5Fis_accessible(filename, fapl_id) < 0)
        TEST_ERROR;

    /* H5Fopen */
    if ((fid = H5Fopen(filename, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Can't compare VFD properties for several VFDs */
    if ((bool)(strcmp(env_h5_drvr, "split") != 0 && strcmp(env_h5_drvr, "multi") != 0 &&
               strcmp(env_h5_drvr, "family") != 0 && strcmp(env_h5_drvr, "direct") != 0 &&
               strcmp(env_h5_drvr, "core") != 0 && strcmp(env_h5_drvr, "core_paged") != 0 &&
               strcmp(env_h5_drvr, "mpio") != 0 && strcmp(env_h5_drvr, "splitter") != 0)) {
        /* H5Fget_access_plist */
        if ((fapl_id2 = H5Fget_access_plist(fid)) < 0)
            TEST_ERROR;
        if (H5Pequal(fapl_id, fapl_id2) != true)
            TEST_ERROR;
        if (H5Pclose(fapl_id2) < 0)
            TEST_ERROR;
    } /* end if */

    if ((fid_reopen = H5Freopen(fid)) < 0)
        TEST_ERROR;

    /* Can't compare VFD properties for several VFDs */
    if ((bool)(strcmp(env_h5_drvr, "split") != 0 && strcmp(env_h5_drvr, "multi") != 0 &&
               strcmp(env_h5_drvr, "family") != 0 && strcmp(env_h5_drvr, "direct") != 0 &&
               strcmp(env_h5_drvr, "core") != 0 && strcmp(env_h5_drvr, "core_paged") != 0 &&
               strcmp(env_h5_drvr, "mpio") != 0 && strcmp(env_h5_drvr, "splitter") != 0)) {
        /* H5Fget_access_plist */
        if ((fapl_id2 = H5Fget_access_plist(fid_reopen)) < 0)
            TEST_ERROR;
        if (H5Pequal(fapl_id, fapl_id2) != true)
            TEST_ERROR;
        if (H5Pclose(fapl_id2) < 0)
            TEST_ERROR;
    } /* end if */

    if (H5Fclose(fid) < 0)
        TEST_ERROR;
    if (H5Fclose(fid_reopen) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(fid);
        H5Fclose(fid_reopen);
        H5Pclose(fapl_id);
        H5Pclose(fapl_id2);
        H5Pclose(fcpl_id);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_file_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_group_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL group operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_group_operation(void)
{
    hid_t      fid     = H5I_INVALID_HID;
    hid_t      fapl_id = H5I_INVALID_HID;
    hid_t      gid     = H5I_INVALID_HID;
    hid_t      gid_a   = H5I_INVALID_HID;
    hid_t      gcpl_id = H5I_INVALID_HID;
    char       filename[1024];
    H5G_info_t info;
    bool       driver_is_parallel;

    TESTING("Basic VOL group operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;

    /* H5Gcreate */
    if ((gid = H5Gcreate2(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Gget_create_plist */
    if ((gcpl_id = H5Gget_create_plist(gid)) < 0)
        TEST_ERROR;
    if (H5Pclose(gcpl_id) < 0)
        TEST_ERROR;

    /* H5Gget_info */
    if (H5Gget_info(gid, &info) < 0)
        TEST_ERROR;
    if (H5Gget_info(fid, &info) < 0)
        TEST_ERROR;

    /* H5Gget_info_by_name */
    if (H5Gget_info_by_name(fid, NATIVE_VOL_TEST_GROUP_NAME, &info, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Gget_info_by_idx */
    if (H5Gget_info_by_idx(fid, "/", H5_INDEX_NAME, H5_ITER_NATIVE, 0, &info, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Gflush - skip for parallel file drivers as flush calls cause assertions in the library */
    if (h5_using_parallel_driver(fapl_id, &driver_is_parallel) < 0)
        TEST_ERROR;
    if (!driver_is_parallel)
        if (H5Gflush(gid) < 0)
            TEST_ERROR;

    /* H5Gclose */
    if (H5Gclose(gid) < 0)
        TEST_ERROR;

    /* H5Gopen */
    if ((gid = H5Gopen2(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Gcreate_anon */
    if ((gid_a = H5Gcreate_anon(fid, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Grefresh */
    if (H5Grefresh(gid) < 0)
        TEST_ERROR;

    if (H5Gclose(gid) < 0)
        TEST_ERROR;
    if (H5Gclose(gid_a) < 0)
        TEST_ERROR;
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(fid);
        H5Gclose(gid);
        H5Pclose(fapl_id);
        H5Pclose(gcpl_id);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_group_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_dataset_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL dataset operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_dataset_operation(void)
{
    hid_t fid     = H5I_INVALID_HID;
    hid_t fapl_id = H5I_INVALID_HID;
    hid_t dcpl_id = H5I_INVALID_HID;
    hid_t dapl_id = H5I_INVALID_HID;
    hid_t did     = H5I_INVALID_HID;
    hid_t did_a   = H5I_INVALID_HID;
    hid_t sid     = H5I_INVALID_HID;
    hid_t tid     = H5I_INVALID_HID;

    char filename[1024];

    hsize_t curr_dims = 0;
    hsize_t max_dims  = H5S_UNLIMITED;

    hsize_t            storage_size;
    haddr_t            offset;
    H5D_space_status_t status;

    bool driver_is_parallel;

    int in_buf[N_ELEMENTS];
    int out_buf[N_ELEMENTS];

    int i;

    TESTING("Basic VOL dataset operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;
    for (i = 0; i < N_ELEMENTS; i++) {
        in_buf[i]  = i;
        out_buf[i] = 0;
    }

    /* H5Dcreate */
    curr_dims = 0;
    if ((sid = H5Screate_simple(1, &curr_dims, &max_dims)) < 0)
        TEST_ERROR;
    curr_dims = N_ELEMENTS;
    if ((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    if (H5Pset_chunk(dcpl_id, 1, &curr_dims) < 0)
        TEST_ERROR;
    if ((did = H5Dcreate2(fid, NATIVE_VOL_TEST_DATASET_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id,
                          H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Dcreate_anon */
    if ((did_a = H5Dcreate_anon(fid, H5T_NATIVE_INT, sid, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Sclose(sid) < 0)
        TEST_ERROR;
    if (H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /* H5Dset_extent */
    curr_dims = N_ELEMENTS;
    if (H5Dset_extent(did, &curr_dims) < 0)
        TEST_ERROR;

    /* H5Dflush - skip for parallel file drivers as flush calls cause assertions in the library */
    if (h5_using_parallel_driver(fapl_id, &driver_is_parallel) < 0)
        TEST_ERROR;
    if (!driver_is_parallel)
        if (H5Dflush(did) < 0)
            TEST_ERROR;

    /* H5Dwrite */
    if (H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, in_buf) < 0)
        TEST_ERROR;

    /* H5Drefresh */
    if (H5Drefresh(did) < 0)
        TEST_ERROR;

    /* H5Dclose */
    if (H5Dclose(did) < 0)
        TEST_ERROR;
    if (H5Dclose(did_a) < 0)
        TEST_ERROR;

    /* H5Dopen */
    if ((did = H5Dopen2(fid, NATIVE_VOL_TEST_DATASET_NAME, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Dget_space */
    if ((sid = H5Dget_space(did)) < 0)
        TEST_ERROR;
    if (H5Sclose(sid) < 0)
        TEST_ERROR;

    /* H5Dget_space_status */
    if (H5Dget_space_status(did, &status) < 0)
        TEST_ERROR;

    /* H5Dget_type */
    if ((tid = H5Dget_type(did)) < 0)
        TEST_ERROR;
    if (H5Tclose(tid) < 0)
        TEST_ERROR;

    /* H5Tcopy (when used w/ a dataset, it gets an H5VL struct */
    if ((tid = H5Tcopy(did)) < 0)
        TEST_ERROR;
    if (H5Tclose(tid) < 0)
        TEST_ERROR;

    /* H5Dget_create_plist */
    if ((dcpl_id = H5Dget_create_plist(did)) < 0)
        TEST_ERROR;
    if (H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /* H5Dget_access_plist */
    if ((dapl_id = H5Dget_access_plist(did)) < 0)
        TEST_ERROR;
    if (H5Pclose(dapl_id) < 0)
        TEST_ERROR;

    /* H5Dget_storage_size */
    /* XXX: This is a terrible API call that can't truly indicate failure */
    if (0 == (storage_size = H5Dget_storage_size(did)))
        TEST_ERROR;

    /* H5Dget_offset */
    /* XXX: Another bad API call that can't flag error values. Also, this
     *      returns HADDR_UNDEF for chunked datasets, which is bizarre.
     */
    if (HADDR_UNDEF != (offset = H5Dget_offset(did)))
        TEST_ERROR;

    /* H5Dread */
    if (H5Dread(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, out_buf) < 0)
        TEST_ERROR;

    for (i = 0; i < N_ELEMENTS; i++)
        if (in_buf[i] != out_buf[i])
            TEST_ERROR;

    if (H5Dclose(did) < 0)
        TEST_ERROR;
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(fid);
        H5Dclose(did);
        H5Dclose(did_a);
        H5Sclose(sid);
        H5Tclose(tid);
        H5Pclose(fapl_id);
        H5Pclose(dapl_id);
        H5Pclose(dcpl_id);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_dataset_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_attribute_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL attribute operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_attribute_operation(void)
{
    hid_t fid      = H5I_INVALID_HID;
    hid_t fapl_id  = H5I_INVALID_HID;
    hid_t gid      = H5I_INVALID_HID;
    hid_t aid      = H5I_INVALID_HID;
    hid_t aid_name = H5I_INVALID_HID;
    hid_t sid      = H5I_INVALID_HID;

    char filename[1024];

    hsize_t dims = 1;

    int data_in  = 42;
    int data_out = 0;

    TESTING("Basic VOL attribute operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;
    if ((gid = H5Gcreate2(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    dims = 1;
    if ((sid = H5Screate_simple(1, &dims, &dims)) < 0)
        TEST_ERROR;

    /* H5Acreate */
    if ((aid = H5Acreate2(fid, NATIVE_VOL_TEST_ATTRIBUTE_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT,
                          H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Awrite */
    if (H5Awrite(aid, H5T_NATIVE_INT, &data_in) < 0)
        TEST_ERROR;

    /* H5Aread */
    if (H5Aread(aid, H5T_NATIVE_INT, &data_out) < 0)
        TEST_ERROR;
    if (data_in != data_out)
        TEST_ERROR;

    /* H5Aclose */
    if (H5Aclose(aid) < 0)
        TEST_ERROR;

    /* H5Aopen */
    if ((aid = H5Aopen(fid, NATIVE_VOL_TEST_ATTRIBUTE_NAME, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if (H5Aclose(aid) < 0)
        TEST_ERROR;

    /* H5Adelete */
    if (H5Adelete(fid, NATIVE_VOL_TEST_ATTRIBUTE_NAME) < 0)
        TEST_ERROR;

    /* H5Acreate_by_name */
    if ((aid_name = H5Acreate_by_name(fid, NATIVE_VOL_TEST_GROUP_NAME, NATIVE_VOL_TEST_ATTRIBUTE_NAME,
                                      H5T_NATIVE_INT, sid, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    /* H5Aclose */
    if (H5Aclose(aid_name) < 0)
        TEST_ERROR;

    /* H5Adelete_by_name */
    if (H5Adelete_by_name(fid, NATIVE_VOL_TEST_GROUP_NAME, NATIVE_VOL_TEST_ATTRIBUTE_NAME, H5P_DEFAULT) < 0)
        TEST_ERROR;

    if (H5Sclose(sid) < 0)
        TEST_ERROR;
    if (H5Gclose(gid) < 0)
        TEST_ERROR;
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(fid);
        H5Pclose(fapl_id);
        H5Gclose(gid);
        H5Sclose(sid);
        H5Aclose(aid);
        H5Aclose(aid_name);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_attribute_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_object_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL object operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_object_operation(void)
{
    hid_t fid     = H5I_INVALID_HID;
    hid_t fapl_id = H5I_INVALID_HID;
    hid_t gid     = H5I_INVALID_HID;
    hid_t oid     = H5I_INVALID_HID;

    char        filename[1024];
    H5O_info2_t object_info;

    TESTING("Basic VOL object operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;
    if ((gid = H5Gcreate2(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Oget_info */
    if (H5Oget_info3(fid, &object_info, H5O_INFO_ALL) < 0)
        TEST_ERROR;

    //! [H5Oget_info_by_name3_snip]

    /* H5Oget_info_by_name */
    if (H5Oget_info_by_name3(fid, NATIVE_VOL_TEST_GROUP_NAME, &object_info, H5O_INFO_ALL, H5P_DEFAULT) < 0)
        TEST_ERROR;

    //! [H5Oget_info_by_name3_snip]

    /* H5Oexists_by_name */
    if (H5Oexists_by_name(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT) != true)
        TEST_ERROR;

    /* H5Oopen/close */
    if ((oid = H5Oopen(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if (H5Oclose(oid) < 0)
        TEST_ERROR;

    if (H5Gclose(gid) < 0)
        TEST_ERROR;
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(fid);
        H5Pclose(fapl_id);
        H5Gclose(gid);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_object_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_link_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL link operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_link_operation(void)
{
    hid_t fid     = H5I_INVALID_HID;
    hid_t gid     = H5I_INVALID_HID;
    hid_t fapl_id = H5I_INVALID_HID;
    char  filename[1024];

    TESTING("Basic VOL link operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;
    if ((gid = H5Gcreate2(fid, NATIVE_VOL_TEST_GROUP_NAME, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Lcreate_hard */
    if (H5Lcreate_hard(fid, "/", gid, NATIVE_VOL_TEST_HARD_LINK_NAME, H5P_DEFAULT, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Lcreate_soft (to itself) */
    if (H5Lcreate_soft("/", fid, NATIVE_VOL_TEST_SOFT_LINK_NAME, H5P_DEFAULT, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Lexists */
    if (H5Lexists(gid, NATIVE_VOL_TEST_HARD_LINK_NAME, H5P_DEFAULT) < 0)
        TEST_ERROR;
    if (H5Lexists(fid, NATIVE_VOL_TEST_SOFT_LINK_NAME, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Lcopy */
    if (H5Lcopy(gid, NATIVE_VOL_TEST_HARD_LINK_NAME, fid, NATIVE_VOL_TEST_COPY_LINK_NAME, H5P_DEFAULT,
                H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Lmove */
    if (H5Lmove(fid, NATIVE_VOL_TEST_COPY_LINK_NAME, gid, NATIVE_VOL_TEST_MOVE_LINK_NAME, H5P_DEFAULT,
                H5P_DEFAULT) < 0)
        TEST_ERROR;

    if (H5Gclose(gid) < 0)
        TEST_ERROR;
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(fid);
        H5Fclose(gid);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_link_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_basic_datatype_operation()
 *
 * Purpose:     Uses the native VOL connector to test basic VOL datatype operations
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_basic_datatype_operation(void)
{
    hid_t fid      = H5I_INVALID_HID;
    hid_t fapl_id  = H5I_INVALID_HID;
    hid_t tid      = H5I_INVALID_HID;
    hid_t tid_anon = H5I_INVALID_HID;
    hid_t tcpl_id  = H5I_INVALID_HID;
    char  filename[1024];
    bool  driver_is_parallel;

    TESTING("Basic VOL datatype operations");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();
    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((fid = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;
    if ((tid = H5Tcopy(H5T_NATIVE_INT)) < 0)
        TEST_ERROR;

    /* H5Tcommit */
    if (H5Tcommit2(fid, NATIVE_VOL_TEST_DATATYPE_NAME, tid, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* H5Tflush - skip for parallel file drivers as flush calls cause assertions in the library */
    if (h5_using_parallel_driver(fapl_id, &driver_is_parallel) < 0)
        TEST_ERROR;
    if (!driver_is_parallel)
        if (H5Tflush(tid) < 0)
            TEST_ERROR;

    /* H5Trefresh */
    if (H5Trefresh(tid) < 0)
        TEST_ERROR;

    /* H5Tclose */
    if (H5Tclose(tid) < 0)
        TEST_ERROR;

    /* H5Topen */
    if ((tid = H5Topen2(fid, NATIVE_VOL_TEST_DATATYPE_NAME, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* H5Tget_create_plist */
    if ((tcpl_id = H5Tget_create_plist(tid)) < 0)
        TEST_ERROR;

    /* H5Tcommit_anon */
    if ((tid_anon = H5Tcopy(H5T_NATIVE_INT)) < 0)
        TEST_ERROR;
    if (H5Tcommit_anon(fid, tid_anon, H5P_DEFAULT, H5P_DEFAULT) < 0)
        TEST_ERROR;

    if (H5Pclose(tcpl_id) < 0)
        TEST_ERROR;
    if (H5Tclose(tid) < 0)
        TEST_ERROR;
    if (H5Tclose(tid_anon) < 0)
        TEST_ERROR;
    if (H5Fclose(fid) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    /* H5Pclose */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Pclose(tcpl_id);
        H5Fclose(fid);
        H5Pclose(fapl_id);
        H5Tclose(tid);
        H5Tclose(tid_anon);
    }
    H5E_END_TRY

    return FAIL;

} /* end test_basic_datatype_operation() */

typedef herr_t (*reg_opt_obj_oper_t)(const char *app_file, const char *app_func, unsigned app_line,
                                     hid_t obj_id, H5VL_optional_args_t *args, hid_t dxpl_id, hid_t es_id);
typedef herr_t (*reg_opt_link_oper_t)(const char *app_file, const char *app_func, unsigned app_line,
                                      hid_t obj_id, const char *name, hid_t lapl_id,
                                      H5VL_optional_args_t *args, hid_t dxpl_id, hid_t es_id);
typedef union {
    reg_opt_obj_oper_t  obj_op;
    reg_opt_link_oper_t link_op;
} reg_opt_oper_t;

/*-------------------------------------------------------------------------
 * Function:    exercise_reg_opt_oper()
 *
 * Purpose:     Exercise a particular optional operation for a type.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
exercise_reg_opt_oper(hid_t fake_vol_id, hid_t reg_opt_vol_id, H5VL_subclass_t subcls,
                      const char *subcls_name, H5I_type_t id_type, reg_opt_oper_t reg_opt_op)
{
    char                 op_name[256]; /* Operation name to register */
    hid_t                obj_id = H5I_INVALID_HID;
    H5VL_object_t       *vol_obj;
    H5VL_optional_args_t vol_cb_args;
    int                  fake_obj, fake_arg;
    int                  op_val = -1, op_val2 = -1;
    int                  find_op_val;
    herr_t               ret = SUCCEED;

    /* Test registering optional operation */
    snprintf(op_name, sizeof(op_name), "%s-op1", subcls_name);
    if (H5VLregister_opt_operation(subcls, op_name, &op_val) < 0)
        TEST_ERROR;

    /* Verify that the reserved amount of optional operations is obeyed */
    /* (The first optional operation registered should be at the lower limit) */
    if (op_val < H5VL_RESERVED_NATIVE_OPTIONAL)
        TEST_ERROR;

    /* Look up 1st registered optional operation */
    find_op_val = 0;
    if (H5VLfind_opt_operation(subcls, op_name, &find_op_val) < 0)
        TEST_ERROR;

    /* Verify that the operation was looked up successfully */
    if (op_val != find_op_val)
        TEST_ERROR;

    /* Test registering second optional operation */
    snprintf(op_name, sizeof(op_name), "%s-op2", subcls_name);
    if (H5VLregister_opt_operation(subcls, op_name, &op_val2) < 0)
        TEST_ERROR;

    /* Verify that the reserved amount of optional operations is obeyed */
    /* (The 2nd optional operation registered should be at the lower limit + 1) */
    if (op_val2 < (H5VL_RESERVED_NATIVE_OPTIONAL + 1))
        TEST_ERROR;

    /* Look up 2nd registered optional operation */
    find_op_val = 0;
    if (H5VLfind_opt_operation(subcls, op_name, &find_op_val) < 0)
        TEST_ERROR;

    /* Verify that the operation was looked up successfully */
    if (op_val2 != find_op_val)
        TEST_ERROR;

    /* Push a new API context on the stack */
    /* (Necessary for the named datatype construction routines) */
    if (H5VL_SUBCLS_DATATYPE == subcls)
        H5CX_push();

    /* Create fake object on fake VOL connector */
    if (H5I_INVALID_HID == (obj_id = H5VL_register_using_vol_id(id_type, &fake_obj, fake_vol_id, true)))
        TEST_ERROR;

    /* Pop the API context off the stack */
    if (H5VL_SUBCLS_DATATYPE == subcls)
        H5CX_pop(false);

    /* Attempt to issue operation on fake VOL connector */
    fake_obj            = -1;
    fake_arg            = -1;
    vol_cb_args.op_type = op_val;
    vol_cb_args.args    = &fake_arg;
    H5E_BEGIN_TRY
    {
        if (H5VL_SUBCLS_LINK == subcls || H5VL_SUBCLS_OBJECT == subcls)
            ret = (*reg_opt_op.link_op)(__FILE__, __func__, __LINE__, obj_id, ".", H5P_DEFAULT, &vol_cb_args,
                                        H5P_DEFAULT, H5ES_NONE);
        else
            ret = (*reg_opt_op.obj_op)(__FILE__, __func__, __LINE__, obj_id, &vol_cb_args, H5P_DEFAULT,
                                       H5ES_NONE);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to perform an optional operation with a NULL callback");
    if ((-1) != fake_obj)
        FAIL_PUTS_ERROR("'fake_obj' changed during failed operation?");
    if ((-1) != fake_arg)
        FAIL_PUTS_ERROR("'fake_arg' changed during failed operation?");

    /* Named datatypes must be destroyed differently */
    if (H5VL_SUBCLS_DATATYPE == subcls) {
        H5T_t *dt;

        /* Destroy fake datatype object */
        if (NULL == (dt = H5I_remove(obj_id)))
            TEST_ERROR;
        if (H5VL_free_object(dt->vol_obj) < 0)
            TEST_ERROR;
        dt->vol_obj = NULL;
        if (H5T_close(dt) < 0)
            TEST_ERROR;
    } /* end if */
    else {
        /* Destroy fake object */
        if (NULL == (vol_obj = H5I_remove(obj_id)))
            TEST_ERROR;
        if (H5VL_free_object(vol_obj) < 0)
            TEST_ERROR;
    } /* end else */

    /* Push a new API context on the stack */
    /* (Necessary for the named datatype construction routines) */
    if (H5VL_SUBCLS_DATATYPE == subcls)
        H5CX_push();

    /* Create fake object on reg_opt VOL connector */
    if (H5I_INVALID_HID == (obj_id = H5VL_register_using_vol_id(id_type, &fake_obj, reg_opt_vol_id, true)))
        TEST_ERROR;

    /* Pop the API context off the stack */
    if (H5VL_SUBCLS_DATATYPE == subcls)
        H5CX_pop(false);

    /* Issue first operation */
    fake_obj            = -1;
    fake_arg            = -1;
    reg_opt_curr_op_val = op_val;
    vol_cb_args.op_type = op_val;
    vol_cb_args.args    = &fake_arg;
    if (H5VL_SUBCLS_LINK == subcls || H5VL_SUBCLS_OBJECT == subcls)
        ret = (*reg_opt_op.link_op)(__FILE__, __func__, __LINE__, obj_id, ".", H5P_DEFAULT, &vol_cb_args,
                                    H5P_DEFAULT, H5ES_NONE);
    else
        ret =
            (*reg_opt_op.obj_op)(__FILE__, __func__, __LINE__, obj_id, &vol_cb_args, H5P_DEFAULT, H5ES_NONE);
    if (ret < 0)
        TEST_ERROR;

    /* Verify that fake object & argument were modified correctly */
    if (op_val != fake_obj)
        FAIL_PUTS_ERROR("'fake_obj' not updated");
    if (op_val != fake_arg)
        FAIL_PUTS_ERROR("'fake_arg' not updated");

    /* Issue second operation */
    fake_obj            = -1;
    fake_arg            = -1;
    reg_opt_curr_op_val = op_val2;
    vol_cb_args.op_type = op_val2;
    vol_cb_args.args    = &fake_arg;
    if (H5VL_SUBCLS_LINK == subcls || H5VL_SUBCLS_OBJECT == subcls)
        ret = (*reg_opt_op.link_op)(__FILE__, __func__, __LINE__, obj_id, ".", H5P_DEFAULT, &vol_cb_args,
                                    H5P_DEFAULT, H5ES_NONE);
    else
        ret =
            (*reg_opt_op.obj_op)(__FILE__, __func__, __LINE__, obj_id, &vol_cb_args, H5P_DEFAULT, H5ES_NONE);
    if (ret < 0)
        TEST_ERROR;

    /* Verify that fake object & argument were modified correctly */
    if (op_val2 != fake_obj)
        FAIL_PUTS_ERROR("'fake_obj' not updated");
    if (op_val2 != fake_arg)
        FAIL_PUTS_ERROR("'fake_arg' not updated");

    /* Named datatypes must be destroyed differently */
    if (H5VL_SUBCLS_DATATYPE == subcls) {
        H5T_t *dt;

        /* Destroy fake datatype object */
        if (NULL == (dt = H5I_remove(obj_id)))
            TEST_ERROR;
        if (H5VL_free_object(dt->vol_obj) < 0)
            TEST_ERROR;
        dt->vol_obj = NULL;
        if (H5T_close(dt) < 0)
            TEST_ERROR;
    } /* end if */
    else {
        /* Destroy fake object */
        if (NULL == (vol_obj = H5I_remove(obj_id)))
            TEST_ERROR;
        if (H5VL_free_object(vol_obj) < 0)
            TEST_ERROR;
    } /* end else */

    /* Unregister 2nd registered optional operation */
    if (H5VLunregister_opt_operation(subcls, op_name) < 0)
        TEST_ERROR;

    return SUCCEED;

error:
    return FAIL;
} /* end exercise_reg_opt_oper() */

/*-------------------------------------------------------------------------
 * Function:    test_register_opt_operation()
 *
 * Purpose:     Tests if we can load, register, and close a simple
 *              VOL connector.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_register_opt_operation(void)
{
    hid_t fake_vol_id    = H5I_INVALID_HID;
    hid_t reg_opt_vol_id = H5I_INVALID_HID;
    struct {
        H5VL_subclass_t subcls;
        const char     *subcls_name;
        H5I_type_t      id_type;
        reg_opt_oper_t  reg_opt_op;
    } test_params[] = {{H5VL_SUBCLS_ATTR, "attr", H5I_ATTR, {.obj_op = H5VLattr_optional_op}},
                       {H5VL_SUBCLS_DATASET, "dataset", H5I_DATASET, {.obj_op = H5VLdataset_optional_op}},
                       {H5VL_SUBCLS_DATATYPE, "datatype", H5I_DATATYPE, {.obj_op = H5VLdatatype_optional_op}},
                       {H5VL_SUBCLS_FILE, "file", H5I_FILE, {.obj_op = H5VLfile_optional_op}},
                       {H5VL_SUBCLS_GROUP, "group", H5I_GROUP, {.obj_op = H5VLgroup_optional_op}},
                       {H5VL_SUBCLS_LINK, "link", H5I_GROUP, {.link_op = H5VLlink_optional_op}},
                       {H5VL_SUBCLS_OBJECT, "object", H5I_GROUP, {.link_op = H5VLobject_optional_op}}};
    int      op_val = -1;
    unsigned u;
    herr_t   ret = SUCCEED;

    TESTING("dynamically registering optional operations");

    /* Register the VOL connectors for testing */
    if ((fake_vol_id = H5VLregister_connector(&fake_vol_g, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if ((reg_opt_vol_id = H5VLregister_connector(&reg_opt_vol_g, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Test registering invalid optional VOL subclass operations */
    H5E_BEGIN_TRY
    {
        ret = H5VLregister_opt_operation(H5VL_SUBCLS_NONE, "fail", &op_val);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to register an optional operation for the 'NONE' VOL subclass");
    if ((-1) != op_val)
        FAIL_PUTS_ERROR("'op_val' changed during failed operation?");
    H5E_BEGIN_TRY
    {
        ret = H5VLregister_opt_operation(H5VL_SUBCLS_INFO, "fail2", &op_val);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to register an optional operation for the 'INFO' VOL subclass");
    if ((-1) != op_val)
        FAIL_PUTS_ERROR("'op_val' changed during failed operation?");
    H5E_BEGIN_TRY
    {
        ret = H5VLregister_opt_operation(H5VL_SUBCLS_WRAP, "fail3", &op_val);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to register an optional operation for the 'WRAP' VOL subclass");
    if ((-1) != op_val)
        FAIL_PUTS_ERROR("'op_val' changed during failed operation?");
    H5E_BEGIN_TRY
    {
        ret = H5VLregister_opt_operation(H5VL_SUBCLS_BLOB, "fail4", &op_val);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to register an optional operation for the 'BLOB' VOL subclass");
    if ((-1) != op_val)
        FAIL_PUTS_ERROR("'op_val' changed during failed operation?");
    H5E_BEGIN_TRY
    {
        ret = H5VLregister_opt_operation(H5VL_SUBCLS_TOKEN, "fail5", &op_val);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to register an optional operation for the 'TOKEN' VOL subclass");
    if ((-1) != op_val)
        FAIL_PUTS_ERROR("'op_val' changed during failed operation?");

    /* Test registering valid optional VOL subclass operation with NULL op_val ptr*/
    H5E_BEGIN_TRY
    {
        ret = H5VLregister_opt_operation(H5VL_SUBCLS_FILE, "fail6", NULL);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to register an optional operation with a NULL 'op_val'");

    /* Try finding a non-existent optional VOL subclass operation */
    H5E_BEGIN_TRY
    {
        ret = H5VLfind_opt_operation(H5VL_SUBCLS_DATASET, "fail", &op_val);
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to find a non-existent optional operation");

    /* Try unregistering a non-existent optional VOL subclass operation */
    H5E_BEGIN_TRY
    {
        ret = H5VLunregister_opt_operation(H5VL_SUBCLS_DATASET, "fail");
    }
    H5E_END_TRY
    if (FAIL != ret)
        FAIL_PUTS_ERROR("should not be able to unregister a non-existent optional operation");

    /* Optional operations on requests are supported (but difficult to test further) */
    if (H5VLregister_opt_operation(H5VL_SUBCLS_REQUEST, "req_op", &op_val) < 0)
        TEST_ERROR;

    /* Register & test calling optional operations for each valid VOL subclass */
    /* (Table-driven, with test_params array) */
    for (u = 0; u < NELMTS(test_params); u++)
        /* Exercise appropriate callback, for each VOL subclass */
        if (exercise_reg_opt_oper(fake_vol_id, reg_opt_vol_id, test_params[u].subcls,
                                  test_params[u].subcls_name, test_params[u].id_type,
                                  test_params[u].reg_opt_op) < 0)
            TEST_ERROR;

    /* Unregister the VOL connectors */
    if (H5VLunregister_connector(fake_vol_id) < 0)
        TEST_ERROR;
    if (H5VLunregister_connector(reg_opt_vol_id) < 0)
        TEST_ERROR;

    PASSED();

    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5VLunregister_connector(fake_vol_id);
        H5VLunregister_connector(reg_opt_vol_id);
    }
    H5E_END_TRY

    return FAIL;
} /* end test_register_opt_operation() */

/*-------------------------------------------------------------------------
 * Function:    test_async_vol_props()
 *
 * Purpose:     Test properties related to asynchronous VOL connector operation
 *
 * Note:        Overrides the HDF5_VOL_CONNECTOR environment variable, to
 *              provide stable testing environment.
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_async_vol_props(void)
{
    hid_t                    fapl_id = H5I_INVALID_HID;
    hid_t                    vol_id  = H5I_INVALID_HID;
    H5VL_pass_through_info_t passthru_info;
    char                    *conn_env_str = NULL;

    TESTING("Async VOL props");

    vol_cap_flags_g = H5VL_CAP_FLAG_NONE;

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();

    /* Test 'capability flags' property */

    /* Test query w/NULL for cap_flags parameter */
    if (H5Pget_vol_cap_flags(fapl_id, NULL) < 0)
        FAIL_STACK_ERROR;

    /* Override possible environment variable & re-initialize default VOL connector */
    conn_env_str = getenv(HDF5_VOL_CONNECTOR);
    if (conn_env_str) {
        if (NULL == (conn_env_str = strdup(conn_env_str)))
            TEST_ERROR;
        if (HDunsetenv(HDF5_VOL_CONNECTOR) < 0)
            TEST_ERROR;
        if (H5VL__reparse_def_vol_conn_variable_test() < 0)
            TEST_ERROR;
    }

    /* Test query w/default VOL, which should indicate no async, since native connector
     * doesn't support async.
     */
    if (H5Pget_vol_cap_flags(fapl_id, &vol_cap_flags_g) < 0)
        FAIL_STACK_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) > 0)
        TEST_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_NATIVE_FILES) == 0)
        TEST_ERROR;

    /* Close FAPL */
    if (H5Pclose(fapl_id) < 0)
        FAIL_STACK_ERROR;

    /* Register a fake VOL connector that sets the async capability flag */
    if ((vol_id = H5VLregister_connector(&fake_async_vol_g, H5P_DEFAULT)) < 0)
        FAIL_STACK_ERROR;

    /* Set environment variable to use 'fake async' connector & re-init default connector */
    if (HDsetenv(HDF5_VOL_CONNECTOR, "fake_async", true) < 0)
        TEST_ERROR;
    if (H5VL__reparse_def_vol_conn_variable_test() < 0)
        TEST_ERROR;

    /* Retrieve the file access property again */
    fapl_id = h5_fileaccess();

    /* Test query w/fake async VOL, which should succeed */
    vol_cap_flags_g = H5VL_CAP_FLAG_NONE;
    if (H5Pget_vol_cap_flags(fapl_id, &vol_cap_flags_g) < 0)
        FAIL_STACK_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) == 0)
        TEST_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_NATIVE_FILES) > 0)
        TEST_ERROR;

    /* Reset environment variable & re-init default connector */
    if (HDunsetenv(HDF5_VOL_CONNECTOR) < 0)
        TEST_ERROR;
    if (H5VL__reparse_def_vol_conn_variable_test() < 0)
        TEST_ERROR;

    /* Close FAPL */
    if (H5Pclose(fapl_id) < 0)
        FAIL_STACK_ERROR;

    /* Retrieve the file access property again */
    fapl_id = h5_fileaccess();

    /* Set the VOL connector for the FAPL to the fake async connector */
    if (H5Pset_vol(fapl_id, vol_id, NULL) < 0)
        FAIL_STACK_ERROR;

    /* Test query w/fake async VOL, which should succeed */
    vol_cap_flags_g = H5VL_CAP_FLAG_NONE;
    if (H5Pget_vol_cap_flags(fapl_id, &vol_cap_flags_g) < 0)
        FAIL_STACK_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) == 0)
        TEST_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_NATIVE_FILES) > 0)
        TEST_ERROR;

    /* Stack the [internal] passthrough VOL connector on top of the fake async connector */
    passthru_info.under_vol_id   = vol_id;
    passthru_info.under_vol_info = NULL;
    if (H5Pset_vol(fapl_id, H5VL_PASSTHRU, &passthru_info) < 0)
        FAIL_STACK_ERROR;

    /* Test query w/passthru -> fake async VOL, which should succeed */
    vol_cap_flags_g = H5VL_CAP_FLAG_NONE;
    if (H5Pget_vol_cap_flags(fapl_id, &vol_cap_flags_g) < 0)
        FAIL_STACK_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_ASYNC) == 0)
        TEST_ERROR;
    if ((vol_cap_flags_g & H5VL_CAP_FLAG_NATIVE_FILES) > 0)
        TEST_ERROR;

    /* Unregister the fake async VOL ID */
    if (H5VLunregister_connector(vol_id) < 0)
        TEST_ERROR;

    /* Close FAPL */
    if (H5Pclose(fapl_id) < 0)
        FAIL_STACK_ERROR;

    /* Restore environment variable, if there was one */
    if (conn_env_str) {
        if (HDsetenv(HDF5_VOL_CONNECTOR, conn_env_str, true) < 0)
            TEST_ERROR;
        free(conn_env_str);

        if (H5VL__reparse_def_vol_conn_variable_test() < 0)
            TEST_ERROR;
    }

    PASSED();

    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Pclose(fapl_id);
        H5VLunregister_connector(vol_id);
    }
    H5E_END_TRY
    free(conn_env_str);

    return FAIL;
} /* end test_async_vol_props() */

/*-------------------------------------------------------------------------
 * Function:    test_vol_cap_flags()
 *
 * Purpose:     Tests the VOL cap flags
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_vol_cap_flags(void)
{
    hid_t                    fapl_id = H5I_INVALID_HID;
    hid_t                    vol_id  = H5I_INVALID_HID;
    char                    *vol_env = NULL;
    H5VL_pass_through_info_t passthru_info;

    TESTING("VOL capability flags");

    vol_cap_flags_g = H5VL_CAP_FLAG_NONE;

    /* Register a fake VOL */
    if ((vol_id = H5VLregister_connector(&fake_vol_g, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if (H5Pset_vol(fapl_id, vol_id, NULL) < 0)
        TEST_ERROR;

    /* Verify the correctness of the VOL capacity flags */
    if (H5Pget_vol_cap_flags(fapl_id, &vol_cap_flags_g) < 0)
        TEST_ERROR;

    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC))
        TEST_ERROR;

    if (vol_cap_flags_g & H5VL_CAP_FLAG_ATTR_BASIC)
        TEST_ERROR;

    /* If using the native VOL by default, check flags again with H5P_DEFAULT */
    vol_env = getenv(HDF5_VOL_CONNECTOR);
    if (!vol_env || (0 == strcmp(vol_env, "native"))) {
        H5VL_class_t *cls;
        hid_t         connector_id;

        if (H5Pget_vol_id(H5P_DEFAULT, &connector_id) < 0)
            TEST_ERROR;
        if (NULL == (cls = H5I_object(connector_id)))
            TEST_ERROR;

        vol_cap_flags_g = H5VL_CAP_FLAG_NONE;

        if (H5Pget_vol_cap_flags(H5P_DEFAULT, &vol_cap_flags_g) < 0)
            TEST_ERROR;

        if (vol_cap_flags_g != cls->cap_flags)
            TEST_ERROR;

        if (H5VLclose(connector_id) < 0)
            TEST_ERROR;
    }

    /* Stack the [internal] passthrough VOL connector on top of the fake connector */
    passthru_info.under_vol_id   = vol_id;
    passthru_info.under_vol_info = NULL;

    if (H5Pset_vol(fapl_id, H5VL_PASSTHRU, &passthru_info) < 0)
        FAIL_STACK_ERROR;

    /* Verify the correctness of the VOL capacity flags */
    vol_cap_flags_g = H5VL_CAP_FLAG_NONE;

    if (H5Pget_vol_cap_flags(fapl_id, &vol_cap_flags_g) < 0)
        TEST_ERROR;

    if (!(vol_cap_flags_g & H5VL_CAP_FLAG_FILE_BASIC))
        TEST_ERROR;

    if (vol_cap_flags_g & H5VL_CAP_FLAG_ATTR_BASIC)
        TEST_ERROR;

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    /* Unregister the fake VOL ID */
    if (H5VLunregister_connector(vol_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5VLunregister_connector(vol_id);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return FAIL;
} /* end test_vol_cap_flags() */

/*-------------------------------------------------------------------------
 * Function:    test_get_vol_name()
 *
 * Purpose:     Tests getting VOL name using H5VLget_connector_name
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_get_vol_name(void)
{
    hid_t       fapl_id = H5I_INVALID_HID;
    hid_t       file_id = H5I_INVALID_HID;
    char        filename[NAME_LEN];
    char        vol_name[NAME_LEN];
    const char *conn_env_str = NULL;

    TESTING("getting connector name");

    conn_env_str = getenv(HDF5_VOL_CONNECTOR);
    if (NULL == (conn_env_str = getenv("HDF5_VOL_CONNECTOR")))
        conn_env_str = "native";

    /* Skip the connectors other than the native and pass_through connector */
    if (strcmp(conn_env_str, "native") && strcmp(conn_env_str, "pass_through")) {
        SKIPPED();
        printf("    only test the native or internal pass_through connector\n");
        return SUCCEED;
    }

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();

    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((file_id = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;

    if (H5VLget_connector_name(file_id, vol_name, NAME_LEN) < 0)
        TEST_ERROR;

    /* When comparing the pass_through connector, ignore the rest information (under_vol=0;under_info={}) */
    if ((!strcmp(conn_env_str, "native") && strcmp(vol_name, "native")) ||
        (!strcmp(conn_env_str, "pass_through") && strcmp(vol_name, "pass_through")))
        TEST_ERROR;

    if (H5Fclose(file_id) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();

    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Fclose(file_id);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return FAIL;
} /* end test_get_vol_name() */

/*-------------------------------------------------------------------------
 * Function:    test_wrap_register()
 *
 * Purpose:     Tests the failure of calling H5VLwrap_register in application
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_wrap_register(void)
{
    hid_t fapl_id  = H5I_INVALID_HID;
    hid_t file_id  = H5I_INVALID_HID;
    hid_t group_id = H5I_INVALID_HID;
    hid_t wrap_id  = H5I_INVALID_HID;
    char  filename[NAME_LEN];
    void *vol_obj;

    TESTING("failure of calling H5VLwrap_register");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();

    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((file_id = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;

    if ((group_id = H5Gcreate2(file_id, "test", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Retrieve the VOL object for the group */
    if (!(vol_obj = H5VLobject(group_id)))
        TEST_ERROR;

    /* Try to register a second ID for the group.  It should fail because this routine is mainly
     * targeted toward wrapping objects for iteration routine callbacks, not for application use
     */
    H5E_BEGIN_TRY
    {
        wrap_id = H5VLwrap_register(vol_obj, H5I_GROUP);
    }
    H5E_END_TRY

    if (H5I_INVALID_HID != wrap_id)
        FAIL_PUTS_ERROR("should not be able to call H5VLwrap_register in an application");

    if (H5Gclose(group_id) < 0)
        TEST_ERROR;

    if (H5Fclose(file_id) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();

    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Gclose(group_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return FAIL;
} /* end test_wrap_register() */

/*-------------------------------------------------------------------------
 * Function:    test_info_to_str()
 *
 * Purpose:     Tests the conversion between a VOL info and a string
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_info_to_str(void)
{
    hid_t fapl_id  = H5I_INVALID_HID;
    hid_t vol_id   = H5I_INVALID_HID;
    int   info     = INT_MAX;
    char *ret_str  = NULL;
    int  *ret_info = NULL;

    TESTING("conversion between a VOL info and a string");

    /* Register a fake VOL */
    if ((vol_id = H5VLregister_connector(&fake_vol_g, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if (H5Pset_vol(fapl_id, vol_id, NULL) < 0)
        TEST_ERROR;

    /* Serialize the VOL info into a string */
    if (H5VLconnector_info_to_str(&info, vol_id, &ret_str) < 0)
        TEST_ERROR;

    /* Parse the string and construct it into a VOL info */
    if (H5VLconnector_str_to_info(ret_str, vol_id, (void **)(&ret_info)) < 0)
        TEST_ERROR;

    if (*ret_info != info)
        FAIL_PUTS_ERROR("the returned VOL info doesn't match the original info");

    /* Free the VOL info being returned */
    if (H5VLfree_connector_info(vol_id, ret_info) < 0)
        TEST_ERROR;

    /* Free the string being returned */
    if (ret_str)
        free(ret_str);

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    /* Unregister the fake VOL ID */
    if (H5VLunregister_connector(vol_id) < 0)
        TEST_ERROR;

    PASSED();

    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5VLunregister_connector(vol_id);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return FAIL;
} /* end test_info_to_str() */

/*-------------------------------------------------------------------------
 * Function:    test_query_optional
 *
 * Purpose:     Tests the bug fix (HDFFV-11208) that a committed datatype
 *              triggered an assertion failure in H5VLquery_optional
 *
 * Return:      SUCCEED/FAIL
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_query_optional(void)
{
    hid_t    fapl_id  = H5I_INVALID_HID;
    hid_t    file_id  = H5I_INVALID_HID;
    hid_t    group_id = H5I_INVALID_HID;
    hid_t    dtype_id = H5I_INVALID_HID;
    char     filename[NAME_LEN];
    uint64_t supported = 0;

    TESTING("H5VLquery_optional");

    /* Retrieve the file access property for testing */
    fapl_id = h5_fileaccess();

    h5_fixname(FILENAME[0], fapl_id, filename, sizeof filename);

    if ((file_id = H5Fcreate(filename, H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id)) < 0)
        TEST_ERROR;

    if ((group_id = H5Gcreate2(file_id, "test_group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Test H5VLquery_optional with a group */
    if (H5VLquery_optional(group_id, H5VL_SUBCLS_OBJECT, H5VL_NATIVE_OBJECT_GET_COMMENT, &supported) < 0)
        TEST_ERROR;

    if ((dtype_id = H5Tcopy(H5T_NATIVE_INT)) < 0)
        TEST_ERROR;

    /* Commit the datatype into the file */
    if (H5Tcommit2(file_id, "test_dtype", dtype_id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT) < 0)
        TEST_ERROR;

    /* Test H5VLquery_optional with a committed datatype where the assertion failure happened in the past */
    if (H5VLquery_optional(dtype_id, H5VL_SUBCLS_OBJECT, H5VL_NATIVE_OBJECT_GET_COMMENT, &supported) < 0)
        TEST_ERROR;

    if (H5Gclose(group_id) < 0)
        TEST_ERROR;

    if (H5Tclose(dtype_id) < 0)
        TEST_ERROR;

    if (H5Fclose(file_id) < 0)
        TEST_ERROR;

    h5_delete_test_file(FILENAME[0], fapl_id);

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    PASSED();

    return SUCCEED;

error:
    H5E_BEGIN_TRY
    {
        H5Gclose(group_id);
        H5Tclose(dtype_id);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return FAIL;
} /* end test_query_optional() */

/*-------------------------------------------------------------------------
 * Function:    main
 *
 * Purpose:     Tests the virtual object layer interface (H5VL)
 *
 * Return:      EXIT_SUCCESS/EXIT_FAILURE
 *
 *-------------------------------------------------------------------------
 */
int
main(void)
{
    const char *env_h5_drvr; /* File driver value from environment */
    int         nerrors = 0;

    /* Get the VFD to use */
    env_h5_drvr = getenv(HDF5_DRIVER);
    if (env_h5_drvr == NULL)
        env_h5_drvr = "nomatch";

    h5_reset();

    puts("Testing basic Virtual Object Layer (VOL) functionality.");

    nerrors += test_vol_registration() < 0 ? 1 : 0;
    nerrors += test_register_opt_operation() < 0 ? 1 : 0;
    nerrors += test_native_vol_init() < 0 ? 1 : 0;
    nerrors += test_basic_file_operation(env_h5_drvr) < 0 ? 1 : 0;
    nerrors += test_basic_group_operation() < 0 ? 1 : 0;
    nerrors += test_basic_dataset_operation() < 0 ? 1 : 0;
    nerrors += test_basic_attribute_operation() < 0 ? 1 : 0;
    nerrors += test_basic_object_operation() < 0 ? 1 : 0;
    nerrors += test_basic_link_operation() < 0 ? 1 : 0;
    nerrors += test_basic_datatype_operation() < 0 ? 1 : 0;
    nerrors += test_async_vol_props() < 0 ? 1 : 0;
    nerrors += test_vol_cap_flags() < 0 ? 1 : 0;
    nerrors += test_get_vol_name() < 0 ? 1 : 0;
    nerrors += test_wrap_register() < 0 ? 1 : 0;
    nerrors += test_info_to_str() < 0 ? 1 : 0;
    nerrors += test_query_optional() < 0 ? 1 : 0;

    if (nerrors) {
        printf("***** %d Virtual Object Layer TEST%s FAILED! *****\n", nerrors, nerrors > 1 ? "S" : "");
        exit(EXIT_FAILURE);
    }

    puts("All Virtual Object Layer (VOL) tests passed.");

    exit(EXIT_SUCCESS);

} /* end main() */