/* 
 * copyright 2010-2012 Edscott Wilson Garcia (GPL-license)
 *
 * This is very simple example program to test 64 bit 
 * functions of the Disk Based Hash (DBH) and
 * verify correct handling of dbh files greater than
 * 2 Gb in size (up to 256^8/2).

 * A dbh file is created from a specified filesystem.
 * Paths are indexed with g_string hash key
 * Hash key collisions are noted in dbh file COLLISIONS
 * Hash key<->path associations are noted in dbh file KEY*
 * Hash key<->file are noted in dbh file INDEX
 *
 * usage: ./filesystem path option
 * Option can be:
 *    "index" (create KEY, COLLISIONS and INDEX dbh files)
 *    "dump"  (do a foreach on all records and print summary)
 *    "regen" (recreate INDEX dbh file with optimized fisical structure)
 *    "compare" (compare each file in INDEX with actual file on disk)
 *    "parallel"
 *    "thread"
 *    "fulltest" (all of the above)

 */
#include "config.h"

#ifdef HAVE_LSTAT
# define LSTAT lstat
#else
# define LSTAT stat
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dbh.h>
#include <dirent.h>
#include <sys/types.h>
#include <inttypes.h>

#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <glib.h>

#define DIRECTORY "testfiles"
#define KEY "testfiles/filesystem.key.dbh"
#define INDEX "testfiles/filesystem.index.dbh"
#define REBUILT "testfiles/filesystem.index.rebuilt.dbh"
#define TEST_INDEX "testfiles/parfilesystem.index.dbh"
#define COLLISIONS "testfiles/filesystem.collisions.dbh"

#define HELP \
"     Options:\n"\
"       index: Create an index of items within the specified \"path\"\n"\
"        dump: Recalculate size of data items in DBH table (sweep/fanout)\n"\
"       regen: Regenerate the DBH table (sweep/fanout)\n"\
"      thread: Test concurrent light weight process activity\n"\
"    parallel: Test concurrent heavy weight process activity\n"\
"     compare: Compare data in DBH table to actual data\n"\
"    fulltest: All tests\n"\
" *To test a DBH table larger than 4 GB, choose a \"path\" with more than 4GB.\n"\
"  Do not alter any item within \"path\" during the test or error will occur."


typedef struct dump_t{
    char **argv;
    int original_count;
    long long original_sum;
    long long sum;
    int which;
    int count;
}dump_t;


static DBHashTable *dbh_key;
static
gchar *get_hash_key(unsigned char bucket, const char *pre_key){
    GString *gs = g_string_new(pre_key);
    gchar *key;
    key=g_strdup_printf("%c%10u", bucket, g_string_hash(gs));
    g_string_free(gs, TRUE);
    return key;
}

static int 
read_filesystem(DBHashTable *dbh, const char *path, dump_t *dump_p)
{
    DIR *directory; 
    int count = 0;
    struct dirent *d;

    directory = opendir(path);
    if(!directory) {
	fprintf(stderr,"Cannot open %s\n" ,path);
	return -1;
    }
#define     _BSD_SOURCE 1
while((d = readdir(directory)) != NULL)
    {

        char *fullpath=NULL;
	gchar  *key;
	gboolean is_dir=FALSE;
	unsigned char bucket='A';
        if(strcmp(d->d_name, ".")==0)  continue;
        if(strcmp(d->d_name, "..")==0)  continue;




        fullpath=g_build_filename(path,d->d_name,NULL);
                

	do {
	    key=get_hash_key(bucket,fullpath);
            dbh_set_key (dbh,(unsigned char *)key);
	    bucket++;
	}
	while(dbh_load(dbh));
	bucket--;
	if (bucket > 'A'){
	    printf("HASH colision: %s -> %s\n", key,fullpath);
	    DBHashTable *dbh_inverse=dbh_new(COLLISIONS, NULL, 0);
	    char inverse_key[255];
	    memset(inverse_key,0,255);
	    strncpy(inverse_key,fullpath, 254);
	    dbh_set_key (dbh_inverse,(unsigned char *)inverse_key);
	    dbh_set_size(dbh_inverse,DBH_KEYLENGTH(dbh));
	    dbh_set_data(dbh_inverse,(void *)DBH_KEY(dbh),DBH_KEYLENGTH(dbh));
	    dbh_update(dbh_inverse);
	    dbh_close(dbh_inverse);
	} 

	dbh_set_key (dbh_key,(unsigned char *)key);
	dbh_set_size(dbh_key,strlen(fullpath)+1);
	dbh_set_data(dbh_key,(void *)fullpath,strlen(fullpath)+1);
	dbh_update(dbh_key);
	 struct stat st;
	 if (LSTAT(fullpath,&st)<0){
	    printf("cannot stat %s: %s\n",fullpath,strerror(errno));
	    continue;
	 }


	if (S_ISDIR(st.st_mode)) is_dir=TRUE;

        if (!is_dir) {

	 if (st.st_size == 0) continue;
         // Let's put a 100 MB limit for the test. 
	 if (st.st_size > 100000000LL) { 
	     printf("Skipping %s: file is too big, really (%lld)\n",fullpath, (long long)st.st_size);
	     continue;
	 }
	 if (!S_ISREG(st.st_mode)) {
	     continue;
	 }
	 // This is useful if our data size grows over 1024 B:
	 if (DBH_MAXIMUM_RECORD_SIZE(dbh) < st.st_size) {
	     dbh_set_size(dbh,st.st_size);
	     printf("dbh_set_size set to %lld\n",(long long)st.st_size);
	 }
	 int fd=open(fullpath,O_RDONLY);
	 if (fd < 0) {
	    printf("cannot open %s for read\n",fullpath);
	    continue;
	 }
	 // This works instead of dbh_set_data():
	 if (read(fd,DBH_DATA(dbh),st.st_size) < 0){
	    printf("problem reading %lld bytes from %s\n",
		    (long long)st.st_size,fullpath);
	    close(fd);
	    continue;
	 }
	 close(fd);
	 dbh_set_recordsize(dbh,st.st_size);
	 dbh_update(dbh);
	 dump_p->sum += st.st_size;
	 count++;
         //fprintf(stderr,"%s\n",fullpath);
	 //
	}
        if (is_dir) {
		int retval;
		retval = read_filesystem(dbh,fullpath, dump_p);
		if (retval > 0) count += retval;
	}

	g_free(fullpath);
    }
    closedir(directory);
//	printf ("%s -> %d files\n",path,count);
    return (count);
}

static void  operate (DBHashTable *dbh){
    dump_t *dump_p = dbh->user_data;
    dump_p->count++;
    //sum += strlen((char *)DBH_DATA(dbh));
    dump_p->sum += DBH_RECORD_SIZE(dbh);
}
static void  compare (DBHashTable *dbh){
    dbh_set_key (dbh_key,(unsigned char *)DBH_KEY(dbh));
    dbh_load(dbh_key);
    char *path=DBH_DATA(dbh_key);
    int fd=open(path,O_RDONLY);
	 if (fd < 0) {
	    printf("cannot open %s for read\n",path);
	    return;
	 }
	 // This works instead of dbh_set_data():
    struct stat st;
    LSTAT(path,&st);
    void *p=malloc(st.st_size);
     if (p == NULL) {
	 fprintf(stderr, "malloc: %s\n", strerror(errno));
	exit(1);
     }     
	 if (read(fd,p,st.st_size) < 0){
	    printf("problem reading %lld bytes from %s\n",
		    (long long)st.st_size,path);
	    close(fd);
	    free(p);
	    return;
	 }
	 close(fd);
    if (memcmp(p,DBH_DATA(dbh),st.st_size) != 0) {
	printf("%s does not compare!\n",path);
    } else {
	static int count=0;
	if (count++ % 1000 == 0) {
	    printf ("."); fflush(stdout);
	}
    }
    free(p);
}

static int
dump(dump_t *dump_p) {
    //char **argv, int which, int original_count, long long original_sum){
    dump_p->count=0;   dump_p->sum=0;
    const char *text;
    if (dump_p->which) text = "Sweep"; else text = "Fanout";
    fprintf(stdout,"%s is now being performed by pid %d\n", text, getpid());
    // PARALLEL SAFE need not be specified on READ_ONLY
    DBHashTable *dbh=dbh_new(INDEX, NULL, DBH_READ_ONLY);
    dbh->user_data = dump_p;
    if (dump_p->which) dbh_foreach_sweep (dbh,operate);
    else dbh_foreach_fanout (dbh,operate);
    dbh_close(dbh);
    if (strcmp(dump_p->argv[2],"fulltest")==0) {
	if (dump_p->sum != dump_p->original_sum){
	  //g_warning("Original sum does not match %s sum (%I64d != %I64d)\nTest FAILED.\n",
	  g_warning("Original sum does not match %s sum (%lld != %lld)\nTest FAILED.\n",
		text, dump_p->original_sum, dump_p->sum);
	  exit(1);
	} 
	if (dump_p->count != dump_p->original_count){
	    g_warning("Original count does not match %s count (%d != %d)\nTest FAILED.\n",
		text, dump_p->original_count, dump_p->count);
	  exit(1);
	}
    }
    fprintf(stdout,
"  Sweep data:\n"\
"    Items in the DBH table (filesystem count) = %d\n"\
"    Sum of data items size saved in DBH table = %lld\n",
	    dump_p->count, dump_p->sum);
    if (strcmp(dump_p->argv[2],"fulltest")==0) {
	fprintf(stderr, "Test %s PASSED\n", text);
    }
    return 1;
}

static void
check_files(void){
    if (!g_file_test(INDEX, G_FILE_TEST_EXISTS)){
      g_warning("Index file %s has not yet been created\n", 
	      INDEX);
      exit(1);
    }
    if (!g_file_test(COLLISIONS, G_FILE_TEST_EXISTS)){
      g_warning("DBH table %s has not yet been created\n", 
	      COLLISIONS);
      exit(1);
    }
    if (!g_file_test(KEY, G_FILE_TEST_EXISTS)){
      g_warning("DBH table %s has not yet been created\n", 
	      KEY);
      exit(1);
    }
}

DBHashTable *newdbh;
void  partest (DBHashTable *dbh){
    // Assign key
    memcpy(newdbh->key, dbh->key, DBH_KEYLENGTH(dbh));
   
    // Assign record size. This is not implicit with dbh_set_data, why not?
    dbh_set_recordsize (newdbh, DBH_RECORD_SIZE(dbh));

    // Assign data
    dbh_set_data (newdbh, dbh->data, DBH_RECORD_SIZE(dbh));
    // Update record
    dbh_update(newdbh);
}


static void *
parallel_test(void *data){
    // sweep it.
    // Open source dbh
    DBHashTable *dbh;
    dbh=dbh_new(INDEX, NULL, DBH_PARALLEL_SAFE);
    if (!dbh) return NULL;
    newdbh=dbh_new(TEST_INDEX, NULL, DBH_PARALLEL_SAFE);
    dbh_foreach_sweep (dbh, partest);
    //dbh_foreach_sweep (dbh,operate);
    dbh_close(dbh);
    dbh_close(newdbh);
    return NULL;
}

static void  rebuild (DBHashTable *dbh_thread){
    DBHashTable *rebuilt_dbh = dbh_thread->user_data;
    // Adquire mutex.
    dbh_mutex_lock(rebuilt_dbh);

    // Copy key and data to rebuilt_dbh
    dbh_set_key(rebuilt_dbh, DBH_KEY(dbh_thread));
    dbh_set_recordsize (rebuilt_dbh, DBH_RECORD_SIZE(dbh_thread));
    dbh_set_data(rebuilt_dbh, DBH_DATA(dbh_thread), DBH_RECORD_SIZE(dbh_thread));
    // Write to rebuilt dbh
    dbh_update(rebuilt_dbh);
    // Release mutex
    dbh_mutex_unlock(rebuilt_dbh);
    return;
}

static void *
thread_test_f(gpointer data){
    // Each thread has its own copy, read-only
    // Since no thread will be writing, DBH_PARALLEL_SAFE is not necesary.
    DBHashTable *dbh_thread=dbh_new(INDEX, NULL, DBH_READ_ONLY);
    // If any lingering locks are left behind by a parallel safe 
    // SIGINT, cleanup:
    dbh_clear_locks(dbh_thread);
    // Assign open thread-safe dbh to user_data
    dbh_thread->user_data = data;
    // This gets a writelock, only when PARALLEL_SAFE is defined,
    // which is not necessary here.
    dbh_foreach_sweep (dbh_thread, rebuild);
    dbh_close(dbh_thread);
    return NULL;
}
    
static void *
thread_dump_f(gpointer data){
    dump_t *dump_p = data;
    int i; for(i=1; i>=0; i--) {dump_p->which = i; dump(dump_p);}
    g_free(dump_p);
    return NULL;
}



int main(int argc, char **argv){
    dump_t dump_v;
    memset(&dump_v, 0, sizeof(dump_t));
    if (g_mkdir_with_parents(DIRECTORY, 0770) < 0){
	if (!g_file_test(DIRECTORY, G_FILE_TEST_IS_DIR)){
	    g_warning("mkdir(%s): %s\n", DIRECTORY, strerror(errno));
	    exit(1);
	}
    }
    if (!g_file_test(DIRECTORY, G_FILE_TEST_IS_DIR)){
	g_warning("Failed test: g_file_test(%s, G_FILE_TEST_IS_DIR)\n",
		DIRECTORY );
	exit(1);
    }

  if (argc < 3) {
   fprintf(stderr,"insufficient arguments (%d < 3), usage: %s (path) (option)\n%s\n",
	   argc, argv[0], HELP);
   exit(1);
  }
  dump_v.argv=argv;

  // This DBH uses more than one bucket in order to handle hashtable
  // key collisions.
  // This is done in a single thread, non parallel mode.
  if (strcmp(argv[2],"index")==0 || strcmp(argv[2],"fulltest")==0) {
    fprintf(stderr, "///////////////////  DBH generation //////////////////////////\n");
    fprintf(stdout,"Creating index now, process 0x%x recursively reading %s\n", getpid(), argv[1]);
    // This table is the bucket index file. The index file is also
    // the data table.
    unsigned char key_length = 11;
//    DBHashTable *dbh=dbh_create(INDEX, key_length);
    DBHashTable *dbh=dbh_new(INDEX, &key_length, DBH_CREATE);
//    dbh_key=dbh_create(KEY, key_length);
    dbh_key=dbh_new(KEY, &key_length, DBH_CREATE);
    // This table handles collisions. If a path is indexed here (first 254
    // bytes of the string), then the data element is the actual hash table
    // key. This avoids a collision with a path that has already been indexed.
    key_length = 254;
//    DBHashTable *dbh_inverse=dbh_create(COLLISIONS, key_length);
    DBHashTable *dbh_inverse=dbh_new(COLLISIONS, &key_length, DBH_CREATE);
    dbh_close(dbh_inverse);
    // Read the filesystem data into the DBH table.
    dump_v.sum = 0;
    dump_v.original_count=read_filesystem(dbh,argv[1],&dump_v);
    dump_v.original_sum = dump_v.sum;
    dbh_close(dbh);
    dbh_close(dbh_key);
    fprintf(stdout,
"  Index created:\n"\
"    Items in the DBH table (filesystem count) = %d\n"\
"    Sum of data items size saved in DBH table = %lld\n",
	    dump_v.original_count, dump_v.original_sum);
    if (strcmp(argv[2],"index")==0) exit(0);
  }
  // Full or specific test follows. 
  check_files();
  // Dump test
  if (strcmp(argv[2],"dump")==0 || strcmp(argv[2],"fulltest")==0) {
      // Find out how many items and total size of data records
      // a sweep/fanout of DBH table will find
      int i; for(i=1; i>=0; i--) {dump_v.which = i; dump(&dump_v);}
  }

  // Regen tests
  if (strcmp(argv[2],"regen")==0 || strcmp(argv[2],"fulltest")==0) {
    fprintf(stderr, "///////////////////  Serial tests //////////////////////////\n");
    fprintf(stdout,"Performing regen_sweep now...\n");
    DBHashTable *dbh;
    dbh=dbh_new(INDEX, NULL, 0);
    dbh_regen_sweep(&dbh);
    dbh_close(dbh);
    // Find out how many items and total size of data records
    // a sweep of DBH table will find
    dump_v.which = 1;
    dump(&dump_v);
    fprintf(stdout,"Performing regen_fanout now...\n");
    dbh=dbh_new(INDEX, NULL, 0);
    dbh_regen_fanout(&dbh);
    dbh_close(dbh);
    // Find out how many items and total size of data records
    // a sweep of DBH table will find
    dump_v.which = 0;
    dump(&dump_v);
  }


  // Parallel test
#ifndef HAVE_FORK
  if (strcmp(argv[2],"parallel_read")==0){
          fprintf(stdout, "Parallel read test, pid: %d\n", getpid()); fflush(stdout);
            int i; for(i=1; i>=0; i--){
                    dump_v.which = i;
                    dump(&dump_v);
            }
	    return(0x0);
  }
  if (strcmp(argv[2],"parallel_write")==0){
          fprintf(stdout, "Parallel write test, pid: %d\n", getpid()); fflush(stdout);
	    parallel_test(NULL);
	    return(0x0);
  }
#endif
  if (strcmp(argv[2],"parallel")==0 || strcmp(argv[2],"fulltest")==0){

#define CHILDREN 5
#ifdef HAVE_FORK
    pid_t pid[CHILDREN];
#else
    int pid[CHILDREN];
#endif
    int id;
    // Read test...
    fprintf(stderr, "///////////////////  Parallel read tests //////////////////////////\n");
    fprintf(stdout,"Performing parallel read test on %d processes...\n", CHILDREN); 
    for (id=0; id<CHILDREN; id++){
#ifdef HAVE_FORK
	pid[id] = fork();
	if (!pid[id]) {
	    int i; for(i=1; i>=0; i--) {dump_v.which = i; dump(&dump_v);}
	    return(0x01);
	}
#else
        const char *arg[] = {argv[0], argv[1], "parallel_read" ,NULL};
	pid[id] = _spawnvp(P_NOWAIT, arg[0], arg);
#endif
       
    }
    
    for (id=0; id<CHILDREN; id++){
	int status;
#ifdef HAVE_FORK

	int p = wait(&status);
	fprintf(stderr,"read test for process 0x%x done... status=%s\n", 
		(int) p, WIFEXITED(status) && WEXITSTATUS(status)?"PASSED":"FAILED");
#else
        int ret = _cwait(&status, pid[id], 0);
	fprintf(stderr,"read test for process 0x%x done... (%s)\n", 
			pid[id], (ret < 0)?strerror(errno):"exit OK");
#endif

    }

#ifndef PARALLEL_SAFE
    fprintf(stderr,"Skipping parallel write tests (not implemented on this platform)...\n"); 
#else
    fprintf(stderr, "///////////////////  Parallel write tests //////////////////////////\n");
    fprintf(stdout,"Performing parallel write test on %d processes...\n", CHILDREN); 
    // Write test
    DBHashTable *dbh;
    dbh=dbh_new(INDEX, NULL, 0);
    unsigned char key_length = DBH_KEYLENGTH(dbh);
    newdbh=dbh_new(TEST_INDEX, &key_length, DBH_CREATE);
    if (DBH_MAXIMUM_RECORD_SIZE(dbh) > DBH_MAXIMUM_RECORD_SIZE(newdbh)){
	dbh_set_size (newdbh, DBH_MAXIMUM_RECORD_SIZE(dbh));
    }
    dbh_close(dbh);
    dbh_close(newdbh);
    for (id=0; id<CHILDREN; id++){
#ifdef HAVE_FORK
	pid[id] = fork();
	if (!pid[id]) {
	    parallel_test(NULL);
	    return 1;
	}
#else
        const char *arg[] = {argv[0], argv[1], "parallel_write" ,NULL};
	pid[id] = _spawnvp(P_NOWAIT, arg[0], arg);
#endif
    }
    for (id=0; id<CHILDREN; id++){
	int status;
#ifdef HAVE_FORK
	pid_t p = wait(&status);
	fprintf(stderr,"write test for process 0x%x done... status=%s\n", 
		(int) p, WIFEXITED(status) && WEXITSTATUS(status)?"PASSED":"FAILED");
#else
        int ret = _cwait(&status, pid[id], 0);
	fprintf(stderr,"write test for process 0x%x done...  (%s)\n", 
		pid[id],  (ret < 0)?strerror(errno):"exit OK");
#endif
    }
    fprintf(stdout,"Verifying integrity of parallel write test output...\n"); 
    // Copy newly created index to index.

    if (rename(TEST_INDEX, INDEX) < 0){
	g_warning("rename (%s, %s): %s\n", TEST_INDEX, INDEX, strerror(errno));
    }
   
    //dbh=dbh_open(INDEX);
    //dbh_regen_sweep(&dbh);
    //dbh_close(dbh);
    // Find out how many items and total size of data records
    // a sweep of DBH table will find
    int i; for(i=1; i>=0; i--) {dump_v.which = i; dump(&dump_v);}

#endif
  }

#if GLIB_MAJOR_VERSION==2 && GLIB_MINOR_VERSION<32   
    g_thread_init(NULL);
#endif

  // Thread test
  if (strcmp(argv[2],"thread")==0 || strcmp(argv[2],"fulltest")==0){
    fprintf(stderr, "///////////////////  Thread read test //////////////////////////\n");
#define THREADS 6
    GThread *thread[THREADS];

    int id;
    for (id=0; id<THREADS; id++){
	dump_t *dump_p = (dump_t *)malloc(sizeof(dump_t));
     if (dump_p == NULL) {
	 fprintf(stderr, "malloc: %s\n", strerror(errno));
	exit(1);
     }     
	memcpy(dump_p, &dump_v, sizeof(dump_t));
#if GLIB_MAJOR_VERSION==2 && GLIB_MINOR_VERSION<32
	thread[id] = g_thread_create(thread_dump_f, dump_p, TRUE, NULL);
#else
	thread[id] = g_thread_try_new(NULL, thread_dump_f, dump_p, NULL);
#endif
	fprintf(stdout,"  thread 0x%x started\n", GPOINTER_TO_INT(thread[id]));
	fflush(stdout);
    }
    for (id=0; id<THREADS; id++){
	if (thread[id])	{
	    g_thread_join(thread[id]);
	    fprintf(stdout,"  thread 0x%x joined.\n", GPOINTER_TO_INT(thread[id]));
	    fflush(stdout);
	}
    }

    fprintf(stderr, "///////////////////  Thread read/write test //////////////////////////\n");
    
#if 10
    // Create empty DBH table:      
    unsigned char k = 11;
    // DBH_THREAD_SAFE sets up the dbh table mutex for thread safe operation
    DBHashTable *rebuilt_dbh=dbh_new(REBUILT, &k, DBH_CREATE|DBH_THREAD_SAFE);

    for (id=0; id<THREADS; id++){
#if GLIB_MAJOR_VERSION==2 && GLIB_MINOR_VERSION<32
	thread[id] = g_thread_create(thread_test_f, rebuilt_dbh, TRUE, NULL);
#else
	thread[id] = g_thread_try_new(NULL, thread_test_f, rebuilt_dbh, NULL);
#endif
	fprintf(stdout,"  thread 0x%x started\n", GPOINTER_TO_INT(thread[id]));
	fflush(stdout);
    }
    for (id=0; id<THREADS; id++){
	if (thread[id])	{
	    g_thread_join(thread[id]);
	    fprintf(stdout,"  thread 0x%x joined.\n", GPOINTER_TO_INT(thread[id]));
	    fflush(stdout);
	}
    }
    dbh_close(rebuilt_dbh);
    rename (REBUILT, INDEX);
    int i; for(i=1; i>=0; i--) {dump_v.which = i; dump(&dump_v);}
#endif
  }
  fprintf(stderr, "///////////////////  Final comparison test //////////////////////////\n");
  // Comparison test  
  if (strcmp(argv[2],"compare")==0 || strcmp(argv[2],"fulltest")==0) {
    fprintf(stdout,"Performing comparison test now (sweep)...\n");
    DBHashTable *dbh;
    dbh=dbh_new(INDEX, NULL, DBH_READ_ONLY);
    dbh_key=dbh_new(KEY, NULL, DBH_READ_ONLY);
    dbh_foreach_sweep (dbh,compare);
    dbh_close(dbh);
    dbh_close(dbh_key);
    fprintf(stdout,"\n");
    fprintf(stderr, "Test PASSED\n");

    fprintf(stdout,"Performing comparison test now (fanout)...\n");
    dbh=dbh_new(INDEX, NULL, DBH_READ_ONLY);
    dbh_key=dbh_new(KEY, NULL, DBH_READ_ONLY);
    dbh_foreach_fanout (dbh,compare);
    dbh_close(dbh);
    dbh_close(dbh_key);
    fprintf(stdout,"\n");
    fprintf(stderr, "Test PASSED\n");

  }
  fprintf(stderr, "All tests passed.\n"); 
  exit(0);
}
