1 /*
2  * Copyright (c) 2004, Bull S.A..  All rights reserved.
3  * Created by: Sebastien Decugis
4 
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it would be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 
17  * This file is a stress test for the function pthread_cond_wait.
18  *
19  *It aims to check the following assertion:
20  *  When inside the function, the thread releases the mutex
21  *  before waiting for the conditionnal variable.
22  *  Those two operations are atomic in the mean that
23  *  no other thread can gain access to the mutex
24  *  then signal (or broadcast) the condition
25  *  without the blocked thread behaving as if
26  *  this signal (or broadcast) had happened
27  *  after it blocked on the conditionnal variable.
28 
29  * The steps are:
30  * -> Create N mutex & N cond vars with different attributes
31  * -> Create N threads A, which
32  *    -> lock the mutex
33  *    -> create a thread B, which
34  *       -> locks the mutex
35  *       -> while the boolean is false,
36  *         -> broadcasts the condvar
37  *         -> sets an alarm timer
38  *         -> waits the condition
39  *       -> broadcast the condvar
40  *       -> unlock the mutex
41  *    -> while the boolean is false,
42  *      -> set an alarm timer
43  *      -> wait the condvar
44  *      -> signals the condvar
45  *    -> unlock the mutex
46  *    -> joins the thread B
47  * -> sets the boolean True when it receives SIGUSR1
48  * -> report FAILED if the alarm timer expires (meaning one of the signal was lost).
49  * -> joins the N threads A.
50  *
51  * To test for pshared primitive, thread B can be in another process.
52  */
53 
54 /********************************************************************************************/
55 /****************************** standard includes *****************************************/
56 /********************************************************************************************/
57 #include <pthread.h>
58 #include <stdarg.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <unistd.h>
62 
63 #include <errno.h>
64 #include <signal.h>
65 #include <sys/wait.h>
66 #include <sys/mman.h>
67 #include <string.h>
68 #include <time.h>
69 
70 /********************************************************************************************/
71 /******************************   Test framework   *****************************************/
72 /********************************************************************************************/
73 #include "testfrmw.h"
74 #include "testfrmw.c"
75  /* This header is responsible for defining the following macros:
76   * UNRESOLVED(ret, descr);
77   *    where descr is a description of the error and ret is an int (error code for example)
78   * FAILED(descr);
79   *    where descr is a short text saying why the test has failed.
80   * PASSED();
81   *    No parameter.
82   *
83   * Both three macros shall terminate the calling process.
84   * The testcase shall not terminate in any other maneer.
85   *
86   * The other file defines the functions
87   * void output_init()
88   * void output(char * string, ...)
89   *
90   * Those may be used to output information.
91   */
92 
93 /********************************************************************************************/
94 /********************************** Configuration ******************************************/
95 /********************************************************************************************/
96 #ifndef SCALABILITY_FACTOR
97 #define SCALABILITY_FACTOR 1
98 #endif
99 #ifndef VERBOSE
100 #define VERBOSE 1
101 #endif
102 
103 /* Number of children for each test scenario */
104 #define NCHILDREN (5)
105 
106 #define TIMEOUT 120
107 
108 #ifndef WITHOUT_ALTCLK
109 #define USE_ALTCLK		/* make tests with MONOTONIC CLOCK if supported */
110 #endif
111 
112 /********************************************************************************************/
113 /***********************************    Test case   *****************************************/
114 /********************************************************************************************/
115 
116 #ifdef WITHOUT_XOPEN
117 /* We define those to avoid compilation errors, but they won't be used */
118 #define PTHREAD_MUTEX_DEFAULT 0
119 #define PTHREAD_MUTEX_NORMAL 0
120 #define PTHREAD_MUTEX_ERRORCHECK 0
121 #define PTHREAD_MUTEX_RECURSIVE 0
122 
123 #endif
124 
125 struct _scenar {
126 	int m_type;		/* Mutex type to use */
127 	int mc_pshared;		/* 0: mutex and cond are process-private (default) ~ !0: Both are process-shared, if supported */
128 	int c_clock;		/* 0: cond uses the default clock. ~ !0: Cond uses monotonic clock, if supported. */
129 	int fork;		/* 0: Test between threads. ~ !0: Test across processes, if supported (mmap) */
130 	char *descr;		/* Case description */
131 } scenarii[] = {
132 	{
133 	PTHREAD_MUTEX_DEFAULT, 0, 0, 0, "Default mutex"}
134 	, {
135 	PTHREAD_MUTEX_NORMAL, 0, 0, 0, "Normal mutex"}
136 	, {
137 	PTHREAD_MUTEX_ERRORCHECK, 0, 0, 0, "Errorcheck mutex"}
138 	, {
139 	PTHREAD_MUTEX_RECURSIVE, 0, 0, 0, "Recursive mutex"}
140 
141 	, {
142 	PTHREAD_MUTEX_DEFAULT, 1, 0, 0, "PShared default mutex"}
143 	, {
144 	PTHREAD_MUTEX_NORMAL, 1, 0, 0, "Pshared normal mutex"}
145 	, {
146 	PTHREAD_MUTEX_ERRORCHECK, 1, 0, 0, "Pshared errorcheck mutex"}
147 	, {
148 	PTHREAD_MUTEX_RECURSIVE, 1, 0, 0, "Pshared recursive mutex"}
149 
150 	, {
151 	PTHREAD_MUTEX_DEFAULT, 1, 0, 1,
152 		    "Pshared default mutex across processes"}
153 	, {
154 	PTHREAD_MUTEX_NORMAL, 1, 0, 1,
155 		    "Pshared normal mutex across processes"}
156 	, {
157 	PTHREAD_MUTEX_ERRORCHECK, 1, 0, 1,
158 		    "Pshared errorcheck mutex across processes"}
159 	, {
160 	PTHREAD_MUTEX_RECURSIVE, 1, 0, 1,
161 		    "Pshared recursive mutex across processes"}
162 
163 #ifdef USE_ALTCLK
164 	, {
165 	PTHREAD_MUTEX_DEFAULT, 1, 1, 1,
166 		    "Pshared default mutex and alt clock condvar across processes"}
167 	, {
168 	PTHREAD_MUTEX_NORMAL, 1, 1, 1,
169 		    "Pshared normal mutex and alt clock condvar across processes"}
170 	, {
171 	PTHREAD_MUTEX_ERRORCHECK, 1, 1, 1,
172 		    "Pshared errorcheck mutex and alt clock condvar across processes"}
173 	, {
174 	PTHREAD_MUTEX_RECURSIVE, 1, 1, 1,
175 		    "Pshared recursive mutex and alt clock condvar across processes"}
176 
177 	, {
178 	PTHREAD_MUTEX_DEFAULT, 0, 1, 0,
179 		    "Default mutex and alt clock condvar"}
180 	, {
181 	PTHREAD_MUTEX_NORMAL, 0, 1, 0,
182 		    "Normal mutex and alt clock condvar"}
183 	, {
184 	PTHREAD_MUTEX_ERRORCHECK, 0, 1, 0,
185 		    "Errorcheck mutex and alt clock condvar"}
186 	, {
187 	PTHREAD_MUTEX_RECURSIVE, 0, 1, 0,
188 		    "Recursive mutex and alt clock condvar"}
189 
190 	, {
191 	PTHREAD_MUTEX_DEFAULT, 1, 1, 0,
192 		    "PShared default mutex and alt clock condvar"}
193 	, {
194 	PTHREAD_MUTEX_NORMAL, 1, 1, 0,
195 		    "Pshared normal mutex and alt clock condvar"}
196 	, {
197 	PTHREAD_MUTEX_ERRORCHECK, 1, 1, 0,
198 		    "Pshared errorcheck mutex and alt clock condvar"}
199 	, {
200 	PTHREAD_MUTEX_RECURSIVE, 1, 1, 0,
201 		    "Pshared recursive mutex and alt clock condvar"}
202 #endif
203 };
204 
205 #define NSCENAR (sizeof(scenarii)/sizeof(scenarii[0]))
206 
207 #define NTOT (NSCENAR * SCALABILITY_FACTOR * NCHILDREN)
208 
209 struct childdata {
210 	pthread_mutex_t mtx;
211 	pthread_cond_t cnd;
212 	int fork;
213 	int *pBool;
214 };
215 
216 typedef struct {
217 	struct childdata cd[NTOT];
218 	int boolean;
219 } testdata_t;
220 
221 pthread_attr_t ta;
222 
223 /***
224  * The grand child function (either sub-thread or sub-process)
225  */
threaded_B(void * arg)226 void *threaded_B(void *arg)
227 {
228 	int ret;
229 	struct childdata *cd = (struct childdata *)arg;
230 
231 	ret = pthread_mutex_lock(&(cd->mtx));
232 	if (ret != 0) {
233 		UNRESOLVED(ret, "[gchild] Unable to lock mutex");
234 	}
235 
236 	while (*(cd->pBool) == 0) {
237 		ret = pthread_cond_broadcast(&(cd->cnd));
238 		if (ret != 0) {
239 			UNRESOLVED(ret, "[gchild] Broadcast failed");
240 		}
241 
242 		alarm(TIMEOUT);	// even if we are a sub-process, the main process will timeout too
243 
244 		ret = pthread_cond_wait(&(cd->cnd), &(cd->mtx));
245 		if (ret != 0) {
246 			UNRESOLVED(ret, "[gchild] Failed to wait the cond");
247 		}
248 	}
249 
250 	/* We shall broadcast again to be sure the parent is not hung */
251 	ret = pthread_cond_broadcast(&(cd->cnd));
252 	if (ret != 0) {
253 		UNRESOLVED(ret, "[gchild] Broadcast failed");
254 	}
255 
256 	ret = pthread_mutex_unlock(&(cd->mtx));
257 	if (ret != 0) {
258 		UNRESOLVED(ret, "[gchild] Failed to finally release the mutex");
259 	}
260 
261 	return NULL;
262 }
263 
264 /***
265  * The child function (always in the main process)
266  */
threaded_A(void * arg)267 void *threaded_A(void *arg)
268 {
269 	struct childdata *cd = (struct childdata *)arg;
270 	int ret, status;
271 	pid_t child_p = 0, wrc;
272 	pthread_t child_t;
273 
274 	ret = pthread_mutex_lock(&(cd->mtx));
275 	if (ret != 0) {
276 		UNRESOLVED(ret, "[child] Unable to lock mutex");
277 	}
278 
279 	/* Create the grand child */
280 	if (cd->fork == 0) {
281 		ret = pthread_create(&child_t, &ta, threaded_B, arg);
282 		if (ret != 0) {
283 			UNRESOLVED(ret,
284 				   "[child] Failed to create a grand child thread");
285 		}
286 	} else {
287 		child_p = fork();
288 		if (child_p == -1) {
289 			UNRESOLVED(ret,
290 				   "[child] Failed to create a grand child proces");
291 		}
292 
293 		if (child_p == 0) {	/* grand child */
294 			threaded_B(arg);
295 			exit(0);
296 		}
297 	}
298 
299 	while (*(cd->pBool) == 0) {
300 		alarm(TIMEOUT);
301 
302 		ret = pthread_cond_wait(&(cd->cnd), &(cd->mtx));
303 		if (ret != 0) {
304 			UNRESOLVED(ret, "[child] Failed to wait the cond");
305 		}
306 
307 		ret = pthread_cond_signal(&(cd->cnd));
308 		if (ret != 0) {
309 			UNRESOLVED(ret, "[child] Signal failed");
310 		}
311 	}
312 
313 	ret = pthread_mutex_unlock(&(cd->mtx));
314 	if (ret != 0) {
315 		UNRESOLVED(ret, "[gchild] Failed to finally release the mutex");
316 	}
317 
318 	/* Wait for the grand child termination */
319 	if (cd->fork == 0) {
320 		ret = pthread_join(child_t, NULL);
321 		if (ret != 0) {
322 			UNRESOLVED(ret,
323 				   "[child] Failed to join a grand child thread");
324 		}
325 	} else {
326 		wrc = waitpid(child_p, &status, 0);
327 		if (wrc != child_p) {
328 			output("Expected pid: %i. Got %i\n", (int)child_p,
329 			       (int)wrc);
330 			UNRESOLVED(errno, "Waitpid failed");
331 		}
332 
333 		if (WIFSIGNALED(status)) {
334 			output("Child process killed with signal %d\n",
335 			       WTERMSIG(status));
336 			UNRESOLVED(0, "Child process was killed");
337 		}
338 
339 		if (WIFEXITED(status)) {
340 			ret = WEXITSTATUS(status);
341 		} else {
342 			UNRESOLVED(0,
343 				   "Child process was neither killed nor exited");
344 		}
345 	}
346 
347 	/* the end */
348 	return NULL;
349 }
350 
351 int *pBoolean = NULL;
352 
353 /***
354  * Signal handler
355  */
sighdl(int sig)356 void sighdl(int sig)
357 {
358 	if (sig == SIGUSR1) {
359 #if VERBOSE > 1
360 		output("Received the USR1 signal; stopping everything\n");
361 #endif
362 		*pBoolean = 1;
363 	}
364 	if (sig == SIGALRM) {
365 		FAILED
366 		    ("A wait operation timed out. A condition signaling was lost.");
367 	}
368 }
369 
main(int argc,char * argv[])370 int main(int argc, char *argv[])
371 {
372 	int ret, i, j;
373 	struct sigaction sa;
374 
375 	pthread_mutexattr_t ma;
376 	pthread_condattr_t ca;
377 	clockid_t cid = CLOCK_REALTIME;
378 
379 	testdata_t *td;
380 	testdata_t alternativ;
381 
382 	int do_fork;
383 	long pshared, monotonic, cs, mf;
384 
385 	pthread_t th[NTOT];
386 
387 	output_init();
388 
389 	pshared = sysconf(_SC_THREAD_PROCESS_SHARED);
390 	cs = sysconf(_SC_CLOCK_SELECTION);
391 	monotonic = sysconf(_SC_MONOTONIC_CLOCK);
392 	mf = sysconf(_SC_MAPPED_FILES);
393 
394 #if VERBOSE > 0
395 	output("Test starting\n");
396 	output("System abilities:\n");
397 	output(" TPS : %li\n", pshared);
398 	output(" CS  : %li\n", cs);
399 	output(" MON : %li\n", monotonic);
400 	output(" MF  : %li\n", mf);
401 	if ((mf < 0) || (pshared < 0))
402 		output("Process-shared attributes won't be tested\n");
403 	if ((cs < 0) || (monotonic < 0))
404 		output("Alternative clock won't be tested\n");
405 #endif
406 
407 	/* We are not interested in testing the clock if we have no other clock available.. */
408 	if (monotonic < 0)
409 		cs = -1;
410 
411 #ifndef USE_ALTCLK
412 	if (cs > 0)
413 		output
414 		    ("Implementation supports the MONOTONIC CLOCK but option is disabled in test.\n");
415 #endif
416 
417 /**********
418  * Allocate space for the testdata structure
419  */
420 	if (mf < 0) {
421 		/* Cannot mmap a file, we use an alternative method */
422 		td = &alternativ;
423 		pshared = -1;	/* We won't do this testing anyway */
424 #if VERBOSE > 0
425 		output("Testdata allocated in the process memory.\n");
426 #endif
427 	} else {
428 		/* We will place the test data in a mmaped file */
429 		char filename[] = "/tmp/cond_timedwait_st1-XXXXXX";
430 		size_t sz, ps;
431 		void *mmaped;
432 		int fd;
433 		char *tmp;
434 
435 		/* We now create the temp files */
436 		fd = mkstemp(filename);
437 		if (fd == -1) {
438 			UNRESOLVED(errno,
439 				   "Temporary file could not be created");
440 		}
441 
442 		/* and make sure the file will be deleted when closed */
443 		unlink(filename);
444 
445 #if VERBOSE > 1
446 		output("Temp file created (%s).\n", filename);
447 #endif
448 
449 		ps = (size_t) sysconf(_SC_PAGESIZE);
450 		sz = ((sizeof(testdata_t) / ps) + 1) * ps;	/* # pages needed to store the testdata */
451 
452 		tmp = calloc(1, sz);
453 		if (tmp == NULL) {
454 			UNRESOLVED(errno, "Memory allocation failed");
455 		}
456 
457 		/* Write the data to the file.  */
458 		if (write(fd, tmp, sz) != (ssize_t) sz) {
459 			UNRESOLVED(sz, "Writting to the file failed");
460 		}
461 
462 		free(tmp);
463 
464 		/* Now we can map the file in memory */
465 		mmaped =
466 		    mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
467 		if (mmaped == MAP_FAILED) {
468 			UNRESOLVED(errno, "mmap failed");
469 		}
470 
471 		td = (testdata_t *) mmaped;
472 
473 		/* Our datatest structure is now in shared memory */
474 #if VERBOSE > 1
475 		output("Testdata allocated in shared memory (%ib).\n",
476 		       sizeof(testdata_t));
477 #endif
478 	}
479 
480 	/* Init the signal handler variable */
481 	pBoolean = &(td->boolean);
482 
483 	/* Init the structure */
484 	for (i = 0; i < NSCENAR; i++) {
485 #if VERBOSE > 1
486 		output("[parent] Preparing attributes for: %s\n",
487 		       scenarii[i].descr);
488 #ifdef WITHOUT_XOPEN
489 		output("[parent] Mutex attributes DISABLED -> not used\n");
490 #endif
491 #endif
492 		/* set / reset everything */
493 		do_fork = 0;
494 		ret = pthread_mutexattr_init(&ma);
495 		if (ret != 0) {
496 			UNRESOLVED(ret,
497 				   "[parent] Unable to initialize the mutex attribute object");
498 		}
499 		ret = pthread_condattr_init(&ca);
500 		if (ret != 0) {
501 			UNRESOLVED(ret,
502 				   "[parent] Unable to initialize the cond attribute object");
503 		}
504 #ifndef WITHOUT_XOPEN
505 		/* Set the mutex type */
506 		ret = pthread_mutexattr_settype(&ma, scenarii[i].m_type);
507 		if (ret != 0) {
508 			UNRESOLVED(ret, "[parent] Unable to set mutex type");
509 		}
510 #if VERBOSE > 1
511 		output("[parent] Mutex type : %i\n", scenarii[i].m_type);
512 #endif
513 #endif
514 
515 		/* Set the pshared attributes, if supported */
516 		if ((pshared > 0) && (scenarii[i].mc_pshared != 0)) {
517 			ret =
518 			    pthread_mutexattr_setpshared(&ma,
519 							 PTHREAD_PROCESS_SHARED);
520 			if (ret != 0) {
521 				UNRESOLVED(ret,
522 					   "[parent] Unable to set the mutex process-shared");
523 			}
524 			ret =
525 			    pthread_condattr_setpshared(&ca,
526 							PTHREAD_PROCESS_SHARED);
527 			if (ret != 0) {
528 				UNRESOLVED(ret,
529 					   "[parent] Unable to set the cond var process-shared");
530 			}
531 #if VERBOSE > 1
532 			output("[parent] Mutex & cond are process-shared\n");
533 #endif
534 		}
535 #if VERBOSE > 1
536 		else {
537 			output("[parent] Mutex & cond are process-private\n");
538 		}
539 #endif
540 
541 		/* Set the alternative clock, if supported */
542 #ifdef USE_ALTCLK
543 		if ((cs > 0) && (scenarii[i].c_clock != 0)) {
544 			ret = pthread_condattr_setclock(&ca, CLOCK_MONOTONIC);
545 			if (ret != 0) {
546 				UNRESOLVED(ret,
547 					   "[parent] Unable to set the monotonic clock for the cond");
548 			}
549 #if VERBOSE > 1
550 			output("[parent] Cond uses the Monotonic clock\n");
551 #endif
552 		}
553 #if VERBOSE > 1
554 		else {
555 			output("[parent] Cond uses the default clock\n");
556 		}
557 #endif
558 		ret = pthread_condattr_getclock(&ca, &cid);
559 		if (ret != 0) {
560 			UNRESOLVED(ret, "Unable to get clock from cond attr");
561 		}
562 #endif
563 
564 		/* Tell whether the test will be across processes */
565 		if ((pshared > 0) && (scenarii[i].fork != 0)) {
566 			do_fork = 1;
567 #if VERBOSE > 1
568 			output("[parent] Child will be a new process\n");
569 #endif
570 		}
571 #if VERBOSE > 1
572 		else {
573 			output("[parent] Child will be a new thread\n");
574 		}
575 #endif
576 
577 		/* Initialize all the mutex and condvars which uses those attributes */
578 		for (j = 0; j < SCALABILITY_FACTOR * NCHILDREN; j++) {
579 #define CD (td->cd[i+(j*NSCENAR)])
580 			CD.pBool = &(td->boolean);
581 			CD.fork = do_fork;
582 			CD.cid = cid;
583 
584 			/* initialize the condvar */
585 			ret = pthread_cond_init(&(CD.cnd), &ca);
586 			if (ret != 0) {
587 				UNRESOLVED(ret, "[parent] Cond init failed");
588 			}
589 
590 			/* initialize the mutex */
591 			ret = pthread_mutex_init(&(CD.mtx), &ma);
592 			if (ret != 0) {
593 				UNRESOLVED(ret, "[parent] Mutex init failed");
594 			}
595 #undef CD
596 		}
597 
598 		ret = pthread_condattr_destroy(&ca);
599 		if (ret != 0) {
600 			UNRESOLVED(ret,
601 				   "Failed to destroy the cond var attribute object");
602 		}
603 
604 		ret = pthread_mutexattr_destroy(&ma);
605 		if (ret != 0) {
606 			UNRESOLVED(ret,
607 				   "Failed to destroy the mutex attribute object");
608 		}
609 	}
610 #if VERBOSE > 1
611 	output("[parent] All condvars & mutex are ready\n");
612 #endif
613 
614 	ret = pthread_attr_init(&ta);
615 	if (ret != 0) {
616 		UNRESOLVED(ret,
617 			   "[parent] Failed to initialize a thread attribute object");
618 	}
619 	ret = pthread_attr_setstacksize(&ta, sysconf(_SC_THREAD_STACK_MIN));
620 	if (ret != 0) {
621 		UNRESOLVED(ret, "[parent] Failed to set thread stack size");
622 	}
623 
624 	sigemptyset(&sa.sa_mask);
625 	sa.sa_flags = 0;
626 	sa.sa_handler = sighdl;
627 	if ((ret = sigaction(SIGUSR1, &sa, NULL))) {
628 		UNRESOLVED(ret, "Unable to register signal handler");
629 	}
630 	if ((ret = sigaction(SIGALRM, &sa, NULL))) {
631 		UNRESOLVED(ret, "Unable to register signal handler");
632 	}
633 #if VERBOSE > 1
634 	output("[parent] Signal handler registered\n");
635 #endif
636 
637 	for (i = 0; i < NTOT; i++) {
638 		ret = pthread_create(&th[i], &ta, threaded_A, &(td->cd[i]));
639 		/* In case of failure we can exit; the child processes will die after a while */
640 		if (ret != 0) {
641 			UNRESOLVED(ret, "[Parent] Failed to create a thread");
642 		}
643 #if VERBOSE > 1
644 		if ((i % 10) == 0)
645 			output("[parent] %i threads created...\n", i + 1);
646 #endif
647 	}
648 
649 #if VERBOSE > 1
650 	output("[parent] All %i threads are running...\n", NTOT);
651 #endif
652 
653 	for (i = 0; i < NTOT; i++) {
654 		ret = pthread_join(th[i], NULL);
655 		if (ret != 0) {
656 			UNRESOLVED(ret, "[Parent] Failed to join a thread");
657 		}
658 	}
659 
660 	/* Destroy everything */
661 	for (i = 0; i < NTOT; i++) {
662 		/* destroy the condvar */
663 		ret = pthread_cond_destroy(&(td->cd[i].cnd));
664 		if (ret != 0) {
665 			UNRESOLVED(ret, "[parent] Cond destroy failed");
666 		}
667 
668 		/* destroy the mutex */
669 		ret = pthread_mutex_init(&(td->cd[i].mtx), &ma);
670 		if (ret != 0) {
671 			UNRESOLVED(ret, "[parent] Mutex destroy failed");
672 		}
673 	}
674 
675 #if VERBOSE > 0
676 	output("Test passed\n");
677 #endif
678 
679 	PASSED;
680 }
681