/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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:  Raymond Lu <songyulu@hdfgroup.org>
 *              7 September 2010
 *
 * Purpose:     Make sure dataset, file, and library can close properly when a
 *              mandatory filter fails.
 */

#include "h5test.h"

#define DSET_NAME 		"dset_fail"
#define H5Z_FILTER_FAIL_TEST    312
#define DIM                     10
#define FILTER_CHUNK_DIM        2

const char *FILENAME[] = {
    "filter_fail_with_cache",
    "filter_fail_without_cache",
    NULL
};

static size_t filter_fail(unsigned int flags, size_t cd_nelmts,
    const unsigned int *cd_values, size_t nbytes, size_t *buf_size, void **buf);

/* This message derives from H5Z */
const H5Z_class2_t H5Z_FAIL_TEST[1] = {{
    H5Z_CLASS_T_VERS,           /* H5Z_class_t version */
    H5Z_FILTER_FAIL_TEST,	/* Filter id number		*/
    1, 1,                       /* Encoding and decoding enabled */
    "filter_fail_test",		/* Filter name for debugging	*/
    NULL,                       /* The "can apply" callback     */
    NULL,                       /* The "set local" callback     */
    filter_fail,		/* The actual filter function	*/
}};


/*-------------------------------------------------------------------------
 * Function:    filter_fail
 *
 * Purpose:     For testing library's behavior when a mandatory filter 
 *              fails to write a chunk. 
 *
 * Return:	Success:	Data chunk size
 *		Failure:	0
 *
 * Programmer:	Raymond Lu
 *              7 September 2010
 *
 *-------------------------------------------------------------------------
 */
static size_t
filter_fail(unsigned int flags, size_t H5_ATTR_UNUSED cd_nelmts,
      const unsigned int H5_ATTR_UNUSED *cd_values, size_t nbytes,
      size_t *buf_size, void **buf)
{
    int   *dst = (int*)(*buf);
    size_t         ret_value = 0;

    if(flags & H5Z_FLAG_REVERSE) { /* do nothing during read */
        *buf_size = nbytes;
        ret_value = nbytes;
    }  /* end if */
    else { /* Write data */
        /* If it's the last chunk, pretend to fail. Otherwise, do nothing. */ 
        if(*dst == 8 || *dst == 9) {
            ret_value = 0;
        } else {
            *buf_size = nbytes;
            ret_value = *buf_size;
        }
    } /* end else */

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


/*-------------------------------------------------------------------------
 * Function:    test_filter_write_failure
 *
 * Purpose:     Tests the library's behavior when a mandate filter returns 
 *              failure.  There're only 5 chunks with each of them having
 *              2 integers.  The filter will fail in the last chunk.  The 
 *              dataset should release all resources even though the last 
 *              chunk can't be flushed to file.  The file should close
 *              successfully.
 *
 * Return:  
 *              Success:         0
 *              Failure:         -1
 *
 * Programmer:  Raymond Lu
 *              25 August 2010
 *
 * Modifications:
 *              Raymond Lu
 *              5 Oct 2010
 *              Test when the chunk cache is enable and disabled to make 
 *              sure the library behaves properly.
 *-------------------------------------------------------------------------
 */
static herr_t
test_filter_write(char *file_name, hid_t my_fapl, hbool_t cache_enabled)
{
    hid_t        file = -1;
    hid_t        dataset=-1;                /* dataset ID */
    hid_t        sid=-1;                   /* dataspace ID */
    hid_t        dcpl=-1;                  /* dataset creation property list ID */
    hsize_t      dims[1]={DIM};           /* dataspace dimension - 10*/
    hsize_t      chunk_dims[1]={FILTER_CHUNK_DIM}; /* chunk dimension - 2*/
    int          points[DIM];          /* Data */
    herr_t       ret;                   /* generic return value */
    int          i;

    if(cache_enabled) {
        TESTING("data writing when a mandatory filter fails and chunk cache is enabled");
    } else {
        TESTING("data writing when a mandatory filter fails and chunk cache is disabled");
    }

    /* Create file */
    if((file = H5Fcreate(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, my_fapl)) < 0) TEST_ERROR

    /* create the data space */
    if((sid = H5Screate_simple(1, dims, NULL)) < 0) TEST_ERROR

    /* Create dcpl and register the filter */
    if((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0) TEST_ERROR

    if(H5Pset_chunk(dcpl, 1, chunk_dims) < 0) TEST_ERROR

    if(H5Zregister (H5Z_FAIL_TEST) < 0) TEST_ERROR

    /* Check that the filter was registered */
    if(TRUE != H5Zfilter_avail(H5Z_FILTER_FAIL_TEST)) FAIL_STACK_ERROR

    /* Enable the filter as mandatory */
    if(H5Pset_filter(dcpl, H5Z_FILTER_FAIL_TEST, 0, (size_t)0, NULL) < 0)
        TEST_ERROR

    /* create a dataset */
    if((dataset = H5Dcreate2(file, DSET_NAME, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0) TEST_ERROR 

    /* Initialize the write buffer */
    for(i = 0; i < DIM; i++)
        points[i] = i;

    /* Write data.  If the chunk cache is enabled, H5Dwrite should succeed.  If it is
     * diabled, H5Dwrite should fail. */
    if(cache_enabled) {
        if(H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, sid, H5P_DEFAULT, points) < 0) 
            TEST_ERROR
    } else {
        /* Data writing should fail */
        H5E_BEGIN_TRY {
            ret = H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, sid, H5P_DEFAULT, points);
        } H5E_END_TRY;
        if(ret >= 0) {
	    H5_FAILED();
	    puts("    Data writing is supposed to fail because the chunk can't be written to file.");
	    TEST_ERROR
        }
    }

    /* clean up objects used for this test */
    if(H5Pclose (dcpl) < 0) TEST_ERROR
    if(H5Sclose (sid) < 0) TEST_ERROR

    /* Close dataset.  If the chunk cache is enabled, the flushing of chunks should fail
     * during H5Dclose.  If it is diabled, H5Dwrite should fail but H5Dclose should succeed. */
    if(cache_enabled) {
        H5E_BEGIN_TRY {
            ret = H5Dclose (dataset);
        } H5E_END_TRY;
        if(ret >= 0) {
	    H5_FAILED();
	    puts("    Dataset is supposed to fail because the chunk can't be flushed to file.");
	    TEST_ERROR
        }
    } else {
        if(H5Dclose (dataset) < 0)
            TEST_ERROR
    }

    /* Even though H5Dclose or H5Dwrite fails, it should release all resources.  
     * So the file should close successfully. */
    if(H5Fclose (file) < 0) TEST_ERROR

    PASSED();
    return 0;

error:
    H5E_BEGIN_TRY {
        H5Pclose(dcpl);
        H5Sclose(sid);
        H5Dclose(dataset);
        H5Fclose(file);
    } H5E_END_TRY;
    return -1;
} /* end test_filter_write() */


/*-------------------------------------------------------------------------
 * Function:    test_filter_read
 *
 * Purpose:     Tests the library's behavior when a mandate filter returns 
 *              failure.  The first 4 chunks should be in the file.  The 
 *              last chunk should not.
 *
 * Return:  
 *              Success:         0
 *              Failure:         -1
 *
 * Programmer:  Raymond Lu
 *              25 August 2010
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static herr_t
test_filter_read(char *file_name, hid_t my_fapl)
{
    hid_t        file = -1;
    hid_t        dataset=-1;                /* dataset ID */
    hid_t        sid = -1;
    hid_t        mspace = -1;
    hsize_t      dims[1]={DIM};           /* dataspace dimension - 10*/
    int          rbuf[DIM];          /* Data */
    hsize_t      dset_size = 0;          /* Dataset storage size */
    hsize_t      hs_offset[H5S_MAX_RANK];
    hsize_t      hs_size[H5S_MAX_RANK];
    hsize_t      stride[1] = {2};
    hsize_t      zero[8];
    hsize_t      nelmts = DIM/2;
    int          i;

    TESTING("data reading when a mandatory filter fails");

    /* Open file */
    if((file = H5Fopen(file_name, H5F_ACC_RDONLY, my_fapl)) < 0) TEST_ERROR

    /* Open dataset */
    if((dataset = H5Dopen2(file, DSET_NAME, H5P_DEFAULT)) < 0) TEST_ERROR

    /* Verify the storage size is equal to 4 chunks */
    if((dset_size = H5Dget_storage_size(dataset)) == 0) TEST_ERROR

    if(dset_size != 4 * FILTER_CHUNK_DIM * sizeof(int)) TEST_ERROR

    /* Read the chunks */
    HDmemset(rbuf, 0, DIM * sizeof(int));
    if(H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rbuf) < 0)
        TEST_ERROR

    /* Check that the values read are the same as the values written.
     * The last chunk should not be in the file. */
    for(i = 0; i < DIM; i++) {
        if(i < DIM-2 && rbuf[i] != i) {
            H5_FAILED();
            printf("    Read different values than written.\n");
            printf("    At index %d\n", i);
            printf("    rbuf[%d]=%d\n", i, rbuf[i]);
            TEST_ERROR
        } else if(i >= DIM-2 && rbuf[i] != 0) {
            H5_FAILED();
            printf("    No value should be read.\n");
            printf("    At index %d\n", i);
            printf("    rbuf[%d]=%d\n", i, rbuf[i]);
            TEST_ERROR
        }
    }

    /* Try to read in hyperslab simulating the h5dump's way of printing data */
    if((sid = H5Dget_space(dataset)) < 0) TEST_ERROR

    HDmemset(hs_offset, 0, sizeof(hs_offset));
    HDmemset(hs_size,   0, sizeof(hs_size));
    hs_size[0] = DIM/2;

    if(H5Sselect_hyperslab(sid, H5S_SELECT_SET, hs_offset, stride, hs_size, NULL) < 0)
        TEST_ERROR

    /* create the data space */
    if((mspace = H5Screate_simple(1, dims, NULL)) < 0) TEST_ERROR

    HDmemset(zero, 0, sizeof zero);

    if(H5Sselect_hyperslab(mspace, H5S_SELECT_SET, zero, stride, &nelmts, NULL) < 0)
        TEST_ERROR

    HDmemset(rbuf, 0, DIM * sizeof(int));
    if(H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, sid, H5P_DEFAULT, rbuf) < 0)
        TEST_ERROR

    /* Check that the values read are the same as the values written. 
     * The last chunk should not be in the file. */
    for(i = 0; i < DIM; i+=2) {
        if(i < DIM-2 && rbuf[i] != i) {
            H5_FAILED();
            printf("    Read different values than written.\n");
            printf("    At index %d\n", i);
            printf("    rbuf[%d]=%d\n", i, rbuf[i]);
            TEST_ERROR
        } else if(i >= DIM-2 && rbuf[i] != 0) {
            H5_FAILED();
            printf("    No value should be read.\n");
            printf("    At index %d\n", i);
            printf("    rbuf[%d]=%d\n", i, rbuf[i]);
            TEST_ERROR
        }
    }

    if(H5Sclose (sid) < 0) TEST_ERROR
    if(H5Sclose (mspace) < 0) TEST_ERROR
    if(H5Dclose (dataset) < 0) TEST_ERROR
    if(H5Fclose (file) < 0) TEST_ERROR

    PASSED();
    return 0;

error:
    H5E_BEGIN_TRY {
        H5Sclose(sid);
        H5Sclose(mspace);
        H5Dclose(dataset);
        H5Fclose(file);
    } H5E_END_TRY;
    return -1;
} /* end test_filter_read() */

/*-------------------------------------------------------------------------
 * Function:    main
 *
 * Purpose:     Tests the library's behavior when a mandate filter returns 
 *              failure.
 *
 * Return:      Success:        exit(EXIT_SUCCESS)
 *              Failure:        exit(EXIT_FAILURE)
 * 
 * Programmer:  Raymond Lu
 *              25 August 2010
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int main(void)
{
    hid_t       fapl;
    int         mdc_nelmts  = 0;
    size_t      rdcc_nelmts = 0;
    size_t      rdcc_nbytes = 0;
    double      rdcc_w0     = 0;
    char        filename[1024];
    unsigned 	nerrors = 0;

    h5_reset();
    fapl = h5_fileaccess();

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

    /* The chunk cache is used so that the flushing of data chunks happens
     * during H5Dclose.  All values are default. */
    nerrors += (test_filter_write(filename, fapl, TRUE) < 0	? 1 : 0);
    nerrors += (test_filter_read(filename, fapl) < 0		? 1 : 0);

    h5_fixname(FILENAME[1], fapl, filename, sizeof filename);

    /* Disable the chunk cache so that the writing of data chunks happens
     * during H5Dwrite. */
    if(H5Pset_cache(fapl, mdc_nelmts, rdcc_nelmts, rdcc_nbytes, rdcc_w0) < 0)
        TEST_ERROR

    /* Run the test again. */
    nerrors += (test_filter_write(filename, fapl, FALSE) < 0	? 1 : 0);
    nerrors += (test_filter_read(filename, fapl) < 0		? 1 : 0);

    /* Verify symbol table messages are cached */
    nerrors += (h5_verify_cached_stabs(FILENAME, fapl) < 0 ? 1 : 0);

    h5_cleanup(FILENAME, fapl);

    /* Make sure we can close the library */
    if(H5close() < 0) TEST_ERROR

    if (nerrors) TEST_ERROR

    return 0;

error:
    if (nerrors) {
        printf("***** %u FAILURE%s! *****\n",
               nerrors, 1==nerrors?"":"S");
        HDexit(EXIT_FAILURE);
    }
}
