1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * pg_regress --- regression test driver |
4 | * |
5 | * This is a C implementation of the previous shell script for running |
6 | * the regression tests, and should be mostly compatible with it. |
7 | * Initial author of C translation: Magnus Hagander |
8 | * |
9 | * This code is released under the terms of the PostgreSQL License. |
10 | * |
11 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
12 | * Portions Copyright (c) 1994, Regents of the University of California |
13 | * |
14 | * src/test/regress/pg_regress.c |
15 | * |
16 | *------------------------------------------------------------------------- |
17 | */ |
18 | |
19 | #include "postgres_fe.h" |
20 | |
21 | #include <ctype.h> |
22 | #include <sys/stat.h> |
23 | #include <sys/wait.h> |
24 | #include <signal.h> |
25 | #include <unistd.h> |
26 | |
27 | #ifdef HAVE_SYS_RESOURCE_H |
28 | #include <sys/time.h> |
29 | #include <sys/resource.h> |
30 | #endif |
31 | |
32 | #include "pg_regress.h" |
33 | |
34 | #include "common/logging.h" |
35 | #include "common/restricted_token.h" |
36 | #include "common/username.h" |
37 | #include "getopt_long.h" |
38 | #include "libpq/pqcomm.h" /* needed for UNIXSOCK_PATH() */ |
39 | #include "pg_config_paths.h" |
40 | #include "portability/instr_time.h" |
41 | |
42 | /* for resultmap we need a list of pairs of strings */ |
43 | typedef struct _resultmap |
44 | { |
45 | char *test; |
46 | char *type; |
47 | char *resultfile; |
48 | struct _resultmap *next; |
49 | } _resultmap; |
50 | |
51 | /* |
52 | * Values obtained from Makefile. |
53 | */ |
54 | char *host_platform = HOST_TUPLE; |
55 | |
56 | #ifndef WIN32 /* not used in WIN32 case */ |
57 | static char *shellprog = SHELLPROG; |
58 | #endif |
59 | |
60 | /* |
61 | * On Windows we use -w in diff switches to avoid problems with inconsistent |
62 | * newline representation. The actual result files will generally have |
63 | * Windows-style newlines, but the comparison files might or might not. |
64 | */ |
65 | #ifndef WIN32 |
66 | const char *basic_diff_opts = "" ; |
67 | const char *pretty_diff_opts = "-U3" ; |
68 | #else |
69 | const char *basic_diff_opts = "-w" ; |
70 | const char *pretty_diff_opts = "-w -U3" ; |
71 | #endif |
72 | |
73 | /* options settable from command line */ |
74 | _stringlist *dblist = NULL; |
75 | bool debug = false; |
76 | char *inputdir = "." ; |
77 | char *outputdir = "." ; |
78 | char *bindir = PGBINDIR; |
79 | char *launcher = NULL; |
80 | static _stringlist *loadlanguage = NULL; |
81 | static _stringlist *loadextension = NULL; |
82 | static int max_connections = 0; |
83 | static int max_concurrent_tests = 0; |
84 | static char *encoding = NULL; |
85 | static _stringlist *schedulelist = NULL; |
86 | static _stringlist * = NULL; |
87 | static char *temp_instance = NULL; |
88 | static _stringlist *temp_configs = NULL; |
89 | static bool nolocale = false; |
90 | static bool use_existing = false; |
91 | static char *hostname = NULL; |
92 | static int port = -1; |
93 | static bool port_specified_by_user = false; |
94 | static char *dlpath = PKGLIBDIR; |
95 | static char *user = NULL; |
96 | static _stringlist * = NULL; |
97 | static char *config_auth_datadir = NULL; |
98 | |
99 | /* internal variables */ |
100 | static const char *progname; |
101 | static char *logfilename; |
102 | static FILE *logfile; |
103 | static char *difffilename; |
104 | static const char *sockdir; |
105 | #ifdef HAVE_UNIX_SOCKETS |
106 | static const char *temp_sockdir; |
107 | static char sockself[MAXPGPATH]; |
108 | static char socklock[MAXPGPATH]; |
109 | #endif |
110 | |
111 | static _resultmap *resultmap = NULL; |
112 | |
113 | static PID_TYPE postmaster_pid = INVALID_PID; |
114 | static bool postmaster_running = false; |
115 | |
116 | static int success_count = 0; |
117 | static int fail_count = 0; |
118 | static int fail_ignore_count = 0; |
119 | |
120 | static bool directory_exists(const char *dir); |
121 | static void make_directory(const char *dir); |
122 | |
123 | static void header(const char *fmt,...) pg_attribute_printf(1, 2); |
124 | static void status(const char *fmt,...) pg_attribute_printf(1, 2); |
125 | static void psql_command(const char *database, const char *query,...) pg_attribute_printf(2, 3); |
126 | |
127 | /* |
128 | * allow core files if possible. |
129 | */ |
130 | #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE) |
131 | static void |
132 | unlimit_core_size(void) |
133 | { |
134 | struct rlimit lim; |
135 | |
136 | getrlimit(RLIMIT_CORE, &lim); |
137 | if (lim.rlim_max == 0) |
138 | { |
139 | fprintf(stderr, |
140 | _("%s: could not set core size: disallowed by hard limit\n" ), |
141 | progname); |
142 | return; |
143 | } |
144 | else if (lim.rlim_max == RLIM_INFINITY || lim.rlim_cur < lim.rlim_max) |
145 | { |
146 | lim.rlim_cur = lim.rlim_max; |
147 | setrlimit(RLIMIT_CORE, &lim); |
148 | } |
149 | } |
150 | #endif |
151 | |
152 | |
153 | /* |
154 | * Add an item at the end of a stringlist. |
155 | */ |
156 | void |
157 | add_stringlist_item(_stringlist **listhead, const char *str) |
158 | { |
159 | _stringlist *newentry = pg_malloc(sizeof(_stringlist)); |
160 | _stringlist *oldentry; |
161 | |
162 | newentry->str = pg_strdup(str); |
163 | newentry->next = NULL; |
164 | if (*listhead == NULL) |
165 | *listhead = newentry; |
166 | else |
167 | { |
168 | for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next) |
169 | /* skip */ ; |
170 | oldentry->next = newentry; |
171 | } |
172 | } |
173 | |
174 | /* |
175 | * Free a stringlist. |
176 | */ |
177 | static void |
178 | free_stringlist(_stringlist **listhead) |
179 | { |
180 | if (listhead == NULL || *listhead == NULL) |
181 | return; |
182 | if ((*listhead)->next != NULL) |
183 | free_stringlist(&((*listhead)->next)); |
184 | free((*listhead)->str); |
185 | free(*listhead); |
186 | *listhead = NULL; |
187 | } |
188 | |
189 | /* |
190 | * Split a delimited string into a stringlist |
191 | */ |
192 | static void |
193 | split_to_stringlist(const char *s, const char *delim, _stringlist **listhead) |
194 | { |
195 | char *sc = pg_strdup(s); |
196 | char *token = strtok(sc, delim); |
197 | |
198 | while (token) |
199 | { |
200 | add_stringlist_item(listhead, token); |
201 | token = strtok(NULL, delim); |
202 | } |
203 | free(sc); |
204 | } |
205 | |
206 | /* |
207 | * Print a progress banner on stdout. |
208 | */ |
209 | static void |
210 | (const char *fmt,...) |
211 | { |
212 | char tmp[64]; |
213 | va_list ap; |
214 | |
215 | va_start(ap, fmt); |
216 | vsnprintf(tmp, sizeof(tmp), fmt, ap); |
217 | va_end(ap); |
218 | |
219 | fprintf(stdout, "============== %-38s ==============\n" , tmp); |
220 | fflush(stdout); |
221 | } |
222 | |
223 | /* |
224 | * Print "doing something ..." --- supplied text should not end with newline |
225 | */ |
226 | static void |
227 | status(const char *fmt,...) |
228 | { |
229 | va_list ap; |
230 | |
231 | va_start(ap, fmt); |
232 | vfprintf(stdout, fmt, ap); |
233 | fflush(stdout); |
234 | va_end(ap); |
235 | |
236 | if (logfile) |
237 | { |
238 | va_start(ap, fmt); |
239 | vfprintf(logfile, fmt, ap); |
240 | va_end(ap); |
241 | } |
242 | } |
243 | |
244 | /* |
245 | * Done "doing something ..." |
246 | */ |
247 | static void |
248 | status_end(void) |
249 | { |
250 | fprintf(stdout, "\n" ); |
251 | fflush(stdout); |
252 | if (logfile) |
253 | fprintf(logfile, "\n" ); |
254 | } |
255 | |
256 | /* |
257 | * shut down temp postmaster |
258 | */ |
259 | static void |
260 | stop_postmaster(void) |
261 | { |
262 | if (postmaster_running) |
263 | { |
264 | /* We use pg_ctl to issue the kill and wait for stop */ |
265 | char buf[MAXPGPATH * 2]; |
266 | int r; |
267 | |
268 | /* On Windows, system() seems not to force fflush, so... */ |
269 | fflush(stdout); |
270 | fflush(stderr); |
271 | |
272 | snprintf(buf, sizeof(buf), |
273 | "\"%s%spg_ctl\" stop -D \"%s/data\" -s" , |
274 | bindir ? bindir : "" , |
275 | bindir ? "/" : "" , |
276 | temp_instance); |
277 | r = system(buf); |
278 | if (r != 0) |
279 | { |
280 | fprintf(stderr, _("\n%s: could not stop postmaster: exit code was %d\n" ), |
281 | progname, r); |
282 | _exit(2); /* not exit(), that could be recursive */ |
283 | } |
284 | |
285 | postmaster_running = false; |
286 | } |
287 | } |
288 | |
289 | #ifdef HAVE_UNIX_SOCKETS |
290 | /* |
291 | * Remove the socket temporary directory. pg_regress never waits for a |
292 | * postmaster exit, so it is indeterminate whether the postmaster has yet to |
293 | * unlink the socket and lock file. Unlink them here so we can proceed to |
294 | * remove the directory. Ignore errors; leaking a temporary directory is |
295 | * unimportant. This can run from a signal handler. The code is not |
296 | * acceptable in a Windows signal handler (see initdb.c:trapsig()), but |
297 | * Windows is not a HAVE_UNIX_SOCKETS platform. |
298 | */ |
299 | static void |
300 | remove_temp(void) |
301 | { |
302 | Assert(temp_sockdir); |
303 | unlink(sockself); |
304 | unlink(socklock); |
305 | rmdir(temp_sockdir); |
306 | } |
307 | |
308 | /* |
309 | * Signal handler that calls remove_temp() and reraises the signal. |
310 | */ |
311 | static void |
312 | signal_remove_temp(int signum) |
313 | { |
314 | remove_temp(); |
315 | |
316 | pqsignal(signum, SIG_DFL); |
317 | raise(signum); |
318 | } |
319 | |
320 | /* |
321 | * Create a temporary directory suitable for the server's Unix-domain socket. |
322 | * The directory will have mode 0700 or stricter, so no other OS user can open |
323 | * our socket to exploit our use of trust authentication. Most systems |
324 | * constrain the length of socket paths well below _POSIX_PATH_MAX, so we |
325 | * place the directory under /tmp rather than relative to the possibly-deep |
326 | * current working directory. |
327 | * |
328 | * Compared to using the compiled-in DEFAULT_PGSOCKET_DIR, this also permits |
329 | * testing to work in builds that relocate it to a directory not writable to |
330 | * the build/test user. |
331 | */ |
332 | static const char * |
333 | make_temp_sockdir(void) |
334 | { |
335 | char *template = pg_strdup("/tmp/pg_regress-XXXXXX" ); |
336 | |
337 | temp_sockdir = mkdtemp(template); |
338 | if (temp_sockdir == NULL) |
339 | { |
340 | fprintf(stderr, _("%s: could not create directory \"%s\": %s\n" ), |
341 | progname, template, strerror(errno)); |
342 | exit(2); |
343 | } |
344 | |
345 | /* Stage file names for remove_temp(). Unsafe in a signal handler. */ |
346 | UNIXSOCK_PATH(sockself, port, temp_sockdir); |
347 | snprintf(socklock, sizeof(socklock), "%s.lock" , sockself); |
348 | |
349 | /* Remove the directory during clean exit. */ |
350 | atexit(remove_temp); |
351 | |
352 | /* |
353 | * Remove the directory before dying to the usual signals. Omit SIGQUIT, |
354 | * preserving it as a quick, untidy exit. |
355 | */ |
356 | pqsignal(SIGHUP, signal_remove_temp); |
357 | pqsignal(SIGINT, signal_remove_temp); |
358 | pqsignal(SIGPIPE, signal_remove_temp); |
359 | pqsignal(SIGTERM, signal_remove_temp); |
360 | |
361 | return temp_sockdir; |
362 | } |
363 | #endif /* HAVE_UNIX_SOCKETS */ |
364 | |
365 | /* |
366 | * Check whether string matches pattern |
367 | * |
368 | * In the original shell script, this function was implemented using expr(1), |
369 | * which provides basic regular expressions restricted to match starting at |
370 | * the string start (in conventional regex terms, there's an implicit "^" |
371 | * at the start of the pattern --- but no implicit "$" at the end). |
372 | * |
373 | * For now, we only support "." and ".*" as non-literal metacharacters, |
374 | * because that's all that anyone has found use for in resultmap. This |
375 | * code could be extended if more functionality is needed. |
376 | */ |
377 | static bool |
378 | string_matches_pattern(const char *str, const char *pattern) |
379 | { |
380 | while (*str && *pattern) |
381 | { |
382 | if (*pattern == '.' && pattern[1] == '*') |
383 | { |
384 | pattern += 2; |
385 | /* Trailing .* matches everything. */ |
386 | if (*pattern == '\0') |
387 | return true; |
388 | |
389 | /* |
390 | * Otherwise, scan for a text position at which we can match the |
391 | * rest of the pattern. |
392 | */ |
393 | while (*str) |
394 | { |
395 | /* |
396 | * Optimization to prevent most recursion: don't recurse |
397 | * unless first pattern char might match this text char. |
398 | */ |
399 | if (*str == *pattern || *pattern == '.') |
400 | { |
401 | if (string_matches_pattern(str, pattern)) |
402 | return true; |
403 | } |
404 | |
405 | str++; |
406 | } |
407 | |
408 | /* |
409 | * End of text with no match. |
410 | */ |
411 | return false; |
412 | } |
413 | else if (*pattern != '.' && *str != *pattern) |
414 | { |
415 | /* |
416 | * Not the single-character wildcard and no explicit match? Then |
417 | * time to quit... |
418 | */ |
419 | return false; |
420 | } |
421 | |
422 | str++; |
423 | pattern++; |
424 | } |
425 | |
426 | if (*pattern == '\0') |
427 | return true; /* end of pattern, so declare match */ |
428 | |
429 | /* End of input string. Do we have matching pattern remaining? */ |
430 | while (*pattern == '.' && pattern[1] == '*') |
431 | pattern += 2; |
432 | if (*pattern == '\0') |
433 | return true; /* end of pattern, so declare match */ |
434 | |
435 | return false; |
436 | } |
437 | |
438 | /* |
439 | * Replace all occurrences of a string in a string with a different string. |
440 | * NOTE: Assumes there is enough room in the target buffer! |
441 | */ |
442 | void |
443 | replace_string(char *string, const char *replace, const char *replacement) |
444 | { |
445 | char *ptr; |
446 | |
447 | while ((ptr = strstr(string, replace)) != NULL) |
448 | { |
449 | char *dup = pg_strdup(string); |
450 | |
451 | strlcpy(string, dup, ptr - string + 1); |
452 | strcat(string, replacement); |
453 | strcat(string, dup + (ptr - string) + strlen(replace)); |
454 | free(dup); |
455 | } |
456 | } |
457 | |
458 | /* |
459 | * Convert *.source found in the "source" directory, replacing certain tokens |
460 | * in the file contents with their intended values, and put the resulting files |
461 | * in the "dest" directory, replacing the ".source" prefix in their names with |
462 | * the given suffix. |
463 | */ |
464 | static void |
465 | convert_sourcefiles_in(const char *source_subdir, const char *dest_dir, const char *dest_subdir, const char *suffix) |
466 | { |
467 | char testtablespace[MAXPGPATH]; |
468 | char indir[MAXPGPATH]; |
469 | struct stat st; |
470 | int ret; |
471 | char **name; |
472 | char **names; |
473 | int count = 0; |
474 | |
475 | snprintf(indir, MAXPGPATH, "%s/%s" , inputdir, source_subdir); |
476 | |
477 | /* Check that indir actually exists and is a directory */ |
478 | ret = stat(indir, &st); |
479 | if (ret != 0 || !S_ISDIR(st.st_mode)) |
480 | { |
481 | /* |
482 | * No warning, to avoid noise in tests that do not have these |
483 | * directories; for example, ecpg, contrib and src/pl. |
484 | */ |
485 | return; |
486 | } |
487 | |
488 | names = pgfnames(indir); |
489 | if (!names) |
490 | /* Error logged in pgfnames */ |
491 | exit(2); |
492 | |
493 | snprintf(testtablespace, MAXPGPATH, "%s/testtablespace" , outputdir); |
494 | |
495 | #ifdef WIN32 |
496 | |
497 | /* |
498 | * On Windows only, clean out the test tablespace dir, or create it if it |
499 | * doesn't exist. On other platforms we expect the Makefile to take care |
500 | * of that. (We don't migrate that functionality in here because it'd be |
501 | * harder to cope with platform-specific issues such as SELinux.) |
502 | * |
503 | * XXX it would be better if pg_regress.c had nothing at all to do with |
504 | * testtablespace, and this were handled by a .BAT file or similar on |
505 | * Windows. See pgsql-hackers discussion of 2008-01-18. |
506 | */ |
507 | if (directory_exists(testtablespace)) |
508 | if (!rmtree(testtablespace, true)) |
509 | { |
510 | fprintf(stderr, _("\n%s: could not remove test tablespace \"%s\"\n" ), |
511 | progname, testtablespace); |
512 | exit(2); |
513 | } |
514 | make_directory(testtablespace); |
515 | #endif |
516 | |
517 | /* finally loop on each file and do the replacement */ |
518 | for (name = names; *name; name++) |
519 | { |
520 | char srcfile[MAXPGPATH]; |
521 | char destfile[MAXPGPATH]; |
522 | char prefix[MAXPGPATH]; |
523 | FILE *infile, |
524 | *outfile; |
525 | char line[1024]; |
526 | |
527 | /* reject filenames not finishing in ".source" */ |
528 | if (strlen(*name) < 8) |
529 | continue; |
530 | if (strcmp(*name + strlen(*name) - 7, ".source" ) != 0) |
531 | continue; |
532 | |
533 | count++; |
534 | |
535 | /* build the full actual paths to open */ |
536 | snprintf(prefix, strlen(*name) - 6, "%s" , *name); |
537 | snprintf(srcfile, MAXPGPATH, "%s/%s" , indir, *name); |
538 | snprintf(destfile, MAXPGPATH, "%s/%s/%s.%s" , dest_dir, dest_subdir, |
539 | prefix, suffix); |
540 | |
541 | infile = fopen(srcfile, "r" ); |
542 | if (!infile) |
543 | { |
544 | fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n" ), |
545 | progname, srcfile, strerror(errno)); |
546 | exit(2); |
547 | } |
548 | outfile = fopen(destfile, "w" ); |
549 | if (!outfile) |
550 | { |
551 | fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n" ), |
552 | progname, destfile, strerror(errno)); |
553 | exit(2); |
554 | } |
555 | while (fgets(line, sizeof(line), infile)) |
556 | { |
557 | replace_string(line, "@abs_srcdir@" , inputdir); |
558 | replace_string(line, "@abs_builddir@" , outputdir); |
559 | replace_string(line, "@testtablespace@" , testtablespace); |
560 | replace_string(line, "@libdir@" , dlpath); |
561 | replace_string(line, "@DLSUFFIX@" , DLSUFFIX); |
562 | fputs(line, outfile); |
563 | } |
564 | fclose(infile); |
565 | fclose(outfile); |
566 | } |
567 | |
568 | /* |
569 | * If we didn't process any files, complain because it probably means |
570 | * somebody neglected to pass the needed --inputdir argument. |
571 | */ |
572 | if (count <= 0) |
573 | { |
574 | fprintf(stderr, _("%s: no *.source files found in \"%s\"\n" ), |
575 | progname, indir); |
576 | exit(2); |
577 | } |
578 | |
579 | pgfnames_cleanup(names); |
580 | } |
581 | |
582 | /* Create the .sql and .out files from the .source files, if any */ |
583 | static void |
584 | convert_sourcefiles(void) |
585 | { |
586 | convert_sourcefiles_in("input" , outputdir, "sql" , "sql" ); |
587 | convert_sourcefiles_in("output" , outputdir, "expected" , "out" ); |
588 | } |
589 | |
590 | /* |
591 | * Scan resultmap file to find which platform-specific expected files to use. |
592 | * |
593 | * The format of each line of the file is |
594 | * testname/hostplatformpattern=substitutefile |
595 | * where the hostplatformpattern is evaluated per the rules of expr(1), |
596 | * namely, it is a standard regular expression with an implicit ^ at the start. |
597 | * (We currently support only a very limited subset of regular expressions, |
598 | * see string_matches_pattern() above.) What hostplatformpattern will be |
599 | * matched against is the config.guess output. (In the shell-script version, |
600 | * we also provided an indication of whether gcc or another compiler was in |
601 | * use, but that facility isn't used anymore.) |
602 | */ |
603 | static void |
604 | load_resultmap(void) |
605 | { |
606 | char buf[MAXPGPATH]; |
607 | FILE *f; |
608 | |
609 | /* scan the file ... */ |
610 | snprintf(buf, sizeof(buf), "%s/resultmap" , inputdir); |
611 | f = fopen(buf, "r" ); |
612 | if (!f) |
613 | { |
614 | /* OK if it doesn't exist, else complain */ |
615 | if (errno == ENOENT) |
616 | return; |
617 | fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n" ), |
618 | progname, buf, strerror(errno)); |
619 | exit(2); |
620 | } |
621 | |
622 | while (fgets(buf, sizeof(buf), f)) |
623 | { |
624 | char *platform; |
625 | char *file_type; |
626 | char *expected; |
627 | int i; |
628 | |
629 | /* strip trailing whitespace, especially the newline */ |
630 | i = strlen(buf); |
631 | while (i > 0 && isspace((unsigned char) buf[i - 1])) |
632 | buf[--i] = '\0'; |
633 | |
634 | /* parse out the line fields */ |
635 | file_type = strchr(buf, ':'); |
636 | if (!file_type) |
637 | { |
638 | fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n" ), |
639 | buf); |
640 | exit(2); |
641 | } |
642 | *file_type++ = '\0'; |
643 | |
644 | platform = strchr(file_type, ':'); |
645 | if (!platform) |
646 | { |
647 | fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n" ), |
648 | buf); |
649 | exit(2); |
650 | } |
651 | *platform++ = '\0'; |
652 | expected = strchr(platform, '='); |
653 | if (!expected) |
654 | { |
655 | fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n" ), |
656 | buf); |
657 | exit(2); |
658 | } |
659 | *expected++ = '\0'; |
660 | |
661 | /* |
662 | * if it's for current platform, save it in resultmap list. Note: by |
663 | * adding at the front of the list, we ensure that in ambiguous cases, |
664 | * the last match in the resultmap file is used. This mimics the |
665 | * behavior of the old shell script. |
666 | */ |
667 | if (string_matches_pattern(host_platform, platform)) |
668 | { |
669 | _resultmap *entry = pg_malloc(sizeof(_resultmap)); |
670 | |
671 | entry->test = pg_strdup(buf); |
672 | entry->type = pg_strdup(file_type); |
673 | entry->resultfile = pg_strdup(expected); |
674 | entry->next = resultmap; |
675 | resultmap = entry; |
676 | } |
677 | } |
678 | fclose(f); |
679 | } |
680 | |
681 | /* |
682 | * Check in resultmap if we should be looking at a different file |
683 | */ |
684 | static |
685 | const char * |
686 | get_expectfile(const char *testname, const char *file) |
687 | { |
688 | char *file_type; |
689 | _resultmap *rm; |
690 | |
691 | /* |
692 | * Determine the file type from the file name. This is just what is |
693 | * following the last dot in the file name. |
694 | */ |
695 | if (!file || !(file_type = strrchr(file, '.'))) |
696 | return NULL; |
697 | |
698 | file_type++; |
699 | |
700 | for (rm = resultmap; rm != NULL; rm = rm->next) |
701 | { |
702 | if (strcmp(testname, rm->test) == 0 && strcmp(file_type, rm->type) == 0) |
703 | { |
704 | return rm->resultfile; |
705 | } |
706 | } |
707 | |
708 | return NULL; |
709 | } |
710 | |
711 | /* |
712 | * Handy subroutine for setting an environment variable "var" to "val" |
713 | */ |
714 | static void |
715 | doputenv(const char *var, const char *val) |
716 | { |
717 | char *s; |
718 | |
719 | s = psprintf("%s=%s" , var, val); |
720 | putenv(s); |
721 | } |
722 | |
723 | /* |
724 | * Prepare environment variables for running regression tests |
725 | */ |
726 | static void |
727 | initialize_environment(void) |
728 | { |
729 | putenv("PGAPPNAME=pg_regress" ); |
730 | |
731 | if (nolocale) |
732 | { |
733 | /* |
734 | * Clear out any non-C locale settings |
735 | */ |
736 | unsetenv("LC_COLLATE" ); |
737 | unsetenv("LC_CTYPE" ); |
738 | unsetenv("LC_MONETARY" ); |
739 | unsetenv("LC_NUMERIC" ); |
740 | unsetenv("LC_TIME" ); |
741 | unsetenv("LANG" ); |
742 | |
743 | /* |
744 | * Most platforms have adopted the POSIX locale as their |
745 | * implementation-defined default locale. Exceptions include native |
746 | * Windows, macOS with --enable-nls, and Cygwin with --enable-nls. |
747 | * (Use of --enable-nls matters because libintl replaces setlocale().) |
748 | * Also, PostgreSQL does not support macOS with locale environment |
749 | * variables unset; see PostmasterMain(). |
750 | */ |
751 | #if defined(WIN32) || defined(__CYGWIN__) || defined(__darwin__) |
752 | putenv("LANG=C" ); |
753 | #endif |
754 | } |
755 | |
756 | /* |
757 | * Set translation-related settings to English; otherwise psql will |
758 | * produce translated messages and produce diffs. (XXX If we ever support |
759 | * translation of pg_regress, this needs to be moved elsewhere, where psql |
760 | * is actually called.) |
761 | */ |
762 | unsetenv("LANGUAGE" ); |
763 | unsetenv("LC_ALL" ); |
764 | putenv("LC_MESSAGES=C" ); |
765 | |
766 | /* |
767 | * Set encoding as requested |
768 | */ |
769 | if (encoding) |
770 | doputenv("PGCLIENTENCODING" , encoding); |
771 | else |
772 | unsetenv("PGCLIENTENCODING" ); |
773 | |
774 | /* |
775 | * Set timezone and datestyle for datetime-related tests |
776 | */ |
777 | putenv("PGTZ=PST8PDT" ); |
778 | putenv("PGDATESTYLE=Postgres, MDY" ); |
779 | |
780 | /* |
781 | * Likewise set intervalstyle to ensure consistent results. This is a bit |
782 | * more painful because we must use PGOPTIONS, and we want to preserve the |
783 | * user's ability to set other variables through that. |
784 | */ |
785 | { |
786 | const char *my_pgoptions = "-c intervalstyle=postgres_verbose" ; |
787 | const char *old_pgoptions = getenv("PGOPTIONS" ); |
788 | char *new_pgoptions; |
789 | |
790 | if (!old_pgoptions) |
791 | old_pgoptions = "" ; |
792 | new_pgoptions = psprintf("PGOPTIONS=%s %s" , |
793 | old_pgoptions, my_pgoptions); |
794 | putenv(new_pgoptions); |
795 | } |
796 | |
797 | if (temp_instance) |
798 | { |
799 | /* |
800 | * Clear out any environment vars that might cause psql to connect to |
801 | * the wrong postmaster, or otherwise behave in nondefault ways. (Note |
802 | * we also use psql's -X switch consistently, so that ~/.psqlrc files |
803 | * won't mess things up.) Also, set PGPORT to the temp port, and set |
804 | * PGHOST depending on whether we are using TCP or Unix sockets. |
805 | */ |
806 | unsetenv("PGDATABASE" ); |
807 | unsetenv("PGUSER" ); |
808 | unsetenv("PGSERVICE" ); |
809 | unsetenv("PGSSLMODE" ); |
810 | unsetenv("PGREQUIRESSL" ); |
811 | unsetenv("PGCONNECT_TIMEOUT" ); |
812 | unsetenv("PGDATA" ); |
813 | #ifdef HAVE_UNIX_SOCKETS |
814 | if (hostname != NULL) |
815 | doputenv("PGHOST" , hostname); |
816 | else |
817 | { |
818 | sockdir = getenv("PG_REGRESS_SOCK_DIR" ); |
819 | if (!sockdir) |
820 | sockdir = make_temp_sockdir(); |
821 | doputenv("PGHOST" , sockdir); |
822 | } |
823 | #else |
824 | Assert(hostname != NULL); |
825 | doputenv("PGHOST" , hostname); |
826 | #endif |
827 | unsetenv("PGHOSTADDR" ); |
828 | if (port != -1) |
829 | { |
830 | char s[16]; |
831 | |
832 | sprintf(s, "%d" , port); |
833 | doputenv("PGPORT" , s); |
834 | } |
835 | } |
836 | else |
837 | { |
838 | const char *pghost; |
839 | const char *pgport; |
840 | |
841 | /* |
842 | * When testing an existing install, we honor existing environment |
843 | * variables, except if they're overridden by command line options. |
844 | */ |
845 | if (hostname != NULL) |
846 | { |
847 | doputenv("PGHOST" , hostname); |
848 | unsetenv("PGHOSTADDR" ); |
849 | } |
850 | if (port != -1) |
851 | { |
852 | char s[16]; |
853 | |
854 | sprintf(s, "%d" , port); |
855 | doputenv("PGPORT" , s); |
856 | } |
857 | if (user != NULL) |
858 | doputenv("PGUSER" , user); |
859 | |
860 | /* |
861 | * Report what we're connecting to |
862 | */ |
863 | pghost = getenv("PGHOST" ); |
864 | pgport = getenv("PGPORT" ); |
865 | #ifndef HAVE_UNIX_SOCKETS |
866 | if (!pghost) |
867 | pghost = "localhost" ; |
868 | #endif |
869 | |
870 | if (pghost && pgport) |
871 | printf(_("(using postmaster on %s, port %s)\n" ), pghost, pgport); |
872 | if (pghost && !pgport) |
873 | printf(_("(using postmaster on %s, default port)\n" ), pghost); |
874 | if (!pghost && pgport) |
875 | printf(_("(using postmaster on Unix socket, port %s)\n" ), pgport); |
876 | if (!pghost && !pgport) |
877 | printf(_("(using postmaster on Unix socket, default port)\n" )); |
878 | } |
879 | |
880 | convert_sourcefiles(); |
881 | load_resultmap(); |
882 | } |
883 | |
884 | #ifdef ENABLE_SSPI |
885 | |
886 | /* support for config_sspi_auth() */ |
887 | static const char * |
888 | fmtHba(const char *raw) |
889 | { |
890 | static char *ret; |
891 | const char *rp; |
892 | char *wp; |
893 | |
894 | wp = ret = realloc(ret, 3 + strlen(raw) * 2); |
895 | |
896 | *wp++ = '"'; |
897 | for (rp = raw; *rp; rp++) |
898 | { |
899 | if (*rp == '"') |
900 | *wp++ = '"'; |
901 | *wp++ = *rp; |
902 | } |
903 | *wp++ = '"'; |
904 | *wp++ = '\0'; |
905 | |
906 | return ret; |
907 | } |
908 | |
909 | /* |
910 | * Get account and domain/realm names for the current user. This is based on |
911 | * pg_SSPI_recvauth(). The returned strings use static storage. |
912 | */ |
913 | static void |
914 | current_windows_user(const char **acct, const char **dom) |
915 | { |
916 | static char accountname[MAXPGPATH]; |
917 | static char domainname[MAXPGPATH]; |
918 | HANDLE token; |
919 | TOKEN_USER *tokenuser; |
920 | DWORD retlen; |
921 | DWORD accountnamesize = sizeof(accountname); |
922 | DWORD domainnamesize = sizeof(domainname); |
923 | SID_NAME_USE accountnameuse; |
924 | |
925 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token)) |
926 | { |
927 | fprintf(stderr, |
928 | _("%s: could not open process token: error code %lu\n" ), |
929 | progname, GetLastError()); |
930 | exit(2); |
931 | } |
932 | |
933 | if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122) |
934 | { |
935 | fprintf(stderr, |
936 | _("%s: could not get token information buffer size: error code %lu\n" ), |
937 | progname, GetLastError()); |
938 | exit(2); |
939 | } |
940 | tokenuser = pg_malloc(retlen); |
941 | if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen)) |
942 | { |
943 | fprintf(stderr, |
944 | _("%s: could not get token information: error code %lu\n" ), |
945 | progname, GetLastError()); |
946 | exit(2); |
947 | } |
948 | |
949 | if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize, |
950 | domainname, &domainnamesize, &accountnameuse)) |
951 | { |
952 | fprintf(stderr, |
953 | _("%s: could not look up account SID: error code %lu\n" ), |
954 | progname, GetLastError()); |
955 | exit(2); |
956 | } |
957 | |
958 | free(tokenuser); |
959 | |
960 | *acct = accountname; |
961 | *dom = domainname; |
962 | } |
963 | |
964 | /* |
965 | * Rewrite pg_hba.conf and pg_ident.conf to use SSPI authentication. Permit |
966 | * the current OS user to authenticate as the bootstrap superuser and as any |
967 | * user named in a --create-role option. |
968 | * |
969 | * In --config-auth mode, the --user switch can be used to specify the |
970 | * bootstrap superuser's name, otherwise we assume it is the default. |
971 | */ |
972 | static void |
973 | config_sspi_auth(const char *pgdata, const char *superuser_name) |
974 | { |
975 | const char *accountname, |
976 | *domainname; |
977 | char *errstr; |
978 | bool have_ipv6; |
979 | char fname[MAXPGPATH]; |
980 | int res; |
981 | FILE *hba, |
982 | *ident; |
983 | _stringlist *sl; |
984 | |
985 | /* Find out the name of the current OS user */ |
986 | current_windows_user(&accountname, &domainname); |
987 | |
988 | /* Determine the bootstrap superuser's name */ |
989 | if (superuser_name == NULL) |
990 | { |
991 | /* |
992 | * Compute the default superuser name the same way initdb does. |
993 | * |
994 | * It's possible that this result always matches "accountname", the |
995 | * value SSPI authentication discovers. But the underlying system |
996 | * functions do not clearly guarantee that. |
997 | */ |
998 | superuser_name = get_user_name(&errstr); |
999 | if (superuser_name == NULL) |
1000 | { |
1001 | fprintf(stderr, "%s: %s\n" , progname, errstr); |
1002 | exit(2); |
1003 | } |
1004 | } |
1005 | |
1006 | /* |
1007 | * Like initdb.c:setup_config(), determine whether the platform recognizes |
1008 | * ::1 (IPv6 loopback) as a numeric host address string. |
1009 | */ |
1010 | { |
1011 | struct addrinfo *gai_result; |
1012 | struct addrinfo hints; |
1013 | WSADATA wsaData; |
1014 | |
1015 | hints.ai_flags = AI_NUMERICHOST; |
1016 | hints.ai_family = AF_UNSPEC; |
1017 | hints.ai_socktype = 0; |
1018 | hints.ai_protocol = 0; |
1019 | hints.ai_addrlen = 0; |
1020 | hints.ai_canonname = NULL; |
1021 | hints.ai_addr = NULL; |
1022 | hints.ai_next = NULL; |
1023 | |
1024 | have_ipv6 = (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0 && |
1025 | getaddrinfo("::1" , NULL, &hints, &gai_result) == 0); |
1026 | } |
1027 | |
1028 | /* Check a Write outcome and report any error. */ |
1029 | #define CW(cond) \ |
1030 | do { \ |
1031 | if (!(cond)) \ |
1032 | { \ |
1033 | fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), \ |
1034 | progname, fname, strerror(errno)); \ |
1035 | exit(2); \ |
1036 | } \ |
1037 | } while (0) |
1038 | |
1039 | res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf" , pgdata); |
1040 | if (res < 0 || res >= sizeof(fname)) |
1041 | { |
1042 | /* |
1043 | * Truncating this name is a fatal error, because we must not fail to |
1044 | * overwrite an original trust-authentication pg_hba.conf. |
1045 | */ |
1046 | fprintf(stderr, _("%s: directory name too long\n" ), progname); |
1047 | exit(2); |
1048 | } |
1049 | hba = fopen(fname, "w" ); |
1050 | if (hba == NULL) |
1051 | { |
1052 | fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n" ), |
1053 | progname, fname, strerror(errno)); |
1054 | exit(2); |
1055 | } |
1056 | CW(fputs("# Configuration written by config_sspi_auth()\n" , hba) >= 0); |
1057 | CW(fputs("host all all 127.0.0.1/32 sspi include_realm=1 map=regress\n" , |
1058 | hba) >= 0); |
1059 | if (have_ipv6) |
1060 | CW(fputs("host all all ::1/128 sspi include_realm=1 map=regress\n" , |
1061 | hba) >= 0); |
1062 | CW(fclose(hba) == 0); |
1063 | |
1064 | snprintf(fname, sizeof(fname), "%s/pg_ident.conf" , pgdata); |
1065 | ident = fopen(fname, "w" ); |
1066 | if (ident == NULL) |
1067 | { |
1068 | fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n" ), |
1069 | progname, fname, strerror(errno)); |
1070 | exit(2); |
1071 | } |
1072 | CW(fputs("# Configuration written by config_sspi_auth()\n" , ident) >= 0); |
1073 | |
1074 | /* |
1075 | * Double-quote for the benefit of account names containing whitespace or |
1076 | * '#'. Windows forbids the double-quote character itself, so don't |
1077 | * bother escaping embedded double-quote characters. |
1078 | */ |
1079 | CW(fprintf(ident, "regress \"%s@%s\" %s\n" , |
1080 | accountname, domainname, fmtHba(superuser_name)) >= 0); |
1081 | for (sl = extraroles; sl; sl = sl->next) |
1082 | CW(fprintf(ident, "regress \"%s@%s\" %s\n" , |
1083 | accountname, domainname, fmtHba(sl->str)) >= 0); |
1084 | CW(fclose(ident) == 0); |
1085 | } |
1086 | |
1087 | #endif /* ENABLE_SSPI */ |
1088 | |
1089 | /* |
1090 | * Issue a command via psql, connecting to the specified database |
1091 | * |
1092 | * Since we use system(), this doesn't return until the operation finishes |
1093 | */ |
1094 | static void |
1095 | psql_command(const char *database, const char *query,...) |
1096 | { |
1097 | char query_formatted[1024]; |
1098 | char query_escaped[2048]; |
1099 | char psql_cmd[MAXPGPATH + 2048]; |
1100 | va_list args; |
1101 | char *s; |
1102 | char *d; |
1103 | |
1104 | /* Generate the query with insertion of sprintf arguments */ |
1105 | va_start(args, query); |
1106 | vsnprintf(query_formatted, sizeof(query_formatted), query, args); |
1107 | va_end(args); |
1108 | |
1109 | /* Now escape any shell double-quote metacharacters */ |
1110 | d = query_escaped; |
1111 | for (s = query_formatted; *s; s++) |
1112 | { |
1113 | if (strchr("\\\"$`" , *s)) |
1114 | *d++ = '\\'; |
1115 | *d++ = *s; |
1116 | } |
1117 | *d = '\0'; |
1118 | |
1119 | /* And now we can build and execute the shell command */ |
1120 | snprintf(psql_cmd, sizeof(psql_cmd), |
1121 | "\"%s%spsql\" -X -c \"%s\" \"%s\"" , |
1122 | bindir ? bindir : "" , |
1123 | bindir ? "/" : "" , |
1124 | query_escaped, |
1125 | database); |
1126 | |
1127 | if (system(psql_cmd) != 0) |
1128 | { |
1129 | /* psql probably already reported the error */ |
1130 | fprintf(stderr, _("command failed: %s\n" ), psql_cmd); |
1131 | exit(2); |
1132 | } |
1133 | } |
1134 | |
1135 | /* |
1136 | * Spawn a process to execute the given shell command; don't wait for it |
1137 | * |
1138 | * Returns the process ID (or HANDLE) so we can wait for it later |
1139 | */ |
1140 | PID_TYPE |
1141 | spawn_process(const char *cmdline) |
1142 | { |
1143 | #ifndef WIN32 |
1144 | pid_t pid; |
1145 | |
1146 | /* |
1147 | * Must flush I/O buffers before fork. Ideally we'd use fflush(NULL) here |
1148 | * ... does anyone still care about systems where that doesn't work? |
1149 | */ |
1150 | fflush(stdout); |
1151 | fflush(stderr); |
1152 | if (logfile) |
1153 | fflush(logfile); |
1154 | |
1155 | pid = fork(); |
1156 | if (pid == -1) |
1157 | { |
1158 | fprintf(stderr, _("%s: could not fork: %s\n" ), |
1159 | progname, strerror(errno)); |
1160 | exit(2); |
1161 | } |
1162 | if (pid == 0) |
1163 | { |
1164 | /* |
1165 | * In child |
1166 | * |
1167 | * Instead of using system(), exec the shell directly, and tell it to |
1168 | * "exec" the command too. This saves two useless processes per |
1169 | * parallel test case. |
1170 | */ |
1171 | char *cmdline2; |
1172 | |
1173 | cmdline2 = psprintf("exec %s" , cmdline); |
1174 | execl(shellprog, shellprog, "-c" , cmdline2, (char *) NULL); |
1175 | fprintf(stderr, _("%s: could not exec \"%s\": %s\n" ), |
1176 | progname, shellprog, strerror(errno)); |
1177 | _exit(1); /* not exit() here... */ |
1178 | } |
1179 | /* in parent */ |
1180 | return pid; |
1181 | #else |
1182 | PROCESS_INFORMATION pi; |
1183 | char *cmdline2; |
1184 | HANDLE restrictedToken; |
1185 | |
1186 | memset(&pi, 0, sizeof(pi)); |
1187 | cmdline2 = psprintf("cmd /c \"%s\"" , cmdline); |
1188 | |
1189 | if ((restrictedToken = |
1190 | CreateRestrictedProcess(cmdline2, &pi)) == 0) |
1191 | exit(2); |
1192 | |
1193 | CloseHandle(pi.hThread); |
1194 | return pi.hProcess; |
1195 | #endif |
1196 | } |
1197 | |
1198 | /* |
1199 | * Count bytes in file |
1200 | */ |
1201 | static long |
1202 | file_size(const char *file) |
1203 | { |
1204 | long r; |
1205 | FILE *f = fopen(file, "r" ); |
1206 | |
1207 | if (!f) |
1208 | { |
1209 | fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n" ), |
1210 | progname, file, strerror(errno)); |
1211 | return -1; |
1212 | } |
1213 | fseek(f, 0, SEEK_END); |
1214 | r = ftell(f); |
1215 | fclose(f); |
1216 | return r; |
1217 | } |
1218 | |
1219 | /* |
1220 | * Count lines in file |
1221 | */ |
1222 | static int |
1223 | file_line_count(const char *file) |
1224 | { |
1225 | int c; |
1226 | int l = 0; |
1227 | FILE *f = fopen(file, "r" ); |
1228 | |
1229 | if (!f) |
1230 | { |
1231 | fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n" ), |
1232 | progname, file, strerror(errno)); |
1233 | return -1; |
1234 | } |
1235 | while ((c = fgetc(f)) != EOF) |
1236 | { |
1237 | if (c == '\n') |
1238 | l++; |
1239 | } |
1240 | fclose(f); |
1241 | return l; |
1242 | } |
1243 | |
1244 | bool |
1245 | file_exists(const char *file) |
1246 | { |
1247 | FILE *f = fopen(file, "r" ); |
1248 | |
1249 | if (!f) |
1250 | return false; |
1251 | fclose(f); |
1252 | return true; |
1253 | } |
1254 | |
1255 | static bool |
1256 | directory_exists(const char *dir) |
1257 | { |
1258 | struct stat st; |
1259 | |
1260 | if (stat(dir, &st) != 0) |
1261 | return false; |
1262 | if (S_ISDIR(st.st_mode)) |
1263 | return true; |
1264 | return false; |
1265 | } |
1266 | |
1267 | /* Create a directory */ |
1268 | static void |
1269 | make_directory(const char *dir) |
1270 | { |
1271 | if (mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) < 0) |
1272 | { |
1273 | fprintf(stderr, _("%s: could not create directory \"%s\": %s\n" ), |
1274 | progname, dir, strerror(errno)); |
1275 | exit(2); |
1276 | } |
1277 | } |
1278 | |
1279 | /* |
1280 | * In: filename.ext, Return: filename_i.ext, where 0 < i <= 9 |
1281 | */ |
1282 | static char * |
1283 | get_alternative_expectfile(const char *expectfile, int i) |
1284 | { |
1285 | char *last_dot; |
1286 | int ssize = strlen(expectfile) + 2 + 1; |
1287 | char *tmp; |
1288 | char *s; |
1289 | |
1290 | if (!(tmp = (char *) malloc(ssize))) |
1291 | return NULL; |
1292 | |
1293 | if (!(s = (char *) malloc(ssize))) |
1294 | { |
1295 | free(tmp); |
1296 | return NULL; |
1297 | } |
1298 | |
1299 | strcpy(tmp, expectfile); |
1300 | last_dot = strrchr(tmp, '.'); |
1301 | if (!last_dot) |
1302 | { |
1303 | free(tmp); |
1304 | free(s); |
1305 | return NULL; |
1306 | } |
1307 | *last_dot = '\0'; |
1308 | snprintf(s, ssize, "%s_%d.%s" , tmp, i, last_dot + 1); |
1309 | free(tmp); |
1310 | return s; |
1311 | } |
1312 | |
1313 | /* |
1314 | * Run a "diff" command and also check that it didn't crash |
1315 | */ |
1316 | static int |
1317 | run_diff(const char *cmd, const char *filename) |
1318 | { |
1319 | int r; |
1320 | |
1321 | r = system(cmd); |
1322 | if (!WIFEXITED(r) || WEXITSTATUS(r) > 1) |
1323 | { |
1324 | fprintf(stderr, _("diff command failed with status %d: %s\n" ), r, cmd); |
1325 | exit(2); |
1326 | } |
1327 | #ifdef WIN32 |
1328 | |
1329 | /* |
1330 | * On WIN32, if the 'diff' command cannot be found, system() returns 1, |
1331 | * but produces nothing to stdout, so we check for that here. |
1332 | */ |
1333 | if (WEXITSTATUS(r) == 1 && file_size(filename) <= 0) |
1334 | { |
1335 | fprintf(stderr, _("diff command not found: %s\n" ), cmd); |
1336 | exit(2); |
1337 | } |
1338 | #endif |
1339 | |
1340 | return WEXITSTATUS(r); |
1341 | } |
1342 | |
1343 | /* |
1344 | * Check the actual result file for the given test against expected results |
1345 | * |
1346 | * Returns true if different (failure), false if correct match found. |
1347 | * In the true case, the diff is appended to the diffs file. |
1348 | */ |
1349 | static bool |
1350 | results_differ(const char *testname, const char *resultsfile, const char *default_expectfile) |
1351 | { |
1352 | char expectfile[MAXPGPATH]; |
1353 | char diff[MAXPGPATH]; |
1354 | char cmd[MAXPGPATH * 3]; |
1355 | char best_expect_file[MAXPGPATH]; |
1356 | FILE *difffile; |
1357 | int best_line_count; |
1358 | int i; |
1359 | int l; |
1360 | const char *platform_expectfile; |
1361 | |
1362 | /* |
1363 | * We can pass either the resultsfile or the expectfile, they should have |
1364 | * the same type (filename.type) anyway. |
1365 | */ |
1366 | platform_expectfile = get_expectfile(testname, resultsfile); |
1367 | |
1368 | strlcpy(expectfile, default_expectfile, sizeof(expectfile)); |
1369 | if (platform_expectfile) |
1370 | { |
1371 | /* |
1372 | * Replace everything after the last slash in expectfile with what the |
1373 | * platform_expectfile contains. |
1374 | */ |
1375 | char *p = strrchr(expectfile, '/'); |
1376 | |
1377 | if (p) |
1378 | strcpy(++p, platform_expectfile); |
1379 | } |
1380 | |
1381 | /* Name to use for temporary diff file */ |
1382 | snprintf(diff, sizeof(diff), "%s.diff" , resultsfile); |
1383 | |
1384 | /* OK, run the diff */ |
1385 | snprintf(cmd, sizeof(cmd), |
1386 | "diff %s \"%s\" \"%s\" > \"%s\"" , |
1387 | basic_diff_opts, expectfile, resultsfile, diff); |
1388 | |
1389 | /* Is the diff file empty? */ |
1390 | if (run_diff(cmd, diff) == 0) |
1391 | { |
1392 | unlink(diff); |
1393 | return false; |
1394 | } |
1395 | |
1396 | /* There may be secondary comparison files that match better */ |
1397 | best_line_count = file_line_count(diff); |
1398 | strcpy(best_expect_file, expectfile); |
1399 | |
1400 | for (i = 0; i <= 9; i++) |
1401 | { |
1402 | char *alt_expectfile; |
1403 | |
1404 | alt_expectfile = get_alternative_expectfile(expectfile, i); |
1405 | if (!alt_expectfile) |
1406 | { |
1407 | fprintf(stderr, _("Unable to check secondary comparison files: %s\n" ), |
1408 | strerror(errno)); |
1409 | exit(2); |
1410 | } |
1411 | |
1412 | if (!file_exists(alt_expectfile)) |
1413 | { |
1414 | free(alt_expectfile); |
1415 | continue; |
1416 | } |
1417 | |
1418 | snprintf(cmd, sizeof(cmd), |
1419 | "diff %s \"%s\" \"%s\" > \"%s\"" , |
1420 | basic_diff_opts, alt_expectfile, resultsfile, diff); |
1421 | |
1422 | if (run_diff(cmd, diff) == 0) |
1423 | { |
1424 | unlink(diff); |
1425 | free(alt_expectfile); |
1426 | return false; |
1427 | } |
1428 | |
1429 | l = file_line_count(diff); |
1430 | if (l < best_line_count) |
1431 | { |
1432 | /* This diff was a better match than the last one */ |
1433 | best_line_count = l; |
1434 | strlcpy(best_expect_file, alt_expectfile, sizeof(best_expect_file)); |
1435 | } |
1436 | free(alt_expectfile); |
1437 | } |
1438 | |
1439 | /* |
1440 | * fall back on the canonical results file if we haven't tried it yet and |
1441 | * haven't found a complete match yet. |
1442 | */ |
1443 | |
1444 | if (platform_expectfile) |
1445 | { |
1446 | snprintf(cmd, sizeof(cmd), |
1447 | "diff %s \"%s\" \"%s\" > \"%s\"" , |
1448 | basic_diff_opts, default_expectfile, resultsfile, diff); |
1449 | |
1450 | if (run_diff(cmd, diff) == 0) |
1451 | { |
1452 | /* No diff = no changes = good */ |
1453 | unlink(diff); |
1454 | return false; |
1455 | } |
1456 | |
1457 | l = file_line_count(diff); |
1458 | if (l < best_line_count) |
1459 | { |
1460 | /* This diff was a better match than the last one */ |
1461 | best_line_count = l; |
1462 | strlcpy(best_expect_file, default_expectfile, sizeof(best_expect_file)); |
1463 | } |
1464 | } |
1465 | |
1466 | /* |
1467 | * Use the best comparison file to generate the "pretty" diff, which we |
1468 | * append to the diffs summary file. |
1469 | */ |
1470 | |
1471 | /* Write diff header */ |
1472 | difffile = fopen(difffilename, "a" ); |
1473 | if (difffile) |
1474 | { |
1475 | fprintf(difffile, |
1476 | "diff %s %s %s\n" , |
1477 | pretty_diff_opts, best_expect_file, resultsfile); |
1478 | fclose(difffile); |
1479 | } |
1480 | |
1481 | /* Run diff */ |
1482 | snprintf(cmd, sizeof(cmd), |
1483 | "diff %s \"%s\" \"%s\" >> \"%s\"" , |
1484 | pretty_diff_opts, best_expect_file, resultsfile, difffilename); |
1485 | run_diff(cmd, difffilename); |
1486 | |
1487 | unlink(diff); |
1488 | return true; |
1489 | } |
1490 | |
1491 | /* |
1492 | * Wait for specified subprocesses to finish, and return their exit |
1493 | * statuses into statuses[] and stop times into stoptimes[] |
1494 | * |
1495 | * If names isn't NULL, print each subprocess's name as it finishes |
1496 | * |
1497 | * Note: it's OK to scribble on the pids array, but not on the names array |
1498 | */ |
1499 | static void |
1500 | wait_for_tests(PID_TYPE * pids, int *statuses, instr_time *stoptimes, |
1501 | char **names, int num_tests) |
1502 | { |
1503 | int tests_left; |
1504 | int i; |
1505 | |
1506 | #ifdef WIN32 |
1507 | PID_TYPE *active_pids = pg_malloc(num_tests * sizeof(PID_TYPE)); |
1508 | |
1509 | memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE)); |
1510 | #endif |
1511 | |
1512 | tests_left = num_tests; |
1513 | while (tests_left > 0) |
1514 | { |
1515 | PID_TYPE p; |
1516 | |
1517 | #ifndef WIN32 |
1518 | int exit_status; |
1519 | |
1520 | p = wait(&exit_status); |
1521 | |
1522 | if (p == INVALID_PID) |
1523 | { |
1524 | fprintf(stderr, _("failed to wait for subprocesses: %s\n" ), |
1525 | strerror(errno)); |
1526 | exit(2); |
1527 | } |
1528 | #else |
1529 | DWORD exit_status; |
1530 | int r; |
1531 | |
1532 | r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE); |
1533 | if (r < WAIT_OBJECT_0 || r >= WAIT_OBJECT_0 + tests_left) |
1534 | { |
1535 | fprintf(stderr, _("failed to wait for subprocesses: error code %lu\n" ), |
1536 | GetLastError()); |
1537 | exit(2); |
1538 | } |
1539 | p = active_pids[r - WAIT_OBJECT_0]; |
1540 | /* compact the active_pids array */ |
1541 | active_pids[r - WAIT_OBJECT_0] = active_pids[tests_left - 1]; |
1542 | #endif /* WIN32 */ |
1543 | |
1544 | for (i = 0; i < num_tests; i++) |
1545 | { |
1546 | if (p == pids[i]) |
1547 | { |
1548 | #ifdef WIN32 |
1549 | GetExitCodeProcess(pids[i], &exit_status); |
1550 | CloseHandle(pids[i]); |
1551 | #endif |
1552 | pids[i] = INVALID_PID; |
1553 | statuses[i] = (int) exit_status; |
1554 | INSTR_TIME_SET_CURRENT(stoptimes[i]); |
1555 | if (names) |
1556 | status(" %s" , names[i]); |
1557 | tests_left--; |
1558 | break; |
1559 | } |
1560 | } |
1561 | } |
1562 | |
1563 | #ifdef WIN32 |
1564 | free(active_pids); |
1565 | #endif |
1566 | } |
1567 | |
1568 | /* |
1569 | * report nonzero exit code from a test process |
1570 | */ |
1571 | static void |
1572 | log_child_failure(int exitstatus) |
1573 | { |
1574 | if (WIFEXITED(exitstatus)) |
1575 | status(_(" (test process exited with exit code %d)" ), |
1576 | WEXITSTATUS(exitstatus)); |
1577 | else if (WIFSIGNALED(exitstatus)) |
1578 | { |
1579 | #if defined(WIN32) |
1580 | status(_(" (test process was terminated by exception 0x%X)" ), |
1581 | WTERMSIG(exitstatus)); |
1582 | #else |
1583 | status(_(" (test process was terminated by signal %d: %s)" ), |
1584 | WTERMSIG(exitstatus), pg_strsignal(WTERMSIG(exitstatus))); |
1585 | #endif |
1586 | } |
1587 | else |
1588 | status(_(" (test process exited with unrecognized status %d)" ), |
1589 | exitstatus); |
1590 | } |
1591 | |
1592 | /* |
1593 | * Run all the tests specified in one schedule file |
1594 | */ |
1595 | static void |
1596 | run_schedule(const char *schedule, test_function tfunc) |
1597 | { |
1598 | #define MAX_PARALLEL_TESTS 100 |
1599 | char *tests[MAX_PARALLEL_TESTS]; |
1600 | _stringlist *resultfiles[MAX_PARALLEL_TESTS]; |
1601 | _stringlist *expectfiles[MAX_PARALLEL_TESTS]; |
1602 | _stringlist *tags[MAX_PARALLEL_TESTS]; |
1603 | PID_TYPE pids[MAX_PARALLEL_TESTS]; |
1604 | instr_time starttimes[MAX_PARALLEL_TESTS]; |
1605 | instr_time stoptimes[MAX_PARALLEL_TESTS]; |
1606 | int statuses[MAX_PARALLEL_TESTS]; |
1607 | _stringlist *ignorelist = NULL; |
1608 | char scbuf[1024]; |
1609 | FILE *scf; |
1610 | int line_num = 0; |
1611 | |
1612 | memset(tests, 0, sizeof(tests)); |
1613 | memset(resultfiles, 0, sizeof(resultfiles)); |
1614 | memset(expectfiles, 0, sizeof(expectfiles)); |
1615 | memset(tags, 0, sizeof(tags)); |
1616 | |
1617 | scf = fopen(schedule, "r" ); |
1618 | if (!scf) |
1619 | { |
1620 | fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n" ), |
1621 | progname, schedule, strerror(errno)); |
1622 | exit(2); |
1623 | } |
1624 | |
1625 | while (fgets(scbuf, sizeof(scbuf), scf)) |
1626 | { |
1627 | char *test = NULL; |
1628 | char *c; |
1629 | int num_tests; |
1630 | bool inword; |
1631 | int i; |
1632 | |
1633 | line_num++; |
1634 | |
1635 | /* strip trailing whitespace, especially the newline */ |
1636 | i = strlen(scbuf); |
1637 | while (i > 0 && isspace((unsigned char) scbuf[i - 1])) |
1638 | scbuf[--i] = '\0'; |
1639 | |
1640 | if (scbuf[0] == '\0' || scbuf[0] == '#') |
1641 | continue; |
1642 | if (strncmp(scbuf, "test: " , 6) == 0) |
1643 | test = scbuf + 6; |
1644 | else if (strncmp(scbuf, "ignore: " , 8) == 0) |
1645 | { |
1646 | c = scbuf + 8; |
1647 | while (*c && isspace((unsigned char) *c)) |
1648 | c++; |
1649 | add_stringlist_item(&ignorelist, c); |
1650 | |
1651 | /* |
1652 | * Note: ignore: lines do not run the test, they just say that |
1653 | * failure of this test when run later on is to be ignored. A bit |
1654 | * odd but that's how the shell-script version did it. |
1655 | */ |
1656 | continue; |
1657 | } |
1658 | else |
1659 | { |
1660 | fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n" ), |
1661 | schedule, line_num, scbuf); |
1662 | exit(2); |
1663 | } |
1664 | |
1665 | num_tests = 0; |
1666 | inword = false; |
1667 | for (c = test;; c++) |
1668 | { |
1669 | if (*c == '\0' || isspace((unsigned char) *c)) |
1670 | { |
1671 | if (inword) |
1672 | { |
1673 | /* Reached end of a test name */ |
1674 | char sav; |
1675 | |
1676 | if (num_tests >= MAX_PARALLEL_TESTS) |
1677 | { |
1678 | fprintf(stderr, _("too many parallel tests (more than %d) in schedule file \"%s\" line %d: %s\n" ), |
1679 | MAX_PARALLEL_TESTS, schedule, line_num, scbuf); |
1680 | exit(2); |
1681 | } |
1682 | sav = *c; |
1683 | *c = '\0'; |
1684 | tests[num_tests] = pg_strdup(test); |
1685 | num_tests++; |
1686 | *c = sav; |
1687 | inword = false; |
1688 | } |
1689 | if (*c == '\0') |
1690 | break; /* loop exit is here */ |
1691 | } |
1692 | else if (!inword) |
1693 | { |
1694 | /* Start of a test name */ |
1695 | test = c; |
1696 | inword = true; |
1697 | } |
1698 | } |
1699 | |
1700 | if (num_tests == 0) |
1701 | { |
1702 | fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n" ), |
1703 | schedule, line_num, scbuf); |
1704 | exit(2); |
1705 | } |
1706 | |
1707 | if (num_tests == 1) |
1708 | { |
1709 | status(_("test %-28s ... " ), tests[0]); |
1710 | pids[0] = (tfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]); |
1711 | INSTR_TIME_SET_CURRENT(starttimes[0]); |
1712 | wait_for_tests(pids, statuses, stoptimes, NULL, 1); |
1713 | /* status line is finished below */ |
1714 | } |
1715 | else if (max_concurrent_tests > 0 && max_concurrent_tests < num_tests) |
1716 | { |
1717 | fprintf(stderr, _("too many parallel tests (more than %d) in schedule file \"%s\" line %d: %s\n" ), |
1718 | max_concurrent_tests, schedule, line_num, scbuf); |
1719 | exit(2); |
1720 | } |
1721 | else if (max_connections > 0 && max_connections < num_tests) |
1722 | { |
1723 | int oldest = 0; |
1724 | |
1725 | status(_("parallel group (%d tests, in groups of %d): " ), |
1726 | num_tests, max_connections); |
1727 | for (i = 0; i < num_tests; i++) |
1728 | { |
1729 | if (i - oldest >= max_connections) |
1730 | { |
1731 | wait_for_tests(pids + oldest, statuses + oldest, |
1732 | stoptimes + oldest, |
1733 | tests + oldest, i - oldest); |
1734 | oldest = i; |
1735 | } |
1736 | pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]); |
1737 | INSTR_TIME_SET_CURRENT(starttimes[i]); |
1738 | } |
1739 | wait_for_tests(pids + oldest, statuses + oldest, |
1740 | stoptimes + oldest, |
1741 | tests + oldest, i - oldest); |
1742 | status_end(); |
1743 | } |
1744 | else |
1745 | { |
1746 | status(_("parallel group (%d tests): " ), num_tests); |
1747 | for (i = 0; i < num_tests; i++) |
1748 | { |
1749 | pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]); |
1750 | INSTR_TIME_SET_CURRENT(starttimes[i]); |
1751 | } |
1752 | wait_for_tests(pids, statuses, stoptimes, tests, num_tests); |
1753 | status_end(); |
1754 | } |
1755 | |
1756 | /* Check results for all tests */ |
1757 | for (i = 0; i < num_tests; i++) |
1758 | { |
1759 | _stringlist *rl, |
1760 | *el, |
1761 | *tl; |
1762 | bool differ = false; |
1763 | |
1764 | if (num_tests > 1) |
1765 | status(_(" %-28s ... " ), tests[i]); |
1766 | |
1767 | /* |
1768 | * Advance over all three lists simultaneously. |
1769 | * |
1770 | * Compare resultfiles[j] with expectfiles[j] always. Tags are |
1771 | * optional but if there are tags, the tag list has the same |
1772 | * length as the other two lists. |
1773 | */ |
1774 | for (rl = resultfiles[i], el = expectfiles[i], tl = tags[i]; |
1775 | rl != NULL; /* rl and el have the same length */ |
1776 | rl = rl->next, el = el->next, |
1777 | tl = tl ? tl->next : NULL) |
1778 | { |
1779 | bool newdiff; |
1780 | |
1781 | newdiff = results_differ(tests[i], rl->str, el->str); |
1782 | if (newdiff && tl) |
1783 | { |
1784 | printf("%s " , tl->str); |
1785 | } |
1786 | differ |= newdiff; |
1787 | } |
1788 | |
1789 | if (differ) |
1790 | { |
1791 | bool ignore = false; |
1792 | _stringlist *sl; |
1793 | |
1794 | for (sl = ignorelist; sl != NULL; sl = sl->next) |
1795 | { |
1796 | if (strcmp(tests[i], sl->str) == 0) |
1797 | { |
1798 | ignore = true; |
1799 | break; |
1800 | } |
1801 | } |
1802 | if (ignore) |
1803 | { |
1804 | status(_("failed (ignored)" )); |
1805 | fail_ignore_count++; |
1806 | } |
1807 | else |
1808 | { |
1809 | status(_("FAILED" )); |
1810 | fail_count++; |
1811 | } |
1812 | } |
1813 | else |
1814 | { |
1815 | status(_("ok " )); /* align with FAILED */ |
1816 | success_count++; |
1817 | } |
1818 | |
1819 | if (statuses[i] != 0) |
1820 | log_child_failure(statuses[i]); |
1821 | |
1822 | INSTR_TIME_SUBTRACT(stoptimes[i], starttimes[i]); |
1823 | status(_(" %8.0f ms" ), INSTR_TIME_GET_MILLISEC(stoptimes[i])); |
1824 | |
1825 | status_end(); |
1826 | } |
1827 | |
1828 | for (i = 0; i < num_tests; i++) |
1829 | { |
1830 | pg_free(tests[i]); |
1831 | tests[i] = NULL; |
1832 | free_stringlist(&resultfiles[i]); |
1833 | free_stringlist(&expectfiles[i]); |
1834 | free_stringlist(&tags[i]); |
1835 | } |
1836 | } |
1837 | |
1838 | free_stringlist(&ignorelist); |
1839 | |
1840 | fclose(scf); |
1841 | } |
1842 | |
1843 | /* |
1844 | * Run a single test |
1845 | */ |
1846 | static void |
1847 | run_single_test(const char *test, test_function tfunc) |
1848 | { |
1849 | PID_TYPE pid; |
1850 | instr_time starttime; |
1851 | instr_time stoptime; |
1852 | int exit_status; |
1853 | _stringlist *resultfiles = NULL; |
1854 | _stringlist *expectfiles = NULL; |
1855 | _stringlist *tags = NULL; |
1856 | _stringlist *rl, |
1857 | *el, |
1858 | *tl; |
1859 | bool differ = false; |
1860 | |
1861 | status(_("test %-28s ... " ), test); |
1862 | pid = (tfunc) (test, &resultfiles, &expectfiles, &tags); |
1863 | INSTR_TIME_SET_CURRENT(starttime); |
1864 | wait_for_tests(&pid, &exit_status, &stoptime, NULL, 1); |
1865 | |
1866 | /* |
1867 | * Advance over all three lists simultaneously. |
1868 | * |
1869 | * Compare resultfiles[j] with expectfiles[j] always. Tags are optional |
1870 | * but if there are tags, the tag list has the same length as the other |
1871 | * two lists. |
1872 | */ |
1873 | for (rl = resultfiles, el = expectfiles, tl = tags; |
1874 | rl != NULL; /* rl and el have the same length */ |
1875 | rl = rl->next, el = el->next, |
1876 | tl = tl ? tl->next : NULL) |
1877 | { |
1878 | bool newdiff; |
1879 | |
1880 | newdiff = results_differ(test, rl->str, el->str); |
1881 | if (newdiff && tl) |
1882 | { |
1883 | printf("%s " , tl->str); |
1884 | } |
1885 | differ |= newdiff; |
1886 | } |
1887 | |
1888 | if (differ) |
1889 | { |
1890 | status(_("FAILED" )); |
1891 | fail_count++; |
1892 | } |
1893 | else |
1894 | { |
1895 | status(_("ok " )); /* align with FAILED */ |
1896 | success_count++; |
1897 | } |
1898 | |
1899 | if (exit_status != 0) |
1900 | log_child_failure(exit_status); |
1901 | |
1902 | INSTR_TIME_SUBTRACT(stoptime, starttime); |
1903 | status(_(" %8.0f ms" ), INSTR_TIME_GET_MILLISEC(stoptime)); |
1904 | |
1905 | status_end(); |
1906 | } |
1907 | |
1908 | /* |
1909 | * Create the summary-output files (making them empty if already existing) |
1910 | */ |
1911 | static void |
1912 | open_result_files(void) |
1913 | { |
1914 | char file[MAXPGPATH]; |
1915 | FILE *difffile; |
1916 | |
1917 | /* create outputdir directory if not present */ |
1918 | if (!directory_exists(outputdir)) |
1919 | make_directory(outputdir); |
1920 | |
1921 | /* create the log file (copy of running status output) */ |
1922 | snprintf(file, sizeof(file), "%s/regression.out" , outputdir); |
1923 | logfilename = pg_strdup(file); |
1924 | logfile = fopen(logfilename, "w" ); |
1925 | if (!logfile) |
1926 | { |
1927 | fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n" ), |
1928 | progname, logfilename, strerror(errno)); |
1929 | exit(2); |
1930 | } |
1931 | |
1932 | /* create the diffs file as empty */ |
1933 | snprintf(file, sizeof(file), "%s/regression.diffs" , outputdir); |
1934 | difffilename = pg_strdup(file); |
1935 | difffile = fopen(difffilename, "w" ); |
1936 | if (!difffile) |
1937 | { |
1938 | fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n" ), |
1939 | progname, difffilename, strerror(errno)); |
1940 | exit(2); |
1941 | } |
1942 | /* we don't keep the diffs file open continuously */ |
1943 | fclose(difffile); |
1944 | |
1945 | /* also create the results directory if not present */ |
1946 | snprintf(file, sizeof(file), "%s/results" , outputdir); |
1947 | if (!directory_exists(file)) |
1948 | make_directory(file); |
1949 | } |
1950 | |
1951 | static void |
1952 | drop_database_if_exists(const char *dbname) |
1953 | { |
1954 | header(_("dropping database \"%s\"" ), dbname); |
1955 | psql_command("postgres" , "DROP DATABASE IF EXISTS \"%s\"" , dbname); |
1956 | } |
1957 | |
1958 | static void |
1959 | create_database(const char *dbname) |
1960 | { |
1961 | _stringlist *sl; |
1962 | |
1963 | /* |
1964 | * We use template0 so that any installation-local cruft in template1 will |
1965 | * not mess up the tests. |
1966 | */ |
1967 | header(_("creating database \"%s\"" ), dbname); |
1968 | if (encoding) |
1969 | psql_command("postgres" , "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'%s" , dbname, encoding, |
1970 | (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "" ); |
1971 | else |
1972 | psql_command("postgres" , "CREATE DATABASE \"%s\" TEMPLATE=template0%s" , dbname, |
1973 | (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "" ); |
1974 | psql_command(dbname, |
1975 | "ALTER DATABASE \"%s\" SET lc_messages TO 'C';" |
1976 | "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';" |
1977 | "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';" |
1978 | "ALTER DATABASE \"%s\" SET lc_time TO 'C';" |
1979 | "ALTER DATABASE \"%s\" SET bytea_output TO 'hex';" |
1980 | "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';" , |
1981 | dbname, dbname, dbname, dbname, dbname, dbname); |
1982 | |
1983 | /* |
1984 | * Install any requested procedural languages. We use CREATE OR REPLACE |
1985 | * so that this will work whether or not the language is preinstalled. |
1986 | */ |
1987 | for (sl = loadlanguage; sl != NULL; sl = sl->next) |
1988 | { |
1989 | header(_("installing %s" ), sl->str); |
1990 | psql_command(dbname, "CREATE OR REPLACE LANGUAGE \"%s\"" , sl->str); |
1991 | } |
1992 | |
1993 | /* |
1994 | * Install any requested extensions. We use CREATE IF NOT EXISTS so that |
1995 | * this will work whether or not the extension is preinstalled. |
1996 | */ |
1997 | for (sl = loadextension; sl != NULL; sl = sl->next) |
1998 | { |
1999 | header(_("installing %s" ), sl->str); |
2000 | psql_command(dbname, "CREATE EXTENSION IF NOT EXISTS \"%s\"" , sl->str); |
2001 | } |
2002 | } |
2003 | |
2004 | static void |
2005 | drop_role_if_exists(const char *rolename) |
2006 | { |
2007 | header(_("dropping role \"%s\"" ), rolename); |
2008 | psql_command("postgres" , "DROP ROLE IF EXISTS \"%s\"" , rolename); |
2009 | } |
2010 | |
2011 | static void |
2012 | create_role(const char *rolename, const _stringlist *granted_dbs) |
2013 | { |
2014 | header(_("creating role \"%s\"" ), rolename); |
2015 | psql_command("postgres" , "CREATE ROLE \"%s\" WITH LOGIN" , rolename); |
2016 | for (; granted_dbs != NULL; granted_dbs = granted_dbs->next) |
2017 | { |
2018 | psql_command("postgres" , "GRANT ALL ON DATABASE \"%s\" TO \"%s\"" , |
2019 | granted_dbs->str, rolename); |
2020 | } |
2021 | } |
2022 | |
2023 | static void |
2024 | help(void) |
2025 | { |
2026 | printf(_("PostgreSQL regression test driver\n" )); |
2027 | printf(_("\n" )); |
2028 | printf(_("Usage:\n %s [OPTION]... [EXTRA-TEST]...\n" ), progname); |
2029 | printf(_("\n" )); |
2030 | printf(_("Options:\n" )); |
2031 | printf(_(" --bindir=BINPATH use BINPATH for programs that are run;\n" )); |
2032 | printf(_(" if empty, use PATH from the environment\n" )); |
2033 | printf(_(" --config-auth=DATADIR update authentication settings for DATADIR\n" )); |
2034 | printf(_(" --create-role=ROLE create the specified role before testing\n" )); |
2035 | printf(_(" --dbname=DB use database DB (default \"regression\")\n" )); |
2036 | printf(_(" --debug turn on debug mode in programs that are run\n" )); |
2037 | printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n" )); |
2038 | printf(_(" --encoding=ENCODING use ENCODING as the encoding\n" )); |
2039 | printf(_(" -h, --help show this help, then exit\n" )); |
2040 | printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n" )); |
2041 | printf(_(" --launcher=CMD use CMD as launcher of psql\n" )); |
2042 | printf(_(" --load-extension=EXT load the named extension before running the\n" )); |
2043 | printf(_(" tests; can appear multiple times\n" )); |
2044 | printf(_(" --load-language=LANG load the named language before running the\n" )); |
2045 | printf(_(" tests; can appear multiple times\n" )); |
2046 | printf(_(" --max-connections=N maximum number of concurrent connections\n" )); |
2047 | printf(_(" (default is 0, meaning unlimited)\n" )); |
2048 | printf(_(" --max-concurrent-tests=N maximum number of concurrent tests in schedule\n" )); |
2049 | printf(_(" (default is 0, meaning unlimited)\n" )); |
2050 | printf(_(" --outputdir=DIR place output files in DIR (default \".\")\n" )); |
2051 | printf(_(" --schedule=FILE use test ordering schedule from FILE\n" )); |
2052 | printf(_(" (can be used multiple times to concatenate)\n" )); |
2053 | printf(_(" --temp-instance=DIR create a temporary instance in DIR\n" )); |
2054 | printf(_(" --use-existing use an existing installation\n" )); |
2055 | printf(_(" -V, --version output version information, then exit\n" )); |
2056 | printf(_("\n" )); |
2057 | printf(_("Options for \"temp-instance\" mode:\n" )); |
2058 | printf(_(" --no-locale use C locale\n" )); |
2059 | printf(_(" --port=PORT start postmaster on PORT\n" )); |
2060 | printf(_(" --temp-config=FILE append contents of FILE to temporary config\n" )); |
2061 | printf(_("\n" )); |
2062 | printf(_("Options for using an existing installation:\n" )); |
2063 | printf(_(" --host=HOST use postmaster running on HOST\n" )); |
2064 | printf(_(" --port=PORT use postmaster running at PORT\n" )); |
2065 | printf(_(" --user=USER connect as USER\n" )); |
2066 | printf(_("\n" )); |
2067 | printf(_("The exit status is 0 if all tests passed, 1 if some tests failed, and 2\n" )); |
2068 | printf(_("if the tests could not be run for some reason.\n" )); |
2069 | printf(_("\n" )); |
2070 | printf(_("Report bugs to <pgsql-bugs@lists.postgresql.org>.\n" )); |
2071 | } |
2072 | |
2073 | int |
2074 | regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc) |
2075 | { |
2076 | static struct option long_options[] = { |
2077 | {"help" , no_argument, NULL, 'h'}, |
2078 | {"version" , no_argument, NULL, 'V'}, |
2079 | {"dbname" , required_argument, NULL, 1}, |
2080 | {"debug" , no_argument, NULL, 2}, |
2081 | {"inputdir" , required_argument, NULL, 3}, |
2082 | {"load-language" , required_argument, NULL, 4}, |
2083 | {"max-connections" , required_argument, NULL, 5}, |
2084 | {"encoding" , required_argument, NULL, 6}, |
2085 | {"outputdir" , required_argument, NULL, 7}, |
2086 | {"schedule" , required_argument, NULL, 8}, |
2087 | {"temp-instance" , required_argument, NULL, 9}, |
2088 | {"no-locale" , no_argument, NULL, 10}, |
2089 | {"host" , required_argument, NULL, 13}, |
2090 | {"port" , required_argument, NULL, 14}, |
2091 | {"user" , required_argument, NULL, 15}, |
2092 | {"bindir" , required_argument, NULL, 16}, |
2093 | {"dlpath" , required_argument, NULL, 17}, |
2094 | {"create-role" , required_argument, NULL, 18}, |
2095 | {"temp-config" , required_argument, NULL, 19}, |
2096 | {"use-existing" , no_argument, NULL, 20}, |
2097 | {"launcher" , required_argument, NULL, 21}, |
2098 | {"load-extension" , required_argument, NULL, 22}, |
2099 | {"config-auth" , required_argument, NULL, 24}, |
2100 | {"max-concurrent-tests" , required_argument, NULL, 25}, |
2101 | {NULL, 0, NULL, 0} |
2102 | }; |
2103 | |
2104 | _stringlist *sl; |
2105 | int c; |
2106 | int i; |
2107 | int option_index; |
2108 | char buf[MAXPGPATH * 4]; |
2109 | char buf2[MAXPGPATH * 4]; |
2110 | |
2111 | pg_logging_init(argv[0]); |
2112 | progname = get_progname(argv[0]); |
2113 | set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress" )); |
2114 | |
2115 | get_restricted_token(); |
2116 | |
2117 | atexit(stop_postmaster); |
2118 | |
2119 | #ifndef HAVE_UNIX_SOCKETS |
2120 | /* no unix domain sockets available, so change default */ |
2121 | hostname = "localhost" ; |
2122 | #endif |
2123 | |
2124 | /* |
2125 | * We call the initialization function here because that way we can set |
2126 | * default parameters and let them be overwritten by the commandline. |
2127 | */ |
2128 | ifunc(argc, argv); |
2129 | |
2130 | if (getenv("PG_REGRESS_DIFF_OPTS" )) |
2131 | pretty_diff_opts = getenv("PG_REGRESS_DIFF_OPTS" ); |
2132 | |
2133 | while ((c = getopt_long(argc, argv, "hV" , long_options, &option_index)) != -1) |
2134 | { |
2135 | switch (c) |
2136 | { |
2137 | case 'h': |
2138 | help(); |
2139 | exit(0); |
2140 | case 'V': |
2141 | puts("pg_regress (PostgreSQL) " PG_VERSION); |
2142 | exit(0); |
2143 | case 1: |
2144 | |
2145 | /* |
2146 | * If a default database was specified, we need to remove it |
2147 | * before we add the specified one. |
2148 | */ |
2149 | free_stringlist(&dblist); |
2150 | split_to_stringlist(optarg, "," , &dblist); |
2151 | break; |
2152 | case 2: |
2153 | debug = true; |
2154 | break; |
2155 | case 3: |
2156 | inputdir = pg_strdup(optarg); |
2157 | break; |
2158 | case 4: |
2159 | add_stringlist_item(&loadlanguage, optarg); |
2160 | break; |
2161 | case 5: |
2162 | max_connections = atoi(optarg); |
2163 | break; |
2164 | case 6: |
2165 | encoding = pg_strdup(optarg); |
2166 | break; |
2167 | case 7: |
2168 | outputdir = pg_strdup(optarg); |
2169 | break; |
2170 | case 8: |
2171 | add_stringlist_item(&schedulelist, optarg); |
2172 | break; |
2173 | case 9: |
2174 | temp_instance = make_absolute_path(optarg); |
2175 | break; |
2176 | case 10: |
2177 | nolocale = true; |
2178 | break; |
2179 | case 13: |
2180 | hostname = pg_strdup(optarg); |
2181 | break; |
2182 | case 14: |
2183 | port = atoi(optarg); |
2184 | port_specified_by_user = true; |
2185 | break; |
2186 | case 15: |
2187 | user = pg_strdup(optarg); |
2188 | break; |
2189 | case 16: |
2190 | /* "--bindir=" means to use PATH */ |
2191 | if (strlen(optarg)) |
2192 | bindir = pg_strdup(optarg); |
2193 | else |
2194 | bindir = NULL; |
2195 | break; |
2196 | case 17: |
2197 | dlpath = pg_strdup(optarg); |
2198 | break; |
2199 | case 18: |
2200 | split_to_stringlist(optarg, "," , &extraroles); |
2201 | break; |
2202 | case 19: |
2203 | add_stringlist_item(&temp_configs, optarg); |
2204 | break; |
2205 | case 20: |
2206 | use_existing = true; |
2207 | break; |
2208 | case 21: |
2209 | launcher = pg_strdup(optarg); |
2210 | break; |
2211 | case 22: |
2212 | add_stringlist_item(&loadextension, optarg); |
2213 | break; |
2214 | case 24: |
2215 | config_auth_datadir = pg_strdup(optarg); |
2216 | break; |
2217 | case 25: |
2218 | max_concurrent_tests = atoi(optarg); |
2219 | break; |
2220 | default: |
2221 | /* getopt_long already emitted a complaint */ |
2222 | fprintf(stderr, _("\nTry \"%s -h\" for more information.\n" ), |
2223 | progname); |
2224 | exit(2); |
2225 | } |
2226 | } |
2227 | |
2228 | /* |
2229 | * if we still have arguments, they are extra tests to run |
2230 | */ |
2231 | while (argc - optind >= 1) |
2232 | { |
2233 | add_stringlist_item(&extra_tests, argv[optind]); |
2234 | optind++; |
2235 | } |
2236 | |
2237 | if (config_auth_datadir) |
2238 | { |
2239 | #ifdef ENABLE_SSPI |
2240 | config_sspi_auth(config_auth_datadir, user); |
2241 | #endif |
2242 | exit(0); |
2243 | } |
2244 | |
2245 | if (temp_instance && !port_specified_by_user) |
2246 | |
2247 | /* |
2248 | * To reduce chances of interference with parallel installations, use |
2249 | * a port number starting in the private range (49152-65535) |
2250 | * calculated from the version number. This aids !HAVE_UNIX_SOCKETS |
2251 | * systems; elsewhere, the use of a private socket directory already |
2252 | * prevents interference. |
2253 | */ |
2254 | port = 0xC000 | (PG_VERSION_NUM & 0x3FFF); |
2255 | |
2256 | inputdir = make_absolute_path(inputdir); |
2257 | outputdir = make_absolute_path(outputdir); |
2258 | dlpath = make_absolute_path(dlpath); |
2259 | |
2260 | /* |
2261 | * Initialization |
2262 | */ |
2263 | open_result_files(); |
2264 | |
2265 | initialize_environment(); |
2266 | |
2267 | #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE) |
2268 | unlimit_core_size(); |
2269 | #endif |
2270 | |
2271 | if (temp_instance) |
2272 | { |
2273 | FILE *pg_conf; |
2274 | const char *env_wait; |
2275 | int wait_seconds; |
2276 | |
2277 | /* |
2278 | * Prepare the temp instance |
2279 | */ |
2280 | |
2281 | if (directory_exists(temp_instance)) |
2282 | { |
2283 | header(_("removing existing temp instance" )); |
2284 | if (!rmtree(temp_instance, true)) |
2285 | { |
2286 | fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n" ), |
2287 | progname, temp_instance); |
2288 | exit(2); |
2289 | } |
2290 | } |
2291 | |
2292 | header(_("creating temporary instance" )); |
2293 | |
2294 | /* make the temp instance top directory */ |
2295 | make_directory(temp_instance); |
2296 | |
2297 | /* and a directory for log files */ |
2298 | snprintf(buf, sizeof(buf), "%s/log" , outputdir); |
2299 | if (!directory_exists(buf)) |
2300 | make_directory(buf); |
2301 | |
2302 | /* initdb */ |
2303 | header(_("initializing database system" )); |
2304 | snprintf(buf, sizeof(buf), |
2305 | "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1" , |
2306 | bindir ? bindir : "" , |
2307 | bindir ? "/" : "" , |
2308 | temp_instance, |
2309 | debug ? " --debug" : "" , |
2310 | nolocale ? " --no-locale" : "" , |
2311 | outputdir); |
2312 | if (system(buf)) |
2313 | { |
2314 | fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n" ), progname, outputdir, buf); |
2315 | exit(2); |
2316 | } |
2317 | |
2318 | /* |
2319 | * Adjust the default postgresql.conf for regression testing. The user |
2320 | * can specify a file to be appended; in any case we expand logging |
2321 | * and set max_prepared_transactions to enable testing of prepared |
2322 | * xacts. (Note: to reduce the probability of unexpected shmmax |
2323 | * failures, don't set max_prepared_transactions any higher than |
2324 | * actually needed by the prepared_xacts regression test.) |
2325 | */ |
2326 | snprintf(buf, sizeof(buf), "%s/data/postgresql.conf" , temp_instance); |
2327 | pg_conf = fopen(buf, "a" ); |
2328 | if (pg_conf == NULL) |
2329 | { |
2330 | fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n" ), progname, buf, strerror(errno)); |
2331 | exit(2); |
2332 | } |
2333 | fputs("\n# Configuration added by pg_regress\n\n" , pg_conf); |
2334 | fputs("log_autovacuum_min_duration = 0\n" , pg_conf); |
2335 | fputs("log_checkpoints = on\n" , pg_conf); |
2336 | fputs("log_line_prefix = '%m [%p] %q%a '\n" , pg_conf); |
2337 | fputs("log_lock_waits = on\n" , pg_conf); |
2338 | fputs("log_temp_files = 128kB\n" , pg_conf); |
2339 | fputs("max_prepared_transactions = 2\n" , pg_conf); |
2340 | |
2341 | for (sl = temp_configs; sl != NULL; sl = sl->next) |
2342 | { |
2343 | char *temp_config = sl->str; |
2344 | FILE *; |
2345 | char line_buf[1024]; |
2346 | |
2347 | extra_conf = fopen(temp_config, "r" ); |
2348 | if (extra_conf == NULL) |
2349 | { |
2350 | fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n" ), progname, temp_config, strerror(errno)); |
2351 | exit(2); |
2352 | } |
2353 | while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL) |
2354 | fputs(line_buf, pg_conf); |
2355 | fclose(extra_conf); |
2356 | } |
2357 | |
2358 | fclose(pg_conf); |
2359 | |
2360 | #ifdef ENABLE_SSPI |
2361 | |
2362 | /* |
2363 | * Since we successfully used the same buffer for the much-longer |
2364 | * "initdb" command, this can't truncate. |
2365 | */ |
2366 | snprintf(buf, sizeof(buf), "%s/data" , temp_instance); |
2367 | config_sspi_auth(buf, NULL); |
2368 | #elif !defined(HAVE_UNIX_SOCKETS) |
2369 | #error Platform has no means to secure the test installation. |
2370 | #endif |
2371 | |
2372 | /* |
2373 | * Check if there is a postmaster running already. |
2374 | */ |
2375 | snprintf(buf2, sizeof(buf2), |
2376 | "\"%s%spsql\" -X postgres <%s 2>%s" , |
2377 | bindir ? bindir : "" , |
2378 | bindir ? "/" : "" , |
2379 | DEVNULL, DEVNULL); |
2380 | |
2381 | for (i = 0; i < 16; i++) |
2382 | { |
2383 | if (system(buf2) == 0) |
2384 | { |
2385 | char s[16]; |
2386 | |
2387 | if (port_specified_by_user || i == 15) |
2388 | { |
2389 | fprintf(stderr, _("port %d apparently in use\n" ), port); |
2390 | if (!port_specified_by_user) |
2391 | fprintf(stderr, _("%s: could not determine an available port\n" ), progname); |
2392 | fprintf(stderr, _("Specify an unused port using the --port option or shut down any conflicting PostgreSQL servers.\n" )); |
2393 | exit(2); |
2394 | } |
2395 | |
2396 | fprintf(stderr, _("port %d apparently in use, trying %d\n" ), port, port + 1); |
2397 | port++; |
2398 | sprintf(s, "%d" , port); |
2399 | doputenv("PGPORT" , s); |
2400 | } |
2401 | else |
2402 | break; |
2403 | } |
2404 | |
2405 | /* |
2406 | * Start the temp postmaster |
2407 | */ |
2408 | header(_("starting postmaster" )); |
2409 | snprintf(buf, sizeof(buf), |
2410 | "\"%s%spostgres\" -D \"%s/data\" -F%s " |
2411 | "-c \"listen_addresses=%s\" -k \"%s\" " |
2412 | "> \"%s/log/postmaster.log\" 2>&1" , |
2413 | bindir ? bindir : "" , |
2414 | bindir ? "/" : "" , |
2415 | temp_instance, debug ? " -d 5" : "" , |
2416 | hostname ? hostname : "" , sockdir ? sockdir : "" , |
2417 | outputdir); |
2418 | postmaster_pid = spawn_process(buf); |
2419 | if (postmaster_pid == INVALID_PID) |
2420 | { |
2421 | fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n" ), |
2422 | progname, strerror(errno)); |
2423 | exit(2); |
2424 | } |
2425 | |
2426 | /* |
2427 | * Wait till postmaster is able to accept connections; normally this |
2428 | * is only a second or so, but Cygwin is reportedly *much* slower, and |
2429 | * test builds using Valgrind or similar tools might be too. Hence, |
2430 | * allow the default timeout of 60 seconds to be overridden from the |
2431 | * PGCTLTIMEOUT environment variable. |
2432 | */ |
2433 | env_wait = getenv("PGCTLTIMEOUT" ); |
2434 | if (env_wait != NULL) |
2435 | { |
2436 | wait_seconds = atoi(env_wait); |
2437 | if (wait_seconds <= 0) |
2438 | wait_seconds = 60; |
2439 | } |
2440 | else |
2441 | wait_seconds = 60; |
2442 | |
2443 | for (i = 0; i < wait_seconds; i++) |
2444 | { |
2445 | /* Done if psql succeeds */ |
2446 | if (system(buf2) == 0) |
2447 | break; |
2448 | |
2449 | /* |
2450 | * Fail immediately if postmaster has exited |
2451 | */ |
2452 | #ifndef WIN32 |
2453 | if (waitpid(postmaster_pid, NULL, WNOHANG) == postmaster_pid) |
2454 | #else |
2455 | if (WaitForSingleObject(postmaster_pid, 0) == WAIT_OBJECT_0) |
2456 | #endif |
2457 | { |
2458 | fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster.log for the reason\n" ), progname, outputdir); |
2459 | exit(2); |
2460 | } |
2461 | |
2462 | pg_usleep(1000000L); |
2463 | } |
2464 | if (i >= wait_seconds) |
2465 | { |
2466 | fprintf(stderr, _("\n%s: postmaster did not respond within %d seconds\nExamine %s/log/postmaster.log for the reason\n" ), |
2467 | progname, wait_seconds, outputdir); |
2468 | |
2469 | /* |
2470 | * If we get here, the postmaster is probably wedged somewhere in |
2471 | * startup. Try to kill it ungracefully rather than leaving a |
2472 | * stuck postmaster that might interfere with subsequent test |
2473 | * attempts. |
2474 | */ |
2475 | #ifndef WIN32 |
2476 | if (kill(postmaster_pid, SIGKILL) != 0 && |
2477 | errno != ESRCH) |
2478 | fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n" ), |
2479 | progname, strerror(errno)); |
2480 | #else |
2481 | if (TerminateProcess(postmaster_pid, 255) == 0) |
2482 | fprintf(stderr, _("\n%s: could not kill failed postmaster: error code %lu\n" ), |
2483 | progname, GetLastError()); |
2484 | #endif |
2485 | |
2486 | exit(2); |
2487 | } |
2488 | |
2489 | postmaster_running = true; |
2490 | |
2491 | #ifdef _WIN64 |
2492 | /* need a series of two casts to convert HANDLE without compiler warning */ |
2493 | #define ULONGPID(x) (unsigned long) (unsigned long long) (x) |
2494 | #else |
2495 | #define ULONGPID(x) (unsigned long) (x) |
2496 | #endif |
2497 | printf(_("running on port %d with PID %lu\n" ), |
2498 | port, ULONGPID(postmaster_pid)); |
2499 | } |
2500 | else |
2501 | { |
2502 | /* |
2503 | * Using an existing installation, so may need to get rid of |
2504 | * pre-existing database(s) and role(s) |
2505 | */ |
2506 | if (!use_existing) |
2507 | { |
2508 | for (sl = dblist; sl; sl = sl->next) |
2509 | drop_database_if_exists(sl->str); |
2510 | for (sl = extraroles; sl; sl = sl->next) |
2511 | drop_role_if_exists(sl->str); |
2512 | } |
2513 | } |
2514 | |
2515 | /* |
2516 | * Create the test database(s) and role(s) |
2517 | */ |
2518 | if (!use_existing) |
2519 | { |
2520 | for (sl = dblist; sl; sl = sl->next) |
2521 | create_database(sl->str); |
2522 | for (sl = extraroles; sl; sl = sl->next) |
2523 | create_role(sl->str, dblist); |
2524 | } |
2525 | |
2526 | /* |
2527 | * Ready to run the tests |
2528 | */ |
2529 | header(_("running regression test queries" )); |
2530 | |
2531 | for (sl = schedulelist; sl != NULL; sl = sl->next) |
2532 | { |
2533 | run_schedule(sl->str, tfunc); |
2534 | } |
2535 | |
2536 | for (sl = extra_tests; sl != NULL; sl = sl->next) |
2537 | { |
2538 | run_single_test(sl->str, tfunc); |
2539 | } |
2540 | |
2541 | /* |
2542 | * Shut down temp installation's postmaster |
2543 | */ |
2544 | if (temp_instance) |
2545 | { |
2546 | header(_("shutting down postmaster" )); |
2547 | stop_postmaster(); |
2548 | } |
2549 | |
2550 | /* |
2551 | * If there were no errors, remove the temp instance immediately to |
2552 | * conserve disk space. (If there were errors, we leave the instance in |
2553 | * place for possible manual investigation.) |
2554 | */ |
2555 | if (temp_instance && fail_count == 0 && fail_ignore_count == 0) |
2556 | { |
2557 | header(_("removing temporary instance" )); |
2558 | if (!rmtree(temp_instance, true)) |
2559 | fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n" ), |
2560 | progname, temp_instance); |
2561 | } |
2562 | |
2563 | fclose(logfile); |
2564 | |
2565 | /* |
2566 | * Emit nice-looking summary message |
2567 | */ |
2568 | if (fail_count == 0 && fail_ignore_count == 0) |
2569 | snprintf(buf, sizeof(buf), |
2570 | _(" All %d tests passed. " ), |
2571 | success_count); |
2572 | else if (fail_count == 0) /* fail_count=0, fail_ignore_count>0 */ |
2573 | snprintf(buf, sizeof(buf), |
2574 | _(" %d of %d tests passed, %d failed test(s) ignored. " ), |
2575 | success_count, |
2576 | success_count + fail_ignore_count, |
2577 | fail_ignore_count); |
2578 | else if (fail_ignore_count == 0) /* fail_count>0 && fail_ignore_count=0 */ |
2579 | snprintf(buf, sizeof(buf), |
2580 | _(" %d of %d tests failed. " ), |
2581 | fail_count, |
2582 | success_count + fail_count); |
2583 | else |
2584 | /* fail_count>0 && fail_ignore_count>0 */ |
2585 | snprintf(buf, sizeof(buf), |
2586 | _(" %d of %d tests failed, %d of these failures ignored. " ), |
2587 | fail_count + fail_ignore_count, |
2588 | success_count + fail_count + fail_ignore_count, |
2589 | fail_ignore_count); |
2590 | |
2591 | putchar('\n'); |
2592 | for (i = strlen(buf); i > 0; i--) |
2593 | putchar('='); |
2594 | printf("\n%s\n" , buf); |
2595 | for (i = strlen(buf); i > 0; i--) |
2596 | putchar('='); |
2597 | putchar('\n'); |
2598 | putchar('\n'); |
2599 | |
2600 | if (file_size(difffilename) > 0) |
2601 | { |
2602 | printf(_("The differences that caused some tests to fail can be viewed in the\n" |
2603 | "file \"%s\". A copy of the test summary that you see\n" |
2604 | "above is saved in the file \"%s\".\n\n" ), |
2605 | difffilename, logfilename); |
2606 | } |
2607 | else |
2608 | { |
2609 | unlink(difffilename); |
2610 | unlink(logfilename); |
2611 | } |
2612 | |
2613 | if (fail_count != 0) |
2614 | exit(1); |
2615 | |
2616 | return 0; |
2617 | } |
2618 | |