/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * 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://support.hdfgroup.org/ftp/HDF5/releases.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * Programmer:  Dana Robinson
 *              Spring 2016
 *
 * Purpose:     Tests the basic operation of the evict-on-close cache
 *              behavior. Tests that ensure the tagging is handled correctly
 *              are located in cache.c.
 */

#define H5C_FRIEND		/*suppress error about including H5Cpkg   */
#define H5D_FRIEND		/*suppress error about including H5Dpkg	  */
#define H5D_TESTING
#define H5F_FRIEND		/*suppress error about including H5Fpkg	  */
#define H5F_TESTING
#define H5G_FRIEND		/*suppress error about including H5Gpkg	  */
#define H5I_FRIEND		/*suppress error about including H5Ipkg	  */
#define H5I_TESTING


#include "h5test.h"
#include "H5Cpkg.h"
#include "H5Dpkg.h"
#include "H5Fpkg.h"
#include "H5Gpkg.h"
#include "H5Ipkg.h"

/* Evict on close is not supported under parallel at this time.
 * In the meantime, we just run a simple check that EoC can't be
 * enabled in parallel HDF5.
 */
#ifndef H5_HAVE_PARALLEL

/* Uncomment to manually inspect cache states */
/* (Requires debug build of the library) */
/* #define EOC_MANUAL_INSPECTION */

const char *FILENAMES[] = {
    "evict-on-close",           /* 0 */
    NULL
};
#define FILENAME_BUF_SIZE       1024

/* Group names */
#define GROUP_OLD_STYLE_NAME        "old_style_groups"
#define GROUP_NEW_STYLE_NAME        "new_style_groups"

/* Dataset names */
#define DSET_COMPACT_NAME           "compact"
#define DSET_CONTIGUOUS_NAME        "contiguous"
#define DSET_BTREE_NAME             "v1_btree"
#define DSET_EARRAY_NAME            "earray"
#define DSET_BT2_NAME               "v2_btree"
#define DSET_FARRAY_NAME            "farray"
#define DSET_SINGLE_NAME            "single"

/* Number of data elements in a dataset */
#define NELEMENTS                   1024

/* Number of subgroups in each style of group */
#define NSUBGROUPS                  128

/* Max size of subgroup name, not including NULL */
#define SUBGROUP_NAME_SIZE          16

/* Prototypes */
static hbool_t verify_tag_not_in_cache(H5F_t *f, haddr_t tag);
static herr_t check_evict_on_close_api(void);
static hid_t generate_eoc_test_file(hid_t fapl_id);
static herr_t check_dset_scheme(hid_t fid, const char *dset_name);
static herr_t check_group_layout(hid_t fid, const char *group_name);


/*-------------------------------------------------------------------------
 * Function:    verify_tag_not_in_cache()
 *
 * Purpose:     Ensure that metadata cache entries with a given tag are not
 *              present in the cache.
 *
 * Return:      TRUE/FALSE
 *
 * Programmer:  Dana Robinson
 *              Fall 2016
 *
 *-------------------------------------------------------------------------
 */
static hbool_t
verify_tag_not_in_cache(H5F_t *f, haddr_t tag)
{
    H5C_t *cache_ptr = NULL;                /* cache pointer                */
    int i = 0;                              /* iterator                     */

    /* Get Internal Cache Pointers */
    cache_ptr = f->shared->cache;

    for(i = 0; i < H5C__HASH_TABLE_LEN; i++) {
        H5C_cache_entry_t *entry_ptr;    /* entry pointer                */

        entry_ptr = cache_ptr->index[i];
        while(entry_ptr != NULL) {
            if(tag == entry_ptr->tag_info->tag)
                return TRUE;
            else
                entry_ptr = entry_ptr->ht_next;
        } /* end while */
    } /* end for */

    return FALSE;
} /* end verify_tag_not_in_cache() */


/*-------------------------------------------------------------------------
 * Function:    generate_eoc_test_file()
 *
 * Purpose:     Generate the evict-on-close test file.
 *
 * Return:      Success: The file ID of the created file
 *              Failure: -1
 *
 * Programmer:  Dana Robinson
 *              Fall 2016
 *
 *-------------------------------------------------------------------------
 */
static hid_t
generate_eoc_test_file(hid_t fapl_id)
{
    char    filename[FILENAME_BUF_SIZE];    /* decorated file name          */
    hid_t   fid = -1;                       /* file ID (returned)           */
    hid_t   fapl_copy_id = -1;              /* ID of copied fapl            */
    hid_t   gid1 = -1, gid2 = -1;           /* group IDs                    */
    hid_t   sid = -1;                       /* dataspace ID                 */
    hid_t   dcpl_id = -1;                   /* dataset creation plist       */
    hid_t   did = -1;                       /* dataset ID                   */
    int     rank;                           /* # of array dimensions        */
    hsize_t current_dims[2];                /* current dataset size         */
    hsize_t maximum_dims[2];                /* maximum dataset size         */
    hsize_t chunk_dims[2];                  /* chunk dimensions             */
    H5D_chunk_index_t idx_type;             /* dataset chunk index type     */
    H5D_layout_t layout_type;               /* dataset layout type          */
    int     *data = NULL;                   /* buffer for fake data         */
    int     n;                              /* # of data elements           */
    int     i;                              /* iterator (# subgroups)       */

    TESTING("generating evict-on-close test file");

    /* Get a VFD-specific filename */
    h5_fixname(FILENAMES[0], fapl_id, filename, sizeof(filename));

    /* Copy the fapl and set the latest file format */
    if((fapl_copy_id = H5Pcopy(fapl_id)) < 0)
        TEST_ERROR;
    if(H5Pset_libver_bounds(fapl_copy_id, H5F_LIBVER_LATEST, H5F_LIBVER_LATEST) < 0)
        TEST_ERROR;

    /* Create a data buffer for dataset writes */
    if(NULL == (data = (int *)HDcalloc(NELEMENTS, sizeof(int))))
        TEST_ERROR;

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


    /*******************************************/
    /* CREATE OBJECTS WITH THE OLD FILE FORMAT */
    /*******************************************/

    /*******************/
    /* Generate groups */
    /*******************/

    /*********************************************/
    /* Old-style (version 1 B-tree + local heap) */
    /*********************************************/

    /* A single group is created in the root group, followed
     * by a large number of groups in the new group. This will
     * ensure that the file data structures for groups include
     * multiple cache entries.
     */
    if((gid1 = H5Gcreate2(fid, GROUP_OLD_STYLE_NAME, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create sub-groups */
    for(i = 0; i < NSUBGROUPS; i++) {
        char subgroup_name[SUBGROUP_NAME_SIZE];

        /* Create the group name */
        HDmemset(subgroup_name, '\0', SUBGROUP_NAME_SIZE);
        if(HDsnprintf(subgroup_name, (size_t)(SUBGROUP_NAME_SIZE - 1), "%d", i) < 0)
            TEST_ERROR

        if((gid2 = H5Gcreate2(gid1, subgroup_name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
            TEST_ERROR;
        if(H5Gclose(gid2) < 0)
            TEST_ERROR;
    } /* end for */

    if(H5Gclose(gid1) < 0)
        TEST_ERROR;

    /********************************************************************/
    /* Generate datasets and ensure that the chunking scheme is correct */
    /********************************************************************/

    /***********************************/
    /* Old file format data structures */
    /***********************************/

    /********************/
    /* Version 1 B-tree */
    /********************/

    /* Create dataspace */
    n = NELEMENTS;
    rank = 1;
    current_dims[0] = (hsize_t)n;
    maximum_dims[0] = H5S_UNLIMITED;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dcpl and set up chunking */
    if((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    chunk_dims[0] = 1;
    if(H5Pset_chunk(dcpl_id, rank, chunk_dims) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_BTREE_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct chunk indexing scheme */
    if(H5D__layout_idx_type_test(did, &idx_type) < 0)
        TEST_ERROR;
    if(idx_type != H5D_CHUNK_IDX_BTREE)
        FAIL_PUTS_ERROR("should be using version 1 B-tree as the chunk index");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;
    if(H5Pclose(dcpl_id) < 0)
        TEST_ERROR;


    /**********************************************/
    /* CREATE OBJECTS WITH THE LATEST FILE FORMAT */
    /**********************************************/

    /* Close the file */
    if(H5Fclose(fid) < 0)
        TEST_ERROR;

    /* Reopen the file with the "latest file format" fapl */
    if((fid = H5Fopen(filename, H5F_ACC_RDWR, fapl_copy_id)) < 0)
        TEST_ERROR;

    /*******************/
    /* Generate groups */
    /*******************/

    /***********************************************/
    /* New-style (version 2 B-tree + fractal heap) */
    /***********************************************/

    /* A single group is created in the root group, followed
     * by a large number of groups in the new group. This will
     * ensure that the file data structures for groups include
     * multiple cache entries.
     */
    if((gid1 = H5Gcreate2(fid, GROUP_NEW_STYLE_NAME, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Create sub-groups */
    for(i = 0; i < NSUBGROUPS; i++) {
        char subgroup_name[SUBGROUP_NAME_SIZE];

        /* Create the group name */
        HDmemset(subgroup_name, '\0', SUBGROUP_NAME_SIZE);
        if(HDsnprintf(subgroup_name, (size_t)(SUBGROUP_NAME_SIZE - 1), "%d", i) < 0)
            TEST_ERROR

        if((gid2 = H5Gcreate2(gid1, subgroup_name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
            TEST_ERROR;
        if(H5Gclose(gid2) < 0)
            TEST_ERROR;
    } /* end for */

    if(H5Gclose(gid1) < 0)
        TEST_ERROR;

    /********************************************************************/
    /* Generate datasets and ensure that the chunking scheme is correct */
    /********************************************************************/

    /********************/
    /* Extensible Array */
    /********************/

    /* Create dataspace */
    n = NELEMENTS;
    rank = 1;
    current_dims[0] = (hsize_t)n;
    maximum_dims[0] = H5S_UNLIMITED;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dcpl and set up chunking */
    if((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    chunk_dims[0] = 1;
    if(H5Pset_chunk(dcpl_id, rank, chunk_dims) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_EARRAY_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct chunk indexing scheme */
    if(H5D__layout_idx_type_test(did, &idx_type) < 0)
        TEST_ERROR;
    if(idx_type != H5D_CHUNK_IDX_EARRAY)
        FAIL_PUTS_ERROR("should be using extensible array as the chunk index");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;
    if(H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /********************/
    /* Version 2 B-Tree */
    /********************/

    /* Create dataspace */
    n = NELEMENTS;
    rank = 2;
    current_dims[0] = (hsize_t)2;
    current_dims[1] = (hsize_t)(n/2);
    maximum_dims[0] = H5S_UNLIMITED;
    maximum_dims[1] = H5S_UNLIMITED;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dcpl and set up chunking */
    if((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    chunk_dims[0] = 1;
    chunk_dims[1] = 1;
    if(H5Pset_chunk(dcpl_id, rank, chunk_dims) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_BT2_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct chunk indexing scheme */
    if(H5D__layout_idx_type_test(did, &idx_type) < 0)
        TEST_ERROR;
    if(idx_type != H5D_CHUNK_IDX_BT2)
        FAIL_PUTS_ERROR("should be using version 2 B-tree as the chunk index");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;
    if(H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /***************/
    /* Fixed Array */
    /***************/

    /* Create dataspace */
    n = NELEMENTS;
    rank = 1;
    current_dims[0] = (hsize_t)n;
    maximum_dims[0] = (hsize_t)n;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dcpl and set up chunking */
    if((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    chunk_dims[0] = 1;
    chunk_dims[1] = 1;
    if(H5Pset_chunk(dcpl_id, rank, chunk_dims) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_FARRAY_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct chunk indexing scheme */
    if(H5D__layout_idx_type_test(did, &idx_type) < 0)
        TEST_ERROR;
    if(idx_type != H5D_CHUNK_IDX_FARRAY)
        FAIL_PUTS_ERROR("should be using fixed array as the chunk index");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;
    if(H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /****************/
    /* Single Chunk */
    /****************/

    /* Create dataspace */
    n = NELEMENTS;
    rank = 1;
    current_dims[0] = (hsize_t)n;
    maximum_dims[0] = (hsize_t)n;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dcpl and set up chunking */
    if((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    chunk_dims[0] = (hsize_t)n;
    chunk_dims[1] = (hsize_t)n;
    if(H5Pset_chunk(dcpl_id, rank, chunk_dims) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_SINGLE_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct chunk indexing scheme */
    if(H5D__layout_idx_type_test(did, &idx_type) < 0)
        TEST_ERROR;
    if(idx_type != H5D_CHUNK_IDX_SINGLE)
        FAIL_PUTS_ERROR("should be using single chunk as the chunk index");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;
    if(H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /**************/
    /* Contiguous */
    /**************/

    /* Create dataspace */
    n = NELEMENTS;
    rank = 1;
    current_dims[0] = (hsize_t)n;
    maximum_dims[0] = (hsize_t)n;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_CONTIGUOUS_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct layout scheme */
    if(H5D__layout_type_test(did, &layout_type) < 0)
        TEST_ERROR;
    if(layout_type != H5D_CONTIGUOUS)
        FAIL_PUTS_ERROR("should be using contiguous layout");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;

    /***********/
    /* Compact */
    /***********/

    /* Create dataspace */
    n = 1;
    rank = 1;
    current_dims[0] = (hsize_t)n;
    maximum_dims[0] = (hsize_t)n;
    if((sid = H5Screate_simple(rank, current_dims, maximum_dims)) < 0)
        TEST_ERROR;

    /* Create dcpl and set up compact layout */
    if((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
        TEST_ERROR;
    if(H5Pset_layout(dcpl_id, H5D_COMPACT) < 0)
        TEST_ERROR;

    /* Create dataset */
    if((did = H5Dcreate2(fid, DSET_COMPACT_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl_id, H5P_DEFAULT)) < 0)
        TEST_ERROR;

    /* Ensure we're using the correct layout scheme */
    if(H5D__layout_type_test(did, &layout_type) < 0)
        TEST_ERROR;
    if(layout_type != H5D_COMPACT)
        FAIL_PUTS_ERROR("should be using compact layout");

    /* Write a bunch of fake data */
    if(H5Dwrite(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Close IDs for this dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;
    if(H5Sclose(sid) < 0)
        TEST_ERROR;
    if(H5Pclose(dcpl_id) < 0)
        TEST_ERROR;

    /********/
    /* DONE */
    /********/

    /* Close/free everything else */
    if(H5Pclose(fapl_copy_id) < 0)
        TEST_ERROR;

    HDfree(data);

    PASSED();
    return fid;

error:
    H5E_BEGIN_TRY {
        H5Gclose(gid1);
        H5Gclose(gid2);
        H5Fclose(fid);
        H5Dclose(did);
        H5Sclose(sid);
        H5Pclose(dcpl_id);
        H5Pclose(fapl_copy_id);
    } H5E_END_TRY;

    HDfree(data);

    H5_FAILED();
    return -1;

} /* end generate_eoc_test_file() */


/*-------------------------------------------------------------------------
 * Function:    check_group_layout()
 *
 * Purpose:     Verify that the evict-on-close feature works for a given
 *              group layout (new-style vs. old-style).
 *
 * Return:      SUCCEED/FAIL
 *
 * Programmer:  Dana Robinson
 *              Fall 2016
 *
 *-------------------------------------------------------------------------
 */
static herr_t
check_group_layout(hid_t fid, const char *group_name)
{
    H5F_t   *file_ptr = NULL;               /* ptr to internal file struct  */
    hid_t   gid1 = -1, gid2 = -1;           /* group IDs                    */
    H5G_t   *grp_ptr = NULL;                /* ptr to internal group struct */
    haddr_t tag1, tag2;                     /* MD cache tags for groups     */
    uint32_t before, during, after;         /* cache sizes                  */
    int i;                                  /* iterator                     */

    /* NOTE: The TESTING() macro is called in main() */

    /* Get a pointer to the file struct */
    if(NULL == (file_ptr = (H5F_t *)H5I_object_verify(fid, H5I_FILE)))
        TEST_ERROR;

    /* Record the number of cache entries */
    before = file_ptr->shared->cache->index_len;

#ifdef EOC_MANUAL_INSPECTION
    HDprintf("\nCACHE BEFORE GROUP OPEN:\n");
    if(H5AC_dump_cache(file_ptr) < 0)
        TEST_ERROR;
    HDprintf("NUMBER OF CACHE ENTRIES: %u\n", before);
#endif

    /* Open the main group and get its tag */
    if((gid1 = H5Gopen2(fid, group_name, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if(NULL == (grp_ptr = (H5G_t *)H5I_object_verify(gid1, H5I_GROUP)))
        TEST_ERROR;
    tag1 = grp_ptr->oloc.addr;


    /* Open and close all sub-groups */
    for(i = 0; i < NSUBGROUPS; i++) {
        char subgroup_name[SUBGROUP_NAME_SIZE];

        /* Create the group name */
        HDmemset(subgroup_name, '\0', SUBGROUP_NAME_SIZE);
        if(HDsnprintf(subgroup_name, (size_t)(SUBGROUP_NAME_SIZE - 1), "%d", i) < 0)
            TEST_ERROR

        if((gid2 = H5Gopen2(gid1, subgroup_name, H5P_DEFAULT)) < 0)
            TEST_ERROR;

        if(NULL == (grp_ptr = (H5G_t *)H5I_object_verify(gid2, H5I_GROUP)))
            TEST_ERROR;
        tag2 = grp_ptr->oloc.addr;

        if(H5Gclose(gid2) < 0)
            TEST_ERROR;

        if(TRUE == verify_tag_not_in_cache(file_ptr, tag2))
            TEST_ERROR;
    } /* end for */

    /* Record the number of cache entries */
    during = file_ptr->shared->cache->index_len;

#ifdef EOC_MANUAL_INSPECTION
    HDprintf("\nCACHE AFTER OPENING GROUPS (WHILE OPEN):\n");
    if(H5AC_dump_cache(file_ptr) < 0)
        TEST_ERROR;
    HDprintf("MAIN GROUP TAG: %#X\n", tag1);
    HDprintf("NUMBER OF CACHE ENTRIES: %u\n", during);
#endif

    /* Close the main group */
    if(H5Gclose(gid1) < 0)
        TEST_ERROR;

    /* Record the number of cache entries */
    after = file_ptr->shared->cache->index_len;

#ifdef EOC_MANUAL_INSPECTION
    HDprintf("\nCACHE AFTER CLOSING GROUPS:\n");
    if(H5AC_dump_cache(file_ptr) < 0)
        TEST_ERROR;
    HDprintf("NUMBER OF CACHE ENTRIES: %u\n", after);
#endif

    /* Ensure that the cache does not contain entries with the tag */
    if(TRUE == verify_tag_not_in_cache(file_ptr, tag1))
        TEST_ERROR;
    /* Compare the number of cache entries */
    if(before != after || before == during)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY {
        H5Gclose(gid1);
        H5Gclose(gid2);
    } H5E_END_TRY;

    H5_FAILED();
    return FAIL;

} /* end check_group_layout() */


/*-------------------------------------------------------------------------
 * Function:    check_dset_scheme()
 *
 * Purpose:     Verify that the evict-on-close feature works for a given
 *              dataset layout and/or chunk index.
 *
 * Return:      SUCCEED/FAIL
 *
 * Programmer:  Dana Robinson
 *              Fall 2016
 *
 *-------------------------------------------------------------------------
 */
static herr_t
check_dset_scheme(hid_t fid, const char *dset_name)
{
    H5F_t   *file_ptr = NULL;               /* ptr to internal file struct  */
    hid_t   did = -1;                       /* dataset ID                   */
    H5D_t   *dset_ptr = NULL;               /* ptr to internal dset struct  */
    haddr_t tag;                            /* MD cache tag for dataset     */
    int     *data = NULL;                   /* buffer for fake data         */
    uint32_t before, during, after;         /* cache sizes                  */

    /* NOTE: The TESTING() macro is called in main() */

    /* Get a pointer to the file struct */
    if(NULL == (file_ptr = (H5F_t *)H5I_object_verify(fid, H5I_FILE)))
        TEST_ERROR;

    /* Create the data buffer */
    if(NULL == (data = (int *)HDcalloc(NELEMENTS, sizeof(int))))
        TEST_ERROR;

    /* Record the number of cache entries */
    before = file_ptr->shared->cache->index_len;

#ifdef EOC_MANUAL_INSPECTION
    HDprintf("\nCACHE BEFORE DATASET OPEN:\n");
    if(H5AC_dump_cache(file_ptr) < 0)
        TEST_ERROR;
    HDprintf("NUMBER OF CACHE ENTRIES: %u\n", before);
#endif

    /* Open dataset and get the metadata tag */
    if((did = H5Dopen2(fid, dset_name, H5P_DEFAULT)) < 0)
        TEST_ERROR;
    if(NULL == (dset_ptr = (H5D_t *)H5I_object_verify(did, H5I_DATASET)))
        TEST_ERROR;
    tag = dset_ptr->oloc.addr;

    /* Read data from the dataset so the cache gets populated with chunk entries
     * and the like.
     */
    if(H5Dread(did, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
        TEST_ERROR;

    /* Record the number of cache entries */
    during = file_ptr->shared->cache->index_len; 

#ifdef EOC_MANUAL_INSPECTION
    HDprintf("\nCACHE AFTER DATA READ (WHILE OPEN):\n");
    if(H5AC_dump_cache(file_ptr) < 0)
        TEST_ERROR;
    HDprintf("TAG: %#X\n", tag);
    HDprintf("NUMBER OF CACHE ENTRIES: %u\n", during);
#endif

    /* Close the dataset */
    if(H5Dclose(did) < 0)
        TEST_ERROR;

    /* Record the number of cache entries */
    after = file_ptr->shared->cache->index_len;

#ifdef EOC_MANUAL_INSPECTION
    HDprintf("\nCACHE AFTER DATASET CLOSE:\n");
    if(H5AC_dump_cache(file_ptr) < 0)
        TEST_ERROR;
    HDprintf("NUMBER OF CACHE ENTRIES: %u\n", after);
#endif

    /* Ensure that the cache does not contain entries with the tag */
    if(TRUE == verify_tag_not_in_cache(file_ptr, tag))
        TEST_ERROR;

    /* Compare the number of cache entries */
    if(before != after || before == during)
        TEST_ERROR;

    HDfree(data);

    PASSED();
    return SUCCEED;

error:
    H5E_BEGIN_TRY {
        H5Dclose(did);
    } H5E_END_TRY;

    H5_FAILED();
    return FAIL;

} /* check_dset_scheme() */


/*-------------------------------------------------------------------------
 * Function:    check_evict_on_close_api()
 *
 * Purpose:     Verify that the H5Pset/get_evict_on_close() calls behave
 *              correctly.
 *
 * Return:      SUCCEED/FAIL
 *
 * Programmer:  Dana Robinson
 *              Spring 2016
 *
 *-------------------------------------------------------------------------
 */
static herr_t
check_evict_on_close_api(void)
{
    hid_t       fapl_id = -1;
    hid_t       dapl_id = -1;
    hbool_t     evict_on_close;
    herr_t      status;

    TESTING("evict on close API");

    /* Create a fapl */
    if((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    /* Check the default */
    evict_on_close = TRUE;
    if(H5Pget_evict_on_close(fapl_id, &evict_on_close) < 0)
        TEST_ERROR;
    if(evict_on_close != FALSE)
        FAIL_PUTS_ERROR("Incorrect default evict on close value.");

    /* Set the evict on close property */
    evict_on_close = TRUE;
    if(H5Pset_evict_on_close(fapl_id, evict_on_close) < 0)
        TEST_ERROR;

    /* Make sure we can get it back out */
    evict_on_close = FALSE;
    if(H5Pget_evict_on_close(fapl_id, &evict_on_close) < 0)
        TEST_ERROR;
    if(evict_on_close != TRUE)
        FAIL_PUTS_ERROR("Incorrect evict on close value.");

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

    /**********************************************/
    /* Trying passing in a non-fapl property list */
    /**********************************************/

    if((dapl_id = H5Pcreate(H5P_DATASET_ACCESS)) < 0)
        TEST_ERROR;

    /* ensure using an incorrect access plist fails */
    H5E_BEGIN_TRY {
        status = H5Pset_evict_on_close(dapl_id, evict_on_close);
    } H5E_END_TRY;
    if(status >= 0)
        FAIL_PUTS_ERROR("H5Pset_evict_on_close() accepted invalid access plist.");

    /* ensure an invalid plist fails */
    H5E_BEGIN_TRY {
        status = H5Pget_evict_on_close((hid_t)-1, &evict_on_close);
    } H5E_END_TRY;
    if(status >= 0)
        FAIL_PUTS_ERROR("H5Pget_evict_on_close() accepted invalid hid_t.");

    /* close dapl */
    if(H5Pclose(dapl_id) < 0)
        TEST_ERROR;

    PASSED();
    return SUCCEED;

error:
    H5_FAILED();
    return FAIL;

} /* check_evict_on_close_api() */


/*-------------------------------------------------------------------------
 * Function:    main
 *
 * Return:      EXIT_FAILURE/EXIT_SUCCESS
 *
 * Programmer:  Dana Robinson
 *              Spring 2016
 *
 *-------------------------------------------------------------------------
 */
int
main(void)
{
    hid_t       fapl_id = -1;       /* VFD-specific fapl                    */
    hid_t       fid = -1;           /* file ID                              */
    unsigned    nerrors = 0;        /* number of test errors                */

    HDprintf("Testing evict-on-close cache behavior\n");

    /* Initialize */
    h5_reset();

    /* Test H5P call to set up EoC (does not require VFD-specific fapl) */
    nerrors += check_evict_on_close_api() < 0 ? 1 : 0;

    /* Set up VFD-specific fapl */
    if((fapl_id = h5_fileaccess()) < 0) {
        nerrors++;
        PUTS_ERROR("Unable to get VFD-specific fapl\n");
    } /* end if */

    /* Set evict-on-close property */
    if(H5Pset_evict_on_close(fapl_id, TRUE) < 0) {
        nerrors++;
        PUTS_ERROR("Unable to set evict-on-close property\n");
    } /* end if */

    /* Generate the test file */
    if((fid = generate_eoc_test_file(fapl_id)) < 0) {
        nerrors++;
        PUTS_ERROR("Unable to generate test file\n");
    } /* end if */

    /* Run tests with a variety of dataset configurations
     * PASSED() and H5_FAILED() are handled in check_configuration()
     */
    TESTING("evict on close with version 1 B-tree chunk index");
        nerrors += check_dset_scheme(fid, DSET_BTREE_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with extensible array chunk index");
        nerrors += check_dset_scheme(fid, DSET_EARRAY_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with version 2 B-tree chunk index");
        nerrors += check_dset_scheme(fid, DSET_BT2_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with fixed array chunk index");
        nerrors += check_dset_scheme(fid, DSET_FARRAY_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with \'single chunk\' chunk index");
        nerrors += check_dset_scheme(fid, DSET_SINGLE_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with contiguous layout");
        nerrors += check_dset_scheme(fid, DSET_CONTIGUOUS_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with compact layout");
        nerrors += check_dset_scheme(fid, DSET_COMPACT_NAME) < 0 ? 1 : 0;

    /* Run tests with old- and new-style groups
     * PASSED() and H5_FAILED() are handled in check_configuration()
     */
    TESTING("evict on close with old-style groups");
        nerrors += check_group_layout(fid, GROUP_OLD_STYLE_NAME) < 0 ? 1 : 0;
    TESTING("evict on close with new-style groups");
        nerrors += check_group_layout(fid, GROUP_NEW_STYLE_NAME) < 0 ? 1 : 0;

    /* Close the test file */
    if(H5Fclose(fid) < 0) {
        nerrors++;
        PUTS_ERROR("Unable to close the test file.\n");
    } /* end if */

    /* Clean up files and close the VFD-specific fapl */
    h5_delete_all_test_files(FILENAMES, fapl_id);
    if(H5Pclose(fapl_id) < 0) {
        nerrors++;
        PUTS_ERROR("Unable to close VFD-specific fapl.\n");
    } /* end if */

    if(nerrors)
        goto error;

    HDprintf("All evict-on-close tests passed.\n");

    return EXIT_SUCCESS;

error:

    HDprintf("***** %u evict-on-close test%s FAILED! *****\n",
        nerrors, nerrors > 1 ? "S" : "");

    h5_delete_all_test_files(FILENAMES, fapl_id);
    H5E_BEGIN_TRY {
        H5Fclose(fid);
        H5Pclose(fapl_id);
    } H5E_END_TRY;

    return EXIT_FAILURE;

} /* end main() */

#else


/*-------------------------------------------------------------------------
 * Function:    check_evict_on_close_parallel_fail()
 *
 * Purpose:     Verify that the H5Pset_evict_on_close() call fails in
 *              parallel HDF5.
 *
 * Return:      SUCCEED/FAIL
 *
 * Programmer:  Dana Robinson
 *              Spring 2017
 *
 *-------------------------------------------------------------------------
 */
static herr_t
check_evict_on_close_parallel_fail(void)
{
    hid_t       fapl_id = -1;
    hbool_t     evict_on_close;
    herr_t      status;

    TESTING("evict on close fails in parallel");

    /* Create a fapl */
    if((fapl_id = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        TEST_ERROR;

    /* Set the evict on close property (should fail)*/
    evict_on_close = TRUE;
    H5E_BEGIN_TRY {
        status = H5Pset_evict_on_close(fapl_id, evict_on_close);
    } H5E_END_TRY;
    if(status >= 0)
        FAIL_PUTS_ERROR("H5Pset_evict_on_close() did not fail in parallel HDF5.");

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

    PASSED();
    return SUCCEED;

error:
    H5_FAILED();
    return FAIL;

} /* check_evict_on_close_parallel_fail() */


/*-------------------------------------------------------------------------
 * Function:    main (parallel version)
 *
 * Return:      EXIT_FAILURE/EXIT_SUCCESS
 *
 * Programmer:  Dana Robinson
 *              Spring 2016
 *
 *-------------------------------------------------------------------------
 */
int
main(void)
{
    unsigned    nerrors = 0;        /* number of test errors                */

    HDprintf("Testing evict-on-close cache behavior\n");

    /* Initialize */
    h5_reset();

    /* Test that EoC fails in parallel HDF5 */
    nerrors += check_evict_on_close_parallel_fail() < 0 ? 1 : 0;

    if(nerrors)
        goto error;

    HDprintf("All evict-on-close tests passed.\n");
    HDprintf("Note that EoC is not supported under parallel so most tests are skipped.\n");

    return EXIT_SUCCESS;

error:

    HDprintf("***** %u evict-on-close test%s FAILED! *****\n",
        nerrors, nerrors > 1 ? "S" : "");

    return EXIT_FAILURE;

} /* main() - parallel */

#endif /* H5_HAVE_PARALLEL */

