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

/*
 * Onion Virtual File Driver (VFD)
 *
 * Purpose:
 *
 *      Verify Onion VFD behavior that is not involved with operations on the
 *      backing store.
 */

#include "h5test.h"

#include "H5Fprivate.h"     /* encode/decode macros         */
#include "H5FDonion.h"      /* This file driver's utilities */
#include "H5FDonion_priv.h" /* Onion file driver internals  */

/* The Onion VFD uses H5MM calls internally, so any tests that allocate
 * or free memory for said internal structures (e.g., the revision lists)
 * will need to allocate memory using H5MM calls.
 */
#include "H5MMprivate.h" /* Memory management */

/* 2^n for uint64_t types -- H5_EXP2 unsafe past 32 bits */
#define U64_EXP2(n) ((uint64_t)1 << (n))

#define ONION_TEST_PAGE_SIZE_1                    4
#define ONION_TEST_PAGE_SIZE_5                    32
#define ONION_TEST_FIXNAME_SIZE                   1024
#define ONION_TEST_EXPECTED_HISTORY_REVISIONS_MAX 16
#define ONION_TEST_REV_REV_WRITES_MAX             8
#define ONE_DIM_SIZE                              1024

/* Structure to collect the onion filepaths in one place. */
struct onion_filepaths {
    char *canon;
    char *onion;
    char *recovery;
};

struct expected_revision {
    uint64_t    revision_num;
    uint64_t    parent_revision_num;
    uint64_t    logical_eof;
    uint64_t    n_index_entries;
    const char *comment;
};
struct expected_history {
    uint64_t                 page_size;
    uint64_t                 n_revisions;
    uint64_t                 origin_eof;
    struct expected_revision revisions[ONION_TEST_EXPECTED_HISTORY_REVISIONS_MAX];
};

struct write_info {
    haddr_t              offset;
    haddr_t              size;
    const unsigned char *buf;
};
struct revise_revision {
    bool              truncate; /* onion-create, truncating any existing data */
    uint64_t          revision_num;
    size_t            n_writes;
    struct write_info writes[ONION_TEST_REV_REV_WRITES_MAX];
    const char       *comment;
};

static int  compare_file_bytes_exactly(const char *filepath, hid_t fapl_id, size_t nbytes,
                                       const unsigned char *exp);
static int  do_onion_open_and_writes(const char *filename, H5FD_onion_fapl_info_t *onion_info_p, size_t n_ops,
                                     struct revise_revision *about);
static void onion_filepaths_destroy(struct onion_filepaths *paths);
static struct onion_filepaths *onion_filepaths_init(const char *basename);

/* set at runtime in main() */
static unsigned int flags_create_s = 0;

/* NOTE: b_list must be longer than a_list.
 * Sizes must match respective buffer lengths.
 */

/* twenty-six four-character words beginning with 'a' -> 104 bytes */
static const unsigned char *a_list_s =
    (const unsigned char *)"abetableacedacesacheacidacneadzeafaragedagesaidsairsajarallyalum"
                           "amokantsapesarcsareaartsasksaspsavidaxes";
uint64_t a_list_size_s = 104;

/* fifty-three four-character words beginning with 'b' -> 212 bytes */
static const unsigned char *b_list_s =
    (const unsigned char *)"badebailbaitbalebanebarebaskbeambeanbearbeenbeerbeltbentbestbide"
                           "bikebilebindbirdbiteblipblueboarboatbobsbodyboilboldbollboltbond"
                           "boneboobboorboosbootbradbragbratbraybrewbritbrowbuckbudsbunkbunt"
                           "buoyburnburybustbuys";
static uint64_t b_list_size_s = 212;

/* Allocate and populate filepaths with h5_fixname'd strings as appropriate.
 * Should be released with onion_filepaths_destroy() when done.
 */
static struct onion_filepaths *
onion_filepaths_init(const char *basename)
{
    struct onion_filepaths *paths = NULL;

    if (NULL == (paths = malloc(sizeof(struct onion_filepaths))))
        TEST_ERROR;
    paths->canon    = NULL;
    paths->onion    = NULL;
    paths->recovery = NULL;

    if (NULL == (paths->canon = malloc(sizeof(char) * ONION_TEST_FIXNAME_SIZE)))
        TEST_ERROR;
    snprintf(paths->canon, ONION_TEST_FIXNAME_SIZE, "%s", basename);

    if (NULL == (paths->onion = malloc(sizeof(char) * ONION_TEST_FIXNAME_SIZE)))
        TEST_ERROR;
    snprintf(paths->onion, ONION_TEST_FIXNAME_SIZE, "%s.onion", paths->canon);

    if (NULL == (paths->recovery = malloc(sizeof(char) * ONION_TEST_FIXNAME_SIZE)))
        TEST_ERROR;
    snprintf(paths->recovery, ONION_TEST_FIXNAME_SIZE, "%s.onion.recovery", paths->canon);

    return paths;

error:
    if (paths != NULL) {
        free(paths->canon);
        free(paths->onion);
        free(paths->recovery);
    }
    free(paths);

    return NULL;
}

/* Free onion file paths */
static void
onion_filepaths_destroy(struct onion_filepaths *paths)
{
    free(paths->canon);
    free(paths->onion);
    free(paths->recovery);
    free(paths);
}

/*-----------------------------------------------------------------------------
 * Function:    test_archival_index()
 *
 * Purpose:     Unit-test mechanisms for the onion archival index.
 *              Specifies and verifies index-validation and -search routines.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_archival_index(void)
{
    /* We can ignore each entry's physical address and checksum values */
    H5FD_onion_index_entry_t e0                   = {1, 474};
    H5FD_onion_index_entry_t e1                   = {4, 558};
    H5FD_onion_index_entry_t e2                   = {5, 306};
    H5FD_onion_index_entry_t e3                   = {9, 515};
    H5FD_onion_index_entry_t e4                   = {14, 386};
    H5FD_onion_index_entry_t e5                   = {18, 90};
    H5FD_onion_index_entry_t e6                   = {19, 94};
    H5FD_onion_index_entry_t e7                   = {20, 509};
    H5FD_onion_index_entry_t sorted[8]            = {e0, e1, e2, e3, e4, e5, e6, e7};
    H5FD_onion_index_entry_t sorted_duplicates[8] = {e0, e1, e2, e2, e4, e5, e6, e7};
    H5FD_onion_index_entry_t sorted_incomplete[8] = {e1, e3, e4, e5};
    /* Partially-sorted list also aligned to 2 * page-size */
    H5FD_onion_index_entry_t    sorted_partial[8] = {e1, e4, e5, e7, e0, e6, e2, e3}; /* 0..3 sorted */
    H5FD_onion_index_entry_t    unsorted[8]       = {e3, e1, e4, e5, e0, e6, e2, e7};
    H5FD_onion_archival_index_t aix               = {
        H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR, 1, /* page_size_log2 */
        8,      /* list must be populated and sorted through 0 .. (count-1) */
        sorted, /* list */
    };
    const H5FD_onion_index_entry_t *entry_out_p = NULL;

    TESTING("archival index");

    /*
     * Failing validity checks
     */

    /* Invalid version should fail */
    aix.version++;
    if (H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /* Invalid version should fail */
    aix.version = 0;
    if (H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;
    aix.version = H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR;

    /* NULL list should fail */
    aix.list = NULL;
    if (H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /* List not full should fail */
    aix.list = sorted_incomplete;
    if (H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /* Unsorted list should fail */
    aix.list = unsorted;
    if (H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /* List with duplicates should fail */
    aix.list = sorted_duplicates;
    if (H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /*
     * Passing validity checks
     */

    /* Sorted list should pass */
    aix.list = sorted;
    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /* Extra elements ignored (should pass) */
    aix.list      = sorted_partial;
    aix.n_entries = 4;
    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    /*
     * Archival index search routine
     */

    aix.list      = sorted;
    aix.n_entries = 8;

    /* Check that address not in array returns zero */
    if (H5FD__onion_archival_index_find(&aix, 3, &entry_out_p) != 0)
        TEST_ERROR;
    /* Pointer should remain unset */
    if (entry_out_p != NULL)
        TEST_ERROR;

    /* Address found should return 1 */
    if (H5FD__onion_archival_index_find(&aix, 4, &entry_out_p) != 1)
        TEST_ERROR;
    /* Pointer should be set */
    if (NULL == entry_out_p)
        TEST_ERROR;
    /* Incorrect address recorded */
    if (558 != entry_out_p->phys_addr)
        TEST_ERROR;

    /*
     * Test search edge cases
     */

    aix.list      = sorted_incomplete;
    aix.n_entries = 4;

    /* Address not in array should return 0 */
    if (H5FD__onion_archival_index_find(&aix, 1, &entry_out_p) != 0)
        TEST_ERROR;

    /* Address not in array should return 0 */
    if (H5FD__onion_archival_index_find(&aix, 101, &entry_out_p) != 0)
        TEST_ERROR;

    /*
     * Empty archival index
     */

    entry_out_p   = NULL;
    aix.n_entries = 0; /* actually populated list is irrelevant */
    /* Address not in array should return 0 */
    if (H5FD__onion_archival_index_find(&aix, 3, &entry_out_p) != 0)
        TEST_ERROR;
    /* Pointer should remain unset */
    if (entry_out_p != NULL)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    return -1;
} /* end test_archival_index() */

/*-----------------------------------------------------------------------------
 * Function:    test_revision_index()
 *
 * Purpose:     Test revision index functionality
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_revision_index(void)
{
    H5FD_onion_revision_index_t *rix_p = NULL;
    H5FD_onion_index_entry_t     entry = {
        42,     /* logical_page */
        111112, /* phys_addr */
    };
    const H5FD_onion_index_entry_t *entry_out_p = NULL;

    TESTING("revision index");

    /* Test index creation */

    if (NULL == (rix_p = H5FD__onion_revision_index_init(ONION_TEST_PAGE_SIZE_5)))
        TEST_ERROR;
    if (H5FD_ONION_REVISION_INDEX_VERSION_CURR != rix_p->version)
        TEST_ERROR;
    if (0 != rix_p->n_entries)
        TEST_ERROR;

    /* Test missed search */

    if (H5FD__onion_revision_index_find(rix_p, entry.logical_page, &entry_out_p) != 0)
        TEST_ERROR;

    /* Test successful insertion and lookup */

    /* Insertion failed */
    if (H5FD__onion_revision_index_insert(rix_p, &entry) < 0)
        TEST_ERROR;
    if (1 != rix_p->n_entries)
        TEST_ERROR;
    /* Lookup failed */
    if (H5FD__onion_revision_index_find(rix_p, entry.logical_page, &entry_out_p) < 0)
        TEST_ERROR;
    /* Failure to set output parameter */
    if (NULL == entry_out_p)
        TEST_ERROR;
    if (entry.logical_page != entry_out_p->logical_page)
        TEST_ERROR;
    /* Seeking missing page should miss */
    if (H5FD__onion_revision_index_find(rix_p, entry.logical_page + 1, &entry_out_p) != 0)
        TEST_ERROR;

    /* Test / demonstrate stored entry independent of user object */

    entry.logical_page = 100;
    entry.phys_addr    = 101;
    if (H5FD__onion_revision_index_insert(rix_p, &entry) < 0)
        TEST_ERROR;
    if (2 != rix_p->n_entries)
        TEST_ERROR;
    entry.logical_page = 500;
    entry.phys_addr    = 501;
    if (H5FD__onion_revision_index_find(rix_p, 100, &entry_out_p) < 0)
        TEST_ERROR;
    if (100 != entry_out_p->logical_page || 101 != entry_out_p->phys_addr)
        TEST_ERROR;

    /* Demonstrate updating an entry */

    /* Error cases */

    entry.logical_page = 100; /* phys_addr still 501, checksum bbbbbbbb */
    if (H5FD__onion_revision_index_insert(rix_p, &entry) >= 0)
        TEST_ERROR; /* all components but sum must match */
    entry.phys_addr = 101;

    /* Successful update */

    entry.logical_page = 100;
    entry.phys_addr    = 101;
    if (H5FD__onion_revision_index_insert(rix_p, &entry) < 0)
        TEST_ERROR;

    /* Should still be two unique entries, not three */
    if (2 != rix_p->n_entries)
        TEST_ERROR;
    if (H5FD__onion_revision_index_find(rix_p, 100, &entry_out_p) < 0)
        TEST_ERROR;
    if (100 != entry_out_p->logical_page || 101 != entry_out_p->phys_addr)
        TEST_ERROR;

    if (H5FD__onion_revision_index_destroy(rix_p) < 0)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    if (rix_p != NULL)
        (void)H5FD__onion_revision_index_destroy(rix_p);

    return -1;
} /* end test_revision_index() */

/*-----------------------------------------------------------------------------
 * Function:    test_revision_index_collisions()
 *
 * Purpose:     With knowledge of the revision index implementation, test
 *              hash key collisions.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_revision_index_collisions(void)
{
    H5FD_onion_revision_index_t *rix_p = NULL;
    H5FD_onion_index_entry_t     entry = {
        0, /* logical_page */
        0, /* phys_addr */
    };
    const H5FD_onion_index_entry_t *entry_out_p       = NULL;
    const uint64_t                  n_insert          = 40;
    const uint64_t                  offset_from_power = 5;

    TESTING("revision index collisions");

    if (NULL == (rix_p = H5FD__onion_revision_index_init(ONION_TEST_PAGE_SIZE_5)))
        TEST_ERROR;

    for (uint64_t i = 0; i < n_insert; i++) {
        entry.phys_addr    = i;
        entry.logical_page = U64_EXP2(i) + offset_from_power;
        if (H5FD__onion_revision_index_insert(rix_p, &entry) < 0)
            TEST_ERROR;
    }

    if (n_insert != rix_p->n_entries)
        TEST_ERROR;

    for (uint64_t i = 0; i < n_insert; i++) {
        uint64_t page_id = U64_EXP2(i) + offset_from_power;

        if (H5FD__onion_revision_index_find(rix_p, page_id, &entry_out_p) != 1)
            TEST_ERROR;
        if (entry_out_p->phys_addr != i)
            TEST_ERROR;
    }

    if (H5FD__onion_revision_index_destroy(rix_p) < 0)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    if (rix_p != NULL)
        (void)H5FD__onion_revision_index_destroy(rix_p);

    return -1;
} /* end test_revision_index_collisions() */

/*-----------------------------------------------------------------------------
 *
 * Function:    test_revision_index_resizing()
 *
 * Purpose:     With knowledge of the revision index implementation, test
 *              one or more resizig of the index.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *
 *-----------------------------------------------------------------------------
 */
static int
test_revision_index_resizing(void)
{
    H5FD_onion_revision_index_t *rix_p = NULL;
    H5FD_onion_index_entry_t     entry = {
        0, /* logical_page */
        0, /* phys_addr */
    };
    const H5FD_onion_index_entry_t *entry_out_p = NULL;
    const uint64_t                  n_insert = U64_EXP2((H5FD_ONION_REVISION_INDEX_STARTING_SIZE_LOG2 + 3));

    TESTING("revision index resizing");

    if (NULL == (rix_p = H5FD__onion_revision_index_init(ONION_TEST_PAGE_SIZE_5)))
        TEST_ERROR;

    for (uint64_t i = 0; i < n_insert; i++) {
        entry.logical_page = i;
        entry.phys_addr    = ((uint64_t)(-1) - i);
        if (H5FD__onion_revision_index_insert(rix_p, &entry) < 0)
            TEST_ERROR;
    }

    if (n_insert != rix_p->n_entries)
        TEST_ERROR;

    for (uint64_t i = 0; i < n_insert; i++) {
        uint64_t page_id = i;

        if (H5FD__onion_revision_index_find(rix_p, page_id, &entry_out_p) != 1)
            TEST_ERROR;
        if (entry_out_p->phys_addr != ((uint64_t)(-1) - i))
            TEST_ERROR;
    }

    if (H5FD__onion_revision_index_destroy(rix_p) < 0)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    if (rix_p != NULL)
        (void)H5FD__onion_revision_index_destroy(rix_p);

    return -1;
} /* end test_revision_index_resizing() */

/*-----------------------------------------------------------------------------
 * Function:    test_revision_index_to_archival_index()
 *
 * Purpose:     Verify to_archival_index().
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_revision_index_to_archival_index(void)
{
    H5FD_onion_revision_index_t *rix_p     = NULL;
    H5FD_onion_index_entry_t     rix_entry = {
        0, /* logical_page */
        0, /* phys_addr */
    };
    H5FD_onion_archival_index_t aix = {
        H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR,
        5, /* page_size_log2 */
        0, /* n_entries to be set */
        NULL,
    };
    const uint64_t n_insert = 10;

    TESTING("revision index to archival index");

    /*
     * SETUP
     */

    if (NULL == (rix_p = H5FD__onion_revision_index_init(ONION_TEST_PAGE_SIZE_5)))
        TEST_ERROR;

    /* Add scattered entries in reverse order. */
    for (uint64_t i = 0; i < n_insert; i++) {
        uint64_t n = 2003 * (n_insert - i) + 47;

        rix_entry.logical_page = n;
        rix_entry.phys_addr    = n * 13;
        if (H5FD__onion_revision_index_insert(rix_p, &rix_entry) < 0)
            TEST_ERROR;
    }

    if (n_insert != rix_p->n_entries)
        TEST_ERROR;

    aix.list      = NULL;
    aix.n_entries = 0;

    /* Successful merge into empty archival index */

    if (H5FD__onion_merge_revision_index_into_archival_index(rix_p, &aix) < 0)
        TEST_ERROR;

    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    if (n_insert != aix.n_entries)
        TEST_ERROR;
    for (uint64_t i = 0; i < n_insert; i++) {
        const H5FD_onion_index_entry_t *aix_entry_p = NULL;

        uint64_t n = 2003 * (i + 1) + 47;

        aix_entry_p = &aix.list[i];

        if (aix_entry_p->logical_page != n)
            TEST_ERROR;
        if (aix_entry_p->phys_addr != (n * 13))
            TEST_ERROR;
    }

    /* Successful merge into populated archival index */

    H5MM_xfree(aix.list);
    aix.list = NULL;
    if (NULL == (aix.list = H5MM_malloc(sizeof(H5FD_onion_index_entry_t) * 2)))
        TEST_ERROR;
    aix.list[0].logical_page = 47;
    aix.list[0].phys_addr    = 47 * 13;
    aix.list[1].logical_page = (2003 * (n_insert + 1) + 47);
    aix.list[1].phys_addr    = (2003 * (n_insert + 1) + 47) * 13;
    aix.n_entries            = 2;

    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    if (H5FD__onion_merge_revision_index_into_archival_index(rix_p, &aix) < 0)
        TEST_ERROR;
    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    if (n_insert + 2 != aix.n_entries)
        TEST_ERROR;

    for (uint64_t i = 0; i < (n_insert + 2); i++) {
        const H5FD_onion_index_entry_t *aix_entry_p = NULL;

        uint64_t n = 2003 * i + 47;

        aix_entry_p = &aix.list[i];

        if (aix_entry_p->logical_page != n)
            TEST_ERROR;
        if (aix_entry_p->phys_addr != (n * 13))
            TEST_ERROR;
    }

    /* Merged enties from revision index replace existing entries */

    H5MM_xfree(aix.list);
    aix.list = NULL;
    if (NULL == (aix.list = H5MM_malloc(sizeof(H5FD_onion_index_entry_t) * 2)))
        TEST_ERROR;
    aix.list[0].logical_page = 2003 * (n_insert / 2) + 47;
    aix.list[0].phys_addr    = 103;
    aix.list[1].logical_page = 2003 * (n_insert / 2 + 1) + 47;
    aix.list[1].phys_addr    = 101;
    aix.n_entries            = 2;

    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    if (H5FD__onion_merge_revision_index_into_archival_index(rix_p, &aix) < 0)
        TEST_ERROR;

    if (!H5FD__onion_archival_index_is_valid(&aix))
        TEST_ERROR;

    if (n_insert != aix.n_entries)
        TEST_ERROR;

    for (uint64_t i = 0; i < n_insert; i++) {
        const H5FD_onion_index_entry_t *aix_entry_p = NULL;
        uint64_t                        n           = 2003 * (i + 1) + 47;

        aix_entry_p = &aix.list[i];

        if (aix_entry_p->logical_page != n)
            TEST_ERROR;
        if (aix_entry_p->phys_addr != (n * 13))
            TEST_ERROR;
    }

    /* CLEANUP */

    if (H5FD__onion_revision_index_destroy(rix_p) < 0)
        TEST_ERROR;
    H5MM_xfree(aix.list);

    PASSED();
    return 0;

error:
    if (rix_p)
        (void)H5FD__onion_revision_index_destroy(rix_p);
    H5MM_xfree(aix.list);

    return -1;
} /* end test_revision_index_to_archival_index() */

/*-----------------------------------------------------------------------------
 * Function:    test_fapl()
 *
 * Purpose:     Verify H5Pget and set behavior, and data-consistency checks.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_fapl(void)
{
    H5FD_onion_fapl_info_t info_in = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5P_DEFAULT,                   /* backing_fapl_id                */
        ONION_TEST_PAGE_SIZE_1,        /* page_size                      */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target                   */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,                        /* force_write_open               */
        0,                        /* creation_flags                 */
        "indoor speaking voices", /* comment                        */
    };
    H5FD_onion_fapl_info_t info_out;
    hid_t                  dxpl_id      = H5I_INVALID_HID;
    hid_t                  fapl_id      = H5I_INVALID_HID;
    hid_t                  fapl_id_sec2 = H5I_INVALID_HID;
    herr_t                 ret          = FAIL;

    TESTING("file access property list");

    if ((dxpl_id = H5Pcreate(H5P_DATASET_XFER)) < 0)
        TEST_ERROR;

    if ((fapl_id_sec2 = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_sec2(fapl_id_sec2))
        TEST_ERROR;

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

    /* Set FAPL */

    /* Invalid fapl should fail */
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(H5I_INVALID_HID, &info_in);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* NULL info pointer should fail */
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(fapl_id, NULL);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* Invalid version should fail */
    info_in.version++;
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(fapl_id, &info_in);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;
    info_in.version--;

    /* Page size not a power of 2 should fail */
    info_in.page_size = 7;
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(fapl_id, &info_in);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* Page size <=0 should fail */
    info_in.page_size = 0;
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(fapl_id, &info_in);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;
    info_in.page_size = ONION_TEST_PAGE_SIZE_1;

    /* Invalid backing fapl should fail */
    info_in.backing_fapl_id = H5I_INVALID_HID;
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(fapl_id, &info_in);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* Backing fapl not a fapl should fail */
    info_in.backing_fapl_id = dxpl_id;
    H5E_BEGIN_TRY
    {
        ret = H5Pset_fapl_onion(fapl_id, &info_in);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;
    info_in.backing_fapl_id = H5P_DEFAULT;

    if (H5Pset_fapl_onion(fapl_id, &info_in) < 0)
        TEST_ERROR;

    /* Get onion fapl info */

    /* NULL info_out pointer should fail */
    H5E_BEGIN_TRY
    {
        ret = H5Pget_fapl_onion(fapl_id, NULL);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* Invalid fapl should fail */
    H5E_BEGIN_TRY
    {
        ret = H5Pget_fapl_onion(H5I_INVALID_HID, &info_out);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* Non-onion fapl ID should fail */
    H5E_BEGIN_TRY
    {
        ret = H5Pget_fapl_onion(fapl_id_sec2, &info_out);
    }
    H5E_END_TRY
    if (SUCCEED == ret)
        TEST_ERROR;

    /* Normal case */
    if (H5Pget_fapl_onion(fapl_id, &info_out) < 0)
        TEST_ERROR;
    if (H5FD_ONION_FAPL_INFO_VERSION_CURR != info_out.version)
        TEST_ERROR;
    if (H5P_DEFAULT != info_out.backing_fapl_id)
        TEST_ERROR;
    if (ONION_TEST_PAGE_SIZE_1 != info_out.page_size)
        TEST_ERROR;
    if (H5FD_ONION_STORE_TARGET_ONION != info_out.store_target)
        TEST_ERROR;
    if (H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST != info_out.revision_num)
        TEST_ERROR;
    if (0 != info_out.creation_flags)
        TEST_ERROR;
    if (0 != info_out.force_write_open)
        TEST_ERROR;
    if (strcmp(info_in.comment, info_out.comment))
        TEST_ERROR;

    /* Cleanup */

    if (H5Pclose(dxpl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id_sec2) < 0)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    H5E_BEGIN_TRY
    {
        H5Pclose(dxpl_id);
        H5Pclose(fapl_id);
        H5Pclose(fapl_id_sec2);
    }
    H5E_END_TRY

    return -1;
} /* end test_fapl() */

/*-----------------------------------------------------------------------------
 * Function:    test_header_encode_decode()
 *
 * Purpose:     Verify onion header encoding and decoding behavior.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_header_encode_decode(void)
{
    unsigned char buf[64];
    unsigned char exp[64] = {
        /* bogus but unique values */
        'O',  'H',  'D',  'H', /* NOTE: match signature define in onion_priv.h */
        1,    12,   0,    0,   /* NOTE: update version w/ "current" as needed */
        0,    16,   0,    0,   0x11, 0x00, 0, 0, 0x02, 0, 0, 0, /* origin_eof         */
        0x40, 0xe2, 0x01, 0,   0,    0,    0, 0,                /* history_addr */
        88,   0,    0,    0,   0,    0,    0, 0,                /* history_size */
        0,    0,    0,    0                                     /* sum populated below */
    };
    unsigned char      *ptr          = NULL;
    uint32_t            checksum     = 0;
    uint32_t            checksum_out = 0;
    size_t              i            = 0;
    uint64_t            size_ret     = 0;
    H5FD_onion_header_t hdr;
    H5FD_onion_header_t hdr_out;

    TESTING("encode/decode history header");

    checksum = H5_checksum_fletcher32(exp, H5FD_ONION_ENCODED_SIZE_HEADER - 4);
    ptr      = exp + H5FD_ONION_ENCODED_SIZE_HEADER - 4;
    UINT32ENCODE(ptr, checksum);

    hdr.version      = H5FD_ONION_HEADER_VERSION_CURR;
    hdr.flags        = 12;
    hdr.origin_eof   = 8589934609ull;
    hdr.page_size    = 4096;
    hdr.history_addr = 123456;
    hdr.history_size = 88;

    if (H5FD__onion_header_encode(&hdr, buf, &checksum_out) != H5FD_ONION_ENCODED_SIZE_HEADER)
        TEST_ERROR;

    if (checksum != checksum_out)
        TEST_ERROR;

    for (i = 0; i < H5FD_ONION_ENCODED_SIZE_HEADER; i++) {
        if (exp[i] != buf[i]) {
            printf("first mismatched byte at %zu: %02x %02x\n", i, exp[i], buf[i]);
            TEST_ERROR;
        }
    }

    hdr_out.version      = H5FD_ONION_HEADER_VERSION_CURR;
    hdr_out.flags        = 0;
    hdr_out.page_size    = 0;
    hdr_out.history_addr = 0;
    hdr_out.history_size = 0;

    /* Invalid header signature prevents decoding.
     */

    exp[3] = 'X'; /* invalidate encoded signature */
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_header_decode(exp, &hdr_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    exp[3] = 'H'; /* reset */

    /* Invalid header version prevents decoding.
     */

    exp[4] = 0; /* encoded version 0?!? */
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_header_decode(exp, &hdr_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    exp[4] = H5FD_ONION_HEADER_VERSION_CURR + 1; /* encoded super-version?! */
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_header_decode(exp, &hdr_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    /* Reset */
    exp[4] = H5FD_ONION_HEADER_VERSION_CURR;

    /* Valid header can be decoded */

    if (H5FD__onion_header_decode(buf, &hdr_out) != H5FD_ONION_ENCODED_SIZE_HEADER)
        TEST_ERROR;
    if (H5FD_ONION_HEADER_VERSION_CURR != hdr_out.version)
        TEST_ERROR;
    if (hdr.flags != hdr_out.flags)
        TEST_ERROR;
    if (hdr.page_size != hdr_out.page_size)
        TEST_ERROR;
    if (hdr.history_addr != hdr_out.history_addr)
        TEST_ERROR;
    if (hdr.history_size != hdr_out.history_size)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    return -1;
} /* end test_header_encode_decode() */

/*-----------------------------------------------------------------------------
 * Function:    test_history_encode_decode_empty()
 *
 * Purpose:     Verify onion history encoding and decoding behavior.
 *              Tests the case of the "empty" history.
 *              Verifies behavior in standard error cases.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_history_encode_decode_empty(void)
{
    unsigned char buf[32];
    unsigned char exp[32] = {
        'O', 'W', 'H', 'S',                        /* NOTE: match signature define in onion_priv.h */
        1,   0,   0,   0,                          /* NOTE: update version w/ "current" as needed */
        0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0 /* sum populated below */
    };
    unsigned char       *ptr          = NULL;
    uint32_t             checksum     = 0;
    uint32_t             checksum_out = 0;
    size_t               i            = 0;
    uint64_t             size_ret     = 0;
    H5FD_onion_history_t history      = {
        H5FD_ONION_HISTORY_VERSION_CURR, 0, /* n_revisions */
        NULL,                               /* list */
        0,                                  /* checksum */
    };
    H5FD_onion_history_t history_out = {
        H5FD_ONION_HISTORY_VERSION_CURR, 0, /* n_revisions */
        NULL,                               /* list */
        0,                                  /* checksum */
    };

    TESTING("encode/decode history (empty and failures)");

    /* Generate checksum but don't store it yet */
    checksum = H5_checksum_fletcher32(exp, H5FD_ONION_ENCODED_SIZE_HISTORY - 4);
    ptr      = exp + H5FD_ONION_ENCODED_SIZE_HISTORY - 4;
    UINT32ENCODE(ptr, checksum);

    if (H5FD__onion_history_encode(&history, buf, &checksum_out) != H5FD_ONION_ENCODED_SIZE_HISTORY)
        TEST_ERROR;
    for (i = 0; i < 20; i++) {
        if (exp[i] != buf[i]) {
            printf("first mismatched byte at %zu: %02x %02x\n", i, exp[i], buf[i]);
            TEST_ERROR;
        }
    }
    if (checksum != checksum_out)
        TEST_ERROR;
    history.checksum = checksum; /* set to compare later */

    /* Invalid signature prevents decoding */

    exp[3] = 'X'; /* invalidate encoded signature */
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_history_decode(exp, &history_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    exp[3] = 'H'; /* reset */

    /* Invalid version prevents decoding */

    exp[4] = 0; /* encoded version 0?!? */
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_history_decode(exp, &history_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    exp[4] = H5FD_ONION_HISTORY_VERSION_CURR + 1;
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_history_decode(exp, &history_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    exp[4] = H5FD_ONION_HISTORY_VERSION_CURR; /* reset */

    /* Valid summary can be decoded */

    if (H5FD__onion_history_decode(buf, &history_out) != H5FD_ONION_ENCODED_SIZE_HISTORY)
        TEST_ERROR;
    if (H5FD_ONION_HISTORY_VERSION_CURR != history_out.version)
        TEST_ERROR;
    if (history.n_revisions != history_out.n_revisions)
        TEST_ERROR;
    if (history.checksum != history_out.checksum)
        TEST_ERROR;
    if (NULL != history_out.record_locs)
        TEST_ERROR;

    PASSED();
    return 0;

error:
    return -1;
} /* end test_history_encode_decode_empty() */

/*-----------------------------------------------------------------------------
 * Function:    test_history_encode_decode()
 *
 * Purpose:     Verify onion history encoding and decoding behavior.
 *              Encode/decode with some set of revision record pointers.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_history_encode_decode(void)
{
    unsigned char *buf     = NULL;
    unsigned char  exp[80] = {
        'O', 'W', 'H', 'S', /* NOTE: match signature define in onion_priv.h */
        1, 0, 0, 0,         /* NOTE: update version w/ "current" as needed */
        3, 0, 0, 0, 0, 0, 0, 0,
        /* rev0 pointer */
        56, 2, 0, 0, 0, 0, 0, 0, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* sum populated below */
        /* rev1 pointer */
        121, 173, 3, 0, 0, 0, 0, 0, 203, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* sum populated below */
        /* rev2 pointer */
        96, 158, 52, 198, 213, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* sum populated below */
        /* final checksum */
        0, 0, 0, 0 /* sum populated below */
    };
    unsigned char       *buf_p        = NULL;
    uint32_t             checksum_out = 0;
    size_t               i            = 0;
    H5FD_onion_history_t history      = {
        H5FD_ONION_HISTORY_VERSION_CURR, 3, /* n_revisions */
        NULL,                               /* list set below */
        0,                                  /* checksum  not set by us */
    };
    H5FD_onion_history_t history_out = {
        H5FD_ONION_HISTORY_VERSION_CURR, 0, /* n_revisions must start as zero */
        NULL,                               /* list */
        0,                                  /* checksum */
    };
    size_t exp_size =
        H5FD_ONION_ENCODED_SIZE_HISTORY + H5FD_ONION_ENCODED_SIZE_RECORD_POINTER * history.n_revisions;

    TESTING("encode/decode history");

    if (80 != exp_size)
        TEST_ERROR;

    history.record_locs = calloc(history.n_revisions, sizeof(H5FD_onion_record_loc_t));
    if (NULL == history.record_locs)
        TEST_ERROR;

    /* Must match values in exp */
    history.record_locs[0].phys_addr   = 568ull;
    history.record_locs[0].record_size = 238ull;
    history.record_locs[1].phys_addr   = 241017ull;
    history.record_locs[1].record_size = 4555ull;
    history.record_locs[2].phys_addr   = 918153371232ull;
    history.record_locs[2].record_size = 240ull;

    /* Populate revision pointer sums in exp */
    for (i = 0; i < history.n_revisions; i++) {
        uint64_t history_pre = H5FD_ONION_ENCODED_SIZE_HISTORY - 4;
        uint64_t ptr_pre     = H5FD_ONION_ENCODED_SIZE_RECORD_POINTER - 4;
        uint64_t ptr_size    = H5FD_ONION_ENCODED_SIZE_RECORD_POINTER;

        buf_p                           = exp + history_pre + ptr_size * i;
        history.record_locs[i].checksum = H5_checksum_fletcher32(buf_p, ptr_pre);
        buf_p += ptr_pre;
        UINT32ENCODE(buf_p, history.record_locs[i].checksum);
    }

    /* Compute, populate, and store exp final sum */
    history.checksum = H5_checksum_fletcher32(exp, exp_size - 4);
    buf_p            = exp + exp_size - 4;
    UINT32ENCODE(buf_p, history.checksum);

    if (NULL == (buf = malloc(exp_size)))
        TEST_ERROR;

    if (H5FD__onion_history_encode(&history, buf, &checksum_out) != exp_size)
        TEST_ERROR;
    for (i = 0; i < exp_size; i++) {
        if (exp[i] != buf[i])
            TEST_ERROR;
    }
    if (history.checksum != checksum_out)
        TEST_ERROR;

    /* Initial decode, gets always-present components */

    history_out.n_revisions = 0; /* must be initialized to 0 */
    if (H5FD__onion_history_decode(exp, &history_out) != exp_size)
        TEST_ERROR;
    if (H5FD_ONION_HISTORY_VERSION_CURR != history_out.version)
        TEST_ERROR;
    if (history.n_revisions != history_out.n_revisions)
        TEST_ERROR;
    /* Must be created by us */
    if (NULL != history_out.record_locs)
        TEST_ERROR;

    /* True decode requires allocating space for record pointers */

    history_out.record_locs = calloc(history_out.n_revisions, sizeof(H5FD_onion_record_loc_t));
    if (NULL == history_out.record_locs)
        TEST_ERROR;

    if (H5FD__onion_history_decode(exp, &history_out) != exp_size)
        TEST_ERROR;
    if (H5FD_ONION_HISTORY_VERSION_CURR != history_out.version)
        TEST_ERROR;
    if (history.n_revisions != history_out.n_revisions)
        TEST_ERROR;
    if (history.checksum != history_out.checksum)
        TEST_ERROR;
    if (NULL == history_out.record_locs)
        TEST_ERROR;
    for (i = 0; i < history.n_revisions; i++) {
        H5FD_onion_record_loc_t exp_rp = history.record_locs[i];
        H5FD_onion_record_loc_t act_rp = history_out.record_locs[i];

        if (exp_rp.phys_addr != act_rp.phys_addr)
            TEST_ERROR;
        if (exp_rp.record_size != act_rp.record_size)
            TEST_ERROR;
        if (exp_rp.checksum != act_rp.checksum)
            TEST_ERROR;
    }

    free(history_out.record_locs);
    free(buf);
    free(history.record_locs);

    PASSED();
    return 0;

error:
    free(history_out.record_locs);
    free(buf);
    free(history.record_locs);

    return -1;
} /* end test_history_encode_decode() */

/*-----------------------------------------------------------------------------
 *
 * Function:    test_revision_record_encode_decode()
 *
 * Purpose:     Verify onion revision-record encoding and decoding behavior.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *
 *-----------------------------------------------------------------------------
 */
static int
test_revision_record_encode_decode(void)
{
    /* clang-format off */
    /* Byte array of expected values (FRAGILE!) */
    unsigned char exp[173] = {
        'O', 'R', 'R', 'S',                     /* Bytes 000-003:   signature */
        1, 0, 0, 0,                             /* Bytes 004-007:   version */
        5, 0, 0, 0, 0, 0, 0, 0,                 /* Bytes 008-015:   revision ID */
        2, 0, 0, 0, 0, 0, 0, 0,                 /* Bytes 016-023:   parent revision ID */
        '1', '9', '4', '1', '1', '2', '0', '7', /* Bytes 024-039:   time of creation */
        'T', '1', '9', '0', '6', '4', '3', 'Z', /*                  ... */
        0x11, 0x00, 0, 0, 0x02, 0, 0, 0,        /* Bytes 040-047:   logical file size */
        0, 16, 0, 0,                            /* Bytes 048-051:   page size */
        4, 0, 0, 0, 0, 0, 0, 0,                 /* Bytes 052-059:   # entries */
        25, 0, 0, 0,                            /* Bytes 060-063:   comment size */
        /* ENTRY 0 */
        0, 0xB0, 0x1E, 0, 0, 0, 0, 0,           /* Bytes 064-071:   entry 0: logical offset */
        0x4B, 0x02, 0, 0, 0, 0, 0, 0,           /* Bytes 072-079:   entry 0: physical address */
        0, 0, 0, 0,                             /* Bytes 080-083:   checksum (populated below) */
        /* ENTRY 1 */
        0, 0xF0, 0x2E, 0, 0, 0, 0, 0,           /* Bytes 084-091:   entry 1: logical offset */
        0xA7, 0, 0, 0, 0, 0, 0, 0,              /* Bytes 092-099:   entry 1: physical address */
        0, 0, 0, 0,                             /* Bytes 100-103:   checksum (populated below) */
        /* ENTRY 2 */
        0, 0x50, 0x15, 0, 0, 0x20, 0, 0,        /* Bytes 104-111:   entry 2: logical offset */
        0x11, 0, 0, 0, 0x02, 0, 0, 0,           /* Bytes 112-119:   entry 2: physical address */
        0, 0, 0, 0,                             /* Bytes 120-123:   checksum (populated below) */
        /* ENTRY 3 */
        0, 0xE0, 0x24, 0, 0, 0, 0, 0,           /* Bytes 124-131:   entry 3: logical offset */
        0xB1, 0x01, 0, 0, 0, 0, 0, 0,           /* Bytes 132-139:   entry 3: physical address */
        0, 0, 0, 0,                             /* Bytes 140-143:   checksum (populated below) */
        'E', 'x', 'a', 'm', 'p', 'l', 'e', ' ', /* Bytes 144-168:   comment */
        'c', 'o', 'm', 'm', 'e', 'n', 't', ' ', /*                  ... */
        'm', 'e', 's', 's', 'a', 'g', 'e', '.', /*                  ... */
        '\0',                                   /*                  ... */
        0, 0, 0, 0                              /* Bytes 169-172:   final checksum (populated below) */
    };
    /* clang-format on */
    unsigned char               *buf   = NULL;
    unsigned char               *buf_p = NULL;
    size_t                       i     = 0;
    uint64_t                     size_ret;
    H5FD_onion_revision_record_t r_out;
    uint32_t                     checksum    = 0;
    bool                         badness     = false;
    char                         comment[25] = "Example comment message.";
    H5FD_onion_revision_record_t record      = {
        H5FD_ONION_REVISION_RECORD_VERSION_CURR,
        5,             /* revision ID */
        2,             /* parent revision ID */
        {'\0'},        /* time of creation - populated below */
        8589934609ull, /* logical file size */
        {
            H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR, /* version */
            12,                                     /* page_size_log2 */
            4,                                      /* n_entries */
            NULL,                                   /* list - populated below */
        },                                          /* archival index struct */
        25,                                         /* comment size */
        comment,                                    /* comment */
        0,                                          /* checksum (computed later) */
    };
    size_t exp_size = H5FD_ONION_ENCODED_SIZE_REVISION_RECORD +
                      (H5FD_ONION_ENCODED_SIZE_INDEX_ENTRY * record.archival_index.n_entries) +
                      strlen("Example comment message.") + 1;

    r_out.archival_index.list = NULL;
    r_out.comment             = NULL;

    TESTING("encode/decode revision record");

    memcpy(record.time_of_creation, "19411207T190643Z", 16);
    record.archival_index.list = calloc(record.archival_index.n_entries, sizeof(H5FD_onion_index_entry_t));
    if (NULL == record.archival_index.list)
        TEST_ERROR;

    /* Convert logical_page and should match address in expected buffer */
    record.archival_index.list[0].logical_page = 491ull;
    record.archival_index.list[0].phys_addr    = 587ull;
    record.archival_index.list[1].logical_page = 751ull;
    record.archival_index.list[1].phys_addr    = 167ull;
    record.archival_index.list[2].logical_page = 8589934933ull;
    record.archival_index.list[2].phys_addr    = 8589934609ull;
    record.archival_index.list[3].logical_page = 590ull;
    record.archival_index.list[3].phys_addr    = 433ull;

    /* Set expected checksum for each archival index entry in buffer */
    for (i = 0; i < record.archival_index.n_entries; i++) {
        uint64_t rec_pre  = H5FD_ONION_ENCODED_SIZE_REVISION_RECORD - 4;
        uint64_t idx_pre  = H5FD_ONION_ENCODED_SIZE_INDEX_ENTRY - 4;
        uint64_t idx_size = H5FD_ONION_ENCODED_SIZE_INDEX_ENTRY;

        buf_p    = exp + rec_pre + idx_size * i;
        checksum = H5_checksum_fletcher32(buf_p, idx_pre);
        buf_p += idx_pre;
        UINT32ENCODE(buf_p, checksum);
    }

    checksum = 0;

    record.checksum = H5_checksum_fletcher32(exp, exp_size - 4);
    buf_p           = exp + exp_size - 4;
    UINT32ENCODE(buf_p, record.checksum);

    /* Required initialization for record-out structure */
    r_out.version                  = H5FD_ONION_REVISION_RECORD_VERSION_CURR;
    r_out.comment_size             = 0;
    r_out.comment                  = NULL;
    r_out.archival_index.version   = H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR;
    r_out.archival_index.n_entries = 0;
    r_out.archival_index.list      = NULL;

    if (NULL == (buf = malloc(sizeof(unsigned char) * exp_size)))
        TEST_ERROR;

    /* Test encode */

    if (H5FD__onion_revision_record_encode(&record, buf, &checksum) != exp_size)
        TEST_ERROR;

    for (i = 0; i < exp_size; i++) {
        if (exp[i] != buf[i]) {
            badness = true;
            printf("Bad encoded record - Index %zu: expected 0x%02X but got 0x%02X\n", i,
                   (unsigned int)exp[i], (unsigned int)buf[i]);
        }
    }
    if (badness) {
        /* If this fragile test breaks, this information is helpful... */
        printf("INDEX\n");
        for (i = 0; i < exp_size; i++)
            printf("%4zu ", i);
        printf("\n");

        printf("EXPECTED\n");
        for (i = 0; i < exp_size; i++)
            printf("0x%02X ", (unsigned int)exp[i]);
        printf("\n");

        printf("ACTUAL\n");
        for (i = 0; i < exp_size; i++)
            printf("0x%02X ", (unsigned int)buf[i]);
        printf("\n");
    }
    if (badness)
        TEST_ERROR;
    if (record.checksum != checksum)
        TEST_ERROR;

    /* Test decode (malformed encoding) */

    /* Invalid signature */
    exp[2] = 'Y';
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_revision_record_decode(exp, &r_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;
    exp[2] = 'R'; /* reset */

    /* Zero version */
    exp[4] = 0;
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_revision_record_decode(exp, &r_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;

    /* Advance version */
    exp[4] = H5FD_ONION_REVISION_RECORD_VERSION_CURR + 1;
    H5E_BEGIN_TRY
    {
        size_ret = H5FD__onion_revision_record_decode(exp, &r_out);
    }
    H5E_END_TRY
    if (0 != size_ret)
        TEST_ERROR;
    exp[4] = H5FD_ONION_REVISION_RECORD_VERSION_CURR; /* reset */

    /* Test successful decode */

    /* Initial decode; get variable-length component sizes */
    if (H5FD__onion_revision_record_decode(exp, &r_out) != exp_size)
        TEST_ERROR;
    if (record.comment_size != r_out.comment_size)
        TEST_ERROR;
    if (record.archival_index.n_entries != r_out.archival_index.n_entries)
        TEST_ERROR;

    /* Allocate variable-length components */
    r_out.comment = calloc(r_out.comment_size, sizeof(char));
    if (NULL == r_out.comment)
        TEST_ERROR;
    r_out.archival_index.list = calloc(r_out.archival_index.n_entries, sizeof(H5FD_onion_index_entry_t));
    if (NULL == r_out.archival_index.list)
        TEST_ERROR;

    /* Decode into all components */
    if (H5FD__onion_revision_record_decode(exp, &r_out) != exp_size)
        TEST_ERROR;
    if (H5FD_ONION_REVISION_RECORD_VERSION_CURR != r_out.version)
        TEST_ERROR;
    if (record.revision_num != r_out.revision_num)
        TEST_ERROR;
    if (record.parent_revision_num != r_out.parent_revision_num)
        TEST_ERROR;
    if (record.parent_revision_num != r_out.parent_revision_num)
        TEST_ERROR;
    if (record.checksum != r_out.checksum)
        TEST_ERROR;
    if (strncmp(record.time_of_creation, r_out.time_of_creation, 16) != 0)
        TEST_ERROR;
    if (record.comment_size != r_out.comment_size)
        TEST_ERROR;
    if (record.comment_size != strlen(r_out.comment) + 1)
        TEST_ERROR;
    if (strlen(record.comment) != strlen(r_out.comment))
        TEST_ERROR;
    if (strcmp(record.comment, r_out.comment) != 0)
        TEST_ERROR;

    if (H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR != r_out.archival_index.version)
        TEST_ERROR;
    if (record.archival_index.page_size_log2 != r_out.archival_index.page_size_log2)
        TEST_ERROR;
    if (record.archival_index.n_entries != r_out.archival_index.n_entries)
        TEST_ERROR;
    for (i = 0; i < record.archival_index.n_entries; i++) {
        H5FD_onion_index_entry_t *ep = &record.archival_index.list[i];
        H5FD_onion_index_entry_t *ap = &r_out.archival_index.list[i];

        if (ep->phys_addr != ap->phys_addr)
            TEST_ERROR;
        if (ep->logical_page != ap->logical_page)
            TEST_ERROR;
    }

    /* Cleanup */

    free(r_out.archival_index.list);
    free(r_out.comment);
    free(buf);
    free(record.archival_index.list);

    PASSED();
    return 0;

error:
    free(r_out.archival_index.list);
    free(r_out.comment);
    free(buf);
    free(record.archival_index.list);

    return -1;
} /* end test_revision_record_encode_decode() */

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Use VFL to open target file and check that its bytes exactly match those
 * of given buffer 'exp'[ected].
 *
 * Returns 0 if successful, -1 if error or mismatch.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static int
compare_file_bytes_exactly(const char *filepath, hid_t fapl_id, size_t nbytes, const unsigned char *exp)
{
    H5FD_t        *raw_vfile = NULL; /* virtual file to look at raw file contents */
    unsigned char *act_buf   = NULL; /* allocated area for actual file bytes */
    uint64_t       filesize  = 0;

    if (NULL == (raw_vfile = H5FDopen(filepath, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF)))
        TEST_ERROR;

    /* filesize is wrong w/ stdio - it's zero instead of 40 or whatnot */
    filesize = (uint64_t)H5FDget_eof(raw_vfile, H5FD_MEM_DRAW);
    if ((uint64_t)nbytes != filesize) {
        fprintf(stderr, "\nSizes not the same - nbytes: %zu, filesize: %" PRIu64 "\n", nbytes, filesize);
        TEST_ERROR;
    }

    if (NULL == (act_buf = malloc(nbytes)))
        TEST_ERROR;
    /* Fill buffer with bogus UCHAR_MAX values */
    for (size_t i = 0; i < nbytes; i++)
        act_buf[i] = UCHAR_MAX;
    if (H5FDset_eoa(raw_vfile, H5FD_MEM_DRAW, nbytes) < 0)
        TEST_ERROR;
    if (H5FDread(raw_vfile, H5FD_MEM_DRAW, H5P_DEFAULT, 0, nbytes, act_buf) < 0)
        TEST_ERROR;

    /* Compare raw bytes data */
    for (size_t i = 0; i < nbytes; i++) {
        if (exp[i] != act_buf[i]) {
            printf("first mismatched byte %zu: expected 0x%02X was 0x%02X\n", i, exp[i], act_buf[i]);
            TEST_ERROR;
        }
    }

    if (H5FDclose(raw_vfile) < 0)
        TEST_ERROR;
    free(act_buf);

    return 0;

error:
    if (raw_vfile != NULL)
        H5FDclose(raw_vfile);
    free(act_buf);

    return -1;
} /* end compare_file_bytes_exactly() */

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Do a manual read of the onion history (separate, single "Onion" file).
 * Verify that the history data is well-formed and matches the expected state.
 *
 * Inspect file contents on backing store.
 * Return -1 on problem, 0 if okay.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static int
verify_history_as_expected_onion(H5FD_t *raw_file, struct expected_history *filter)
{
    unsigned char               *buf = NULL; /* allocated area for actual file bytes */
    H5FD_onion_header_t          hdr_out;
    H5FD_onion_history_t         history_out;
    H5FD_onion_revision_record_t rev_out;
    uint64_t                     filesize     = 0;
    uint64_t                     readsize     = 0;
    uint8_t                     *ui8p         = NULL;
    uint32_t                     buf_checksum = 0;

    /* memset to avoid bad frees on errors */
    memset(&rev_out, 0, sizeof(H5FD_onion_revision_record_t));
    memset(&history_out, 0, sizeof(H5FD_onion_history_t));

    hdr_out.version = H5FD_ONION_HEADER_VERSION_CURR;

    history_out.version     = H5FD_ONION_HISTORY_VERSION_CURR;
    history_out.n_revisions = 0;
    history_out.record_locs = NULL;

    rev_out.version                = H5FD_ONION_REVISION_RECORD_VERSION_CURR;
    rev_out.archival_index.version = H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR;

    filesize = (uint64_t)H5FDget_eof(raw_file, H5FD_MEM_DRAW);
    if (H5FDset_eoa(raw_file, H5FD_MEM_DRAW, filesize) < 0)
        TEST_ERROR;

    /* Ingest onion header */

    readsize = MIN(filesize, H5FD_ONION_ENCODED_SIZE_HEADER);
    if (NULL == (buf = malloc(readsize * sizeof(unsigned char))))
        TEST_ERROR;
    if (H5FDread(raw_file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, readsize, buf) < 0)
        TEST_ERROR;

    readsize = H5FD__onion_header_decode(buf, &hdr_out);
    if (0 == readsize)
        TEST_ERROR;
    if (H5FD_ONION_HEADER_VERSION_CURR != hdr_out.version)
        TEST_ERROR;
    /* Decode from the buffer to we can compare on BE systems */
    ui8p = (uint8_t *)(&buf[readsize - 4]);
    UINT32DECODE(ui8p, buf_checksum);
    if (hdr_out.checksum != buf_checksum)
        TEST_ERROR;
    if (hdr_out.checksum != H5_checksum_fletcher32(buf, readsize - 4))
        TEST_ERROR;
    if (filter->page_size != hdr_out.page_size)
        TEST_ERROR;
    if (hdr_out.history_addr + hdr_out.history_size != filesize)
        TEST_ERROR;
    if (filter->origin_eof != hdr_out.origin_eof)
        TEST_ERROR;

    free(buf);
    buf = NULL;

    /* Ingest history */

    readsize = hdr_out.history_size;
    if (NULL == (buf = malloc(readsize * sizeof(unsigned char))))
        TEST_ERROR;
    if (H5FDread(raw_file, H5FD_MEM_DRAW, H5P_DEFAULT, hdr_out.history_addr, readsize, buf) < 0)
        TEST_ERROR;

    /* Initial read, get count of revisions */
    readsize = H5FD__onion_history_decode(buf, &history_out);
    if (0 == readsize)
        TEST_ERROR;
    if (H5FD_ONION_HISTORY_VERSION_CURR != history_out.version)
        TEST_ERROR;
    /* Decode from the buffer to we can compare on BE systems */
    ui8p = (uint8_t *)(&buf[readsize - 4]);
    UINT32DECODE(ui8p, buf_checksum);
    if (history_out.checksum != buf_checksum)
        TEST_ERROR;
    if (history_out.checksum != H5_checksum_fletcher32(buf, readsize - 4))
        TEST_ERROR;
    if (filter->n_revisions != history_out.n_revisions)
        TEST_ERROR;

    /* Final read, populate pointers to revision records */
    history_out.record_locs = calloc(history_out.n_revisions, sizeof(H5FD_onion_record_loc_t));
    if (NULL == history_out.record_locs)
        TEST_ERROR;
    if (H5FD__onion_history_decode(buf, &history_out) != readsize)
        TEST_ERROR;

    /* Reuse buffer space to sanity-check checksum for record pointer(s). */
    assert(readsize >= sizeof(H5FD_onion_record_loc_t));
    for (size_t i = 0; i < history_out.n_revisions; i++) {

        uint64_t phys_addr;
        uint64_t record_size;

        /* Do a checked assignment from the struct value into appropriately
         * sized types. We don't have access to the H5F_t struct for this
         * file, so we can't use the offset/length macros in H5Fprivate.h.
         *
         * Have to do an encode to get these values so the test passes on BE
         * systems.
         */
        H5_CHECKED_ASSIGN(phys_addr, uint64_t, history_out.record_locs[i].phys_addr, haddr_t);
        H5_CHECKED_ASSIGN(record_size, uint64_t, history_out.record_locs[i].record_size, hsize_t);

        ui8p = (uint8_t *)buf;
        UINT64ENCODE(ui8p, phys_addr);

        ui8p = (uint8_t *)(buf + 8);
        UINT64ENCODE(ui8p, record_size);

        if (history_out.record_locs[i].checksum != H5_checksum_fletcher32(buf, 16))
            TEST_ERROR;
    }

    free(buf);
    buf = NULL;

    /* Ingest revision(s) */

    for (size_t i = 0; i < history_out.n_revisions; i++) {
        H5FD_onion_record_loc_t  *rpp = &history_out.record_locs[i];
        struct expected_revision *erp = &filter->revisions[i];

        rev_out.archival_index.list           = NULL;
        rev_out.archival_index.n_entries      = 0;
        rev_out.archival_index.page_size_log2 = 0;
        rev_out.comment_size                  = 0;
        rev_out.comment                       = NULL;

        readsize = rpp->record_size;
        if (NULL == (buf = malloc((size_t)rpp->record_size)))
            TEST_ERROR;
        if (H5FDread(raw_file, H5FD_MEM_DRAW, H5P_DEFAULT, rpp->phys_addr, rpp->record_size, buf) < 0)
            TEST_ERROR;

        /* Initial revision read -- get fixed components */
        readsize = H5FD__onion_revision_record_decode(buf, &rev_out);
        if (0 == readsize)
            TEST_ERROR;
        if (rpp->record_size != readsize)
            TEST_ERROR;
        if (H5FD_ONION_REVISION_RECORD_VERSION_CURR != rev_out.version)
            TEST_ERROR;
        /* Decode from the buffer to we can compare on BE systems */
        ui8p = (uint8_t *)(&buf[readsize - 4]);
        UINT32DECODE(ui8p, buf_checksum);
        if (rev_out.checksum != buf_checksum)
            TEST_ERROR;
        if (rev_out.checksum != H5_checksum_fletcher32(buf, readsize - 4))
            TEST_ERROR;
        if (erp->revision_num != rev_out.revision_num)
            TEST_ERROR;
        if (erp->parent_revision_num != rev_out.parent_revision_num)
            TEST_ERROR;
        if (erp->logical_eof != rev_out.logical_eof)
            TEST_ERROR;

        /* Final read, get variable-length data */
        if (NULL == (rev_out.comment = malloc((size_t)rev_out.comment_size)))
            TEST_ERROR;
        rev_out.archival_index.list =
            calloc(rev_out.archival_index.n_entries, sizeof(H5FD_onion_index_entry_t));
        if (NULL == rev_out.archival_index.list)
            TEST_ERROR;

        readsize = H5FD__onion_revision_record_decode(buf, &rev_out);
        if (rpp->record_size != readsize)
            TEST_ERROR;

        /* Compare revision info with expected filter */
        if (erp->comment == NULL) {
            if (rev_out.comment_size != 0)
                TEST_ERROR;
        }
        else {
            if (strlen(rev_out.comment) != strlen(erp->comment))
                TEST_ERROR;
            if (strcmp(rev_out.comment, erp->comment) != 0)
                TEST_ERROR;
        }
        if (erp->n_index_entries != (uint64_t)(-1) &&
            erp->n_index_entries != rev_out.archival_index.n_entries)
            TEST_ERROR;

        free(buf);
        free(rev_out.comment);
        free(rev_out.archival_index.list);
    }

    free(history_out.record_locs);
    history_out.record_locs = NULL;

    return 0;

error:
    free(buf);
    free(rev_out.comment);
    free(rev_out.archival_index.list);
    free(history_out.record_locs);

    return -1;

} /* end verify_history_as_expected_onion() */

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Verify file bytes on the backing store
 * + onion storage target
 * + create from nothing
 * + stage 0 (initializing)
 * + open (not yet written)
 *     + "Empty" .h5 file created
 *     + .onion file created w/ only header (0 whole-hist addr)
 *     + .onion.recovery created w/ "empty" history
 *     + Cannot open onionized canonical file (incomplete history, no rev)
 *
 * Inspect file contents on backing store.
 * Return -1 on problem, 0 if okay.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static int
verify_stored_onion_create_0_open(struct onion_filepaths *paths, H5FD_onion_fapl_info_t *onion_info)
{
    H5FD_t        *file            = NULL; /* virtual file to look at raw file contents */
    unsigned char *act_buf         = NULL; /* allocated area for actual file bytes */
    hid_t          fapl_id         = onion_info->backing_fapl_id;
    herr_t         err_ret         = FAIL;
    unsigned char  hdr_exp_bytes[] = {
        'O', 'H', 'D', 'H', 1, 1, 0, 0, 0, 0, 0, 0, /* page-size encoded below */
        0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0,   0,   20,  0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* checksum encoded below */
    };
    size_t        history_exp_bytes_size = 20;
    unsigned char history_exp_bytes[]    = {
        'O', 'W', 'H', 'S', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* checksum encoded below */
    };
    unsigned char *ptr      = NULL;
    uint32_t       checksum = 0;

    /* Finish populating expected header bytes */
    ptr = hdr_exp_bytes + 8; /* WARNING: must match format */
    UINT32ENCODE(ptr, onion_info->page_size);
    checksum = H5_checksum_fletcher32(hdr_exp_bytes, H5FD_ONION_ENCODED_SIZE_HEADER - 4);
    ptr      = hdr_exp_bytes + H5FD_ONION_ENCODED_SIZE_HEADER - 4;
    UINT32ENCODE(ptr, checksum);
    ptr = NULL;

    /* Finish populating expected history bytes */
    checksum = H5_checksum_fletcher32(history_exp_bytes, H5FD_ONION_ENCODED_SIZE_HISTORY - 4);
    ptr      = history_exp_bytes + H5FD_ONION_ENCODED_SIZE_HISTORY - 4;
    UINT32ENCODE(ptr, checksum);
    ptr = NULL;

    /* Look at h5 file: should have zero bytes */

    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;

    /* Size here is arbitrary */
    if (NULL == (act_buf = calloc(1, 8)))
        TEST_ERROR;

    /* Should fail when reading from an empty file */
    H5E_BEGIN_TRY
    {
        err_ret = H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, 1, act_buf);
    }
    H5E_END_TRY
    if (err_ret != FAIL)
        TEST_ERROR;

    free(act_buf);
    act_buf = NULL;

    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;

    /* Look at onion file: should have header */
    if (compare_file_bytes_exactly(paths->onion, fapl_id, H5FD_ONION_ENCODED_SIZE_HEADER, hdr_exp_bytes) < 0)
        TEST_ERROR;

    /* Look at history backing file: should have nascent history */
    if (compare_file_bytes_exactly(paths->recovery, fapl_id, history_exp_bytes_size, history_exp_bytes) < 0)
        TEST_ERROR;

    /* Inspect .h5 file contents */
    if (compare_file_bytes_exactly(paths->canon, fapl_id, 8, (const unsigned char *)"ONIONEOF") < 0)
        TEST_ERROR;

    return 0;

error:
    if (file != NULL)
        (void)H5FDclose(file);
    free(act_buf);

    return -1;
} /* end verify_stored_onion_create_0_open() */

/*-----------------------------------------------------------------------------
 * Function:    test_create_oniontarget()
 *
 * Purpose:     Test the ability of the Onion VFD to create a valid
 *              'onionized' file.
 *
 *              When `truncate_canonical` is false, the canonical file is
 *              nonexistent on the backing store on onion-creation.
 *              When `truncate_canonical` is true, a canonical file is created
 *              on the backing store with known contents, which are to be
 *              truncated on onion-creation.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_create_oniontarget(bool truncate_canonical, bool with_initial_data)
{
    const char             *basename   = "somesuch";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5P_DEFAULT,                   /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation_flags   */
        "initial commit" /* comment          */
    };
    H5FD_t                 *vfile_raw = NULL; /* virtual file to look at raw file contents */
    H5FD_t                 *vfile_rw  = NULL; /* Onion virtual file for read/write */
    H5FD_t                 *vfile_ro  = NULL; /* Onion virtual file for read-only */
    struct expected_history filter;
    char                   *buf = NULL;

    if (true == truncate_canonical && true == with_initial_data)
        TESTING("onion creation; truncate extant canonical; w/ initial data");
    else if (true == truncate_canonical)
        TESTING("onion creation; truncate extant canonical; no initial data");
    else if (true == with_initial_data)
        TESTING("onion creation; no extant canonical; w/ initial data");
    else
        TESTING("onion creation; no extant canonical; no initial data");

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

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /* Create canonical file to be truncated */
    if (true == truncate_canonical) {
        /* Create canonical file. */
        vfile_raw = H5FDopen(paths->canon, flags_create_s, onion_info.backing_fapl_id, HADDR_UNDEF);
        if (NULL == vfile_raw)
            TEST_ERROR;
        if (H5FDset_eoa(vfile_raw, H5FD_MEM_DRAW, b_list_size_s) < 0)
            TEST_ERROR;
        if (H5FDwrite(vfile_raw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, b_list_size_s, b_list_s) < 0)
            TEST_ERROR;
        if (H5FDclose(vfile_raw) < 0)
            TEST_ERROR;

        vfile_raw = NULL;
        H5E_BEGIN_TRY
        {
            vfile_raw = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
        }
        H5E_END_TRY

        /* Check if onion history for onion-open created file */
        if (NULL != vfile_raw)
            TEST_ERROR;

        /* Create "existing onion file". */
        vfile_raw = H5FDopen(paths->onion, flags_create_s, onion_info.backing_fapl_id, HADDR_UNDEF);
        if (NULL == vfile_raw)
            TEST_ERROR;
        if (H5FDset_eoa(vfile_raw, H5FD_MEM_DRAW, b_list_size_s) < 0)
            TEST_ERROR;
        if (H5FDwrite(vfile_raw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, 23, "prior history stand-in") < 0)
            TEST_ERROR;
        if (H5FDclose(vfile_raw) < 0)
            TEST_ERROR;
        vfile_raw = NULL;
    } /* end if to create canonical file for truncation */

    /*
     * OPENED
     */

    /* Begin creation of onionized file from nothing */

    vfile_rw = H5FDopen(paths->canon, flags_create_s, fapl_id, HADDR_UNDEF);
    if (NULL == vfile_rw)
        TEST_ERROR;

    if (verify_stored_onion_create_0_open(paths, &onion_info) < 0)
        TEST_ERROR;

    H5E_BEGIN_TRY
    {
        vfile_ro = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    }
    H5E_END_TRY
    /* check if onionization (creation) not complete; nothing to open */
    if (vfile_ro != NULL)
        TEST_ERROR;

    /*
     * WRITING
     */

    if (true == with_initial_data) {
        haddr_t half_size = 0;
        haddr_t buf_size  = 0;

        /* Write the sub-page entry at addr 0 */
        if (4 >= onion_info.page_size)
            TEST_ERROR;
        if (H5FDset_eoa(vfile_rw, H5FD_MEM_DRAW, 4) < 0)
            TEST_ERROR;
        if (H5FDwrite(vfile_rw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, 4, a_list_s) < 0) {
            TEST_ERROR;
        }

        /* Verify logical file contents. */
        if (NULL == (buf = malloc(4 * sizeof(char))))
            TEST_ERROR;
        if (H5FDread(vfile_rw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, 4, buf) < 0)
            TEST_ERROR;
        if (memcmp(a_list_s, buf, 4) != 0)
            TEST_ERROR;
        free(buf);
        buf = NULL;

        /* Write the latter half of buffer at addr 0 (more than one page) */
        half_size = a_list_size_s / 2;
        buf_size  = a_list_size_s - half_size;
        if (buf_size <= onion_info.page_size)
            TEST_ERROR;
        if (H5FDset_eoa(vfile_rw, H5FD_MEM_DRAW, buf_size) < 0)
            TEST_ERROR;
        if (H5FDwrite(vfile_rw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, buf_size, a_list_s + half_size) < 0)
            TEST_ERROR;

        /* Verify logical file contents. */
        if (NULL == (buf = malloc(buf_size * sizeof(char))))
            TEST_ERROR;
        if (H5FDread(vfile_rw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, buf_size, buf) < 0)
            TEST_ERROR;
        if (memcmp(a_list_s + half_size, buf, buf_size) != 0)
            TEST_ERROR;
        free(buf);
        buf = NULL;

        /* Overwrite existing data with entire buffer at addr 0 */
        if (H5FDset_eoa(vfile_rw, H5FD_MEM_DRAW, a_list_size_s) < 0)
            TEST_ERROR;
        if (H5FDwrite(vfile_rw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, a_list_size_s, a_list_s) < 0)
            TEST_ERROR;

        /* Verify logical file contents. */
        if (NULL == (buf = malloc(a_list_size_s * sizeof(char))))
            TEST_ERROR;
        if (H5FDread(vfile_rw, H5FD_MEM_DRAW, H5P_DEFAULT, 0, a_list_size_s, buf) < 0)
            TEST_ERROR;
        if (memcmp(a_list_s, buf, a_list_size_s) != 0)
            TEST_ERROR;
        free(buf);
        buf = NULL;
    } /* end if writing data to logical file */

    /*
     * CLOSED
     */

    if (H5FDclose(vfile_rw) < 0)
        TEST_ERROR;
    vfile_rw = NULL;

    /* Look at h5 file: should be known-empty */
    if (compare_file_bytes_exactly(paths->canon, onion_info.backing_fapl_id, 8,
                                   (const unsigned char *)"ONIONEOF") < 0)
        TEST_ERROR;

    /* Look at recovery file: should be gone */
    H5E_BEGIN_TRY
    {
        vfile_raw = H5FDopen(paths->recovery, H5F_ACC_RDONLY, onion_info.backing_fapl_id, HADDR_UNDEF);
    }
    H5E_END_TRY
    if (NULL != vfile_raw)
        TEST_ERROR;

    /* Inspect onion file */
    vfile_raw = H5FDopen(paths->onion, H5F_ACC_RDONLY, onion_info.backing_fapl_id, HADDR_UNDEF);
    if (NULL == vfile_raw)
        TEST_ERROR;

    filter.page_size                        = onion_info.page_size;
    filter.n_revisions                      = 1;
    filter.origin_eof                       = 0;
    filter.revisions[0].comment             = onion_info.comment;
    filter.revisions[0].n_index_entries     = (uint64_t)(-1); /* don't care */
    filter.revisions[0].revision_num        = 0;
    filter.revisions[0].parent_revision_num = 0;
    filter.revisions[0].logical_eof         = (true == with_initial_data) ? a_list_size_s : 0;

    if (verify_history_as_expected_onion(vfile_raw, &filter) < 0)
        TEST_ERROR;

    if (H5FDclose(vfile_raw) < 0)
        TEST_ERROR;
    vfile_raw = NULL;

    /* R/O open the file with Onion VFD; inspect logical file */

    vfile_ro = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == vfile_ro)
        TEST_ERROR;

    if (true == with_initial_data) {
        /* Initial revision contains data */
        if (H5FDget_eof(vfile_ro, H5FD_MEM_DRAW) != a_list_size_s)
            TEST_ERROR;
        if (H5FDget_eoa(vfile_ro, H5FD_MEM_DRAW) != 0)
            TEST_ERROR;
        if (H5FDset_eoa(vfile_ro, H5FD_MEM_DRAW, a_list_size_s) < 0)
            TEST_ERROR;
        if (NULL == (buf = malloc(a_list_size_s * 64 * sizeof(char))))
            TEST_ERROR;
        if (H5FDread(vfile_ro, H5FD_MEM_DRAW, H5P_DEFAULT, 0, a_list_size_s, buf) < 0)
            TEST_ERROR;
        if (memcmp(a_list_s, buf, a_list_size_s) != 0)
            TEST_ERROR;
        free(buf);
        buf = NULL;
    }
    else {
        /* Initial revision has no data */
        if (H5FDget_eoa(vfile_ro, H5FD_MEM_DRAW) != 0)
            TEST_ERROR;
        if (H5FDget_eof(vfile_ro, H5FD_MEM_DRAW) != 0)
            TEST_ERROR;
    }

    if (H5FDclose(vfile_ro) < 0)
        TEST_ERROR;
    vfile_ro = NULL;

    /*
     * CLEANUP
     */

    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    free(buf);

    if (vfile_raw != NULL)
        (void)H5FDclose(vfile_raw);
    if (vfile_rw != NULL)
        (void)H5FDclose(vfile_rw);
    if (vfile_ro != NULL)
        (void)H5FDclose(vfile_ro);

    H5E_BEGIN_TRY
    {
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    return -1;
} /* end test_create_oniontarget() */

/*-----------------------------------------------------------------------------
 *
 * Function:    test_several_revisions_with_logical_gaps()
 *
 * Purpose:     Test the ability of the Onion VFD to create a valid
 *              'onionized' file.
 *
 *              When `truncate_canonical` is false, the canonical file is
 *              nonexistent on the backing store on onion-creation.
 *              When `truncate_canonical` is true, a canonical file is created
 *              on the backing store with known contents, which are to be
 *              truncated on onion-creation.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *
 *-----------------------------------------------------------------------------
 */
static int
test_several_revisions_with_logical_gaps(void)
{
    const char             *basename   = "somesuch";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,      /* force_write_open */
        0,      /* flags */
        "first" /* comment          */
    };
    H5FD_t                 *file = NULL; /* Onion virtual file for read/write */
    struct expected_history filter;
    unsigned char          *buf = NULL;
    struct revise_revision  about[4];
    H5FD_onion_history_t    history_out;
    size_t                  i     = 0;
    haddr_t                 size  = 0;
    uint64_t                a_off = ONION_TEST_PAGE_SIZE_5 + 7; /* 39 */
    uint64_t                b_off = (((a_off + a_list_size_s + ONION_TEST_PAGE_SIZE_5 - 1) >> 5) << 5) +
                     ONION_TEST_PAGE_SIZE_5 + 7; /* full page between */

    TESTING("multiple revisions with gaps and overlap");

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

    history_out.version     = H5FD_ONION_HISTORY_VERSION_CURR;
    history_out.n_revisions = 0;
    history_out.record_locs = NULL;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /* Empty first revision */
    about[0].truncate     = true;
    about[0].revision_num = H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST;
    about[0].comment      = "first";
    about[0].n_writes     = 0;

    about[1].truncate         = false;
    about[1].revision_num     = H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST;
    about[1].comment          = "second";
    about[1].n_writes         = 1;
    about[1].writes[0].offset = a_off;
    about[1].writes[0].size   = a_list_size_s;
    about[1].writes[0].buf    = a_list_s;

    about[2].truncate         = false;
    about[2].revision_num     = H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST;
    about[2].comment          = "third";
    about[2].n_writes         = 1; /* TODO: several writes */
    about[2].writes[0].offset = b_off;
    about[2].writes[0].size   = b_list_size_s;
    about[2].writes[0].buf    = b_list_s;

    about[3].truncate         = false;
    about[3].revision_num     = H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST;
    about[3].comment          = "fourth";
    about[3].n_writes         = 1;
    about[3].writes[0].offset = 0;
    about[3].writes[0].size   = a_list_size_s;
    about[3].writes[0].buf    = a_list_s;

    if (do_onion_open_and_writes(paths->canon, &onion_info, 4, about) < 0)
        TEST_ERROR;

    /* Inspect logical file */

    /* THIS IS THE INITIAL FILE, SHOULD ONLY HAVE 8 BYTES */
    onion_info.revision_num = 0;
    fapl_id                 = H5Pcreate(H5P_FILE_ACCESS);
    if (H5I_INVALID_HID == fapl_id)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;
    if (8 != H5FDget_eof(file, H5FD_MEM_DRAW)) {
        printf("\nEOF is not zero, it is: %" PRIuHADDR "\n", H5FDget_eof(file, H5FD_MEM_DRAW));
        TEST_ERROR;
    }
    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /* Empty first revision */
    onion_info.revision_num = 1;
    fapl_id                 = H5Pcreate(H5P_FILE_ACCESS);
    if (H5I_INVALID_HID == fapl_id)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;
    if (0 != H5FDget_eof(file, H5FD_MEM_DRAW)) {
        printf("\nEOF is not zero, it is: %" PRIuHADDR "\n", H5FDget_eof(file, H5FD_MEM_DRAW));
        TEST_ERROR;
    }
    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /* One offset block in second revision */
    onion_info.revision_num = 2;
    fapl_id                 = H5Pcreate(H5P_FILE_ACCESS);
    if (H5I_INVALID_HID == fapl_id)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;
    size = a_off + a_list_size_s;
    if (size != H5FDget_eof(file, H5FD_MEM_DRAW)) {
        printf("\nEOF is not %" PRIuHADDR ", it is: %" PRIuHADDR "\n", size,
               H5FDget_eof(file, H5FD_MEM_DRAW));
        TEST_ERROR;
    }
    if (NULL == (buf = malloc(size * sizeof(unsigned char))))
        TEST_ERROR;
    if (H5FDset_eoa(file, H5FD_MEM_DRAW, size) < 0)
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, size, buf) < 0)
        TEST_ERROR;
    for (i = 0; i < a_off; i++) {
        if (0 != buf[i])
            TEST_ERROR;
    }
    if (memcmp(buf + a_off, a_list_s, a_list_size_s) != 0)
        TEST_ERROR;
    free(buf);
    buf = NULL;
    /* Repeat read at page offset; test possible read offset error */
    if (NULL == (buf = malloc(ONION_TEST_PAGE_SIZE_5 * sizeof(unsigned char))))
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, ONION_TEST_PAGE_SIZE_5, ONION_TEST_PAGE_SIZE_5, buf) < 0)
        TEST_ERROR;
    size = a_off - ONION_TEST_PAGE_SIZE_5;
    for (i = 0; i < size; i++) {
        if (0 != buf[i])
            TEST_ERROR;
    }
    if (memcmp(buf + size, a_list_s, ONION_TEST_PAGE_SIZE_5 - size) != 0)
        TEST_ERROR;
    free(buf);
    buf = NULL;
    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /* Two offset blocks in third revision */
    onion_info.revision_num = 3;
    fapl_id                 = H5Pcreate(H5P_FILE_ACCESS);
    if (H5I_INVALID_HID == fapl_id)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;
    size = b_off + b_list_size_s;
    if (size != H5FDget_eof(file, H5FD_MEM_DRAW))
        TEST_ERROR;
    if (NULL == (buf = malloc(size * sizeof(unsigned char))))
        TEST_ERROR;
    if (H5FDset_eoa(file, H5FD_MEM_DRAW, size) < 0)
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, size, buf) < 0)
        TEST_ERROR;
    for (i = 0; i < a_off; i++) {
        if (0 != buf[i])
            TEST_ERROR;
    }
    if (memcmp(buf + a_off, a_list_s, a_list_size_s) != 0)
        TEST_ERROR;
    for (i = a_off + a_list_size_s; i < b_off; i++) {
        if (0 != buf[i])
            TEST_ERROR;
    }
    if (memcmp(buf + b_off, b_list_s, b_list_size_s) != 0)
        TEST_ERROR;
    free(buf);
    buf = NULL;
    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /* From start and partial overwrite in fourth revision */
    onion_info.revision_num = 4;
    fapl_id                 = H5Pcreate(H5P_FILE_ACCESS);
    if (H5I_INVALID_HID == fapl_id)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;
    size = b_off + b_list_size_s;
    if (size != H5FDget_eof(file, H5FD_MEM_DRAW))
        TEST_ERROR;
    buf = (unsigned char *)malloc(sizeof(unsigned char) * size);
    if (NULL == buf)
        TEST_ERROR;
    if (H5FDset_eoa(file, H5FD_MEM_DRAW, size) < 0)
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, size, buf) < 0)
        TEST_ERROR;
    if (memcmp(buf, a_list_s, a_list_size_s) != 0)
        TEST_ERROR;
    if (memcmp(buf + a_list_size_s, a_list_s + a_list_size_s - a_off, a_off) != 0)
        TEST_ERROR;
    for (i = a_off + a_list_size_s; i < b_off; i++) {
        if (0 != buf[i])
            TEST_ERROR;
    }
    if (memcmp(buf + b_off, b_list_s, b_list_size_s) != 0)
        TEST_ERROR;
    free(buf);
    buf = NULL;
    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /* No fifth revision */

    /* Inspect history construction */

    file = H5FDopen(paths->onion, H5F_ACC_RDONLY, onion_info.backing_fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;

    filter.page_size   = onion_info.page_size;
    filter.n_revisions = 4;
    filter.origin_eof  = 0;

    filter.revisions[0].comment             = "first";
    filter.revisions[0].n_index_entries     = 0;
    filter.revisions[0].revision_num        = 0;
    filter.revisions[0].parent_revision_num = 0;
    filter.revisions[0].logical_eof         = 0;

    filter.revisions[1].comment             = "second";
    filter.revisions[1].n_index_entries     = (a_list_size_s + ONION_TEST_PAGE_SIZE_5 - 1) >> 5;
    filter.revisions[1].revision_num        = 1;
    filter.revisions[1].parent_revision_num = 0;
    filter.revisions[1].logical_eof         = a_off + a_list_size_s;

    filter.revisions[2].comment = "third";
    filter.revisions[2].n_index_entries =
        filter.revisions[1].n_index_entries + ((b_list_size_s + ONION_TEST_PAGE_SIZE_5 - 1) >> 5);
    filter.revisions[2].revision_num        = 2;
    filter.revisions[2].parent_revision_num = 1;
    filter.revisions[2].logical_eof         = b_off + b_list_size_s;

    filter.revisions[3].comment             = "fourth";
    filter.revisions[3].n_index_entries     = filter.revisions[2].n_index_entries + 1;
    filter.revisions[3].revision_num        = 3;
    filter.revisions[3].parent_revision_num = 2;
    filter.revisions[3].logical_eof         = b_off + b_list_size_s;

    if (verify_history_as_expected_onion(file, &filter) < 0)
        TEST_ERROR;

    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;

    /* CLEANUP */

    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    free(history_out.record_locs);
    free(buf);

    if (file != NULL)
        (void)H5FDclose(file);

    H5E_BEGIN_TRY
    {
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    return -1;
} /* end test_several_revisions_with_logical_gaps() */

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 *
 * Function:    do_onion_open_and_writes
 *
 * Purpose:     Automate the process of creating/opening a file and performing
 *              a series of writes.
 *
 * Return:      Success : 0
 *              Failure : -1
 *
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
// TODO: Modify to create initial file without onion
static int
do_onion_open_and_writes(const char *filename, H5FD_onion_fapl_info_t *onion_info_p, size_t n_ops,
                         struct revise_revision *about)
{
    hid_t          fapl_id = H5I_INVALID_HID;
    H5FD_t        *file    = NULL; /* Onion virtual file for read/write */
    unsigned char *buf_vfy = NULL;
    size_t         i       = 0;

    for (i = 0; i < n_ops; i++) {
        size_t       j     = 0;
        unsigned int flags = H5F_ACC_RDWR;

        if (i != 0 && about[i].truncate == true)
            goto error;

        if (true == about[i].truncate)
            flags |= H5F_ACC_CREAT | H5F_ACC_TRUNC;

        onion_info_p->revision_num = about[i].revision_num;
        if (about[i].comment != NULL) {
            j = MIN(strlen(about[i].comment), H5FD_ONION_FAPL_INFO_COMMENT_MAX_LEN);
            memcpy(onion_info_p->comment, about[i].comment, j);
        }
        onion_info_p->comment[j] = '\0';
        fapl_id                  = H5Pcreate(H5P_FILE_ACCESS);
        if (H5I_INVALID_HID == fapl_id)
            goto error;
        if (H5Pset_fapl_onion(fapl_id, onion_info_p) < 0)
            goto error;
        file = H5FDopen(filename, flags, fapl_id, HADDR_UNDEF);
        if (NULL == file)
            goto error;

        for (j = 0; j < about[i].n_writes; j++) {
            struct write_info *wi = &about[i].writes[j];

            /* Write to file */
            if (H5FDget_eoa(file, H5FD_MEM_DRAW) < wi->offset + wi->size &&
                H5FDset_eoa(file, H5FD_MEM_DRAW, wi->offset + wi->size) < 0)
                TEST_ERROR;
            if (H5FDwrite(file, H5FD_MEM_DRAW, H5P_DEFAULT, wi->offset, wi->size, wi->buf) < 0)
                TEST_ERROR;
            /* Verify write as expected */
            if (NULL == (buf_vfy = malloc(wi->size * sizeof(unsigned char))))
                TEST_ERROR;
            if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, wi->offset, wi->size, buf_vfy) < 0)
                TEST_ERROR;
            if (memcmp(buf_vfy, wi->buf, wi->size) != 0) {
                const unsigned char *_buf = wi->buf;
                size_t               z    = 0;
                puts("i  exp  act");
                for (z = 0; z < wi->size; z++)
                    printf("%02zx %c %c\n", z, _buf[z], buf_vfy[z]);
                fflush(stdout);
                TEST_ERROR;
            }
            free(buf_vfy);
            buf_vfy = NULL;
        } /* end for each write */

        if (H5FDclose(file) < 0)
            goto error;
        file = NULL;
        if (H5Pclose(fapl_id) < 0)
            goto error;
        fapl_id = H5I_INVALID_HID;
    } /* end for each open-close cycle */

    return 0;

error:
    if (file != NULL)
        (void)H5FDclose(file);

    H5E_BEGIN_TRY
    {
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    free(buf_vfy);

    return -1;
} /* end do_onion_open_and_writes() */

/*-----------------------------------------------------------------------------
 *
 * Function:    test_page_aligned_history_create()
 *
 * Purpose:     Verify that, when specified in FAPL on onionization/creation,
 *              All history writes are aligned to page-size boundaries.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *
 *-----------------------------------------------------------------------------
 */
static int
test_page_aligned_history_create(void)
{
    const char             *basename   = "somesuch";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0, /* force_write_open */
        H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT,
        "initial commit" /* comment          */
    };
    H5FD_t                *file = NULL; /* Onion virtual file for read/write */
    unsigned char         *buf  = NULL;
    struct revise_revision about[2];
    H5FD_onion_header_t    hdr_out;
    H5FD_onion_history_t   history_out;
    size_t                 i     = 0;
    uint64_t               a_off = b_list_size_s - a_list_size_s;

    TESTING("page-aligned history on onion-created file");

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

    hdr_out.version         = H5FD_ONION_HEADER_VERSION_CURR;
    history_out.version     = H5FD_ONION_HISTORY_VERSION_CURR;
    history_out.n_revisions = 0;
    history_out.record_locs = NULL;

    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    about[0].truncate         = true;
    about[0].revision_num     = H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST;
    about[0].comment          = "initial_commit";
    about[0].n_writes         = 1;
    about[0].writes[0].offset = 0;
    about[0].writes[0].size   = b_list_size_s;
    about[0].writes[0].buf    = b_list_s;

    about[1].truncate         = false;
    about[1].revision_num     = H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST;
    about[1].comment          = "second";
    about[1].n_writes         = 1;
    about[1].writes[0].offset = a_off;
    about[1].writes[0].size   = a_list_size_s;
    about[1].writes[0].buf    = a_list_s;

    if (do_onion_open_and_writes(paths->canon, &onion_info, 2, about) < 0)
        TEST_ERROR;

    /* Inspect logical file */
    if (NULL == (buf = malloc(b_list_size_s * sizeof(unsigned char))))
        TEST_ERROR;
    file = H5FDopen(paths->canon, H5F_ACC_RDONLY, fapl_id, HADDR_UNDEF);
    if (NULL == file)
        TEST_ERROR;
    if (b_list_size_s != H5FDget_eof(file, H5FD_MEM_DRAW))
        TEST_ERROR;
    if (H5FDset_eoa(file, H5FD_MEM_DRAW, b_list_size_s) < 0)
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, b_list_size_s, buf) < 0)
        TEST_ERROR;
    if (memcmp(a_list_s, buf + a_off, a_list_size_s) != 0) {
        size_t k;
        printf("aoff: %" PRIu64 "\n", a_off);
        puts("i exp act");
        for (k = 0; k < b_list_size_s; k++) {
            printf("%3zu:: %c : %c\n", k, (k < a_off) ? ' ' : a_list_s[k - a_off], buf[k]);
        }
        fflush(stdout);
        TEST_ERROR;
    }
    if (memcmp(b_list_s, buf, a_off) != 0) {
        size_t k;
        printf("aoff: %" PRIu64 "\n", a_off);
        puts("i exp act");
        for (k = 0; k < b_list_size_s; k++) {
            printf("%3zu:: %c : %c\n", k, (k < a_off) ? b_list_s[k] : ' ', buf[k]);
        }
        fflush(stdout);
        TEST_ERROR;
    }
    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;
    free(buf);
    buf = NULL;

    /* Inspect history construction */

    if (NULL == (file = H5FDopen(paths->onion, H5F_ACC_RDONLY, onion_info.backing_fapl_id, HADDR_UNDEF)))
        TEST_ERROR;
    if (H5FDset_eoa(file, H5FD_MEM_DRAW, H5FDget_eof(file, H5FD_MEM_DRAW)) < 0)
        TEST_ERROR;

    if (NULL == (buf = malloc(H5FD_ONION_ENCODED_SIZE_HEADER)))
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, 0, H5FD_ONION_ENCODED_SIZE_HEADER, buf) < 0)
        TEST_ERROR;
    if (H5FD__onion_header_decode(buf, &hdr_out) != H5FD_ONION_ENCODED_SIZE_HEADER)
        TEST_ERROR;
    if (hdr_out.history_addr & ((1 << 5) - 1)) /* 5::PAGE_SIZE_5 */
        TEST_ERROR;
    free(buf);
    buf = NULL;

    if (NULL == (buf = malloc(hdr_out.history_size)))
        TEST_ERROR;
    if (H5FDread(file, H5FD_MEM_DRAW, H5P_DEFAULT, hdr_out.history_addr, hdr_out.history_size, buf) < 0)
        TEST_ERROR;
    if (H5FD__onion_history_decode(buf, &history_out) != hdr_out.history_size)
        TEST_ERROR;
    if (history_out.n_revisions != 2)
        TEST_ERROR;
    history_out.record_locs = calloc(history_out.n_revisions, sizeof(H5FD_onion_record_loc_t));
    if (NULL == history_out.record_locs)
        TEST_ERROR;
    if (H5FD__onion_history_decode(buf, &history_out) != hdr_out.history_size)
        TEST_ERROR;
    free(buf);
    buf = NULL;

    for (i = 0; i < history_out.n_revisions; i++) {
        H5FD_onion_record_loc_t *rloc = &history_out.record_locs[i];
        if (rloc->phys_addr & ((1 << 5) - 1)) /* 5::PAGE_SIZE_5 */
            TEST_ERROR;
        /* TODO: check phys_addr of each page entry? */
    }

    free(history_out.record_locs);
    history_out.record_locs = NULL;

    if (H5FDclose(file) < 0)
        TEST_ERROR;
    file = NULL;

    /* CLEANUP */

    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    free(buf);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    free(history_out.record_locs);
    free(buf);

    if (file != NULL)
        (void)H5FDclose(file);

    H5E_BEGIN_TRY
    {
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    return -1;
} /* end test_page_aligned_history_create() */

/*-----------------------------------------------------------------------------
 * Function:    test_integration_create()
 *
 * Purpose:     Create and make multiple revisions in an HDF5 file.
 *
 * Return:      PASSED : 0
 *              FAILED : -1
 *-----------------------------------------------------------------------------
 */
static int
test_integration_create(void)
{
    const char             *basename   = "integration_2d.h5";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    hid_t                   file_id    = H5I_INVALID_HID;
    hid_t                   file       = H5I_INVALID_HID;
    hid_t                   space      = H5I_INVALID_HID;
    hid_t                   dset       = H5I_INVALID_HID;
    hid_t                   dcpl       = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation flags, was H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT */
        "initial commit" /* comment          */
    };
    hsize_t dims[2]    = {128, 256};
    hsize_t maxdims[2] = {H5S_UNLIMITED, H5S_UNLIMITED};
    hsize_t chunk[2]   = {4, 4};
    int     fillval;
    struct {
        int arr[128][256];
    } *wdata = NULL;
    struct {
        int arr[128][256];
    } *rdata = NULL;
    struct {
        int arr[128][256];
    } *dset_data = NULL;

    TESTING("onion-created two dimensional HDF5 file with revisions");

    /* SETUP */

    if (NULL == (wdata = calloc(1, sizeof(*wdata))))
        TEST_ERROR;
    if (NULL == (rdata = calloc(1, sizeof(*rdata))))
        TEST_ERROR;
    if (NULL == (dset_data = calloc(1, sizeof(*dset_data))))
        TEST_ERROR;

    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */

    /* Initialize data */
    for (int i = 0; i < 128; i++)
        for (int j = 0; j < 256; j++)
            wdata->arr[i][j] = i * j - j;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions */
    if ((space = H5Screate_simple(2, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create the dataset creation property list, and set the chunk
     * size
     */
    if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    if (H5Pset_chunk(dcpl, 2, chunk) < 0)
        TEST_ERROR;

    /* Set the fill value for the dataset */
    fillval = 99;
    if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
        TEST_ERROR;

    /* Set the allocation time to "early".  This way we can be sure
     * that reading from the dataset immediately after creation will
     * return the fill value.
     */
    if (H5Pset_alloc_time(dcpl, H5D_ALLOC_TIME_EARLY) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    /* Close and release resources */
    if (H5Pclose(dcpl) < 0)
        TEST_ERROR;
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < 128; i++)
        for (int j = 0; j < 256; j++)
            dset_data->arr[i][j] = i * 6 + j + 1;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < 128; i++)
        for (int j = 0; j < 256; j++)
            dset_data->arr[i][j] = i * 3 + j + 5;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    /* CLEANUP */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     *  Start to verify the revision
     *----------------------------------------------------------------------
     */
    /*----------------------------------------------------------------------
     * Verify the original file
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 0;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;

    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < 128; i++) {
        for (int j = 0; j < 256; j++) {
            int expected = i * j - j;
            if (rdata->arr[i][j] != expected) {
                printf("ERROR!!! Expected: %d, Got: %d\n", expected, rdata->arr[i][j]);
                fflush(stdout);
                TEST_ERROR;
            }
        }
    }

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Verify the first revision
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 1;
    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if (H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < 128; i++) {
        for (int j = 0; j < 256; j++) {
            int expected = i * 6 + j + 1;
            if (rdata->arr[i][j] != expected) {
                printf("ERROR!!! Expected: %d, Got: %d\n", expected, rdata->arr[i][j]);
                fflush(stdout);
                TEST_ERROR;
            }
        }
    }

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Verify the second revision
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 2;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if (H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < 128; i++) {
        for (int j = 0; j < 256; j++) {
            int expected = i * 3 + j + 5;
            if (rdata->arr[i][j] != expected) {
                printf("ERROR!!! Expected: %d, Got: %d\n", expected, rdata->arr[i][j]);
                fflush(stdout);
                TEST_ERROR;
            }
            else {
                fflush(stdout);
            }
        }
    }

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    free(wdata);
    free(rdata);
    free(dset_data);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    free(wdata);
    free(rdata);
    free(dset_data);

    return -1;
} /* end test_integration_create() */

static int
test_integration_create_simple(void)
{
    const char             *basename   = "integration_1d.h5";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation flags, was H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT */
        "initial commit" /* comment          */
    };
    hid_t   file_id    = H5I_INVALID_HID;
    hid_t   file       = H5I_INVALID_HID;
    hid_t   space      = H5I_INVALID_HID;
    hid_t   dset       = H5I_INVALID_HID;
    hid_t   dcpl       = H5I_INVALID_HID;
    hsize_t dims[2]    = {1, ONE_DIM_SIZE};
    hsize_t maxdims[2] = {1, ONE_DIM_SIZE};
    int     fillval;
    struct {
        int arr[ONE_DIM_SIZE];
    } *wdata = NULL;
    struct {
        int arr[ONE_DIM_SIZE];
    } *rdata = NULL;
    struct {
        int arr[ONE_DIM_SIZE];
    } *dset_data = NULL;

    TESTING("onion-created one-dimensional HDF5 file with revisions");

    /* Setup */
    if (NULL == (wdata = calloc(1, sizeof(*wdata))))
        TEST_ERROR;
    if (NULL == (rdata = calloc(1, sizeof(*rdata))))
        TEST_ERROR;
    if (NULL == (dset_data = calloc(1, sizeof(*dset_data))))
        TEST_ERROR;

    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */
    /* Initialize data */
    for (int i = 0; i < ONE_DIM_SIZE; i++)
        wdata->arr[i] = i;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions*/
    if ((space = H5Screate_simple(2, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create the dataset creation property list */
    if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;

    /* Set the fill value for the dataset */
    fillval = 99;
    if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
        TEST_ERROR;

    /* Set the allocation time to "early".  This way we can be sure
     * that reading from the dataset immediately after creation will
     * return the fill value.
     */
    if (H5Pset_alloc_time(dcpl, H5D_ALLOC_TIME_EARLY) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    /* Close everything */
    if (H5Pclose(dcpl) < 0)
        TEST_ERROR;
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i++)
        dset_data->arr[i] = i + ONE_DIM_SIZE;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i++)
        dset_data->arr[i] = i + 2048;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    /* CLEANUP */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Third revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i += 20)
        dset_data->arr[i] = i + 3072;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    /* CLEANUP */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     *  Start to verify the revision
     *----------------------------------------------------------------------
     */
    /*----------------------------------------------------------------------
     * Verify the second revision
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 2;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i += 20) {
        int expected = i + 2048;
        if (rdata->arr[i] != expected) {
            printf("ERROR!!! Expected: %d, Got: %d\n", expected, rdata->arr[i]);
            TEST_ERROR;
        }
    }

    /* Close everything */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    free(wdata);
    free(rdata);
    free(dset_data);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    free(wdata);
    free(rdata);
    free(dset_data);

    return -1;
} /* end test_integration_create_simple() */

static int
test_integration_create_delete_objects(void)
{
    const char             *basename   = "integration_objs.h5";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation flags, was H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT */
        "initial commit" /* comment          */
    };
    hid_t   group_id      = H5I_INVALID_HID;
    hid_t   attr_space_id = H5I_INVALID_HID;
    hid_t   attr_id       = H5I_INVALID_HID;
    hsize_t attr_dim[1]   = {4};

    hid_t   file    = H5I_INVALID_HID;
    hid_t   space   = H5I_INVALID_HID;
    hid_t   dset    = H5I_INVALID_HID;
    hid_t   dcpl    = H5I_INVALID_HID;
    hsize_t dims[2] = {4, 4}, maxdims[2] = {H5S_UNLIMITED, H5S_UNLIMITED}, chunk[2] = {4, 4};
    int     wdata[4][4], /* Write buffer */
        fillval, i, j;

    TESTING("onion-created HDF5 file with revisions testing addition and deletion of objects");

    /* Set up */
    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */

    /* Initialize data */
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++)
            wdata[i][j] = i + j;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions */
    if ((space = H5Screate_simple(2, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create the dataset creation property list, and set the chunk
     * size
     */
    if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    if (H5Pset_chunk(dcpl, 2, chunk) < 0)
        TEST_ERROR;

    /* Set the fill value for the dataset */
    fillval = 99;
    if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
        TEST_ERROR;

    /* Set the allocation time to "early".  This way we can be sure
     * that reading from the dataset immediately after creation will
     * return the fill value.
     */
    if (H5Pset_alloc_time(dcpl, H5D_ALLOC_TIME_EARLY) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and add a dataset (DS2) to the file
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS2", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and remove the dataset (DS2),
     * which was added during the first revision.
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if (H5Ldelete(file, "DS2", H5P_DEFAULT) < 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Third revision: open the file with Onion VFD and add an attribute to the file
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Create dataspace for attribute */
    if ((attr_space_id = H5Screate_simple(1, attr_dim, NULL)) < 0)
        TEST_ERROR;

    if ((attr_id =
             H5Acreate2(file, "file_attribute", H5T_STD_I32LE, attr_space_id, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Sclose(attr_space_id) < 0)
        TEST_ERROR;
    if (H5Aclose(attr_id) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Fourth revision: open the file with Onion VFD and delete the attribute
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if (H5Adelete(file, "file_attribute") < 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Fifth revision: open the file with Onion VFD and add a group to the file
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if ((group_id = H5Gcreate2(file, "new_group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Gclose(group_id) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Sixth revision: open the file with Onion VFD and delete the newly added group
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if (H5Ldelete(file, "new_group", H5P_DEFAULT) < 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     *  Start to verify the revision
     *----------------------------------------------------------------------
     */
    /*----------------------------------------------------------------------
     * Verify the first revision: it should have the second dataset (DS2)
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 1;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* The second dataset (DS2) should exist */
    if (H5Lexists(file, "DS2", H5P_DEFAULT) <= 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*------------------------------------------------------------------------
     * Verify the second revision: the second dataset (DS2) should be removed
     *------------------------------------------------------------------------
     */
    onion_info.revision_num = 2;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* The second dataset (DS2) shouldn't exist */
    if (H5Lexists(file, "DS2", H5P_DEFAULT) > 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*-------------------------------------------------------------------------
     * Verify the third revision: the file attribute (file_attribute) should exist
     *-------------------------------------------------------------------------
     */
    onion_info.revision_num = 3;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* The file attribute should exist */
    if (H5Aexists(file, "file_attribute") <= 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*-------------------------------------------------------------------------
     * Verify the fourth revision: the file attribute (file_attribute) should be removed
     *-------------------------------------------------------------------------
     */
    onion_info.revision_num = 4;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* The file attribute should be removed */
    if (H5Aexists(file, "file_attribute") > 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*-------------------------------------------------------------------------
     * Verify the fifth revision: the group (new_group) should exist
     *-------------------------------------------------------------------------
     */
    onion_info.revision_num = 5;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* The new group should exist */
    if (H5Lexists(file, "new_group", H5P_DEFAULT) <= 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*-------------------------------------------------------------------------
     * Verify the sixth revision: the group (new_group) should be removed
     *-------------------------------------------------------------------------
     */
    onion_info.revision_num = 6;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* The new group should exist */
    if (H5Lexists(file, "new_group", H5P_DEFAULT) > 0)
        TEST_ERROR;

    /* Close everything */
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(dcpl) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file);
        H5Pclose(fapl_id);
        H5Sclose(space);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    return -1;
} /* end test_integration_create_delete_objects */

static int
test_integration_dset_extension(void)
{
    const char             *basename   = "integration_dset_ext.h5";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation flags, was H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT */
        "initial commit" /* comment          */
    };
    hid_t   file       = H5I_INVALID_HID;
    hid_t   space      = H5I_INVALID_HID;
    hid_t   dset_space = H5I_INVALID_HID;
    hid_t   dset       = H5I_INVALID_HID;
    hid_t   dcpl       = H5I_INVALID_HID;
    hsize_t dims[2]    = {4, 4};
    hsize_t maxdims[2] = {H5S_UNLIMITED, H5S_UNLIMITED};
    hsize_t chunk[2]   = {4, 4};
    hsize_t size[2];
    hsize_t offset[2];
    int     wdata[4][4]; /* Write buffer */
    int     fillval;
    int     rdata[4][4]; /* Read buffer */

    TESTING("onion-created HDF5 file with revisions testing dataset extension");

    /* Setup */
    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */

    /* Initialize data */
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            wdata[i][j] = i + j;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions*/
    if ((space = H5Screate_simple(2, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create the dataset creation property list, and set the chunk
     * size
     */
    if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    if (H5Pset_chunk(dcpl, 2, chunk) < 0)
        TEST_ERROR;

    /* Set the fill value for the dataset */
    fillval = 99;
    if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
        TEST_ERROR;

    /* Set the allocation time to "early".  This way we can be sure
     * that reading from the dataset immediately after creation will
     * return the fill value.
     */
    if (H5Pset_alloc_time(dcpl, H5D_ALLOC_TIME_EARLY) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and extend the dataset
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Open the dataset */
    if ((dset = H5Dopen2(file, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Extend the dataset and double the rows */
    size[0] = 2 * dims[0];
    size[1] = dims[1];
    if (H5Dset_extent(dset, size) < 0)
        TEST_ERROR;

    if ((dset_space = H5Dget_space(dset)) < 0)
        TEST_ERROR;

    offset[0] = dims[0];
    offset[1] = 0;
    if (H5Sselect_hyperslab(dset_space, H5S_SELECT_SET, offset, NULL, dims, NULL) < 0)
        TEST_ERROR;

    /* Write the data to the dataset. */
    if (H5Dwrite(dset, H5T_NATIVE_INT, space, dset_space, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Sclose(dset_space) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and shrink the dataset
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Open the dataset */
    if ((dset = H5Dopen2(file, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Extend the dataset and shrink back the size */
    if (H5Dset_extent(dset, dims) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     *  Start to verify the revision
     *----------------------------------------------------------------------
     */
    /*----------------------------------------------------------------------
     * Verify the first revision: it should have the extended data
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 1;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Open the dataset */
    if ((dset = H5Dopen2(file, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if ((dset_space = H5Dget_space(dset)) < 0)
        TEST_ERROR;

    offset[0] = dims[0];
    offset[1] = 0;
    if (H5Sselect_hyperslab(dset_space, H5S_SELECT_SET, offset, NULL, dims, NULL) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_NATIVE_INT, space, dset_space, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            if (rdata[i][j] != wdata[i][j])
                TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Verify the second revision: it should have the original data
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 2;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Open the dataset */
    dset = H5Dopen2(file, "DS1", H5P_DEFAULT);

    if ((dset_space = H5Dget_space(dset)) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_NATIVE_INT, space, dset_space, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            if (rdata[i][j] != wdata[i][j])
                TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /* Close and release resources. */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(dcpl) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file);
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    return -1;
} /* end test_integration_dset_extension */

static int
test_integration_ctl(void)
{
    const char             *basename   = "integration_ctl.h5";
    hid_t                   file       = H5I_INVALID_HID;
    hid_t                   space      = H5I_INVALID_HID;
    hid_t                   dset       = H5I_INVALID_HID;
    hid_t                   dcpl       = H5I_INVALID_HID;
    hid_t                   fapl_id    = H5I_INVALID_HID;
    hsize_t                 dims[2]    = {4, 4};
    hsize_t                 maxdims[2] = {H5S_UNLIMITED, H5S_UNLIMITED};
    hsize_t                 chunk[2]   = {4, 4};
    int                     wdata[4][4]; /* Write buffer */
    int                     fillval;
    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation flags, was H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT */
        "initial commit" /* comment */
    };
    uint64_t revision_count;

    TESTING("onion-created HDF5 file with revisions testing H5FDctl");

    /* Set up */
    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */

    /* Initialize data */
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            wdata[i][j] = i + j;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions */
    if ((space = H5Screate_simple(2, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create the dataset creation property list, and set the chunk size */
    if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    if (H5Pset_chunk(dcpl, 2, chunk) < 0)
        TEST_ERROR;

    /* Set the fill value for the dataset */
    fillval = 99;
    if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
        TEST_ERROR;

    /* Set the allocation time to "early".  This way we can be sure
     * that reading from the dataset immediately after creation will
     * return the fill value.
     */
    if (H5Pset_alloc_time(dcpl, H5D_ALLOC_TIME_EARLY) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and add a dataset (DS2) to the file
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS2", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and remove the dataset (DS2),
     * which was added during the first revision.
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if (H5Ldelete(file, "DS2", H5P_DEFAULT) < 0)
        TEST_ERROR;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     *  Start to verify the number of revisions
     *----------------------------------------------------------------------
     */
    if (H5FDonion_get_revision_count(basename, fapl_id, &revision_count) < 0)
        TEST_ERROR;

    if (2 != revision_count)
        TEST_ERROR;

    /* Close and release resources */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Pclose(dcpl) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file);
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    return -1;
}

static int
test_integration_reference(void)
{
    const char *basename   = "integration_refer.h5";
    hid_t       file       = H5I_INVALID_HID;
    hid_t       group      = H5I_INVALID_HID;
    hid_t       space      = H5I_INVALID_HID;
    hid_t       space2     = H5I_INVALID_HID;
    hid_t       space_ref  = H5I_INVALID_HID;
    hid_t       dset       = H5I_INVALID_HID;
    hid_t       dset2      = H5I_INVALID_HID;
    hid_t       fapl_id    = H5I_INVALID_HID;
    hsize_t     dims[2]    = {4, 4};
    hsize_t     dim_ref[1] = {2};
    int         wdata[4][4]; /* Write buffer */
    int         rdata[4][4]; /* Read buffer  */
    H5R_ref_t   wbuf[2];
    H5R_ref_t   rbuf[2];
    H5O_type_t  obj_type;
    hsize_t     start[2];
    hsize_t     stride[2];
    hsize_t     count[2];
    hsize_t     block[2];
    hsize_t     coord1[4][2]; /* Coordinates for point selection */
    hssize_t    nelmts;

    struct onion_filepaths *paths      = NULL;
    H5FD_onion_fapl_info_t  onion_info = {
        H5FD_ONION_FAPL_INFO_VERSION_CURR,
        H5I_INVALID_HID,               /* backing_fapl_id  */
        ONION_TEST_PAGE_SIZE_5,        /* page_size        */
        H5FD_ONION_STORE_TARGET_ONION, /* store_target     */
        H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST,
        0,               /* force_write_open */
        0,               /* creation flags, was H5FD_ONION_FAPL_INFO_CREATE_FLAG_ENABLE_PAGE_ALIGNMENT */
        "initial commit" /* comment */
    };

    TESTING("onion-created HDF5 file with revisions testing references");

    /* Set up */
    if ((onion_info.backing_fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */

    /* Initialize data */
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            wdata[i][j] = i + j;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

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

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and add a dataset (DS2)
     * of object references
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions */
    if ((space_ref = H5Screate_simple(1, dim_ref, NULL)) < 0)
        TEST_ERROR;

    /* Create the dataset of object references */
    if ((dset = H5Dcreate2(file, "DS2", H5T_STD_REF, space_ref, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create reference to dataset */
    if (H5Rcreate_object(file, "DS1", H5P_DEFAULT, &wbuf[0]) < 0)
        TEST_ERROR;

    if (H5Rget_obj_type3(&wbuf[0], H5P_DEFAULT, &obj_type) < 0)
        TEST_ERROR;

    if (obj_type != H5O_TYPE_DATASET)
        TEST_ERROR;

    /* Create reference to the root group */
    if (H5Rcreate_object(file, "/", H5P_DEFAULT, &wbuf[1]) < 0)
        TEST_ERROR;

    if (H5Rget_obj_type3(&wbuf[1], H5P_DEFAULT, &obj_type) < 0)
        TEST_ERROR;

    if (obj_type != H5O_TYPE_GROUP)
        TEST_ERROR;

    /* Write the object reference data to the dataset */
    if (H5Dwrite(dset, H5T_STD_REF, H5S_ALL, H5S_ALL, H5P_DEFAULT, wbuf) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    for (int i = 0; i < 2; i++)
        if (H5Rdestroy(&wbuf[i]) < 0)
            TEST_ERROR;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and add a dataset (DS3)
     * of region references
     *----------------------------------------------------------------------
     */
    if ((file = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    /* Create the dataset of region references */
    if ((dset = H5Dcreate2(file, "DS3", H5T_STD_REF, space_ref, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Select 2x4 hyperslab for first reference */
    start[0]  = 0;
    start[1]  = 0;
    stride[0] = 1;
    stride[1] = 1;
    count[0]  = 1;
    count[1]  = 1;
    block[0]  = 2;
    block[1]  = 4;

    /* Make a hyperslab selection of 2x4 elements */
    if (H5Sselect_hyperslab(space, H5S_SELECT_SET, start, stride, count, block) < 0)
        TEST_ERROR;

    /* Verify the number of selection */
    if ((nelmts = H5Sget_select_npoints(space)) != 8) {
        printf("Number of selected elements is supposed to be 8, but got %" PRIuHSIZE "\n", nelmts);
        TEST_ERROR;
    }

    /* Store first data region */
    if (H5Rcreate_region(file, "/DS1", space, H5P_DEFAULT, &wbuf[0]) < 0)
        TEST_ERROR;

    if (H5Rget_obj_type3(&wbuf[0], H5P_DEFAULT, &obj_type) < 0)
        TEST_ERROR;

    if (obj_type != H5O_TYPE_DATASET)
        TEST_ERROR;

    /* Select the sequence of four points for the second reference */
    coord1[0][0] = 0;
    coord1[0][1] = 0;
    coord1[1][0] = 1;
    coord1[1][1] = 1;
    coord1[2][0] = 2;
    coord1[2][1] = 2;
    coord1[3][0] = 3;
    coord1[3][1] = 3;

    if (H5Sselect_elements(space, H5S_SELECT_SET, 4, (const hsize_t *)coord1) < 0)
        TEST_ERROR;

    /* Store the second data region */
    if (H5Rcreate_region(file, "/DS1", space, H5P_DEFAULT, &wbuf[1]) < 0)
        TEST_ERROR;

    if (H5Rget_obj_type3(&wbuf[1], H5P_DEFAULT, &obj_type) < 0)
        TEST_ERROR;

    if (obj_type != H5O_TYPE_DATASET)
        TEST_ERROR;

    /* Write the region reference data to the dataset */
    if (H5Dwrite(dset, H5T_STD_REF, H5S_ALL, H5S_ALL, H5P_DEFAULT, wbuf) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    for (int i = 0; i < 2; i++)
        if (H5Rdestroy(&wbuf[i]) < 0)
            TEST_ERROR;

    /*----------------------------------------------------------------------
     *  Start to verify the revisions
     *----------------------------------------------------------------------
     */
    /*----------------------------------------------------------------------
     * Verify the first revision: it should have the object references
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 1;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if ((file = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;

    /* Open the dataset of the object references */
    if ((dset = H5Dopen2(file, "DS2", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_STD_REF, H5S_ALL, H5S_ALL, H5P_DEFAULT, rbuf) < 0)
        TEST_ERROR;

    /* Open the referenced dataset and check the data */
    if ((dset2 = H5Ropen_object(&rbuf[0], H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Dread(dset2, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            int expected = i + j;
            if (rdata[i][j] != expected) {
                printf("ERROR!!! Expected: %d, Got: %d\n", expected, rdata[i][j]);
                TEST_ERROR;
            }
        }
    }

    /* Open the referenced group and make sure it's a group object */
    if ((group = H5Ropen_object(&rbuf[1], H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5I_GROUP != H5Iget_type(group))
        TEST_ERROR;

    if (H5Gclose(group) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;

    if (H5Dclose(dset2) < 0)
        TEST_ERROR;
    dset2 = H5I_INVALID_HID;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    for (int i = 0; i < 2; i++)
        if (H5Rdestroy(&rbuf[i]) < 0)
            TEST_ERROR;

    /*----------------------------------------------------------------------
     * Verify the second revision: it should have the region references
     *----------------------------------------------------------------------
     */
    onion_info.revision_num = 2;

    if (H5Pset_fapl_onion(fapl_id, &onion_info) < 0)
        TEST_ERROR;

    if ((file = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;

    /* Open the dataset of the region reference */
    if ((dset = H5Dopen2(file, "DS3", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_STD_REF, H5S_ALL, H5S_ALL, H5P_DEFAULT, rbuf) < 0)
        TEST_ERROR;

    /* Get the hyperslab selection and check the referenced region of the dataset */
    if ((space2 = H5Ropen_region(&rbuf[0], H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if ((nelmts = H5Sget_select_npoints(space2)) != 8) {
        printf("Number of selected elements is supposed to be 8, but got %" PRIuHSIZE "\n", nelmts);
        TEST_ERROR;
    }

    if (H5Sclose(space2) < 0)
        TEST_ERROR;
    space2 = H5I_INVALID_HID;

    /* Get the element selection and check the referenced region of the dataset */
    if ((space2 = H5Ropen_region(&rbuf[1], H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if ((nelmts = H5Sget_select_npoints(space2)) != 4) {
        printf("Number of selected elements is supposed to be 4, but got %" PRIuHSIZE "\n", nelmts);
        TEST_ERROR;
    }

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;

    if (H5Sclose(space2) < 0)
        TEST_ERROR;
    space2 = H5I_INVALID_HID;

    if (H5Fclose(file) < 0)
        TEST_ERROR;
    file = H5I_INVALID_HID;

    for (int i = 0; i < 2; i++)
        if (H5Rdestroy(&rbuf[i]) < 0)
            TEST_ERROR;

    /* Close and release resources */
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Sclose(space_ref) < 0)
        TEST_ERROR;
    if (H5Pclose(onion_info.backing_fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file);
        H5Pclose(fapl_id);
        H5Pclose(onion_info.backing_fapl_id);
    }
    H5E_END_TRY

    return -1;
}

static int
test_integration_create_by_name(void)
{
    const char             *basename   = "integration_by_name.h5";
    hid_t                   fapl_id    = H5I_INVALID_HID;
    struct onion_filepaths *paths      = NULL;
    hid_t                   file_id    = H5I_INVALID_HID;
    hid_t                   file       = H5I_INVALID_HID;
    hid_t                   space      = H5I_INVALID_HID;
    hid_t                   dset       = H5I_INVALID_HID;
    hid_t                   dcpl       = H5I_INVALID_HID;
    hsize_t                 dims[2]    = {1, ONE_DIM_SIZE};
    hsize_t                 maxdims[2] = {1, ONE_DIM_SIZE};
    int                     fillval;
    struct {
        int arr[ONE_DIM_SIZE];
    } *wdata = NULL;
    struct {
        int arr[ONE_DIM_SIZE];
    } *rdata = NULL;
    struct {
        int arr[ONE_DIM_SIZE];
    } *dset_data = NULL;

    TESTING("H5Pset_driver_by_name");

    /* Setup */
    if (NULL == (wdata = calloc(1, sizeof(*wdata))))
        TEST_ERROR;
    if (NULL == (rdata = calloc(1, sizeof(*rdata))))
        TEST_ERROR;
    if (NULL == (dset_data = calloc(1, sizeof(*dset_data))))
        TEST_ERROR;

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

    /* Use H5Pset_driver_by_name to enable the Onion VFD */
    if (H5Pset_driver_by_name(fapl_id, "onion", "{revision_num: H5FD_ONION_FAPL_INFO_REVISION_ID_LATEST}") <
        0)
        TEST_ERROR;

    if (NULL == (paths = onion_filepaths_init(basename)))
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);

    /*----------------------------------------------------------------------
     * Create the skeleton file (create the file without Onion VFD)
     *----------------------------------------------------------------------
     */
    /* Initialize data */
    for (int i = 0; i < ONE_DIM_SIZE; i++)
        wdata->arr[i] = i;

    /* Create a new file using the default properties */
    if ((file = H5Fcreate(paths->canon, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create dataspace with unlimited dimensions*/
    if ((space = H5Screate_simple(2, dims, maxdims)) < 0)
        TEST_ERROR;

    /* Create the dataset creation property list */
    if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;

    /* Set the fill value for the dataset */
    fillval = 99;
    if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
        TEST_ERROR;

    /* Set the allocation time to "early".  This way we can be sure
     * that reading from the dataset immediately after creation will
     * return the fill value.
     */
    if (H5Pset_alloc_time(dcpl, H5D_ALLOC_TIME_EARLY) < 0)
        TEST_ERROR;

    /* Create the dataset using the dataset creation property list */
    if ((dset = H5Dcreate2(file, "DS1", H5T_STD_I32LE, space, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Write the data to the dataset */
    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, wdata) < 0)
        TEST_ERROR;

    /* Close everything */
    if (H5Pclose(dcpl) < 0)
        TEST_ERROR;
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Sclose(space) < 0)
        TEST_ERROR;
    if (H5Fclose(file) < 0)
        TEST_ERROR;

    /*----------------------------------------------------------------------
     * First revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;

    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i++)
        dset_data->arr[i] = i + ONE_DIM_SIZE;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Second revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i++)
        dset_data->arr[i] = i + 2048;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    /* CLEANUP */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     * Third revision: open the file with Onion VFD and change the data
     *----------------------------------------------------------------------
     */
    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDWR, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i += 20)
        dset_data->arr[i] = i + 3072;

    if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, dset_data) < 0)
        TEST_ERROR;

    /* CLEANUP */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    dset = H5I_INVALID_HID;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    file_id = H5I_INVALID_HID;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;
    fapl_id = H5I_INVALID_HID;

    /*----------------------------------------------------------------------
     *  Start to verify the revision with H5Pset_driver_by_name
     *----------------------------------------------------------------------
     */
    /*----------------------------------------------------------------------
     * Verify the second revision
     *----------------------------------------------------------------------
     */
    if ((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;
    if (H5Pset_driver_by_name(fapl_id, "onion", "{revision_num: 2; page_size: 4; }") < 0)
        TEST_ERROR;

    if ((file_id = H5Fopen(paths->canon, H5F_ACC_RDONLY, fapl_id)) < 0)
        TEST_ERROR;
    if ((dset = H5Dopen2(file_id, "DS1", H5P_DEFAULT)) < 0)
        TEST_ERROR;

    if (H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata) < 0)
        TEST_ERROR;

    for (int i = 0; i < ONE_DIM_SIZE; i += 20) {
        int expected = i + 2048;
        if (rdata->arr[i] != expected) {
            printf("ERROR!!! Expected: %d, Got: %d\n", expected, rdata->arr[i]);
            TEST_ERROR;
        }
    }

    /* Close everything */
    if (H5Dclose(dset) < 0)
        TEST_ERROR;
    if (H5Fclose(file_id) < 0)
        TEST_ERROR;
    if (H5Pclose(fapl_id) < 0)
        TEST_ERROR;

    HDremove(paths->canon);
    HDremove(paths->onion);
    HDremove(paths->recovery);
    onion_filepaths_destroy(paths);

    free(wdata);
    free(rdata);
    free(dset_data);

    PASSED();
    return 0;

error:

    if (paths != NULL) {
        HDremove(paths->canon);
        HDremove(paths->onion);
        HDremove(paths->recovery);
        onion_filepaths_destroy(paths);
    }

    H5E_BEGIN_TRY
    {
        H5Dclose(dset);
        H5Fclose(file_id);
        H5Pclose(fapl_id);
    }
    H5E_END_TRY

    free(wdata);
    free(rdata);
    free(dset_data);

    return -1;
} /* end test_integration_create_simple() */

/*-----------------------------------------------------------------------------
 *
 * Function:     main()
 *
 * Purpose:      Perform unit tests on for the Onion VFD.
 *
 *-----------------------------------------------------------------------------
 */
int
main(void)
{
    const char *driver_name; /* VFD value from environment */
    int         nerrors = 0;

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

    h5_test_init();

    /* The onion VFD only supports the sec2 VFD under the hood, so skip this
     * test when the environment variable has been set to something else
     */
    driver_name = h5_get_test_driver_name();
    if (0 != strcmp(driver_name, "sec2")) {
        SKIPPED();
        puts("Onion VFD test skipped due to non-sec2 default VFD");
        exit(EXIT_SUCCESS);
    }

    /* Initialize */
    flags_create_s = H5F_ACC_RDWR | H5F_ACC_CREAT | H5F_ACC_TRUNC;

    /* Run tests. Return values on error are negative. */
    nerrors -= test_archival_index();
    nerrors -= test_revision_index();
    nerrors -= test_revision_index_collisions();
    nerrors -= test_revision_index_resizing();
    nerrors -= test_revision_index_to_archival_index();
    nerrors -= test_fapl();
    nerrors -= test_header_encode_decode();
    nerrors -= test_history_encode_decode_empty();
    nerrors -= test_history_encode_decode();
    nerrors -= test_revision_record_encode_decode();
    nerrors -= test_create_oniontarget(false, false);
    nerrors -= test_create_oniontarget(true, false);
    nerrors -= test_create_oniontarget(false, true);
    nerrors -= test_create_oniontarget(true, true);
    nerrors -= test_several_revisions_with_logical_gaps();
    nerrors -= test_page_aligned_history_create();
    nerrors -= test_integration_create();
    nerrors -= test_integration_create_simple();
    nerrors -= test_integration_create_delete_objects();
    nerrors -= test_integration_dset_extension();
    nerrors -= test_integration_ctl();
    nerrors -= test_integration_reference();
    nerrors -= test_integration_create_by_name();

    if (nerrors > 0) {
        printf("***** %d Onion TEST%s FAILED! *****\n", nerrors, nerrors > 1 ? "S" : "");
        return EXIT_FAILURE;
    }

    printf("All Onion tests passed.\n");
    return EXIT_SUCCESS;

} /* end main() */
