build: update gnulib submodule to latest
[coreutils.git] / src / test.c
blob5af924347400091091a2854c70ff1cd1d8b69a96
1 /* GNU test program (ksb and mjb) */
3 /* Modified to run with the GNU shell by bfox. */
5 /* Copyright (C) 1987-2024 Free Software Foundation, Inc.
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <https://www.gnu.org/licenses/>. */
20 /* Define TEST_STANDALONE to get the /bin/test version. Otherwise, you get
21 the shell builtin version. */
23 #include <config.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <sys/types.h>
28 #define TEST_STANDALONE 1
30 #ifndef LBRACKET
31 # define LBRACKET 0
32 #endif
34 /* The official name of this program (e.g., no 'g' prefix). */
35 #if LBRACKET
36 # define PROGRAM_NAME "["
37 #else
38 # define PROGRAM_NAME "test"
39 #endif
41 #include "system.h"
42 #include "assure.h"
43 #include "quote.h"
44 #include "stat-time.h"
45 #include "strnumcmp.h"
47 #include <stdarg.h>
49 #if HAVE_SYS_PARAM_H
50 # include <sys/param.h>
51 #endif
53 /* Exit status for syntax errors, etc. */
54 enum { TEST_TRUE, TEST_FALSE, TEST_FAILURE };
56 #if defined TEST_STANDALONE
57 # define test_exit(val) exit (val)
58 # define test_main_return(val) return val
59 #else
60 static jmp_buf test_exit_buf;
61 static int test_error_return = 0;
62 # define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1)
63 # define test_main_return(val) test_exit (val)
64 #endif /* !TEST_STANDALONE */
66 static int pos; /* The offset of the current argument in ARGV. */
67 static int argc; /* The number of arguments present in ARGV. */
68 static char **argv; /* The argument list. */
70 static bool unary_operator (void);
71 static bool binary_operator (bool);
72 static bool two_arguments (void);
73 static bool three_arguments (void);
74 static bool posixtest (int);
76 static bool expr (void);
77 static bool term (void);
78 static bool and (void);
79 static bool or (void);
81 static void beyond (void);
83 ATTRIBUTE_FORMAT ((printf, 1, 2))
84 static _Noreturn void
85 test_syntax_error (char const *format, ...)
87 va_list ap;
88 va_start (ap, format);
89 verror (0, 0, format, ap);
90 test_exit (TEST_FAILURE);
93 /* Increment our position in the argument list. Check that we're not
94 past the end of the argument list. This check is suppressed if the
95 argument is false. */
97 static void
98 advance (bool f)
100 ++pos;
102 if (f && pos >= argc)
103 beyond ();
106 static void
107 unary_advance (void)
109 advance (true);
110 ++pos;
114 * beyond - call when we're beyond the end of the argument list (an
115 * error condition)
117 static _Noreturn void
118 beyond (void)
120 test_syntax_error (_("missing argument after %s"), quote (argv[argc - 1]));
123 /* If the characters pointed to by STRING constitute a valid number,
124 return a pointer to the start of the number, skipping any blanks or
125 leading '+'. Otherwise, report an error and exit. */
126 static char const *
127 find_int (char const *string)
129 char const *p;
130 char const *number_start;
132 for (p = string; isspace (to_uchar (*p)); p++)
133 continue;
135 if (*p == '+')
137 p++;
138 number_start = p;
140 else
142 number_start = p;
143 p += (*p == '-');
146 if (ISDIGIT (*p++))
148 while (ISDIGIT (*p))
149 p++;
150 while (isspace (to_uchar (*p)))
151 p++;
152 if (!*p)
153 return number_start;
156 test_syntax_error (_("invalid integer %s"), quote (string));
159 /* Find the modification time of FILE, and stuff it into *MTIME.
160 Return true if successful. */
161 static bool
162 get_mtime (char const *filename, struct timespec *mtime)
164 struct stat finfo;
165 bool ok = (stat (filename, &finfo) == 0);
166 if (ok)
167 *mtime = get_stat_mtime (&finfo);
168 return ok;
171 /* Return true if S is one of the test command's binary operators. */
172 static bool
173 binop (char const *s)
175 return ((STREQ (s, "=")) || (STREQ (s, "!=")) || (STREQ (s, "==")) ||
176 (STREQ (s, "-nt")) ||
177 (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) ||
178 (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) ||
179 (STREQ (s, "-gt")) || (STREQ (s, "-ge")));
183 * term - parse a term and return 1 or 0 depending on whether the term
184 * evaluates to true or false, respectively.
186 * term ::=
187 * '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename
188 * '-'('L'|'x') filename
189 * '-t' int
190 * '-'('z'|'n') string
191 * string
192 * string ('!='|'=') string
193 * <int> '-'(eq|ne|le|lt|ge|gt) <int>
194 * file '-'(nt|ot|ef) file
195 * '(' <expr> ')'
196 * int ::=
197 * '-l' string
198 * positive and negative integers
200 static bool
201 term (void)
203 bool value;
204 bool negated = false;
206 /* Deal with leading 'not's. */
207 while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0')
209 advance (true);
210 negated = !negated;
213 if (pos >= argc)
214 beyond ();
216 /* A paren-bracketed argument. */
217 if (argv[pos][0] == '(' && argv[pos][1] == '\0')
219 int nargs;
221 advance (true);
223 for (nargs = 1;
224 pos + nargs < argc && ! STREQ (argv[pos + nargs], ")");
225 nargs++)
226 if (nargs == 4)
228 nargs = argc - pos;
229 break;
232 value = posixtest (nargs);
233 if (argv[pos] == 0)
234 test_syntax_error (_("%s expected"), quote (")"));
235 else
236 if (argv[pos][0] != ')' || argv[pos][1])
237 test_syntax_error (_("%s expected, found %s"),
238 quote_n (0, ")"), quote_n (1, argv[pos]));
239 advance (false);
242 /* Are there enough arguments left that this could be dyadic? */
243 else if (4 <= argc - pos && STREQ (argv[pos], "-l") && binop (argv[pos + 2]))
244 value = binary_operator (true);
245 else if (3 <= argc - pos && binop (argv[pos + 1]))
246 value = binary_operator (false);
248 /* It might be a switch type argument. */
249 else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0')
250 value = unary_operator ();
251 else
253 value = (argv[pos][0] != '\0');
254 advance (false);
257 return negated ^ value;
260 static bool
261 binary_operator (bool l_is_l)
263 int op;
264 struct stat stat_buf, stat_spare;
265 /* Is the right integer expression of the form '-l string'? */
266 bool r_is_l;
268 if (l_is_l)
269 advance (false);
270 op = pos + 1;
272 if ((op < argc - 2) && STREQ (argv[op + 1], "-l"))
274 r_is_l = true;
275 advance (false);
277 else
278 r_is_l = false;
280 if (argv[op][0] == '-')
282 /* check for eq, nt, and stuff */
283 if ((((argv[op][1] == 'l' || argv[op][1] == 'g')
284 && (argv[op][2] == 'e' || argv[op][2] == 't'))
285 || (argv[op][1] == 'e' && argv[op][2] == 'q')
286 || (argv[op][1] == 'n' && argv[op][2] == 'e'))
287 && !argv[op][3])
289 char lbuf[INT_BUFSIZE_BOUND (uintmax_t)];
290 char rbuf[INT_BUFSIZE_BOUND (uintmax_t)];
291 char const *l = (l_is_l
292 ? umaxtostr (strlen (argv[op - 1]), lbuf)
293 : find_int (argv[op - 1]));
294 char const *r = (r_is_l
295 ? umaxtostr (strlen (argv[op + 2]), rbuf)
296 : find_int (argv[op + 1]));
297 int cmp = strintcmp (l, r);
298 bool xe_operator = (argv[op][2] == 'e');
299 pos += 3;
300 return (argv[op][1] == 'l' ? cmp < xe_operator
301 : argv[op][1] == 'g' ? cmp > - xe_operator
302 : (cmp != 0) == xe_operator);
305 switch (argv[op][1])
307 default:
308 break;
310 case 'n':
311 if (argv[op][2] == 't' && !argv[op][3])
313 /* nt - newer than */
314 struct timespec lt, rt;
315 bool le, re;
316 pos += 3;
317 if (l_is_l || r_is_l)
318 test_syntax_error (_("-nt does not accept -l"));
319 le = get_mtime (argv[op - 1], &lt);
320 re = get_mtime (argv[op + 1], &rt);
321 return le && (!re || timespec_cmp (lt, rt) > 0);
323 break;
325 case 'e':
326 if (argv[op][2] == 'f' && !argv[op][3])
328 /* ef - hard link? */
329 pos += 3;
330 if (l_is_l || r_is_l)
331 test_syntax_error (_("-ef does not accept -l"));
332 return (stat (argv[op - 1], &stat_buf) == 0
333 && stat (argv[op + 1], &stat_spare) == 0
334 && stat_buf.st_dev == stat_spare.st_dev
335 && stat_buf.st_ino == stat_spare.st_ino);
337 break;
339 case 'o':
340 if ('t' == argv[op][2] && '\000' == argv[op][3])
342 /* ot - older than */
343 struct timespec lt, rt;
344 bool le, re;
345 pos += 3;
346 if (l_is_l || r_is_l)
347 test_syntax_error (_("-ot does not accept -l"));
348 le = get_mtime (argv[op - 1], &lt);
349 re = get_mtime (argv[op + 1], &rt);
350 return re && (!le || timespec_cmp (lt, rt) < 0);
352 break;
355 /* FIXME: is this dead code? */
356 test_syntax_error (_("%s: unknown binary operator"), quote (argv[op]));
359 if (argv[op][0] == '='
360 && (!argv[op][1] || ((argv[op][1] == '=') && !argv[op][2])))
362 bool value = STREQ (argv[pos], argv[pos + 2]);
363 pos += 3;
364 return value;
367 if (STREQ (argv[op], "!="))
369 bool value = !STREQ (argv[pos], argv[pos + 2]);
370 pos += 3;
371 return value;
374 /* Not reached. */
375 affirm (false);
378 static bool
379 unary_operator (void)
381 struct stat stat_buf;
383 switch (argv[pos][1])
385 default:
386 test_syntax_error (_("%s: unary operator expected"), quote (argv[pos]));
388 /* All of the following unary operators use unary_advance (), which
389 checks to make sure that there is an argument, and then advances
390 pos right past it. This means that pos - 1 is the location of the
391 argument. */
393 case 'e': /* file exists in the file system? */
394 unary_advance ();
395 return stat (argv[pos - 1], &stat_buf) == 0;
397 case 'r': /* file is readable? */
398 unary_advance ();
399 return euidaccess (argv[pos - 1], R_OK) == 0;
401 case 'w': /* File is writable? */
402 unary_advance ();
403 return euidaccess (argv[pos - 1], W_OK) == 0;
405 case 'x': /* File is executable? */
406 unary_advance ();
407 return euidaccess (argv[pos - 1], X_OK) == 0;
409 case 'N': /* File exists and has been modified since it was last read? */
411 unary_advance ();
412 if (stat (argv[pos - 1], &stat_buf) != 0)
413 return false;
414 struct timespec atime = get_stat_atime (&stat_buf);
415 struct timespec mtime = get_stat_mtime (&stat_buf);
416 return (timespec_cmp (mtime, atime) > 0);
419 case 'O': /* File is owned by you? */
421 unary_advance ();
422 if (stat (argv[pos - 1], &stat_buf) != 0)
423 return false;
424 errno = 0;
425 uid_t euid = geteuid ();
426 uid_t NO_UID = -1;
427 return ! (euid == NO_UID && errno) && euid == stat_buf.st_uid;
430 case 'G': /* File is owned by your group? */
432 unary_advance ();
433 if (stat (argv[pos - 1], &stat_buf) != 0)
434 return false;
435 errno = 0;
436 gid_t egid = getegid ();
437 gid_t NO_GID = -1;
438 return ! (egid == NO_GID && errno) && egid == stat_buf.st_gid;
441 case 'f': /* File is a file? */
442 unary_advance ();
443 /* Under POSIX, -f is true if the given file exists
444 and is a regular file. */
445 return (stat (argv[pos - 1], &stat_buf) == 0
446 && S_ISREG (stat_buf.st_mode));
448 case 'd': /* File is a directory? */
449 unary_advance ();
450 return (stat (argv[pos - 1], &stat_buf) == 0
451 && S_ISDIR (stat_buf.st_mode));
453 case 's': /* File has something in it? */
454 unary_advance ();
455 return (stat (argv[pos - 1], &stat_buf) == 0
456 && 0 < stat_buf.st_size);
458 case 'S': /* File is a socket? */
459 unary_advance ();
460 return (stat (argv[pos - 1], &stat_buf) == 0
461 && S_ISSOCK (stat_buf.st_mode));
463 case 'c': /* File is character special? */
464 unary_advance ();
465 return (stat (argv[pos - 1], &stat_buf) == 0
466 && S_ISCHR (stat_buf.st_mode));
468 case 'b': /* File is block special? */
469 unary_advance ();
470 return (stat (argv[pos - 1], &stat_buf) == 0
471 && S_ISBLK (stat_buf.st_mode));
473 case 'p': /* File is a named pipe? */
474 unary_advance ();
475 return (stat (argv[pos - 1], &stat_buf) == 0
476 && S_ISFIFO (stat_buf.st_mode));
478 case 'L': /* Same as -h */
479 /*FALLTHROUGH*/
481 case 'h': /* File is a symbolic link? */
482 unary_advance ();
483 return (lstat (argv[pos - 1], &stat_buf) == 0
484 && S_ISLNK (stat_buf.st_mode));
486 case 'u': /* File is setuid? */
487 unary_advance ();
488 return (stat (argv[pos - 1], &stat_buf) == 0
489 && (stat_buf.st_mode & S_ISUID));
491 case 'g': /* File is setgid? */
492 unary_advance ();
493 return (stat (argv[pos - 1], &stat_buf) == 0
494 && (stat_buf.st_mode & S_ISGID));
496 case 'k': /* File has sticky bit set? */
497 unary_advance ();
498 return (stat (argv[pos - 1], &stat_buf) == 0
499 && (stat_buf.st_mode & S_ISVTX));
501 case 't': /* File (fd) is a terminal? */
503 long int fd;
504 char const *arg;
505 unary_advance ();
506 arg = find_int (argv[pos - 1]);
507 errno = 0;
508 fd = strtol (arg, nullptr, 10);
509 return (errno != ERANGE && 0 <= fd && fd <= INT_MAX && isatty (fd));
512 case 'n': /* True if arg has some length. */
513 unary_advance ();
514 return argv[pos - 1][0] != 0;
516 case 'z': /* True if arg has no length. */
517 unary_advance ();
518 return argv[pos - 1][0] == '\0';
523 * and:
524 * term
525 * term '-a' and
527 static bool
528 and (void)
530 bool value = true;
532 while (true)
534 value &= term ();
535 if (! (pos < argc && STREQ (argv[pos], "-a")))
536 return value;
537 advance (false);
542 * or:
543 * and
544 * and '-o' or
546 static bool
547 or (void)
549 bool value = false;
551 while (true)
553 value |= and ();
554 if (! (pos < argc && STREQ (argv[pos], "-o")))
555 return value;
556 advance (false);
561 * expr:
562 * or
564 static bool
565 expr (void)
567 if (pos >= argc)
568 beyond ();
570 return or (); /* Same with this. */
573 static bool
574 one_argument (void)
576 return argv[pos++][0] != '\0';
579 static bool
580 two_arguments (void)
582 bool value;
584 if (STREQ (argv[pos], "!"))
586 advance (false);
587 value = ! one_argument ();
589 else if (argv[pos][0] == '-'
590 && argv[pos][1] != '\0'
591 && argv[pos][2] == '\0')
593 value = unary_operator ();
595 else
596 beyond ();
597 return (value);
600 static bool
601 three_arguments (void)
603 bool value;
605 if (binop (argv[pos + 1]))
606 value = binary_operator (false);
607 else if (STREQ (argv[pos], "!"))
609 advance (true);
610 value = !two_arguments ();
612 else if (STREQ (argv[pos], "(") && STREQ (argv[pos + 2], ")"))
614 advance (false);
615 value = one_argument ();
616 advance (false);
618 else if (STREQ (argv[pos + 1], "-a") || STREQ (argv[pos + 1], "-o"))
619 value = expr ();
620 else
621 test_syntax_error (_("%s: binary operator expected"),
622 quote (argv[pos + 1]));
623 return (value);
626 /* This is an implementation of a Posix.2 proposal by David Korn. */
627 static bool
628 posixtest (int nargs)
630 bool value;
632 switch (nargs)
634 case 1:
635 value = one_argument ();
636 break;
638 case 2:
639 value = two_arguments ();
640 break;
642 case 3:
643 value = three_arguments ();
644 break;
646 case 4:
647 if (STREQ (argv[pos], "!"))
649 advance (true);
650 value = !three_arguments ();
651 break;
653 if (STREQ (argv[pos], "(") && STREQ (argv[pos + 3], ")"))
655 advance (false);
656 value = two_arguments ();
657 advance (false);
658 break;
660 FALLTHROUGH;
661 case 5:
662 default:
663 affirm (0 < nargs);
664 value = expr ();
667 return (value);
670 #if defined TEST_STANDALONE
672 void
673 usage (int status)
675 if (status != EXIT_SUCCESS)
676 emit_try_help ();
677 else
679 fputs (_("\
680 Usage: test EXPRESSION\n\
681 or: test\n\
682 or: [ EXPRESSION ]\n\
683 or: [ ]\n\
684 or: [ OPTION\n\
685 "), stdout);
686 fputs (_("\
687 Exit with the status determined by EXPRESSION.\n\
689 "), stdout);
690 fputs (HELP_OPTION_DESCRIPTION, stdout);
691 fputs (VERSION_OPTION_DESCRIPTION, stdout);
692 fputs (_("\
694 An omitted EXPRESSION defaults to false. Otherwise,\n\
695 EXPRESSION is true or false and sets exit status. It is one of:\n\
696 "), stdout);
697 fputs (_("\
699 ( EXPRESSION ) EXPRESSION is true\n\
700 ! EXPRESSION EXPRESSION is false\n\
701 EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true\n\
702 EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true\n\
703 "), stdout);
704 fputs (_("\
706 -n STRING the length of STRING is nonzero\n\
707 STRING equivalent to -n STRING\n\
708 -z STRING the length of STRING is zero\n\
709 STRING1 = STRING2 the strings are equal\n\
710 STRING1 != STRING2 the strings are not equal\n\
711 "), stdout);
712 fputs (_("\
714 INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2\n\
715 INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2\n\
716 INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2\n\
717 INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2\n\
718 INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2\n\
719 INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2\n\
720 "), stdout);
721 fputs (_("\
723 FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers\n\
724 FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2\n\
725 FILE1 -ot FILE2 FILE1 is older than FILE2\n\
726 "), stdout);
727 fputs (_("\
729 -b FILE FILE exists and is block special\n\
730 -c FILE FILE exists and is character special\n\
731 -d FILE FILE exists and is a directory\n\
732 -e FILE FILE exists\n\
733 "), stdout);
734 fputs (_("\
735 -f FILE FILE exists and is a regular file\n\
736 -g FILE FILE exists and is set-group-ID\n\
737 -G FILE FILE exists and is owned by the effective group ID\n\
738 -h FILE FILE exists and is a symbolic link (same as -L)\n\
739 -k FILE FILE exists and has its sticky bit set\n\
740 "), stdout);
741 fputs (_("\
742 -L FILE FILE exists and is a symbolic link (same as -h)\n\
743 -N FILE FILE exists and has been modified since it was last read\n\
744 -O FILE FILE exists and is owned by the effective user ID\n\
745 -p FILE FILE exists and is a named pipe\n\
746 -r FILE FILE exists and the user has read access\n\
747 -s FILE FILE exists and has a size greater than zero\n\
748 "), stdout);
749 fputs (_("\
750 -S FILE FILE exists and is a socket\n\
751 -t FD file descriptor FD is opened on a terminal\n\
752 -u FILE FILE exists and its set-user-ID bit is set\n\
753 -w FILE FILE exists and the user has write access\n\
754 -x FILE FILE exists and the user has execute (or search) access\n\
755 "), stdout);
756 fputs (_("\
758 Except for -h and -L, all FILE-related tests dereference symbolic links.\n\
759 Beware that parentheses need to be escaped (e.g., by backslashes) for shells.\n\
760 INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\
761 "), stdout);
762 fputs (_("\
764 Binary -a and -o are ambiguous. Use 'test EXPR1 && test EXPR2'\n\
765 or 'test EXPR1 || test EXPR2' instead.\n\
766 "), stdout);
767 fputs (_("\
769 '[' honors --help and --version, but 'test' treats them as STRINGs.\n\
770 "), stdout);
771 printf (USAGE_BUILTIN_WARNING, _("test and/or ["));
772 emit_ancillary_info (PROGRAM_NAME);
774 exit (status);
776 #endif /* TEST_STANDALONE */
778 #if !defined TEST_STANDALONE
779 # define main test_command
780 #endif
782 #define AUTHORS \
783 proper_name ("Kevin Braunsdorf"), \
784 proper_name ("Matthew Bradburn")
787 * [:
788 * '[' expr ']'
789 * test:
790 * test expr
793 main (int margc, char **margv)
795 bool value;
797 #if !defined TEST_STANDALONE
798 int code;
800 code = setjmp (test_exit_buf);
802 if (code)
803 return (test_error_return);
804 #else /* TEST_STANDALONE */
805 initialize_main (&margc, &margv);
806 set_program_name (margv[0]);
807 setlocale (LC_ALL, "");
808 bindtextdomain (PACKAGE, LOCALEDIR);
809 textdomain (PACKAGE);
811 initialize_exit_failure (TEST_FAILURE);
812 atexit (close_stdout);
813 #endif /* TEST_STANDALONE */
815 argv = margv;
817 if (LBRACKET)
819 /* Recognize --help or --version, but only when invoked in the
820 "[" form, when the last argument is not "]". Use direct
821 parsing, rather than parse_long_options, to avoid accepting
822 abbreviations. POSIX allows "[ --help" and "[ --version" to
823 have the usual GNU behavior, but it requires "test --help"
824 and "test --version" to exit silently with status 0. */
825 if (margc == 2)
827 if (STREQ (margv[1], "--help"))
828 usage (EXIT_SUCCESS);
830 if (STREQ (margv[1], "--version"))
832 version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
833 (char *) nullptr);
834 test_main_return (EXIT_SUCCESS);
837 if (margc < 2 || !STREQ (margv[margc - 1], "]"))
838 test_syntax_error (_("missing %s"), quote ("]"));
840 --margc;
843 argc = margc;
844 pos = 1;
846 if (pos >= argc)
847 test_main_return (TEST_FALSE);
849 value = posixtest (argc - 1);
851 if (pos != argc)
852 test_syntax_error (_("extra argument %s"), quote (argv[pos]));
854 test_main_return (value ? TEST_TRUE : TEST_FALSE);