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