/*
 * Copyright (c) 2002, 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: t-evthr-slp.c,v 1.8 2006/06/05 18:59:41 ca Exp $")

#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/test.h"
#include "sm/evthr.h"

#include <stdio.h>

#define WHAT_TERM	0
#define WHAT_CONT	1

#define REPS 5

static int Verbose = 0;
static int short_idx = 2;
static int del1[] = { 1, 3, 3, 0 };
static int del2[] = { 2, 1, 1, 0 };
static int diff[] = { 1, 1, 2, 0 };

static timeval_T difft;
static timeval_T startt;
static sm_evthr_task_P	task1, task2;

struct t_ctx_S
{
	int		 called;
	timeval_T	 nextt;
};
typedef struct t_ctx_S t_ctx_T, *t_ctx_P;

static void
chkt(timeval_T exp, char *n, int c)
{
	int r;
	timeval_T now, dt;
	bool ok;

	r = gettimeofday(&now, NULL);
	SM_TEST(r == 0);
	timersub(&now, &exp, &dt);
	ok = timercmp(&dt, &difft, <=);
	SM_TEST(ok);
	if (!ok)
	{
		fprintf(stderr, "%s[%d]: ", n, c);
		ERRPRINTTV("now=", now);
		ERRPRINTTV("       sleep=", exp);
		ERRPRINTTV("        diff=", dt);
	}
}

static int
curt(timeval_P sleept, char *name)
{
	timeval_T dt;
	int r;

	r = gettimeofday(sleept, NULL);
	SM_TEST(r == 0);
	timersub(sleept, &startt, &dt);
	if (Verbose > 1)
		fprintf(stderr, "%s: called %ld.%06ld\n"
			,name
			,dt.tv_sec
			,dt.tv_usec
			);
	return (int) dt.tv_sec;
}

static void
chkdel(char *name, int d, int i, int sh, int del[])
{
	int j, sum;

	sum = 1;
	for (j = 0; j < i; j++)
	{
		if (strcmp(name, "fct1") != 0 || j != short_idx - 1)
			sum += del[j];
		else
		{
			sum += sh;
			if (Verbose > 2)
				printf("%s: j=%d, added sh=%d instead of %d\n",
					name, j, sh, del[j]);
		}
	}
	if (Verbose > 0)
		printf("%s: d=%d, del[%d]=%d\n", name, d, i - 1, sum);
	SM_TEST(d == sum);
}

/*
**  FCT1 -- sleep
*/

static sm_ret_T
fct1(sm_evthr_task_P tsk)
{
	int i, d;
	t_ctx_P fctx;
	timeval_T sleept, delay;

	SM_ASSERT(tsk != NULL);
	SM_ASSERT(evthr_got_slp(tsk));
	fctx = (t_ctx_P) tsk->evthr_t_actx;
	i = fctx->called++;
	chkt(fctx->nextt, "fct1", fctx->called);
	d = curt(&sleept, "fct1");
	if (i > 0)
		chkdel("fct1", d, i,
			diff[(short_idx < (int) (sizeof(diff)/sizeof(diff[0])))
				? short_idx : 0],
			del1);
	if (fctx->called >= REPS)
		return EVTHR_TERM|EVTHR_DEL;
	delay.tv_sec = del1[i];
	delay.tv_usec = 0;
	if (del1[i] == 0)
		return EVTHR_TERM|EVTHR_DEL;
	timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
	fctx->nextt = tsk->evthr_t_sleep;
	return EVTHR_SLPQ;
}

/*
**  FCT2 -- sleep
*/

static sm_ret_T
fct2(sm_evthr_task_P tsk)
{
	int i, d;
	t_ctx_P fctx;
	timeval_T sleept, delay;
	sm_ret_T ret;

	SM_ASSERT(tsk != NULL);
	SM_ASSERT(evthr_got_slp(tsk));
	fctx = (t_ctx_P) tsk->evthr_t_actx;
	i = fctx->called++;
	chkt(fctx->nextt, "fct2", fctx->called);
	d = curt(&sleept, "fct2");
	if (i > 0)
		chkdel("fct2", d, i, 0, del2);
	if (fctx->called >= REPS)
		return EVTHR_TERM|EVTHR_DEL;
	if (i == short_idx)
	{
		delay.tv_sec = 0;
		delay.tv_usec = 200000;
		timeradd(&sleept, &delay, &sleept);
		ret = evthr_new_sl(task1, sleept, true);
		curt(&sleept, "fct2: shorten task1");
		SM_TEST(sm_is_success(ret));
	}
	delay.tv_sec = del2[i];
	delay.tv_usec = 0;
	if (del2[i] == 0)
		return EVTHR_DEL;
	timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
	fctx->nextt = tsk->evthr_t_sleep;
	return EVTHR_SLPQ;
}


static void
testev(int sl1, int sl2)
{
	sm_ret_T ret;
	int r;
	sm_evthr_ctx_P evthr_ctx;
	t_ctx_T tctx1, tctx2;
	timeval_T sleept;

	ret = thr_init();
	SM_TEST(sm_is_success(ret));
	sm_memzero(&sleept, sizeof(sleept));
	ret = evthr_init(&evthr_ctx, 1, 6, 10);
	SM_TEST(sm_is_success(ret));
	SM_TEST(evthr_ctx != NULL);

	if (sl1 >= 0)
	{
		r = gettimeofday(&sleept, NULL);
		SM_TEST(r == 0);
		sleept.tv_sec += sl1;
		tctx1.called = 0;
		tctx1.nextt = sleept;
		ret = evthr_task_new(evthr_ctx, &task1, EVTHR_EV_SL, -1,
				&sleept, fct1, (void *) &tctx1);
		SM_TEST(sm_is_success(ret));
		SM_TEST(task1 != NULL);
	}

	if (sl2 >= 0)
	{
		r = gettimeofday(&sleept, NULL);
		SM_TEST(r == 0);
		sleept.tv_sec += sl2;
		tctx2.called = 0;
		tctx2.nextt = sleept;
		ret = evthr_task_new(evthr_ctx, &task2, EVTHR_EV_SL, -1,
				&sleept, fct2, (void *) &tctx2);
		SM_TEST(sm_is_success(ret));
		SM_TEST(task2 != NULL);
	}

	gettimeofday(&startt, NULL);
	if (Verbose > 0)
	{
		ERRPRINTTV("before loop: ", startt);
	}
	ret = evthr_loop(evthr_ctx);
	SM_TEST(sm_is_success(ret));
	if (!sm_is_success(ret))
		fprintf(stderr, "evthr_loop()=%x\n", ret);

	/*
	**  we should "hold" the system before deleting tasks?
	**  deleting the tasks while they are still in use
	**  will break things.
	*/

	if (Verbose > 0)
	{
		fprintf(stderr, "fct1=%d, fct2=%d\n",
			tctx1.called, tctx2.called);
	}
	ret = evthr_stop(evthr_ctx);
	SM_TEST(sm_is_success(ret));
	if (!sm_is_success(ret))
		fprintf(stderr, "evthr_stop()=%x\n", ret);
	ret = thr_stop();
	SM_TEST(sm_is_success(ret));
}

int
main(int argc, char *argv[])
{
	int c;

	difft.tv_sec = 0;
	difft.tv_usec = 60000;
	while ((c = getopt(argc, argv, "d:sV")) != -1)
	{
		switch (c)
		{
		  case 'd':
			difft.tv_usec = atoi(optarg);
			break;
		  case 's':
			short_idx = 4;
			break;
		  case 'V':
			Verbose++;
			break;
#if 0
		  default:
			usage(argv[0]);
			return(1);
#endif /* 0 */
		}
	}
	sm_test_begin(argc, argv, "test evthr sleep");
	short_idx = 2;
	testev(1, 1);
	short_idx = 5;
	testev(1, 1);
	return sm_test_end();
}


syntax highlighted by Code2HTML, v. 0.9.1