static const char version[] = "$Id: test.c,v 1.10 2005/12/15 22:11:50 rbyrne Exp $";

/* 
 * Copyright 2001-2003, Meiosys (www.meiosys.com). All rights reserved.
 *
 * See the COPYING file for the terms of usage and distribution.
 */

#ifdef HAVE_CONFIG_H
#       include "config.h"
#endif
#include <sd/test.h>
#include <sd/malloc.h>
#include <sd/sprintf.h>
#include <sd/sd_xplatform.h>
 
#include <stdlib.h>
#include <string.h>

typedef XP_UINT64 usec_t;

#define MAX_NFUNC 100

struct __sd_test
{
    const char*         name;
    char                in_filename[128];
    char                ref_filename[128];
    char                out_filename[128];
    FILE*               in;
    FILE*               out;
    FILE*               err;
    int                 verbose;
    int                 timed;
    sd_test_func_t**    funcs;
    int                 size;
    int			argc;
    char**		argv;
};


/******************************************************************************/
static usec_t now(void)
{
    struct timeval tv;

    SD_GETTIMEOFDAY(&tv, NULL);
    return (usec_t) (tv.tv_sec * 1000000 + tv.tv_usec);
}

/******************************************************************************/
static int test_compare(sd_test_t* this, int a_argc, char* a_argv[])
{
    char cmd[1024];


#ifndef _WIN32
#warning access() routine should be defined in sd_xplatform
    if (access(this->ref_filename, R_OK) || access(this->out_filename, R_OK))
      return 1;
    
#else
    /*
      cf. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__access.2c_._waccess.asp
    */
     if (_access(this->ref_filename, 04) == -1 ||
         _access(this->out_filename, 04) == -1)
        return 1;
#endif
     snprintf(cmd, sizeof(cmd), "%s %s %s 1>/dev/null 2>&1", DIFF_CMD,
             this->ref_filename, this->out_filename);
   
    return ! system(cmd);
}

/******************************************************************************/
extern sd_test_t* sd_test_new(int a_argc, char* a_argv[])
{
    sd_test_t* this;
    int c;
    char* ptr;

    this        = sd_calloc(1, sizeof(sd_test_t));
    this->funcs = sd_calloc(MAX_NFUNC, sizeof(sd_test_func_t *));

    /*
     * get rid of libtool frontend script
     */
    ptr = strstr(a_argv[0], "lt-");
    if (ptr)
	this->name = ptr + 3;
    else
      this->name = a_argv[0];    

    snprintf(this->ref_filename, sizeof(this->ref_filename), "%s.ref",
	     this->name);
    snprintf(this->in_filename,  sizeof(this->in_filename),  "%s.in",
	     this->name);
    snprintf(this->out_filename, sizeof(this->out_filename), "%s.out",
	     this->name);
    
    this->in    = fopen(this->in_filename,  "r");
    this->out   = fopen(this->out_filename, "w");
    this->err   = 0;
    this->verbose= 0;
    this->size  = 0;

    while ((c = SD_GETOPT(a_argc, a_argv, "vt")) != EOF) {
        switch(c) {
        case 'v': this->verbose = 1; break;
        case 't': this->timed   = 1; break;
        default:                     break; 
        }
    }

    this->argc = a_argc - (SD_OPTIND - 1);
    this->argv = a_argv + (SD_OPTIND - 1);
    return this;
}

/******************************************************************************/
extern void sd_test_delete(sd_test_t* this)
{
    if (!this)
        return;

    if (this->in) fclose(this->in);
    if (this->out) fclose(this->out);
    free(this->funcs);
    free(this);
}
        
/******************************************************************************/
extern const char* sd_test_get_name(const sd_test_t* this)
{
    return this ? this->name : NULL;
}

/******************************************************************************/
extern int sd_test_get_verbose(const sd_test_t* this)
{
    return this ? this->verbose: 0;
}

/******************************************************************************/
extern int sd_test_set_verbose(sd_test_t* this, int a_verbose)
{
    if (!this)
        return 0;

    return this->verbose = a_verbose;
}

/******************************************************************************/
extern FILE* sd_test_in(sd_test_t* this)
{
    if (!this)
        return NULL;

    return this->in ? this->in : stdin;
}

/******************************************************************************/
extern FILE* sd_test_out(sd_test_t* this)
{
    if (!this)
        return NULL;

    if (this->verbose)
        return stdout;

    return this->out ? this->out : stdout;
}

/******************************************************************************/
extern FILE* sd_test_err(sd_test_t* this)
{
    if (!this)
        return NULL;

    return this->err ? this->err : stderr;
}

/******************************************************************************/
extern int sd_test_run(sd_test_t* this, int argc, char* argv[])
{
    int i, passed = 0;

    if (!this)
        return -1;

    sd_test_add(this, test_compare);
    
    fprintf(sd_test_err(this), "%s: ", this->name);
    
    for (i = 0; i < this->size; i++) {
        int t;
        usec_t elapsed;

        fprintf(sd_test_out(this), "=> test #%d :\n", i);

        elapsed = now();
        t = (*this->funcs[i])(this, this->argc, this->argv);
        elapsed = now() - elapsed;

        fprintf(sd_test_out(this), "=> test #%d : %s\n", i, t ? " passed" : " failed");
        fflush(sd_test_out(this));

        passed += (t != 0);
        
        fprintf(sd_test_err(this), "%c", t ? '+' : '-');
        if (this->timed)
            fprintf(sd_test_err(this), "%lld ", elapsed);
    }
    
    fprintf(sd_test_err(this), " %d/%d %s.\n", passed, this->size,
            passed == this->size ? " passed" : " failed");
    
    return passed == this->size;
}

/******************************************************************************/
extern int sd_test_add(sd_test_t* this, sd_test_func_t a_func)
{
    if (!this)
        return -1;

    if (this->size == MAX_NFUNC)
        return this->size;

    this->funcs[this->size] = a_func;

    return this->size++;
}
