/*
 * webkernel_test.c
 *
 * Tests to stress the webkernel API.
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This program is dual licensed: MIT and in "the public domain".
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sbuf.h"
#include "socklib.h"
#include "webkernel.h"

#define STRING_OK "ok"
#define STRING_FAIL "fail"

typedef enum test_action {
        test_name=0,
        test_description,
        test_run
} test_action;

char *test1(test_action action);
char *socklib_connect(test_action action);
char *socklib_sselect(test_action action);
char *sbuf_memory(test_action action);

struct {
        char *(*test)(/* test_action action */);
} tests[]={{test1},
           {socklib_connect},
           {socklib_sselect},
           {sbuf_memory},
          };

int
main(int argc, char *argv[])
{
        int i;
        int flagall;
        char *resstr;
        int total,totalfail;
        if(argc==1 || strcmp(argv[argc-1],"--help")==0 || argc!=2) {
                printf("Syntax:\n\t%s { --help | --list | test_name | all }\nNOTE: test_name is one of the tests listed in --list\n",argv[0]);
                return(0);
        }
        if(strcmp(argv[argc-1],"--list")==0) {
                for(i=0;i<(sizeof(tests)/sizeof(tests[0]));i++)
                        printf("%s\n\t%s\n",tests[i].test(test_name),tests[i].test(test_description));
                return(0);
        }
        flagall=(strcmp(argv[argc-1],"all")==0)?1:0;
        for(total=totalfail=0,i=0;i<(sizeof(tests)/sizeof(tests[0]));i++) {
                if(!flagall && strcmp(tests[i].test(test_name),argv[argc-1])!=0)
                        continue;
                printf("%20s...",tests[i].test(test_name));
                fflush(stdout);
                resstr=tests[i].test(test_run);
                total++;
                totalfail+=((memcmp(resstr,STRING_OK,strlen(STRING_OK))==0)?0:1);
                printf("%s\n",resstr);

        }
        if(!flagall && i>=(sizeof(tests)/sizeof(tests[0]))) {
                printf("ERROR: test not found\n");
                return(1);
        }
        if(totalfail!=0)
                printf("Failed %i of %i tests.\n",totalfail,total);
        return((totalfail!=0)?1:0);
}

char *
test1(test_action action)
{
        if(action==test_name)
                return("test1");
        else if(action==test_description)
                return("test the testing framework");
        /* run test */
        return("ok");
}

char *
socklib_connect(test_action action)
{
        int server,client;
        char *host;
        long hostsize;
        int port;
        int off;
        int timeout=100;
        if(action==test_name)
                return("socklib_connect");
        if(action==test_description)
                return("listen and connect");
        if((host=ipv4_genip("localhost",&hostsize))==NULL)
                return(STRING_FAIL ": couldn't resove localhost");
        for(server=-1,off=0,port=19747;off<1024;off++,port++) {
                if((server=ipv4_serverbinded(host,hostsize,port))!=-1)
                        break;
        }
        if(server==-1) {
                free(host),host=NULL;
                return(STRING_FAIL ": couldn't find empty port for server\n");
        }
        sock_setunsafe(server);
        if((client=ipv4_preconnect(host,hostsize,port))==-1) {
                close(server),server=-1;
                free(host),host=NULL;
                return(STRING_FAIL ": couldn't connect to server\n");
        }
        if(ipv4_connect(client,timeout)==-1) {
                close(client),client=-1;
                close(server),server=-1;
                free(host),host=NULL;
                return(STRING_FAIL ": timeout on connect\n");
        }
        ipv4_postconnect(client);
        close(client),client=-1;
        close(server),server=-1;
        free(host),host=NULL;
        return(STRING_OK);
}

char *
test_socketsinit(int *server, int *client, char **host, long *hostsize, int *port)
{
        int timeout=100;
        int off;
        *server=*client=*port=-1;
        *hostsize=0;
        *host=NULL;
        *port=-1;
        if((*host=ipv4_genip("localhost",hostsize))==NULL)
                return(STRING_FAIL ": couldn't resove localhost");
        for(*server=-1,off=0,*port=19747;off<1024;off++,(*port)++) {
                if((*server=ipv4_serverbinded(*host,*hostsize,*port))!=-1)
                        break;
        }
        if(*server==-1) {
                free(*host),*host=NULL;
                return(STRING_FAIL ": couldn't find empty port for server\n");
        }
        sock_setunsafe(*server);
        if((*client=ipv4_preconnect(*host,*hostsize,*port))==-1) {
                close(*server),*server=-1;
                free(*host),*host=NULL;
                return(STRING_FAIL ": couldn't connect to server\n");
        }
        if(ipv4_connect(*client,timeout)==-1) {
                close(*client),*client=-1;
                close(*server),*server=-1;
                free(*host),*host=NULL;
                return(STRING_FAIL ": timeout on connect\n");
        }
        ipv4_postconnect(*client);
        return(NULL);
}

void
test_socketsfini(int *server, int *client, char **host, long *hostsize, int *port)
{
        if(*server!=-1)
                close(*server),*server=-1;
        if(*client!=-1)
                close(*client),*client=-1;
        if(*host!=NULL)
                free(*host),*host=NULL;
        *hostsize=0;
        *port=-1;
}

char *
socklib_sselect(test_action action)
{
        int server,client;
        char *host;
        long hostsize;
        int port;
        char *result;
        char buf[128];
        int readyfds[16];
        sselect *ssel;
        int timeout=100;
        int workfd;
        if(action==test_name)
                return("socklib_sselect");
        if(action==test_description)
                return("select over sockets");
        if((result=test_socketsinit(&server,&client,&host,&hostsize,&port))!=NULL) {
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(result);
        }
        if((ssel=sselect_init())==NULL) {
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": couldn't init sselect struct");
        }
        if(sselect_reset(ssel)!=0) {
                sselect_free(ssel),ssel=NULL;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": couldn't test the sselect reset function");
        }
        if(sselect_wait(ssel,timeout)!=0) {
                sselect_free(ssel),ssel=NULL;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": (1) was expecting a timeout, something else happened");
        }
        if(sselect_addread(ssel,server,buf)!=0) {
                sselect_free(ssel),ssel=NULL;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": couldn't add 'read serverfd' to the sselect");
        }
        if(sselect_wait(ssel,timeout)!=1) {
                sselect_free(ssel),ssel=NULL;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": (2) was expecting a ready-to-read, something else happened");
        }
        if((workfd=sock_accept(server))==-1) {
                sselect_free(ssel),ssel=NULL;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": couldn't accept connection");
        }
        write(client,".",1);
        if(sselect_wait(ssel,timeout)!=0) {
                sselect_free(ssel),ssel=NULL;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": (2) was expecting a timeout, something else happened");
        }
        if(sselect_addread(ssel,workfd,&workfd)!=0) {
                sselect_free(ssel),ssel=NULL;
                close(workfd),workfd=-1;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": couldn't add 'read workfd' to the sselect");
        }
        if(sselect_wait(ssel,timeout)!=1) {
                sselect_free(ssel),ssel=NULL;
                close(workfd),workfd=-1;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": was expecting a something-to-read, something else happened");
        }
        if((sselect_getread(ssel,readyfds,sizeof(readyfds)/sizeof(readyfds[0])))!=1 || *readyfds!=workfd) {
                sselect_free(ssel),ssel=NULL;
                close(workfd),workfd=-1;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": the list of ready-to-read fds is wrong");
        }
        if(sselect_getuserptr(ssel,workfd)!=&workfd) {
                sselect_free(ssel),ssel=NULL;
                close(workfd),workfd=-1;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": Couldn't recover the userptr of workfd");
        }
        read(workfd,buf,1);
        if(sselect_addwrite(ssel,client,buf)!=0) {
                sselect_free(ssel),ssel=NULL;
                close(workfd),workfd=-1;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": couldn't add 'write clientfd' to the sselect");
        }
        if(sselect_wait(ssel,timeout)!=1) {
                sselect_free(ssel),ssel=NULL;
                close(workfd),workfd=-1;
                test_socketsfini(&server,&client,&host,&hostsize,&port);
                return(STRING_FAIL ": was expecting an ok-to-write, something else happened");
        }
        sselect_free(ssel),ssel=NULL;
        close(workfd),workfd=-1;
        test_socketsfini(&server,&client,&host,&hostsize,&port);
        return(STRING_OK);
}

char *
sbuf_memory(test_action action)
{
        int bufsize=2048;
        const unsigned char data[]={0,2,4,1,2,4,1,76,91,147,135,253,121,56,5,9};
        unsigned char moredata[16];
        unsigned char *ptr;
        const char *line,lines[]={"one\nand two\nthree"};
        int i;
        sbuf *buf;
        if(action==test_name)
                return("sbuf_memory");
        else if(action==test_description)
                return("memory operations with sbuf");
        if((buf=sbuf_init(bufsize))==NULL)
                return(STRING_FAIL ": couldn't alloc new sbuf");
        if(sbuf_add(buf,(char *)data,sizeof(data))!=sizeof(data)) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": couldn't push data");
        }
        for(i=0;i<sizeof(moredata);i++)
                moredata[i]=i;
        if(sbuf_add(buf,(char *)moredata,sizeof(moredata))!=sizeof(moredata)) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": (2) couldn't push data");
        }
        if(sbuf_count(buf)!=(sizeof(data)+sizeof(moredata))) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": bad count");
        }
        for(i=0;i<sizeof(data);i++) {
                if((ptr=(unsigned char *)sbuf_getbytes(buf,1))==NULL || *ptr!=data[i]) {
                        sbuf_free(buf),buf=NULL;
                        return(STRING_FAIL ": bad retrieval");
                }
        }
        sbuf_discard(buf);
        if((ptr=(unsigned char *)sbuf_getbytes(buf,sizeof(moredata)))==NULL || memcmp(ptr,moredata,sizeof(moredata))!=0) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": (2) bad retrieval");
        }
        if(sbuf_getbytes(buf,1)!=NULL) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": (3) bad retrieval");
        }
        sbuf_discard(buf);
        if(sbuf_addstr(buf,lines)!=(sizeof(lines)-1)) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": (3) couldn't push data");
        }
        if((line=sbuf_getline(buf))==NULL || strcmp(line,"one")!=0) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": bad line retrieval");
        }
        if((line=sbuf_getline(buf))==NULL || strcmp(line,"and two")!=0) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": (2) bad line retrieval");
        }
        if(sbuf_getline(buf)!=NULL) {
                sbuf_free(buf),buf=NULL;
                return(STRING_FAIL ": (3) bad line retrieval");
        }
        sbuf_free(buf),buf=NULL;
        return(STRING_OK);
}