/*
 * re_tests.c
 *
 * A programmers editor
 *
 * Tests (ensure correct functionality of modules)
 *
 * Author: Dario Rodriguez antartica@whereismybit.com
 * This program is licensed under the terms of GNU GPL v2.1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>

#include "re_data.h"

#define PREFIX "retest_"
#define TEST_OK "OK"

typedef struct test_t  {
        char *name;
        char *(*fn)(redata_t *,char *,char *, int, int);
        char *param1;
        char *param2;
        int int1;
        int int2;
} test_t;

static char *malloc_data(int size, int seed);
static int write_file(char *filename, char *buf, int buflen);

char *test_newfile(redata_t *redata, char *filename, char *dummy, int filesize, int dummy2);
char *test_edit(redata_t *redata, char *filename, char *edits, int filesize, int seed);

int
main(int argc, char *argv[])
{
        static int sizes[]={-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,30,31,32,33,63,64,65,127,128,129,1023,1024,1025,16384};
        test_t tests[]={
                {"newfile_16",test_newfile,PREFIX "FILE", NULL, 16, 0},
                {"newfile_1024",test_newfile,PREFIX "FILE", NULL, 1024, 0},
                {"newfile_32767",test_newfile,PREFIX "FILE", NULL, 32767, 0},
                {"newfile_32768",test_newfile,PREFIX "FILE", NULL, 32768, 0},
                {"newfile_32769",test_newfile,PREFIX "FILE", NULL, 32769, 0},
                {"newfile_131072",test_newfile,PREFIX "FILE", NULL, 131072, 0},
                {"testedit_add",test_edit,PREFIX "EDIT", "A0$Testing add$",0,0},
                {"testedit_del",test_edit,PREFIX "EDIT", "A0$Testing add/del$D8$add/$",0,0},
                {"testedit_move",test_edit,PREFIX "EDIT", "A0$This is a text to move.$M17,7$ move$",0,0},
        };
        int flag_exit=0,flag_all=0;
        redata_t *redata;
        int i,s;
        int nerrors,total;
        char *res;
        for(i=1;i<argc;i++) {
                if(strcmp(argv[i],"--help")==0) {
                        fprintf(stderr,"Syntax: %s [--all] [--exit] [--help]\nExplanation:\n\t--all: do even the slow tests\n\t--exit: exit program at first unsuccessful test\n\t--help: this text\n",argv[0]);
                        return(1);
                } else if(strcmp(argv[i],"--all")==0) {
                        flag_all=1;
                } else if(strcmp(argv[i],"--exit")==0) {
                        flag_exit=1;
                }
        }
        nerrors=0;
        total=0;
        for(s=0;s<(sizeof(sizes)/sizeof(sizes[0]));s++) {
                if(sizes[s]!=-1)
                        fprintf(stderr,"SIZE: %i\n",sizes[s]);
                for(i=0;i<(sizeof(tests)/sizeof(tests[0]));i++,total++) {
                        if(!flag_all && tests[i].int1>=1024)
                                continue; /* too slow: skip unless testing --all */
                        if((redata=redata_init(NULL))==NULL) {
                                fprintf(stderr,"ERROR: problem initializing redata module\n");
                                return(1);
                        }
                        if(sizes[s]!=-1)
                                redata_config_chunkdatasize(redata,sizes[s]);
                        fprintf(stderr,"%s...",tests[i].name);
                        res=tests[i].fn(redata,tests[i].param1,tests[i].param2,tests[i].int1,tests[i].int2);
                                if(strcmp(res,TEST_OK)==0) {
                                fprintf(stderr," ok.\n");
                        } else {
                                fprintf(stderr," ERROR: %s\n",res);
                                nerrors++;
                                if(flag_exit) {
                                        /* exit on first error */
                                        s=sizeof(sizes)/sizeof(sizes[0]);
                                        break;
                                }
                        }
                        redata_free(redata),redata=NULL;
                }
                fprintf(stderr,"\n");
        }
        if(nerrors==0)
                fprintf(stderr,"All %i tests passed OK\n",total);
        else
                fprintf(stderr,"%i test(s) failed of %i tests run.\n",nerrors,total);
        redata_free(redata),redata=NULL;
        return((nerrors==0)?0:1);
}

static char *
malloc_data(int size, int seed)
{
        char *mem;
        int i;
        if(size<=0 || (mem=malloc(size))==NULL)
                return(NULL);
        memset(mem,0,size);
        if(seed==0) {
                for(i=0;i<size;i++)
                        ((unsigned char *)mem)[i]=(i%256);
        } else {
                srandom(*((unsigned int *)(&seed)));
                for(i=0;i<size;i++)
                        ((unsigned char *)mem)[i]=(random()&0xff);
        }
        return(mem);

}

static int
write_file(char *filename, char *buf, int buflen)
{
        int fd;
        if((fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0644))==-1) {
                free(buf),buf=NULL;
                return(-1);
        }
        write(fd,buf,buflen);
        close(fd),fd=-1;
        return(0);
}

char *
test_newfile(redata_t *redata, char *filename, char *dummy, int filesize, int dummy2)
{
        char *mem;
        char hash129_pre[129];
        char hash129_post[129];
fprintf(stderr,"\ntest_newfile(%s%s%s,%s%s%s,%i,%i);\nResult: ",(filename!=NULL)?"\"":"",(filename!=NULL)?filename:"NULL",(filename!=NULL)?"\"":"",(dummy!=NULL)?"\"":"",(dummy!=NULL)?dummy:"NULL",(dummy!=NULL)?"\"":"",filesize,dummy2);
        /* prepare file for loading */
        if((mem=malloc_data(filesize,0))==NULL)
                return("insuf. mem. for temp. buffer");
        if(write_file(filename,mem,filesize)==-1)
                return("couldn't create temporary file");
        redata_filehash(redata,filename,hash129_pre);
        free(mem),mem=NULL;
        /* load file */
        if(redata_load(redata,filename,NULL)!=0) {
                unlink(filename);
                return("couldn't load file");
        }
        unlink(filename);
        redata_hash(redata,hash129_post);
        if(strcmp(hash129_pre,hash129_post)!=0)
                return("loaded file is corrupted");
        /* save file */
        redata_save(redata,filename,NULL);
        hash129_post[0]='\0';
        redata_filehash(redata,filename,hash129_post);
        if(strcmp(hash129_pre,hash129_post)!=0)
                return("saved file is corrupted");
        unlink(filename);
        return(TEST_OK);
}

char *
test_edit(redata_t *redata, char *filename, char *edits, int filesize, int seed)
{
        static char errorbuf[256];
        char progress[256];
        int k;
        int cursize,maxsize;
        char *ptr;
        int l,o;
        char *mem;
        char endcode;
        char *ptrend;
        int size;
        char hash129_memold[129];
        char hash129_mem[129];
        char hash129_redata[129];
        mem=NULL;
fprintf(stderr,"\ntest_edit(%s%s%s,%s%s%s,%i,%i);\nResult: ",(filename!=NULL)?"\"":"",(filename!=NULL)?filename:"NULL",(filename!=NULL)?"\"":"",(edits!=NULL)?"\"":"",(edits!=NULL)?edits:"NULL",(edits!=NULL)?"\"":"",filesize,seed);
        /* two passes: k==0 count needed memory, k==1 do edits */
        for(k=0,maxsize=cursize=filesize,progress[0]='\0';k<2;k++,cursize=0,progress[0]='\0') {
                for(ptr=edits;*ptr!='\0';) {
                        if(k!=0) {
                                redata_memhash(redata,mem,cursize,hash129_memold);
                        }
                        if((l=strlen(progress))<(sizeof(progress)-1)) {
                                progress[l]=(l<(sizeof(progress)-2))?*ptr:'+';
                                progress[l+1]='\0';
                        }
if(k!=0) unlink("test.pre"),unlink("test.post"),unlink("test.undo"),unlink("test.redo");
if(k!=0) redata_save(redata,"test.pre",NULL);
                        if(*ptr=='A') {
                                /* A<insertpos><endchar><text><endchar> */
                                ptr++;
                                errno=0;
                                l=(int)strtol(ptr,&ptr,10);
                                if(errno!=0 || l<0 || l>cursize) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        return("test_edit(): error parsing position");
                                }
                                endcode=*ptr;
                                ptr+=(*ptr!='\0')?1:0;
                                ptrend=strchr(ptr,endcode);
                                ptrend=(ptrend==NULL)?ptr+strlen(ptr):ptrend;
                                size=ptrend-ptr;
                                if(k!=0) {
                                        /* editing */
                                        memmove(mem+l+size,mem+l,cursize-l);
                                        memcpy(mem+l,ptr,size);
                                        redata_op_add(redata,l,ptr,size,NULL);
                                }
                                cursize+=size;
                                ptr+=size;
                                ptr+=(*ptr!='\0')?1:0;
                        } else if(*ptr=='D') {
                                /* D<delpos><endchar><text><endchar> */
                                ptr++;
                                errno=0;
                                l=(int)strtol(ptr,&ptr,10);
                                if(errno!=0) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        return("test_edit(): error parsing position");
                                }
                                endcode=*ptr;
                                ptr+=(*ptr!='\0')?1:0;
                                ptrend=strchr(ptr,endcode);
                                ptrend=(ptrend==NULL)?ptr+strlen(ptr):ptrend;
                                size=ptrend-ptr;
                                if(l<0 || (l+size)>cursize) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        return("test_edit(): internal error: invalid pasition or size");
                                }
                                if(k!=0) {
                                        if(memcmp(mem+l,ptr,size)!=0
                                          || redata_data_compare(redata,l,ptr,size)!=0) {
                                                if(mem!=NULL)
                                                        free(mem),mem=NULL;
                                                return("test_edit(): internal error: deletion data doesn't match");
                                        }
                                        /* editing */
                                        memmove(mem+l,mem+l+size,cursize-l-size);
                                        redata_op_del(redata,l,size,NULL);
                                }
                                cursize-=size;
                                ptr+=size;
                                ptr+=(*ptr!='\0')?1:0;
                        } else if(*ptr=='M') {
                                /* M<origpos>,<destpos><endchar><text><endchar> */
                                ptr++;
                                errno=0;
                                l=(int)strtol(ptr,&ptr,10);
                                if(errno!=0) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        return("test_edit(): error parsing position");
                                }
                                ptr+=(*ptr==',')?1:0;
                                errno=0;
                                o=(int)strtol(ptr,&ptr,10);
                                if(errno!=0) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        return("test_edit(): error parsing position");
                                }
                                endcode=*ptr;
                                ptr+=(*ptr!='\0')?1:0;
                                ptrend=strchr(ptr,endcode);
                                ptrend=(ptrend==NULL)?ptr+strlen(ptr):ptrend;
                                size=ptrend-ptr;
                                if(l<0 || (l+size)>cursize || o<0 || o>cursize || (o>=l && o<(l+size))) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        return("test_edit(): internal error: invalid pasition or size");
                                }
                                if(k!=0) {
                                        if(memcmp(mem+l,ptr,size)!=0
                                          || redata_data_compare(redata,l,ptr,size)!=0) {
                                                if(mem!=NULL)
                                                        free(mem),mem=NULL;
                                                return("test_edit(): internal error: move data doesn't match");
                                        }
                                        /* editing */
                                        if(l>o) {
                                                memmove(mem+o+size,mem+o,l-o);
                                                memcpy(mem+o,ptr,size);
                                        } else {
                                                memmove(mem+l,mem+l+size,o-l-size);
                                                memcpy(mem+o-size,ptr,size);
                                        }
                                        redata_op_move(redata,l,size,o,NULL);
                                }
                                ptr+=size;
                                ptr+=(*ptr!='\0')?1:0;
                        } else {
                                if(mem!=NULL)
                                        free(mem),mem=NULL;
                                snprintf(errorbuf,sizeof(errorbuf),
                                  "test_edit(): unknown edit action at pos %i: '%c' (progress: %s)",(int) (ptr-edits),*ptr,progress);
                                errorbuf[sizeof(errorbuf)-1]='\0';
                                return(errorbuf);
                        }
                        if(cursize>maxsize)
                                maxsize=cursize;
                        if(k!=0) {
if(k!=0) redata_save(redata,"test.post",NULL);
                                redata_hash(redata,hash129_redata);
                                redata_memhash(redata,mem,cursize,hash129_mem);
                                if(strcmp(hash129_redata,hash129_mem)!=0) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        snprintf(errorbuf,sizeof(errorbuf),
                                          "corrupted edit before pos %i (progress: %s)",(int) (ptr-edits),progress);
                                        errorbuf[sizeof(errorbuf)-1]='\0';
                                        return(errorbuf);
                                }
                                redata_op_undo(redata,NULL);
redata_save(redata,"test.undo",NULL);
                                redata_hash(redata,hash129_redata);
                                if(strcmp(hash129_redata,hash129_memold)!=0) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        snprintf(errorbuf,sizeof(errorbuf),
                                          "corrupted undo before pos %i (progress: %s)",(int) (ptr-edits),progress);
                                        errorbuf[sizeof(errorbuf)-1]='\0';
                                        return(errorbuf);
                                }
                                redata_op_redo(redata,NULL);
redata_save(redata,"test.redo",NULL);
                                redata_hash(redata,hash129_redata);
                                if(strcmp(hash129_redata,hash129_mem)!=0) {
                                        if(mem!=NULL)
                                                free(mem),mem=NULL;
                                        snprintf(errorbuf,sizeof(errorbuf),
                                          "corrupted redo before pos %i (progress: %s)",(int) (ptr-edits),progress);
                                        errorbuf[sizeof(errorbuf)-1]='\0';
                                        return(errorbuf);
                                }
                        }
                }
                if(k==0) {
                        /* reserve memory, do init */
                        if((mem=malloc_data(maxsize,seed))==NULL)
                                return("insuf. mem. for temp. buffer");
                        if(write_file(filename,mem,filesize)==-1)
                                return("couldn't create temporary file");
                        if(redata_load(redata,filename,NULL)!=0) {
                                unlink(filename);
                                return("couldn't load file");
                        }
                        unlink(filename);
                }
        }
        if(mem!=NULL)
                free(mem),mem=NULL;
unlink("test.pre"),unlink("test.post"),unlink("test.undo"),unlink("test.redo");
        return(TEST_OK);
}