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

/*
 * Read-Only HDFS Virtual File Driver (VFD)
 *
 * Purpose:
 *
 *     Verify behavior for Read-Only HDFS VFD.
 *
 *     Demonstrates basic use cases and fapl interaction.
 *
 */

#include "h5test.h"   /* testing utilities */
#include "H5FDhdfs.h" /* this file driver's utilities */

#ifdef H5_HAVE_LIBHDFS
#define HDFS_TEST_DEBUG        0
#define HDFS_TEST_MAX_BUF_SIZE 256

/*****************************************************************************
 *
 * FILE-LOCAL TESTING MACROS
 *
 * Purpose:
 *
 *     1) Upon test failure, goto-jump to single-location teardown in test
 *        function. E.g., `error:` (consistency with HDF corpus) or
 *        `failed:` (reflects purpose).
 *            >>> using "error", in part because `H5E_BEGIN_TRY` expects it.
 *     2) Increase clarity and reduce overhead found with `TEST_ERROR`.
 *        e.g., "if(somefunction(arg, arg2) < 0) TEST_ERROR:"
 *        requires reading of entire line to know whether this if/call is
 *        part of the test setup, test operation, or a test unto itself.
 *     3) Provide testing macros with optional user-supplied failure message;
 *        if not supplied (NULL), generate comparison output in the spirit of
 *        test-driven development. E.g., "expected 5 but was -3"
 *        User messages clarify test's purpose in code, encouraging description
 *        without relying on comments.
 *     4) Configurable expected-actual order in generated comparison strings.
 *        Some prefer `VERIFY(expected, actual)`, others
 *        `VERIFY(actual, expected)`. Provide preprocessor ifdef switch
 *        to satisfy both parties, assuming one paradigm per test file.
 *        (One could #undef and redefine the flag through the file as desired,
 *         but _why_.)
 *
 *     Provided as courtesy, per consideration for inclusion in the library
 *     proper.
 *
 *     Macros:
 *
 *         JSVERIFY_EXP_ACT - ifdef flag, configures comparison order
 *         FAIL_IF()        - check condition
 *         FAIL_UNLESS()    - check _not_ condition
 *         JSVERIFY()       - long-int equality check; prints reason/comparison
 *         JSVERIFY_NOT()   - long-int inequality check; prints
 *         JSVERIFY_STR()   - string equality check; prints
 *
 *****************************************************************************/

/*----------------------------------------------------------------------------
 *
 * ifdef flag: JSVERIFY_EXP_ACT
 *
 * JSVERIFY macros accept arguments as (EXPECTED, ACTUAL[, reason])
 *   default, if this is undefined, is (ACTUAL, EXPECTED[, reason])
 *
 *----------------------------------------------------------------------------
 */
#define JSVERIFY_EXP_ACT 1L

/*----------------------------------------------------------------------------
 *
 * Macro: JSFAILED_AT()
 *
 * Purpose:
 *
 *     Preface a test failure by printing "*FAILED*" and location to stdout
 *     Similar to `H5_FAILED(); AT();` from h5test.h
 *
 *     *FAILED* at somefile.c:12 in function_name()...
 *
 *----------------------------------------------------------------------------
 */
#define JSFAILED_AT()                                                                                        \
    {                                                                                                        \
        printf("*FAILED* at %s:%d in %s()...\n", __FILE__, __LINE__, __func__);                              \
    }

/*----------------------------------------------------------------------------
 *
 * Macro: FAIL_IF()
 *
 * Purpose:
 *
 *     Make tests more accessible and less cluttered than
 *         `if (thing == otherthing()) TEST_ERROR`
 *         paradigm.
 *
 *     The following lines are roughly equivalent:
 *
 *         `if (myfunc() < 0) TEST_ERROR;` (as seen elsewhere in HDF tests)
 *         `FAIL_IF(myfunc() < 0)`
 *
 *     Prints a generic "FAILED AT" line to stdout and jumps to `error`,
 *     similar to `TEST_ERROR` in h5test.h
 *
 *----------------------------------------------------------------------------
 */
#define FAIL_IF(condition)                                                                                   \
    if (condition) {                                                                                         \
        JSFAILED_AT()                                                                                        \
        goto error;                                                                                          \
    }

/*----------------------------------------------------------------------------
 *
 * Macro: FAIL_UNLESS()
 *
 * Purpose:
 *
 *     TEST_ERROR wrapper to reduce cognitive overhead from "negative tests",
 *     e.g., "a != b".
 *
 *     Opposite of FAIL_IF; fails if the given condition is _not_ true.
 *
 *     `FAIL_IF( 5 != my_op() )`
 *     is equivalent to
 *     `FAIL_UNLESS( 5 == my_op() )`
 *     However, `JSVERIFY(5, my_op(), "bad return")` may be even clearer.
 *         (see JSVERIFY)
 *
 *----------------------------------------------------------------------------
 */
#if 0 /* UNUSED */
#define FAIL_UNLESS(condition)                                                                               \
    if (!(condition)) {                                                                                      \
        JSFAILED_AT()                                                                                        \
        goto error;                                                                                          \
    }
#endif /* UNUSED */

/*----------------------------------------------------------------------------
 *
 * Macro: JSERR_LONG()
 *
 * Purpose:
 *
 *     Print an failure message for long-int arguments.
 *     ERROR-AT printed first.
 *     If `reason` is given, it is printed on own line and newlined after
 *     else, prints "expected/actual" aligned on own lines.
 *
 *     *FAILED* at myfile.c:488 in somefunc()...
 *     forest must be made of trees.
 *
 *     or
 *
 *     *FAILED* at myfile.c:488 in somefunc()...
 *       ! Expected 425
 *       ! Actual   3
 *
 *----------------------------------------------------------------------------
 */
#define JSERR_LONG(expected, actual, reason)                                                                 \
    {                                                                                                        \
        JSFAILED_AT()                                                                                        \
        if (reason != NULL) {                                                                                \
            printf("%s\n", (reason));                                                                        \
        }                                                                                                    \
        else {                                                                                               \
            printf("  ! Expected %ld\n  ! Actual   %ld\n", (long)(expected), (long)(actual));                \
        }                                                                                                    \
    }

/*----------------------------------------------------------------------------
 *
 * Macro: JSERR_STR()
 *
 * Purpose:
 *
 *     Print an failure message for string arguments.
 *     ERROR-AT printed first.
 *     If `reason` is given, it is printed on own line and newlined after
 *     else, prints "expected/actual" aligned on own lines.
 *
 *     *FAILED*  at myfile.c:421 in myfunc()...
 *     Blue and Red strings don't match!
 *
 *     or
 *
 *     *FAILED*  at myfile.c:421 in myfunc()...
 *     !!! Expected:
 *     this is my expected
 *     string
 *     !!! Actual:
 *     not what I expected at all
 *
 *----------------------------------------------------------------------------
 */
#define JSERR_STR(expected, actual, reason)                                                                  \
    {                                                                                                        \
        const char *_reason = reason;                                                                        \
        JSFAILED_AT()                                                                                        \
        if (_reason != NULL) {                                                                               \
            printf("%s\n", _reason);                                                                         \
        }                                                                                                    \
        else {                                                                                               \
            printf("!!! Expected:\n%s\n!!!Actual:\n%s\n", (expected), (actual));                             \
        }                                                                                                    \
    }

#ifdef JSVERIFY_EXP_ACT

/*----------------------------------------------------------------------------
 *
 * Macro: JSVERIFY()
 *
 * Purpose:
 *
 *     Verify that two long integers are equal.
 *     If unequal, print failure message
 *     (with `reason`, if not NULL; expected/actual if NULL)
 *     and jump to `error` at end of function
 *
 *----------------------------------------------------------------------------
 */
#define JSVERIFY(expected, actual, reason)                                                                   \
    if ((long)(actual) != (long)(expected)) {                                                                \
        JSERR_LONG((expected), (actual), (reason))                                                           \
        goto error;                                                                                          \
    } /* JSVERIFY */

/*----------------------------------------------------------------------------
 *
 * Macro: JSVERIFY_NOT()
 *
 * Purpose:
 *
 *     Verify that two long integers are _not_ equal.
 *     If equal, print failure message
 *     (with `reason`, if not NULL; expected/actual if NULL)
 *     and jump to `error` at end of function
 *
 *----------------------------------------------------------------------------
 */
#define JSVERIFY_NOT(expected, actual, reason)                                                               \
    if ((long)(actual) == (long)(expected)) {                                                                \
        JSERR_LONG((expected), (actual), (reason))                                                           \
        goto error;                                                                                          \
    } /* JSVERIFY_NOT */

/*----------------------------------------------------------------------------
 *
 * Macro: JSVERIFY_STR()
 *
 * Purpose:
 *
 *     Verify that two strings are equal.
 *     If unequal, print failure message
 *     (with `reason`, if not NULL; expected/actual if NULL)
 *     and jump to `error` at end of function
 *
 *----------------------------------------------------------------------------
 */
#define JSVERIFY_STR(expected, actual, reason)                                                               \
    if (strcmp((actual), (expected)) != 0) {                                                                 \
        JSERR_STR((expected), (actual), (reason));                                                           \
        goto error;                                                                                          \
    } /* JSVERIFY_STR */

#else
/* JSVERIFY_EXP_ACT not defined
 *
 * Repeats macros above, but with actual/expected parameters reversed.
 */

/*----------------------------------------------------------------------------
 * Macro: JSVERIFY()
 * See: JSVERIFY documentation above.
 *----------------------------------------------------------------------------
 */
#define JSVERIFY(actual, expected, reason)                                                                   \
    if ((long)(actual) != (long)(expected)) {                                                                \
        JSERR_LONG((expected), (actual), (reason));                                                          \
        goto error;                                                                                          \
    } /* JSVERIFY */

/*----------------------------------------------------------------------------
 * Macro: JSVERIFY_NOT()
 * See: JSVERIFY_NOT documentation above.
 *----------------------------------------------------------------------------
 */
#define JSVERIFY_NOT(actual, expected, reason)                                                               \
    if ((long)(actual) == (long)(expected)) {                                                                \
        JSERR_LONG((expected), (actual), (reason))                                                           \
        goto error;                                                                                          \
    } /* JSVERIFY_NOT */

/*----------------------------------------------------------------------------
 * Macro: JSVERIFY_STR()
 * See: JSVERIFY_STR documentation above.
 *----------------------------------------------------------------------------
 */
#define JSVERIFY_STR(actual, expected, reason)                                                               \
    if (strcmp((actual), (expected)) != 0) {                                                                 \
        JSERR_STR((expected), (actual), (reason));                                                           \
        goto error;                                                                                          \
    } /* JSVERIFY_STR */

#endif /* ifdef/else JSVERIFY_EXP_ACT */

/********************************
 * OTHER MACROS AND DEFINITIONS *
 ********************************/

/* copied from src/hdfs.c */
#define MAXADDR (((haddr_t)1 << (8 * sizeof(HDoff_t) - 1)) - 1)

#define HDFS_NAMENODE_NAME_MAX_SIZE 128

/*******************************
 * FILE-LOCAL GLOBAL VARIABLES *
 *******************************/

static const char filename_missing[]    = "/tmp/missing.txt";
static const char filename_bard[]       = "/tmp/t8.shakespeare.txt";
static const char filename_raven[]      = "/tmp/Poe_Raven.txt";
static const char filename_example_h5[] = "/tmp/t.h5";

static H5FD_hdfs_fapl_t default_fa = {
    1,           /* fa version */
    "localhost", /* namenode name */
    0,           /* namenode port */
    "",          /* user name */
    "",          /* kerberos path */
    1024,        /* buffer size */
};
#endif /*  H5_HAVE_LIBHDFS */

/******************
 * TEST FUNCTIONS *
 ******************/

/*---------------------------------------------------------------------------
 *
 * Function: test_fapl_config_validation()
 *
 * Purpose:
 *
 *     Test data consistency of fapl configuration.
 *     Tests `H5FD_hdfs_validate_config` indirectly through `H5Pset_fapl_hdfs`.
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_fapl_config_validation(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS fapl configuration validation");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else
    /*********************
     * test-local macros *
     *********************/

    /*************************
     * test-local structures *
     *************************/

    struct testcase {
        const char      *msg;
        herr_t           expected;
        H5FD_hdfs_fapl_t config;
    };

    /************************
     * test-local variables *
     ************************/

    hid_t            fapl_id = H5I_INVALID_HID; /* file access property list ID */
    H5FD_hdfs_fapl_t config;
    H5FD_hdfs_fapl_t fa_fetch;
    herr_t           success  = SUCCEED;
    unsigned int     i        = 0;
    unsigned int     ncases   = 6;    /* should equal number of cases */
    struct testcase *case_ptr = NULL; /* dumb work-around for possible     */
                                      /* dynamic cases creation because    */
                                      /* of compiler warnings Wlarger-than */
    struct testcase cases_arr[] = {
        {
            "default config fapl",
            SUCCEED,
            {
                1,           /* version */
                "localhost", /* namenode_name */
                0,           /* namenode_port number */
                "some_user", /* user_name */
                "",          /* kerberos_ticket_cache path */
                -1,          /* stream_buffer_size */
            },
        },
        {
            "invalid version number (2)",
            FAIL,
            {
                2,           /* version */
                "localhost", /* namenode_name */
                0,           /* namenode_port number */
                "some_user", /* user_name */
                "",          /* kerberos_ticket_cache path */
                -1,          /* stream_buffer_size */
            },
        },
        {
            "invalid version number (0)",
            FAIL,
            {
                0,           /* version */
                "localhost", /* namenode_name */
                0,           /* namenode_port number */
                "some_user", /* user_name */
                "",          /* kerberos_ticket_cache path */
                -1,          /* stream_buffer_size */
            },
        },
        {
            "nonsense kerberos path still ok?",
            SUCCEED,
            {
                1,                 /* version */
                "localhost",       /* namenode_name */
                0,                 /* namenode_port number */
                "some_user",       /* user_name */
                "pathToSomewhere", /* kerberos_ticket_cache path */
                -1,                /* stream_buffer_size */
            },
        },
        {
            "namenode port number too high",
            FAIL,
            {
                1,           /* version */
                "localhost", /* namenode_name */
                88000,       /* namenode_port number */
                "some_user", /* user_name */
                "",          /* kerberos_ticket_cache path */
                -1,          /* stream_buffer_size */
            },
        },
        {
            "negative namenode port number",
            FAIL,
            {
                1,           /* version */
                "localhost", /* namenode_name */
                -1,          /* namenode_port number */
                "some_user", /* user_name */
                "",          /* kerberos_ticket_cache path */
                -1,          /* stream_buffer_size */
            },
        },
    };

    TESTING("HDFS fapl configuration validation");

    /*********
     * TESTS *
     *********/

    for (i = 0; i < ncases; i++) {

        /*---------------
         * per-test setup
         *---------------
         */
        case_ptr = &cases_arr[i];
        fapl_id  = H5Pcreate(H5P_FILE_ACCESS);
        FAIL_IF(fapl_id < 0) /* sanity-check */

        /*-----------------------------------
         * Actually test -- set fapl.
         * Mute stack trace in failure cases.
         *-----------------------------------
         */
        H5E_BEGIN_TRY
        {
            /* `H5FD_hdfs_validate_config(...)` is static/private
             * to src/hdfs.c and cannot (and should not?) be tested directly?
             * Instead, validate config through public api.
             */
            success = H5Pset_fapl_hdfs(fapl_id, &case_ptr->config);
        }
        H5E_END_TRY

        JSVERIFY(case_ptr->expected, success, case_ptr->msg)

        /* Make sure we can get back what we put in.
         * Only valid if the fapl configuration does not result in error.
         */
        if (success == SUCCEED) {
            config = case_ptr->config;
            JSVERIFY(SUCCEED, H5Pget_fapl_hdfs(fapl_id, &fa_fetch), "unable to get fapl")
            JSVERIFY(H5FD__CURR_HDFS_FAPL_T_VERSION, fa_fetch.version, "invalid version number")
            JSVERIFY(config.version, fa_fetch.version, "version number mismatch")
            JSVERIFY(config.namenode_port, fa_fetch.namenode_port, "namenode port mismatch")
            JSVERIFY(config.stream_buffer_size, fa_fetch.stream_buffer_size, "streambuffer size mismatch")
            JSVERIFY_STR(config.namenode_name, fa_fetch.namenode_name, "node name mismatch")
            JSVERIFY_STR(config.user_name, fa_fetch.user_name, "user name mismatch")
            JSVERIFY_STR(config.kerberos_ticket_cache, fa_fetch.kerberos_ticket_cache,
                         "kerberos ticket cache mismatch")
        }

        /*-----------------------------
         * per-test sanitation/teardown
         *-----------------------------
         */
        FAIL_IF(FAIL == H5Pclose(fapl_id))
        fapl_id = -1;

    } /* for each test case */

    PASSED();
    return 0;

error:
    /***********
     * CLEANUP *
     ***********/

    if (fapl_id < 0) {
        H5E_BEGIN_TRY
        {
            (void)H5Pclose(fapl_id);
        }
        H5E_END_TRY
    }
    return 1;
#endif /* H5_HAVE_LIBHDFS */

} /* end test_fapl_config_validation() */

/*-------------------------------------------------------------------------
 *
 * Function:    test_hdfs_fapl()
 *
 * Purpose:     Tests the file handle interface for the HDFS driver.
 *
 *              For now, test only fapl & flags.  Extend as the
 *              work on the VFD continues.
 *
 * Return:      Success:        0
 *              Failure:        1
 *
 *-------------------------------------------------------------------------
 */
static int
test_hdfs_fapl(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS fapl ");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else
    /************************
     * test-local variables *
     ************************/

    hid_t fapl_id = H5I_INVALID_HID;   /* file access property list ID */
    hid_t driver_id = H5I_INVALID_HID; /* ID for this VFD              */
    unsigned long driver_flags = 0;    /* VFD feature flags            */
    H5FD_hdfs_fapl_t hdfs_fa_0 = {
        1,    /* version*/
        "",   /* node name */
        9000, /* node port */
        "",   /* username */
        "",   /* kerb cache path */
        1024, /* stream buffer size */
    };

    TESTING("HDFS fapl ");

    /* Set property list and file name for HDFS driver.
     */
    fapl_id = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(fapl_id < 0)

    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_id, &hdfs_fa_0))

    driver_id = H5Pget_driver(fapl_id);
    FAIL_IF(driver_id < 0)

    /****************
     * Check that the VFD feature flags are correct
     * SPEC MAY CHANGE
     ******************/

    FAIL_IF(H5FDdriver_query(driver_id, &driver_flags) < 0)

    JSVERIFY_NOT(0, (driver_flags & H5FD_FEAT_DATA_SIEVE),
                 "bit(s) in `driver_flags` must align with "
                 "H5FD_FEAT_DATA_SIEVE")

    JSVERIFY(H5FD_FEAT_DATA_SIEVE, driver_flags, "H5FD_FEAT_DATA_SIEVE should be the only supported flag")

    PASSED();
    return 0;

error:
    H5E_BEGIN_TRY
    {
        (void)H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return 1;
#endif /* H5_HAVE_LIBHDFS */

} /* end test_hdfs_fapl() */

/*---------------------------------------------------------------------------
 *
 * Function: test_vfd_open()
 *
 * Purpose:
 *
 *     Demonstrate/specify VFD-level "Open" failure cases
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_vfd_open(void)
{

#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS VFD-level open");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else

    /*********************
     * test-local macros *
     *********************/

/* selectors for which fapl to use in testcase */
#define FAPL_H5P_DEFAULT  -2
#define FAPL_UNCONFIGURED -3 /* H5P_FILE_ACCESS */
#define FAPL_HDFS         -4

    /*************************
     * test-local structures *
     *************************/

    struct test_condition {
        const char *message;
        const char *url;
        unsigned flags;
        int which_fapl;
        haddr_t maxaddr;
        bool might_use_other_driver;
    };

    /************************
     * test-local variables *
     ************************/

    struct test_condition failing_conditions[] = {
        {
            "default property list (H5P_DEFAULT) is invalid",
            filename_bard,
            H5F_ACC_RDONLY,
            FAPL_H5P_DEFAULT,
            MAXADDR,
            true,
        },
        {
            "generic file access property list is invalid",
            filename_bard,
            H5F_ACC_RDONLY,
            FAPL_UNCONFIGURED,
            MAXADDR,
            true,
        },
        {
            "filename cannot be null",
            NULL,
            H5F_ACC_RDONLY,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "filename cannot be empty",
            "",
            H5F_ACC_RDONLY,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "file at filename must exist",
            filename_missing,
            H5F_ACC_RDONLY,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "read-write flag not supported",
            filename_bard,
            H5F_ACC_RDWR,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "truncate flag not supported",
            filename_bard,
            H5F_ACC_TRUNC,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "create flag not supported",
            filename_bard,
            H5F_ACC_CREAT,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "EXCL flag not supported",
            filename_bard,
            H5F_ACC_EXCL,
            FAPL_HDFS,
            MAXADDR,
            false,
        },
        {
            "maxaddr cannot be 0 (caught in `H5FD_open()`)",
            filename_bard,
            H5F_ACC_RDONLY,
            FAPL_HDFS,
            0,
            false,
        },
    };
    unsigned i = 0;
    unsigned failing_conditions_count = 10;
    H5FD_t *fd = NULL;
    hid_t fapl_hdfs = H5I_INVALID_HID;
    hid_t fapl_unconfigured = H5I_INVALID_HID;

    TESTING("HDFS VFD-level open");

    fapl_unconfigured = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(fapl_unconfigured < 0)

    fapl_hdfs = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(fapl_hdfs < 0)
    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_hdfs, &default_fa))

    /*********
     * TESTS *
     *********/

    /* all the test cases that will _not_ open
     */
    for (i = 0; i < failing_conditions_count; i++) {
        struct test_condition T = failing_conditions[i];
        hid_t fapl_id = H5P_DEFAULT;

        fd = NULL;

        if (T.which_fapl == FAPL_UNCONFIGURED) {
            fapl_id = fapl_unconfigured;
        }
        else if (T.which_fapl == FAPL_HDFS) {
            fapl_id = fapl_hdfs;
        }

#if HDFS_TEST_DEBUG
        fprintf(stderr, "testing: %s\n", T.message);
#endif /* HDFS_TEST_DEBUG */

        H5E_BEGIN_TRY
        {
            fd = H5FDopen(T.url, T.flags, fapl_id, T.maxaddr);
        }
        H5E_END_TRY
        if (NULL != fd) {
            if (true == T.might_use_other_driver && H5FD_HDFS != fd->driver_id) {
                fprintf(stderr, "\n!!!!! WARNING !!!!!\n"
                                "    Successful open of file on local system "
                                "with non-HDFS VFD.\n");
                JSVERIFY(SUCCEED, H5FDclose(fd), "unable to close errant open");
                fd = NULL;
            }
            else {
                JSVERIFY(1, 0, T.message); /* print message and fail */
            }
        }
    }

    FAIL_IF(NULL != fd) /* sanity check */

#if HDFS_TEST_DEBUG
    fprintf(stderr, "nominal open\n");
#endif /* HDFS_TEST_DEBUG */

    /* finally, show that a file can be opened
     */
    fd = H5FDopen(filename_bard, H5F_ACC_RDONLY, fapl_hdfs, MAXADDR);
    FAIL_IF(NULL == fd)

    /************
     * TEARDOWN *
     ************/

#if HDFS_TEST_DEBUG
    fprintf(stderr, "teardown...\n");
#endif /* HDFS_TEST_DEBUG */

    FAIL_IF(FAIL == H5FDclose(fd))
    fd = NULL;

    FAIL_IF(FAIL == H5Pclose(fapl_hdfs))
    fapl_hdfs = -1;

    FAIL_IF(FAIL == H5Pclose(fapl_unconfigured))
    fapl_unconfigured = -1;

    PASSED();
    return 0;

error:

    /***********
     * CLEANUP *
     ***********/

    if (fd) {
        (void)H5FDclose(fd);
    }
    H5E_BEGIN_TRY
    {
        if (fapl_hdfs >= 0) {
            (void)H5Pclose(fapl_hdfs);
        }
        if (fapl_unconfigured >= 0) {
            (void)H5Pclose(fapl_unconfigured);
        }
    }
    H5E_END_TRY

    return 1;

#undef FAPL_H5P_DEFAULT
#undef FAPL_UNCONFIGURED
#undef FAPL_HDFS

#endif /* H5_HAVE_LIBHDFS */

} /* end test_vfd_open() */

/*---------------------------------------------------------------------------
 *
 * Function: test_eof_eoa()
 *
 * Purpose:
 *
 *     Demonstrate behavior of get_eof, get_eoa, and set_eoa.
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_eof_eoa(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS eof/eoa gets and sets");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else

    /*********************
     * test-local macros *
     *********************/

    /*************************
     * test-local structures *
     *************************/

    /************************
     * test-local variables *
     ************************/

    H5FD_t *fd_shakespeare = NULL;
    hid_t fapl_id = H5I_INVALID_HID;

    TESTING("HDFS eof/eoa gets and sets");

    /*********
     * SETUP *
     *********/

    fapl_id = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(0 > fapl_id)
    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_id, &default_fa))

    fd_shakespeare = H5FDopen(filename_bard, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    FAIL_IF(NULL == fd_shakespeare)

    /*********
     * TESTS *
     *********/

    /* verify as found
     */
    JSVERIFY(5458199, H5FDget_eof(fd_shakespeare, H5FD_MEM_DEFAULT), "EOF mismatch")
    JSVERIFY(H5FDget_eof(fd_shakespeare, H5FD_MEM_DEFAULT), H5FDget_eof(fd_shakespeare, H5FD_MEM_DRAW),
             "mismatch between DEFAULT and RAW memory types")
    JSVERIFY(0, H5FDget_eoa(fd_shakespeare, H5FD_MEM_DEFAULT), "EoA should be unset by H5FDopen")

    /* set EoA below EoF
     */
    JSVERIFY(SUCCEED, H5FDset_eoa(fd_shakespeare, H5FD_MEM_DEFAULT, 44442202), "unable to set EoA (lower)")
    JSVERIFY(5458199, H5FDget_eof(fd_shakespeare, H5FD_MEM_DEFAULT), "EoF changed")
    JSVERIFY(44442202, H5FDget_eoa(fd_shakespeare, H5FD_MEM_DEFAULT), "EoA unchanged")

    /* set EoA above EoF
     */
    JSVERIFY(SUCCEED, H5FDset_eoa(fd_shakespeare, H5FD_MEM_DEFAULT, 6789012), "unable to set EoA (higher)")
    JSVERIFY(5458199, H5FDget_eof(fd_shakespeare, H5FD_MEM_DEFAULT), "EoF changed")
    JSVERIFY(6789012, H5FDget_eoa(fd_shakespeare, H5FD_MEM_DEFAULT), "EoA unchanged")

    /************
     * TEARDOWN *
     ************/

    FAIL_IF(FAIL == H5FDclose(fd_shakespeare))
    fd_shakespeare = NULL;

    FAIL_IF(FAIL == H5Pclose(fapl_id))
    fapl_id = -1;

    PASSED();
    return 0;

error:

    /***********
     * CLEANUP *
     ***********/

    if (fd_shakespeare != NULL) {
        (void)H5FDclose(fd_shakespeare);
    }
    if (fapl_id >= 0) {
        H5E_BEGIN_TRY
        {
            (void)H5Pclose(fapl_id);
        }
        H5E_END_TRY
    }

    return 1;

#endif /* H5_HAVE_LIBHDFS */

} /* end test_eof_eoa() */

/*-----------------------------------------------------------------------------
 *
 * Function: test_H5FDread_without_eoa_set_fails()
 *
 * Purpose:
 *
 *     Demonstrate a not-obvious constraint by the library, preventing
 *     file read before EoA is set
 *
 *-----------------------------------------------------------------------------
 */
static int
test_H5FDread_without_eoa_set_fails(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS VFD read-eoa temporal coupling library limitation");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else

    char buffer[HDFS_TEST_MAX_BUF_SIZE];
    unsigned int i = 0;
    H5FD_t *file_shakespeare = NULL;
    hid_t fapl_id = H5I_INVALID_HID;

    TESTING("HDFS VFD read-eoa temporal coupling library limitation");

    /*********
     * SETUP *
     *********/

    /* create HDFS fapl
     */
    fapl_id = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(fapl_id < 0)
    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_id, &default_fa))

    file_shakespeare = H5FDopen(filename_bard, H5F_ACC_RDONLY, fapl_id, MAXADDR);
    FAIL_IF(NULL == file_shakespeare)

    JSVERIFY(0, H5FDget_eoa(file_shakespeare, H5FD_MEM_DEFAULT), "EoA should remain unset by H5FDopen")

    /* zero buffer contents */
    for (i = 0; i < HDFS_TEST_MAX_BUF_SIZE; i++) {
        buffer[i] = 0;
    }

    /********
     * TEST *
     ********/

    H5E_BEGIN_TRY{/* mute stack trace on expected failure */
                  JSVERIFY(FAIL, H5FDread(file_shakespeare, H5FD_MEM_DRAW, H5P_DEFAULT, 1200699, 102, buffer),
                           "cannot read before eoa is set")} H5E_END_TRY
    for (i = 0; i < HDFS_TEST_MAX_BUF_SIZE; i++) {
        JSVERIFY(0, (unsigned)buffer[i], "buffer was modified by write!")
    }

    /************
     * TEARDOWN *
     ************/

    FAIL_IF(FAIL == H5FDclose(file_shakespeare))
    file_shakespeare = NULL;

    FAIL_IF(FAIL == H5Pclose(fapl_id))
    fapl_id = -1;

    PASSED();
    return 0;

error:

    /***********
     * CLEANUP *
     ***********/

    if (file_shakespeare) {
        (void)H5FDclose(file_shakespeare);
    }
    if (fapl_id >= 0) {
        H5E_BEGIN_TRY
        {
            (void)H5Pclose(fapl_id);
        }
        H5E_END_TRY
    }

    return 1;

#endif /* H5_HAVE_LIBHDFS */

} /* end test_H5FDread_without_eoa_set_fails() */

/*---------------------------------------------------------------------------
 *
 * Function: test_read()
 *
 * Purpose:
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_read(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS VFD read/range-gets");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else

    /*********************
     * test-local macros *
     *********************/

    /*************************
     * test-local structures *
     *************************/
    struct testcase {
        const char *message;  /* purpose of test case */
        haddr_t eoa_set;      /* set file EOA to this prior to read */
        size_t addr;          /* offset of read in file */
        size_t len;           /* length of read in file */
        herr_t success;       /* expected return value of read function */
        const char *expected; /* expected contents of buffer; failure ignores */
    };

    /************************
     * test-local variables *
     ************************/
    struct testcase cases[] = {
        {
            "successful range-get",
            6464,
            5691,
            32, /* fancy quotes are three bytes each(?) */
            SUCCEED,
            "Quoth the Raven “Nevermore.”",
        },
        {
            "read past EOA fails (EOA < EOF < addr)",
            3000,
            4000,
            100,
            FAIL,
            NULL,
        },
        {
            "read overlapping EOA fails (EOA < addr < EOF < (addr+len))",
            3000,
            8000,
            100,
            FAIL,
            NULL,
        },
        {
            "read past EOA/EOF fails ((EOA==EOF) < addr)",
            6464,
            7000,
            100,
            FAIL,
            NULL,
        },
        {
            "read overlapping EOA/EOF fails (addr < (EOA==EOF) < (addr+len))",
            6464,
            6400,
            100,
            FAIL,
            NULL,
        },
        {
            "read between EOF and EOA fails (EOF < addr < (addr+len) < EOA)",
            8000,
            7000,
            100,
            FAIL,
            NULL,
        },
    };
    unsigned testcase_count = 6;
    unsigned test_i = 0;
    struct testcase test;
    herr_t open_return = FAIL;
    char buffer[HDFS_TEST_MAX_BUF_SIZE];
    unsigned int i = 0;
    H5FD_t *file_raven = NULL;
    hid_t fapl_id = H5I_INVALID_HID;

    TESTING("HDFS VFD read/range-gets");

    /*********
     * SETUP *
     *********/

    /* create HDFS fapl
     */
    fapl_id = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(fapl_id < 0)
    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_id, &default_fa))

    /* zero buffer contents */
    for (i = 0; i < HDFS_TEST_MAX_BUF_SIZE; i++) {
        buffer[i] = 0;
    }

    /* open file
     */
    file_raven = H5FDopen(filename_raven, H5F_ACC_RDONLY, fapl_id,
                          HADDR_UNDEF); /* Demonstrate success with "automatic" value */
    FAIL_IF(NULL == file_raven)

    JSVERIFY(6464, H5FDget_eof(file_raven, H5FD_MEM_DEFAULT), "EOF mismatch")

    /*********
     * TESTS *
     *********/

    for (test_i = 0; test_i < testcase_count; test_i++) {

        /* -------------- *
         * per-test setup *
         * -------------- */

        test = cases[test_i];
        open_return = FAIL;

        FAIL_IF(HDFS_TEST_MAX_BUF_SIZE < test.len) /* buffer too small! */

        FAIL_IF(FAIL == H5FDset_eoa(file_raven, H5FD_MEM_DEFAULT, test.eoa_set))

        /* zero buffer contents */
        for (i = 0; i < HDFS_TEST_MAX_BUF_SIZE; i++) {
            buffer[i] = 0;
        }

        /* ------------ *
         * conduct test *
         * ------------ */

        H5E_BEGIN_TRY
        {
            open_return = H5FDread(file_raven, H5FD_MEM_DRAW, H5P_DEFAULT, test.addr, test.len, buffer);
        }
        H5E_END_TRY

        JSVERIFY(test.success, open_return, test.message)

        if (open_return == SUCCEED) {
            JSVERIFY_STR(test.expected, buffer, NULL)
        }

    } /* for each testcase */

    /************
     * TEARDOWN *
     ************/

    FAIL_IF(FAIL == H5FDclose(file_raven))
    file_raven = NULL;

    FAIL_IF(FAIL == H5Pclose(fapl_id))
    fapl_id = -1;

    PASSED();
    return 0;

error:

    /***********
     * CLEANUP *
     ***********/

    if (file_raven != 0) {
        (void)H5FDclose(file_raven);
    }
    if (fapl_id >= 0) {
        H5E_BEGIN_TRY
        {
            (void)H5Pclose(fapl_id);
        }
        H5E_END_TRY
    }

    return 1;

#endif /* H5_HAVE_LIBHDFS */

} /* end test_read() */

/*---------------------------------------------------------------------------
 *
 * Function: test_noops_and_autofails()
 *
 * Purpose:
 *
 *     Demonstrate the unavailable and do-nothing routines unique to
 *     Read-Only VFD.
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_noops_and_autofails(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS VFD always-fail and no-op routines");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else

    /*********************
     * test-local macros *
     *********************/

    /*************************
     * test-local structures *
     *************************/

    /************************
     * test-local variables *
     ************************/

    hid_t fapl_id = H5I_INVALID_HID;
    H5FD_t *file = NULL;
    const char data[36] = "The Force shall be with you, always";

    TESTING("HDFS VFD always-fail and no-op routines");

    /*********
     * SETUP *
     *********/

    /* create HDFS fapl
     */
    fapl_id = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(fapl_id < 0)
    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_id, &default_fa))

    /* open file
     */
    file = H5FDopen(filename_bard, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    FAIL_IF(NULL == file)

    /*********
     * TESTS *
     *********/

    /* auto-fail calls to write and truncate
     */
    H5E_BEGIN_TRY{JSVERIFY(FAIL, H5FDwrite(file, H5FD_MEM_DRAW, H5P_DEFAULT, 1000, 35, data),
                           "write must fail")} H5E_END_TRY

    H5E_BEGIN_TRY{JSVERIFY(FAIL, H5FDtruncate(file, H5P_DEFAULT, false), "truncate must fail")} H5E_END_TRY

    H5E_BEGIN_TRY{
        JSVERIFY(FAIL, H5FDtruncate(file, H5P_DEFAULT, true), "truncate must fail (closing)")} H5E_END_TRY

    /************
     * TEARDOWN *
     ************/

    FAIL_IF(FAIL == H5FDclose(file))
    file = NULL;

    FAIL_IF(FAIL == H5Pclose(fapl_id))
    fapl_id = -1;

    PASSED();
    return 0;

error:

    /***********
     * CLEANUP *
     ***********/

    if (fapl_id >= 0) {
        H5E_BEGIN_TRY
        {
            (void)H5Pclose(fapl_id);
        }
        H5E_END_TRY
    }
    if (file != NULL) {
        (void)H5FDclose(file);
    }

    return 1;

#endif /* H5_HAVE_LIBHDFS */

} /* end test_noops_and_autofails() */

/*---------------------------------------------------------------------------
 *
 * Function: test_cmp()
 *
 * Purpose:
 *
 *     Verify "file comparison" behavior.
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_cmp(void)
{
    TESTING("HDFS cmp (comparison)");
    SKIPPED();
    fprintf(stderr, "    TODO: Distinct valid fapls to open the same file.\n");

    return 0;

} /* end test_cmp() */

/*---------------------------------------------------------------------------
 *
 * Function: test_H5F_integration()
 *
 * Purpose:
 *
 *     Demonstrate H5F (File interface) behavior with files on HDFS.
 *
 * Return:
 *
 *     PASSED : 0
 *     FAILED : 1
 *
 *---------------------------------------------------------------------------
 */
static int
test_H5F_integration(void)
{
#ifndef H5_HAVE_LIBHDFS
    TESTING("HDFS file access through HD5F library (H5F API)");
    SKIPPED();
    puts("    HDFS VFD is not enabled");
    fflush(stdout);
    return 0;

#else

    /*********************
     * test-local macros *
     *********************/

    /*************************
     * test-local structures *
     *************************/

    /************************
     * test-local variables *
     ************************/

    hid_t file = H5I_INVALID_HID;
    hid_t fapl_id = H5I_INVALID_HID;

    TESTING("HDFS file access through HD5F library (H5F API)");

    /*********
     * SETUP *
     *********/

    fapl_id = H5Pcreate(H5P_FILE_ACCESS);
    FAIL_IF(0 > fapl_id)
    FAIL_IF(FAIL == H5Pset_fapl_hdfs(fapl_id, &default_fa))

    /*********
     * TESTS *
     *********/

    /* Read-Write Open access is not allowed with this file driver.
     */
    H5E_BEGIN_TRY{FAIL_IF(0 <= H5Fopen(filename_example_h5, H5F_ACC_RDWR, fapl_id))} H5E_END_TRY

    /* H5Fcreate() is not allowed with this file driver.
     */
    H5E_BEGIN_TRY{FAIL_IF(0 <= H5Fcreate(filename_missing, H5F_ACC_RDONLY, H5P_DEFAULT, fapl_id))} H5E_END_TRY

    /* Successful open.
     */
    file = H5Fopen(filename_example_h5, H5F_ACC_RDONLY, fapl_id);
    FAIL_IF(file < 0)

    /************
     * TEARDOWN *
     ************/

    FAIL_IF(FAIL == H5Fclose(file))
    file = -1;

    FAIL_IF(FAIL == H5Pclose(fapl_id))
    fapl_id = -1;

    PASSED();
    return 0;

error:
    /***********
     * CLEANUP *
     ***********/

#if HDFS_TEST_DEBUG
    printf("\nerror!");
    fflush(stdout);
#endif /* HDFS_TEST_DEBUG */

    if (fapl_id >= 0) {
        H5E_BEGIN_TRY
        {
            (void)H5Pclose(fapl_id);
        }
        H5E_END_TRY
    }
    if (file > 0) {
        (void)H5Fclose(file);
    }

    return 1;

#endif /* H5_HAVE_LIBHDFS */

} /* test_H5F_integration */

/*-------------------------------------------------------------------------
 *
 * Function:    main
 *
 * Purpose:     Tests the basic features of Virtual File Drivers
 *
 * Return:      Success: 0
 *              Failure: 1
 *
 *-------------------------------------------------------------------------
 */
int
main(void)
{
    int nerrors = 0;

    /******************
     * commence tests *
     ******************/

#ifdef H5_HAVE_LIBHDFS
    static char hdfs_namenode_name[HDFS_NAMENODE_NAME_MAX_SIZE] = "";
    const char *hdfs_namenode_name_env                          = NULL;

    hdfs_namenode_name_env = getenv("HDFS_TEST_NAMENODE_NAME");
    if (hdfs_namenode_name_env == NULL || hdfs_namenode_name_env[0] == '\0') {
        strncpy(hdfs_namenode_name, "localhost", HDFS_NAMENODE_NAME_MAX_SIZE);
    }
    else {
        strncpy(/* TODO: error-check? */
                default_fa.namenode_name, hdfs_namenode_name_env, HDFS_NAMENODE_NAME_MAX_SIZE);
    }
#endif /* H5_HAVE_LIBHDFS */

    h5_test_init();

    printf("Testing hdfs VFD functionality.\n");

    nerrors += test_fapl_config_validation();
    nerrors += test_hdfs_fapl();
    nerrors += test_vfd_open();
    nerrors += test_eof_eoa();
    nerrors += test_H5FDread_without_eoa_set_fails();
    nerrors += test_read();
    nerrors += test_noops_and_autofails();
    nerrors += test_cmp();
    nerrors += test_H5F_integration();

    if (nerrors > 0) {
        printf("***** %d hdfs TEST%s FAILED! *****\n", nerrors, nerrors > 1 ? "S" : "");
        nerrors = 1;
    }
    else {
        printf("All hdfs tests passed.\n");
    }
    return nerrors; /* 0 if no errors, 1 if any errors */

} /* end main() */
