/* ARISA - Test Suites
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* nickhash test suite */

#define MAX_CHANS 16
#define MAX_ACTORS 8192

typedef struct actor_t {
	char 		*name;
	channel_t 	*chans[MAX_CHANS];
	int 		no_chans;
} actor_t;

typedef struct nht_state_t {
	channel_t 	*chans[MAX_CHANS];
	int		no_chans;
	actor_t		*actors[MAX_ACTORS];
	int		no_actors;
	nickhash_t 	*nh;
	int		cno;
	
	int 		signons;
	int 		quits;
	int		joins;
	int		parts;
	int		renames;
	int 		chanc;
	int		chand;
} nht_state_t;

static actor_t *find_actor(nht_state_t *state, char *name) {
	int i;
	for(i = 0; i < state->no_actors; ++i) {
		if(irc_strcasecmp(state->actors[i]->name,name) == 0)
			return state->actors[i];
	}
	return NULL;
}

static actor_t *gen_actor(nht_state_t *state) {
	actor_t *a = xalloc(sizeof(actor_t));
	int i,nlen = lrand(18)+2;
	
	if(state->no_actors >= MAX_ACTORS) {
		xfree(a);
		return NULL;
	}
	
	a->name = xalloc(nlen + 1);
	do {
		for(i = 0; i < nlen; ++i)
			a->name[i] = (char) (lrand(60)+65);
		a->name[nlen] = '\0';
		// FIXME: possible infinite loop
	} while(find_actor(state,a->name) != NULL);
	a->no_chans = 0;

	printf("+++ %s Signon\n",a->name);
	state->actors[state->no_actors++] = a;
	state->signons++;
	
	return a;
}

static void del_actor(nht_state_t *state, actor_t *a) {
	int i,j;
	
	nickhash_del(state->nh,NULL,a->name);
	
	for(i = 0; i < state->no_actors; ++i) {
		if(state->actors[i] == a)
			break;
	}
	
	if(i < state->no_actors) {
		for(j = i+1; j < state->no_actors; ++i,++j)
			state->actors[i] = state->actors[j];
		state->no_actors--;
	}
	
	printf("+++ %s Quit\n",a->name);
	xfree(a->name);
	xfree(a);
	
	state->quits++;
}

static void join_actor(nht_state_t *state, actor_t *a) {
	channel_t *c;
	int i,j = 0,add;
	
	if(state->no_chans == 0)
		return;
	
	c = state->chans[lrand(state->no_chans)];
	
	do {
		add = 1;
		for(i = 0; i < a->no_chans && add; ++i) {
			if(a->chans[i] == c)
				add = 0;
		}
		if(add) {
			a->chans[a->no_chans++] = c;
			nickhash_add(state->nh,c,a->name);
			printf("+++ %s Joined %s\n",a->name,c->name);
			state->joins++;
		} else {
			if(j < state->no_chans) 
				c = state->chans[j];
			j++;
		}
	} while(add == 0 && j <= state->no_chans);	
}

static void part_actor(nht_state_t *state, actor_t *a) {
	channel_t *c;
	int i,j;
	
	if(a->no_chans == 0)
		return;
	
	i = lrand(a->no_chans);
	c = a->chans[i];
	nickhash_del(state->nh,c,a->name);
	
	for(j = i+1; j < a->no_chans; ++i,++j)
		a->chans[i] = a->chans[j];
	a->no_chans--;
	
	printf("+++ %s Parted %s\n",a->name,c->name);
	state->parts++;
}

static void rename_actor(nht_state_t *state, actor_t *a) {
	char *nname;
	int i,nlen = lrand(18)+2;
	
	nname = xalloc(nlen + 1);
	do {
		for(i = 0; i < nlen; ++i)
			nname[i] = (char) (lrand(60)+65);
		nname[nlen] = '\0';
		// FIXME: possible infinite loop
	} while(find_actor(state,nname) != NULL);
	
	nickhash_rename(state->nh,a->name,nname);
	printf("+++ %s Change Nick to: %s\n",a->name,nname);
	
	xfree(a->name);
	a->name = nname;
	
	state->renames++;
}	

static channel_t *gen_chan(nht_state_t *state) {
	if(state->no_chans < MAX_CHANS) {
		state->chans[state->no_chans++] = alloc_channel();
		state->chans[state->no_chans-1]->name = xalloc(16);
		snprintf(
			state->chans[state->no_chans-1]->name,
			16,"#%d",state->cno++
		);
		printf("+++ Formed Channel: %s\n",
				state->chans[state->no_chans-1]->name);
		state->chanc++;
		return state->chans[state->no_chans-1];
	} else
		return NULL;
}

static void del_channel(nht_state_t *state, channel_t *c) {
	int i,j,k;
	
	nickhash_flush_channel(state->nh,c);
	
	for(i = 0; i < state->no_chans; ++i) {
		if(state->chans[i] == c)
			break;
	}
	if(i < state->no_chans) {
		for(j = i+1; j < state->no_chans; ++i,++j)
			state->chans[i] = state->chans[j];
		state->no_chans--;
		state->chand++;
	}
	printf("+++ Dispanded Channel: %s\n",c->name);
	free_channel(c);

	for(i = 0; i < state->no_actors; ++i) {
		for(j = 0; j < state->actors[i]->no_chans; ++j) {
			if(state->actors[i]->chans[j] == c)
				break;
		}
		if(j < state->actors[i]->no_chans) {
			for(k = j+1; k < state->actors[i]->no_chans; ++j,++k)
				state->actors[i]->chans[j] = state->actors[i]->chans[k];
			state->actors[i]->no_chans--;
		}
	}
}

static void gen_event(nht_state_t *state) {
	int ev = lrand(100);
	
	if(ev < 2) {
		printf("--- Generating Channel\n");
		gen_chan(state);
	} else if(ev < 3) {
		printf("--- Deleting Channel\n");
		if(state->no_chans > 0)
			del_channel(state,state->chans[lrand(state->no_chans)]);
	} else if(ev < 25) {
		printf("--- Generating Actor\n");
		gen_actor(state);
	} else if(ev < 35) {
		printf("--- Deleting Actor\n");
		if(state->no_actors > 0)
			del_actor(state,state->actors[lrand(state->no_actors)]);
	} else if(ev < 60) {
		printf("--- Joining Actor\n");
		if(state->no_actors > 0 && state->no_chans > 0)
			join_actor(state,state->actors[lrand(state->no_actors)]);
	} else if(ev < 80) {
		printf("--- Parting Actor\n");
		if(state->no_actors > 0 && state->no_chans > 0)
			part_actor(state,state->actors[lrand(state->no_actors)]);
	} else if(ev < 100) {
		printf("--- Renaming Actor\n");
		if(state->no_actors > 0)
			rename_actor(state,state->actors[lrand(state->no_actors)]);
	}
}

static void check_state(nht_state_t *state) {
	nh_bucket_t *b;
	actor_t *a;
	int i,j,k;
	
	for(i = 0; i > state->no_actors; ++i) {
		for(j = 0; j < state->actors[i]->no_chans; ++i) {
			if(!nickhash_present(state->nh,state->actors[i]->chans[j],state->actors[i]->name))
				fprintf(stderr,"!!! Nickhash function doesn't think %s is on %s\n",state->actors[i]->name,state->actors[i]->chans[j]->name);
		}
	}
	
	for(i = 0; i < state->nh->size; ++i) {
		b = state->nh->table[i];
		while(b != NULL) {
			a = find_actor(state,b->nick);
			if(a->no_chans != b->no_cms)
				fprintf(stderr,"!!! Nickhash channel count mismatch for %s: %d,%d \n",a->name,a->no_chans,b->no_cms);
			if(a != NULL) {
				for(j = 0; j < a->no_chans; ++j) {
					k = 0;
					while(k < b->no_cms &&
						b->cms[k].channel != a->chans[j])
						k++;
					if(k >= b->no_cms)
						fprintf(stderr,"!!! Nickhash doesn't know %s is on %s\n",a->name,a->chans[j]->name);
				}
			} else
				fprintf(stderr,"!!! Unknown nick in nickhash: %s\n",b->nick);
			b = b->next;
		}
	}		
	
	return;
}

static int test_nickhash(unsigned int seed, int loops,
		int ichan, int iact, int ijoin,
		int it, int epi) {
	nht_state_t *state = xalloc(sizeof(nht_state_t));
	int i,j,l;
	
	fprintf(stderr,
		"Executing nickhash tests.\n"
		"seed: %u, loops: %d, iterations per loop: %d, check interval: %d\n"
		"initial channels: %d, initial actors: %d, initial joins: %d\n",
		seed,loops,it,epi,ichan,iact,ijoin);
	
	srand(seed);

	state->nh		= nickhash_init();
	state->signons		= 0;
	state->quits		= 0;
	state->joins		= 0;
	state->parts		= 0;
	state->renames		= 0;
	state->chanc		= 0;
	state->chand		= 0;
	
	for(l = 0; l < loops; ++l) {
		fprintf(stderr,"Loop %d...\n",l);
		
		state->no_chans		= 0;
		state->no_actors	= 0;
		state->cno		= 0;
		
		for(i = 0; i < ichan; ++i)
			gen_chan(state);
		for(i = 0; i < iact; ++i)
			gen_actor(state);
		if(iact > 0 && ichan > 0) {
			for(i = 0; i < ijoin; ++i)
				join_actor(state,state->actors[lrand(state->no_actors)]);
		}
	
		for(i = 0; i < it; ++i) {
			for(j = 0; j < epi; ++j)
				gen_event(state);
			check_state(state);
		}

		for(i = 0; i < state->no_chans; ++i)
			free_channel(state->chans[i]);
		for(i = 0; i < state->no_actors; ++i) {
			xfree(state->actors[i]->name);
			xfree(state->actors[i]);
		}
		
		nickhash_flush(state->nh);
		fprintf(stderr,"...complete\n");
	}
	
	nickhash_free(state->nh);
	
	fprintf(stderr,
		"Completed nickhash tests.\n"
		"signons: %d, quits: %d, joins: %d, parts: %d\n"
		"renames: %d, chanc: %d, chand: %d\n",
		state->signons,state->quits,state->joins,state->parts,
		state->renames,state->chanc,state->chand
	);
	
	xfree(state);
	
	return 0;
}

static int test_lrand(unsigned int seed, int range, int it) {
	int i,*cnt;
	fprintf(stderr,
		"Executing lrand test. seed: %u, range: %d, iterations: %d\n",
		seed,range,it
	);
	srand(seed);
	
	cnt = xalloc(sizeof(int)*(range+1));
	for(i = 0; i < (range+1); ++i)
		cnt[i] = 0;
	for(i = 0; i < it; ++i) 
		cnt[lrand(range)]++;
	for(i = 0; i < (range+1); ++i)
		fprintf(stderr,"value: %d, count: %d\n",i,cnt[i]);
	
	xfree(cnt);

	fprintf(stderr,"Completed lrand test.\n");
	
	return 0;
}

static struct {
	char *host;
	char *mask;
	int expect;
} hostmask_tests[] = {
	{"test.domain",	"test.domain",	1},
	{"test.domain",	"*.domain",	1},
	{"test.domain",	"test.*",	1},
	{"test.domain",	"*.*",		1},
	{"test.domain",	"*",		1},
	{"test.domain",	"test1.*",	0},
	{"",		"*",		1},
	{"a",		"*",		1},
	{"",		"*.*",		0},
	{"1.2.3.4",	"1.2.*.4",	1},
	{NULL}
};
	
static int test_hostmask_match(void) {
	int i;
	for(i = 0; hostmask_tests[i].host != NULL; ++i) {
		if(irc_hostmask_match(hostmask_tests[i].host,hostmask_tests[i].mask) != hostmask_tests[i].expect) {
			fprintf(stderr,
				"Failure, host: \"%s\", mask: \"%s\", expect: %d\n",
				hostmask_tests[i].host,
				hostmask_tests[i].mask,
				hostmask_tests[i].expect);
		}
	}
	return 0;
}

static void print_opts(char **opts, int no_opts) {
	int i;
	for(i = 0; i < no_opts; ++i)
		printf("%d '%s'\n",i,opts[i]);
}

static int test_split_opt(void) {
	char *str,**opts;
	int no_opts;
	
	fprintf(stderr,"Executing split_opt test.\n");
	
	no_opts = 0;
	str = xstrdup("Test");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 1 || strcmp(opts[0],"Test") != 0)
		fprintf(stderr,"Failure for '%s'\n","Test");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("Test1 Test2");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2") != 0)
		fprintf(stderr,"Failure for '%s'\n","Test1 Test2");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("Test1 Test2 ");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2") != 0)
		fprintf(stderr,"Failure for '%s',0 opt\n","Test1 Test2 ");
	xfree(opts);
	xfree(str);

	no_opts = 2;
	str = xstrdup("Test1 Test2 ");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2 ") != 0)
		fprintf(stderr,"Failure for '%s',2 opt\n","Test1 Test2 ");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("Test1  Test2");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2") != 0)
		fprintf(stderr,"Failure for '%s'\n","Test1  Test2");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup(" Test1 Test2");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2") != 0)
		fprintf(stderr,"Failure for '%s'\n"," Test1 Test2");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("Test1 \"Test2 Test3\"");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2 Test3") != 0)
		fprintf(stderr,"Failure for '%s'\n","Test1 \"Test2 Test3\"");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("Test1 \"Test2 Test3\" ");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1") != 0 || strcmp(opts[1],"Test2 Test3") != 0)
		fprintf(stderr,"Failure for '%s'\n","Test1 \"Test2 Test3\" ");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("\"Test1 Test2\" Test3");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1 Test2") != 0 || strcmp(opts[1],"Test3") != 0)
		fprintf(stderr,"Failure for '%s'\n","\"Test1 Test2\" Test3");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup(" \"Test1 Test2\" Test3");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 2 || strcmp(opts[0],"Test1 Test2") != 0 || strcmp(opts[1],"Test3") != 0)
		fprintf(stderr,"Failure for '%s'\n"," \"Test1 Test2\" Test3");
	xfree(opts);
	xfree(str);

	no_opts = 0;
	str = xstrdup("\"Test1 Test2\"");
	opts = split_opt(str,str,&no_opts);
	print_opts(opts,no_opts);
	if(no_opts != 1 || strcmp(opts[0],"Test1 Test2") != 0)
		fprintf(stderr,"Failure for '%s'\n","\"Test1 Test2\"");
	xfree(opts);
	xfree(str);
	
	fprintf(stderr,"split_opt test complete.\n");
	
	return 0;
}

#define TEST_HOST1 "www.google.com"
#define TEST_HOST2 "www.microsoft.com"
#define TEST_HOST3 "origin2.microsoft.com"
#define LOCAL_HOST "localhost.localdomain"
#define FAIL_HOST "cause.failure" // unless someone adds a failure TLD

static void pf_addrs(struct in_addr *addrs, int no_addr) {
	char buffer[64];
	int i;

	for(i = 0; i < no_addr; ++i) {
		inet_ntop(AF_INET,&(addrs[i]),buffer,sizeof(buffer));
		printf("addrs[%d] = %s\n",i,buffer);
	}

	xfree(addrs);
}

static int test_aresolv(void) {
	aresolv_t 	*res = aresolv_init();
	struct timeval 	t1,t2;
	long		elapsed;
	char 		host[128];
	struct in_addr 	*addr;
	int		ret;

	fprintf(stderr,"Executing aresolv tests.\n");
	
	gettimeofday(&t1,NULL);
	ret = aresolv_nametoaddrs(res,AF_INET,TEST_HOST1,&addr);
	gettimeofday(&t2,NULL);
	if(ret > 0)
		pf_addrs(addr,ret);
	else
		fprintf(stderr,"Error resolving: %s\n",TEST_HOST1);

	elapsed = diff_tv_usec(&t1,&t2);
	printf("Query 1 Elapsed: %ldus\n",elapsed);

	gettimeofday(&t1,NULL);
	ret = aresolv_nametoaddrs(res,AF_INET,TEST_HOST1,&addr);
	gettimeofday(&t2,NULL);
	if(ret > 0)
		pf_addrs(addr,ret);
	else
		fprintf(stderr,"Error resolving: %s\n",TEST_HOST1);

	printf("Query 2 Elapsed: %ldus\n",(long)diff_tv_usec(&t1,&t2));
	
	if(diff_tv_usec(&t1,&t2) > elapsed)
		fprintf(stderr,"Second query took longer than first.\n");

	ret = aresolv_nametoaddrs(res,AF_INET,FAIL_HOST,&addr);
	if(ret != -1) {
		fprintf(stderr,"We were able to query %s, shouldn't work...\n",FAIL_HOST);
		xfree(addr);
	} else
		printf("Failed to query %s, as expected.\n",FAIL_HOST);
	
	ret = aresolv_nametoaddrs(res,AF_INET,TEST_HOST2,&addr);
	if(ret > 0)
		pf_addrs(addr,ret);
	else
		fprintf(stderr,"Error resolving: %s\n",TEST_HOST2);

	ret = aresolv_nametoaddrs(res,AF_INET,TEST_HOST3,&addr);
	if(ret > 0) {
		char addrstr[32];
		int i,no = ret;
		
		for(i = 0; i < no; ++i) {
			inet_ntop(AF_INET,&(addr[i]),addrstr,sizeof(addrstr));
			ret = aresolv_addrtoname(res,AF_INET,&(addr[i]),host,sizeof(host));
			if(ret == -1)
				fprintf(stderr,"Failed to resolv: %s\n",addrstr);
			else if(strcasecmp(host,TEST_HOST3) != 0)
				fprintf(stderr,"error: %s -> %s -> %s\n",TEST_HOST3,addrstr,host);
			else
				printf("%s -> %s\n",addrstr,host);
		}
		pf_addrs(addr,no);
	} else
		fprintf(stderr,"Error resolving: %s\n",TEST_HOST3);

	ret = aresolv_nametoaddrs(res,AF_INET,LOCAL_HOST,&addr);
	if(ret > 0) {
		char addrstr[32];
		pf_addrs(addr,ret);
		ret = aresolv_nametoaddrs(res,AF_INET,LOCAL_HOST,&addr);
		inet_ntop(AF_INET,&(addr[0]),addrstr,sizeof(addrstr));
		ret = aresolv_addrtoname(res,AF_INET,&(addr[0]),host,sizeof(host));
		if(ret == -1)
			fprintf(stderr,"Failed to resolv: %s\n",addrstr);
		else
			printf("%s -> %s\n",addrstr,host);
	} else
		fprintf(stderr,"Error resolving: %s\n",LOCAL_HOST);
	
	fprintf(stderr,"aresolv tests complete.\n");

	aresolv_free(res);
	
	return 0;
}

static int do_lrand_test(int argc, char *argv[]) {
	return test_lrand(777,16,100);
}

static int do_nickhash_test(int argc, char *argv[]) {
	int ret = 0;
	ret += test_nickhash(666,3,4,200,500,1000,1);
	ret += test_nickhash(666,3,4,200,500,1000,5);
	ret += test_nickhash(777,3,4,200,500,1000,5);
	ret += test_nickhash(31337,3,4,200,500,1000,5);
	return ret;
}

static int do_hostmask_match_test(int argc, char *argv[]) {
	return test_hostmask_match();
}

static int do_split_opt_test(int argc, char *argv[]) {
	return test_split_opt();
}
	
static int do_aresolv_test(int argc, char *argv[]) {
	return test_aresolv();
}

#define CYCLE		5000
#define CYCLE_LOW	1
#define CYCLE_HIGH	100

static double get_per_cycle(struct timeval *start, 
		struct timeval *end, int count) {
	double ret = (double) diff_tv_usec(start,end);
	return ret / (double) count;
}

static int do_pq_v_dq(int argc, char *argv[]) {
	struct timeval start, end;
	char *data = "wibble";
	pqueue_t *pq	= pqueue_init(NULL,0);
	dq_t *dq	= dq_alloc_simple();
	double t1,t2;
	int limit;
	int i,j;
	
	for(limit = CYCLE_LOW; limit <= CYCLE_HIGH; ++limit) {
		gettimeofday(&start,NULL);
		for(i = 0; i < CYCLE; ++i) {
			for(j = 0; j < limit; ++j)
				pqueue_push_back(pq,data);
			for(j = 0; pqueue_pop_front(pq) != NULL; ++j) ;
			assert(j == limit);
		}
		gettimeofday(&end,NULL);
		t1 = get_per_cycle(&start,&end,CYCLE);

		gettimeofday(&start,NULL);
		for(i = 0; i < CYCLE; ++i) {
			for(j = 0; j < limit; ++j)
				dq_push_back(dq,data);
			for(j = 0; dq_pop_front(dq) != NULL; ++j) ;
			assert(j == limit);
		}
		gettimeofday(&end,NULL);
		t2 = get_per_cycle(&start,&end,CYCLE);
		
		fprintf(stderr,"pushb+popf %d for %d: pqueue = %.2fus, dq = %.2fus, improvement = %.2f%%\n",
				limit,CYCLE,t1,t2,
				((t1 / t2) * 100.0) - 100.0);
	}

	return 0;
}

static struct {
	const char *name;
	const char *desc;
	int (*func)(int, char **);
} tests[] = {
	{ 
		.name = "lrand", 
		.desc = "local random function test", 
		.func = do_lrand_test 
	},
	{ 
		.name = "nickhash", 
		.desc = "nick hash simulation", 
		.func = do_nickhash_test 
	},
	{
		.name = "hostmask_match",
		.desc = "hostmasking function test",
		.func = do_hostmask_match_test
	},
	{
		.name = "split_opt",
		.desc = "argument / option spliting test",
		.func = do_split_opt_test
	},
	{
		.name = "aresolv",
		.desc = "asynchronous dns test",
		.func = do_aresolv_test
	},
	{
		.name = "pq_v_dq",
		.desc = "pqueue versus dq benchmark",
		.func = do_pq_v_dq
	},
	{
		.name = NULL,
		.desc = NULL,
		.func = NULL
	}
};

static void list_tests(void) {
	int i;
	fprintf(stderr,"Known tests:\n");
	fprintf(stderr,"\tall (\"run all tests\")\n");
	for(i = 0; tests[i].name != NULL; ++i)
		fprintf(stderr,"\t%s (\"%s\")\n",tests[i].name,tests[i].desc);
}	

static void do_test(const char *name, int argc, char *argv[]) {
	int i, found;
	
	for(i = 0, found = 0; tests[i].name != NULL; ++i) {
		if(strcasecmp(name,tests[i].name) == 0 ||
				strcasecmp(name,"all") == 0) {
			int ret = tests[i].func(argc,argv);
			fprintf(stderr,"Test %s (\"%s\") = %d\n",
				tests[i].name,tests[i].desc,ret);
			found++;
		}
	}
	
	if(found == 0) {
		fprintf(stderr,"Unknown test \"%s\"\n",name);
		list_tests();
		exit(1);
	}
}	

/* test suite entry point */

#ifdef USE_TESTSUITE
void testsuite(int argc, char *argv[]) {
	int i, run;
	setvbuf(stderr,NULL,_IONBF,0);
	setvbuf(stdout,NULL,_IONBF,0);
	
	for(i = 1, run = 0; i < argc; ++i) {
		if(argv[i][0] != '-') {
			do_test(argv[i],argc,argv);
			run++;
		}
	}
	if(run == 0)
		list_tests();
}
#else
void testsuite(int argc, char *argv[]) {
	fprintf(stderr,
		"Testsuite not compiled in, recompile --with-testsuite\n");
}
#endif /* USE_TESTSUITE */
