1/*
2** Copyright (C) 1991, 1997 Free Software Foundation, Inc.
3**
4** This file is part of TACK.
5**
6** TACK is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2, or (at your option)
9** any later version.
10**
11** TACK is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with TACK; see the file COPYING.  If not, write to
18** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19** Boston, MA 02110-1301, USA
20*/
21
22#include <tack.h>
23
24#if HAVE_SYS_TIME_H
25#include <sys/time.h>
26#endif
27
28MODULE_ID("$Id: control.c,v 1.8 2005/09/17 19:49:16 tom Exp $")
29
30/* terminfo test program control subroutines */
31
32#if HAVE_GETTIMEOFDAY
33#define MY_TIMER struct timeval
34#else
35#define MY_TIMER time_t
36#endif
37
38/* globals */
39int test_complete;		/* counts number of tests completed */
40
41char txt_longer_test_time[80];	/* +) use longer time */
42char txt_shorter_test_time[80];	/* -) use shorter time */
43static int pad_test_duration = 1;	/* number of seconds for a pad test */
44int auto_pad_mode;		/* run the time tests */
45int no_alarm_event;		/* TRUE if the alarm has not gone off yet */
46unsigned long usec_run_time;	/* length of last test in microseconds */
47static MY_TIMER stop_watch[MAX_TIMERS]; /* Hold the start timers */
48
49char txt_longer_augment[80];	/* >) use bigger augment */
50char txt_shorter_augment[80];	/* <) use smaller augment */
51
52/* caps under test data base */
53int tt_delay_max;		/* max number of milliseconds we can delay */
54int tt_delay_used;		/* number of milliseconds consumed in delay */
55const char *tt_cap[TT_MAX];	/* value of string */
56int tt_affected[TT_MAX];	/* lines or columns effected (repetition factor) */
57int tt_count[TT_MAX];		/* Number of times sent */
58int tt_delay[TT_MAX];		/* Number of milliseconds delay */
59int ttp;			/* number of entries used */
60
61/* Saved value of the above data base */
62const char *tx_cap[TT_MAX];	/* value of string */
63int tx_affected[TT_MAX];	/* lines or columns effected (repetition factor) */
64int tx_count[TT_MAX];		/* Number of times sent */
65int tx_index[TT_MAX];		/* String index */
66int tx_delay[TT_MAX];		/* Number of milliseconds delay */
67int txp;			/* number of entries used */
68int tx_characters;		/* printing characters sent by test */
69unsigned long tx_cps;		/* characters per second */
70static struct test_list *tx_source;	/* The test that generated this data */
71
72#define RESULT_BLOCK		1024
73static int blocks;		/* number of result blocks available */
74static struct test_results *results;	/* pointer to next available */
75static struct test_results *pads[STRCOUNT];	/* save pad results here */
76
77/*
78**	event_start(number)
79**
80**	Begin the stopwatch at the current time-of-day.
81*/
82void
83event_start(int n)
84{
85#if HAVE_GETTIMEOFDAY
86	(void) gettimeofday(&stop_watch[n], (struct timezone *)0);
87#else
88	stop_watch[n] = time((time_t *)0);
89#endif
90}
91
92/*
93**	event_time(number)
94**
95**	Return the number of milliseconds since this stop watch began.
96*/
97long
98event_time(int n)
99{
100#if HAVE_GETTIMEOFDAY
101	MY_TIMER current_time;
102
103	(void) gettimeofday(&current_time, (struct timezone *)0);
104	return ((current_time.tv_sec - stop_watch[n].tv_sec) * 1000000)
105		+ current_time.tv_usec - stop_watch[n].tv_usec;
106#else
107	return (time((time_t *)0) - stop_watch[n]) * 1000;
108#endif
109}
110
111/*****************************************************************************
112 *
113 * Execution control for string capability tests
114 *
115 *****************************************************************************/
116
117/*
118**	get_next_block()
119**
120**	Get a results block for pad test data.
121*/
122static struct test_results *
123get_next_block(void)
124{
125	if (blocks <= 0) {
126		results = (struct test_results *)
127			malloc(sizeof(struct test_results) * RESULT_BLOCK);
128		if (!results) {
129			ptextln("Malloc failed");
130			return (struct test_results *) 0;
131		}
132		blocks = RESULT_BLOCK;
133	}
134	blocks--;
135	return results++;
136}
137
138/*
139**	set_augment_txt()
140**
141**	Initialize the augment menu selections
142*/
143void
144set_augment_txt(void)
145{
146	sprintf(txt_longer_augment,
147		">) Change lines/characters effected to %d", augment << 1);
148	sprintf(txt_shorter_augment,
149		"<) Change lines/characters effected to %d", augment >> 1);
150}
151
152void
153control_init(void)
154{
155	sprintf(txt_longer_test_time, "+) Change test time to %d seconds",
156		pad_test_duration + 1);
157	sprintf(txt_shorter_test_time, "-) Change test time to %d seconds",
158		pad_test_duration - 1);
159	set_augment_txt();
160}
161
162/*
163**	msec_cost(cap, affected-count)
164**
165**	Return the number of milliseconds delay needed by the cap.
166*/
167int
168msec_cost(
169	const char *const cap,
170	int affcnt)
171{
172	int dec, value, total, star, ch;
173	const char *cp;
174
175	if (!cap) {
176		return 0;
177	}
178	total = 0;
179	for (cp = cap; *cp; cp++) {
180		if (*cp == '$' && cp[1] == '<') {
181			star = 1;
182			value = dec = 0;
183			for (cp += 2; (ch = *cp); cp++) {
184				if (ch >= '0' && ch <= '9') {
185					value = value * 10 + (ch - '0');
186					dec *= 10;
187				} else
188				if (ch == '.') {
189					dec = 1;
190				} else
191				if (ch == '*') {
192					star = affcnt;
193				} else
194				if (ch == '>') {
195					break;
196				}
197			}
198			if (dec > 1) {
199				total += (value * star) / dec;
200			} else {
201				total += (value * star);
202			}
203		}
204	}
205	return total;
206}
207
208/*
209**	liberated(cap)
210**
211**	Return the cap without padding
212*/
213char *
214liberated(char *cap)
215{
216	static char cb[1024];
217	char *ts, *ls;
218
219	cb[0] = '\0';
220	ls = NULL;
221	if (cap) {
222		for (ts = cb; (*ts = *cap); ++cap) {
223			if (*cap == '$' && cap[1] == '<') {
224				ls = ts;
225			}
226			++ts;
227			if (*cap == '>') {
228				if (ls) {
229					ts = ls;
230					ls = NULL;
231				}
232			}
233		}
234	}
235	return cb;
236}
237
238/*
239**	page_loop()
240**
241**	send CR/LF or go home and bump letter
242*/
243void
244page_loop(void)
245{
246	if (line_count + 2 >= lines) {
247		NEXT_LETTER;
248		go_home();
249	} else {
250		put_crlf();
251	}
252}
253
254/*
255**	skip_pad_test(test-list-entry, state, ch, text)
256**
257**	Print the start test line.  Handle start up commands.
258**	Return TRUE if a return is requested.
259*/
260int
261skip_pad_test(
262	struct test_list *test,
263	int *state,
264	int *ch,
265	const char *text)
266{
267	char rep_text[16];
268
269	while(1) {
270		if (text) {
271			ptext(text);
272		}
273		if ((test->flags & MENU_LC_MASK)) {
274			sprintf(rep_text, " *%d", augment);
275			ptext(rep_text);
276		}
277		ptext(" [n] > ");
278		*ch = wait_here();
279		if (*ch == 's') {
280			/* Skip is converted to next */
281			*ch = 'n';
282			return TRUE;
283		}
284		if (*ch == 'q') {
285			/* Quit is converted to help */
286			*ch = '?';
287			return TRUE;
288		}
289		if (*ch == '\r' || *ch == '\n' || *ch == 'n' || *ch == 'r') {
290			/* this is the only response that allows the test to run */
291			*ch = 0;
292		}
293		if (subtest_menu(pad_test_list, state, ch)) {
294			continue;
295		}
296		return (*ch != 0);
297	}
298}
299
300/*
301**	pad_done_message(test_list)
302**
303**	Print the Done message and request input.
304*/
305void
306pad_done_message(
307	struct test_list *test,
308	int *state,
309	int *ch)
310{
311	int default_action = 0;
312	char done_message[128];
313	char rep_text[16];
314
315	while (1) {
316		if ((test->flags & MENU_LC_MASK)) {
317			sprintf(rep_text, "*%d", augment);
318		} else {
319			rep_text[0] = '\0';
320		}
321		if (test->caps_done) {
322			sprintf(done_message, "(%s)%s Done ", test->caps_done,
323			rep_text);
324			ptext(done_message);
325		} else {
326			if (rep_text[0]) {
327				ptext(rep_text);
328				ptext(" ");
329			}
330			ptext("Done ");
331		}
332		if (debug_level & 2) {
333			dump_test_stats(test, state, ch);
334		} else {
335			*ch = wait_here();
336		}
337		if (*ch == '\r' || *ch == '\n') {
338			*ch = default_action;
339			return;
340		}
341		if (*ch == 's' || *ch == 'n') {
342			*ch = 0;
343			return;
344		}
345		if (strchr(pad_repeat_test, *ch)) {
346			/* default action is now repeat */
347			default_action = 'r';
348		}
349		if (subtest_menu(pad_test_list, state, ch)) {
350			continue;
351		}
352		return;
353	}
354}
355
356/*
357**	sliding_scale(dividend, factor, divisor)
358**
359**	Return (dividend * factor) / divisor
360*/
361int
362sliding_scale(
363	int dividend,
364	int factor,
365	unsigned long divisor)
366{
367	double d = dividend;
368
369	if (divisor) {
370		d = (d * (double) factor) / (double) divisor;
371		return (int) (d + 0.5);
372	}
373	return 0;
374}
375
376/*
377**	pad_test_startup()
378**
379**	Do the stuff needed to begin a test.
380*/
381void
382pad_test_startup(
383	int do_clear)
384{
385	if (do_clear) {
386		put_clear();
387	}
388	repeats = augment;
389	raw_characters_sent = 0;
390	test_complete = ttp = char_count = tt_delay_used = 0;
391	letter = letters[letter_number = 0];
392	if (pad_test_duration <= 0) {
393		pad_test_duration = 1;
394	}
395	tt_delay_max = pad_test_duration * 1000;
396	set_alarm_clock(pad_test_duration);
397	event_start(TIME_TEST);
398}
399
400/*
401**	still_testing()
402**
403**	This function is called to see if the test loop should be terminated.
404*/
405int
406still_testing(void)
407{
408	fflush(stdout);
409	test_complete++;
410	return EXIT_CONDITION;
411}
412
413/*
414**	pad_test_shutdown()
415**
416**	Do the stuff needed to end a test.
417*/
418void
419pad_test_shutdown(
420	struct test_list *t,
421	int crlf)
422{
423	int i;
424	int counts;			/* total counts */
425	int ss;				/* Save string index */
426	int cpo;			/* characters per operation */
427	int delta;			/* difference in characters */
428	int bogus;			/* Time is inaccurate */
429	struct test_results *r;		/* Results of current test */
430	int ss_index[TT_MAX];		/* String index */
431
432	if (tty_can_sync == SYNC_TESTED) {
433		bogus = tty_sync_error();
434	} else {
435		bogus = 1;
436	}
437	usec_run_time = event_time(TIME_TEST);
438	tx_source = t;
439	tx_characters = raw_characters_sent;
440	tx_cps = sliding_scale(tx_characters, 1000000, usec_run_time);
441
442	/* save the data base */
443	for (txp = ss = counts = 0; txp < ttp; txp++) {
444		tx_cap[txp]   = tt_cap[txp];
445		tx_count[txp] = tt_count[txp];
446		tx_delay[txp] = tt_delay[txp];
447		tx_affected[txp] = tt_affected[txp];
448		tx_index[txp] = get_string_cap_byvalue(tt_cap[txp]);
449		if (tx_index[txp] >= 0) {
450			if (cap_match(t->caps_done, strnames[tx_index[txp]])) {
451				ss_index[ss++] = txp;
452				counts += tx_count[txp];
453			}
454		}
455	}
456
457	if (crlf) {
458		put_crlf();
459	}
460	if (counts == 0 || tty_cps == 0 || bogus) {
461		/* nothing to do */
462		return;
463	}
464	/* calculate the suggested pad times */
465	delta = usec_run_time - sliding_scale(tx_characters, 1000000, tty_cps);
466	if (delta < 0) {
467		/* probably should bump tx_characters */
468		delta = 0;
469	}
470	cpo = delta / counts;
471	for (i = 0; i < ss; i++) {
472		if (!(r = get_next_block())) {
473			return;
474		}
475		r->next = pads[tx_index[ss_index[i]]];
476		pads[tx_index[ss_index[i]]] = r;
477		r->test = t;
478		r->reps = tx_affected[ss_index[i]];
479		r->delay = cpo;
480	}
481}
482
483/*
484**	show_cap_results(index)
485**
486**	Display the previous results
487*/
488static void
489show_cap_results(
490	int x)
491{
492	struct test_results *r;		/* a result */
493	int delay;
494
495	if ((r = pads[x])) {
496		sprintf(temp, "(%s)", strnames[x]);
497		ptext(temp);
498		while (r) {
499			sprintf(temp, "$<%d>", r->delay / 1000);
500			put_columns(temp, (int) strlen(temp), 10);
501			r = r->next;
502		}
503		r = pads[x];
504		while (r) {
505			if (r->reps > 1) {
506				delay = r->delay / (r->reps * 100);
507				sprintf(temp, "$<%d.%d*>", delay / 10, delay % 10);
508				put_columns(temp, (int) strlen(temp), 10);
509			}
510			r = r->next;
511		}
512		put_crlf();
513	}
514}
515
516/*
517**	dump_test_stats(test_list, status, ch)
518**
519**	Dump the statistics about the last test
520*/
521void
522dump_test_stats(
523	struct test_list *t,
524	int *state,
525	int *ch)
526{
527	int i, j;
528	char tbuf[32];
529	int x[32];
530
531	put_crlf();
532	if (tx_source && tx_source->caps_done) {
533		cap_index(tx_source->caps_done, x);
534		if (x[0] >= 0) {
535			sprintf(temp, "Caps summary for (%s)",
536				tx_source->caps_done);
537			ptextln(temp);
538			for (i = 0; x[i] >= 0; i++) {
539				show_cap_results(x[i]);
540			}
541			put_crlf();
542		}
543	}
544	sprintf(tbuf, "%011lu", usec_run_time);
545	sprintf(temp, "Test time: %lu.%s, characters per second %lu, characters %d",
546		usec_run_time / 1000000UL, &tbuf[5], tx_cps, tx_characters);
547	ptextln(temp);
548	for (i = 0; i < txp; i++) {
549		if ((j = get_string_cap_byvalue(tx_cap[i])) >= 0) {
550			sprintf(tbuf, "(%s)", strnames[j]);
551		} else {
552			strcpy(tbuf, "(?)");
553		}
554		sprintf(temp, "%8d  %3d  $<%3d>  %8s %s",
555			tx_count[i], tx_affected[i], tx_delay[i],
556			tbuf, expand(tx_cap[i]));
557		putln(temp);
558	}
559	generic_done_message(t, state, ch);
560}
561
562/*
563**	longer_test_time(test_list, status, ch)
564**
565**	Extend the number of seconds for each test.
566*/
567void
568longer_test_time(
569	struct test_list *t GCC_UNUSED,
570	int *state GCC_UNUSED,
571	int *ch)
572{
573	pad_test_duration += 1;
574	sprintf(txt_longer_test_time, "+) Change test time to %d seconds",
575		pad_test_duration + 1);
576	sprintf(txt_shorter_test_time, "-) Change test time to %d seconds",
577		pad_test_duration - 1);
578	sprintf(temp, "Tests will run for %d seconds", pad_test_duration);
579	ptext(temp);
580	*ch = REQUEST_PROMPT;
581}
582
583/*
584**	shorter_test_time(test_list, status, ch)
585**
586**	Shorten the number of seconds for each test.
587*/
588void
589shorter_test_time(
590	struct test_list *t GCC_UNUSED,
591	int *state GCC_UNUSED,
592	int *ch)
593{
594	if (pad_test_duration > 1) {
595		pad_test_duration -= 1;
596		sprintf(txt_longer_test_time, "+) Change test time to %d seconds",
597			pad_test_duration + 1);
598		sprintf(txt_shorter_test_time, "-) Change test time to %d seconds",
599			pad_test_duration - 1);
600	}
601	sprintf(temp, "Tests will run for %d second%s", pad_test_duration,
602		pad_test_duration > 1 ? "s" : "");
603	ptext(temp);
604	*ch = REQUEST_PROMPT;
605}
606
607/*
608**	longer_augment(test_list, status, ch)
609**
610**	Lengthen the number of lines/characters effected
611*/
612void
613longer_augment(
614	struct test_list *t,
615	int *state GCC_UNUSED,
616	int *ch)
617{
618	augment <<= 1;
619	set_augment_txt();
620	if (augment_test) {
621		t = augment_test;
622	}
623	sprintf(temp, "The pad tests will effect %d %s.", augment,
624		((t->flags & MENU_LC_MASK) == MENU_lines) ?
625		"lines" : "characters");
626	ptextln(temp);
627	*ch = REQUEST_PROMPT;
628}
629
630/*
631**	shorter_augment(test_list, status, ch)
632**
633**	Shorten the number of lines/characters effected
634*/
635void
636shorter_augment(
637	struct test_list *t,
638	int *state GCC_UNUSED,
639	int *ch)
640{
641	if (augment > 1) {
642		/* don't let the augment go to zero */
643		augment >>= 1;
644	}
645	set_augment_txt();
646	if (augment_test) {
647		t = augment_test;
648	}
649	sprintf(temp, "The pad tests will effect %d %s.", augment,
650		((t->flags & MENU_LC_MASK) == MENU_lines) ?
651		"lines" : "characters");
652	ptextln(temp);
653	*ch = REQUEST_PROMPT;
654}
655