Imported Upstream version 3.3.3
[debian/amanda] / common-src / testutils.c
1 /*
2  * Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17  *
18  * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19  * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20  */
21
22 #include "amanda.h"
23 #include "testutils.h"
24
25 gboolean tu_debugging_enabled = FALSE;
26
27 static gboolean run_all = TRUE;
28 static gboolean ignore_timeouts = FALSE;
29 static gboolean skip_fork = FALSE;
30 static gboolean only_one = FALSE;
31 static gboolean loop_forever = FALSE;
32 static guint64 occurrences = 1;
33
34 static void
35 alarm_hdlr(int sig G_GNUC_UNUSED)
36 {
37     g_fprintf(stderr, "-- TEST TIMED OUT --\n");
38     exit(1);
39 }
40
41 /*
42  * Run a single test, accouting for the timeout (if timeouts are not ignored)
43  * and output runtime information (in milliseconds) at the end of the run.
44  * Output avg/min/max only if the number of runs is strictly greater than one.
45  */
46
47 static gboolean run_one_test(TestUtilsTest *test)
48 {
49     guint64 count = 0;
50     gboolean ret = TRUE;
51     const char *test_name = test->name;
52     GTimer *timer;
53
54     gdouble total = 0.0, thisrun, mintime = G_MAXDOUBLE, maxtime = G_MINDOUBLE;
55
56     signal(SIGALRM, alarm_hdlr);
57
58     timer = g_timer_new();
59
60     while (count++ < occurrences) {
61         if (!ignore_timeouts)
62             alarm(test->timeout);
63
64         g_timer_start(timer);
65         ret = test->fn();
66         g_timer_stop(timer);
67
68         thisrun = g_timer_elapsed(timer, NULL);
69         total += thisrun;
70         if (mintime > thisrun)
71             mintime = thisrun;
72         if (maxtime < thisrun)
73             maxtime = thisrun;
74
75         if (!ret)
76             break;
77     }
78
79     g_timer_destroy(timer);
80
81     if (loop_forever)
82         goto out;
83
84     if (ret) {
85         g_fprintf(stderr, " PASS %s (total: %.06f", test_name, total);
86         if (occurrences > 1) {
87             total /= (gdouble) occurrences;
88             g_fprintf(stderr, ", avg/min/max: %.06f/%.06f/%.06f",
89                 total, mintime, maxtime);
90         }
91         g_fprintf(stderr, ")\n");
92     } else
93         g_fprintf(stderr, " FAIL %s (run %ju of %ju, after %.06f secs)\n",
94             test_name, (uintmax_t)count, (uintmax_t)occurrences, total);
95
96 out:
97     return ret;
98 }
99
100 /*
101  * Call testfn in a forked process, such that any failures will trigger a
102  * test failure, but allow the other tests to proceed. The only exception is if
103  * -n is supplied at the command line, but in this case only one test is allowed
104  * to run.
105  */
106
107 static gboolean
108 callinfork(TestUtilsTest *test)
109 {
110     pid_t pid;
111     amwait_t status;
112     gboolean result;
113
114     if (skip_fork)
115         result = run_one_test(test);
116     else {
117         switch (pid = fork()) {
118             case 0:     /* child */
119                 exit(run_one_test(test) ? 0 : 1);
120
121             case -1:
122                 perror("fork");
123                 exit(1);
124
125             default: /* parent */
126                 waitpid(pid, &status, 0);
127                 result = status == 0;
128                 break;
129         }
130     }
131
132     return result;
133 }
134
135 static void
136 usage(
137     TestUtilsTest *tests)
138 {
139     printf("USAGE: <test-script> [options] [testname [testname [..]]]\n"
140         "\n"
141         "Options can be one of:\n"
142         "\n"
143         "\t-h: this message\n"
144         "\t-d: print debugging messages\n"
145         "\t-t: ignore timeouts\n"
146         "\t-n: do not fork\n"
147         "\t-c <count>: run each test <count> times instead of only once\n"
148         "\t-l: loop the same test repeatedly (use with -n for leak checks)\n"
149         "\n"
150         "If no test names are specified, all tests are run.  Available tests:\n"
151         "\n");
152     while (tests->fn) {
153         printf("\t%s\n", tests->name);
154         tests++;
155     }
156 }
157
158 static void
159 ignore_debug_messages(
160             const gchar *log_domain G_GNUC_UNUSED,
161             GLogLevelFlags log_level G_GNUC_UNUSED,
162             const gchar *message G_GNUC_UNUSED,
163             gpointer user_data G_GNUC_UNUSED)
164 {
165 }
166
167 int
168 testutils_run_tests(
169     int argc,
170     char **argv,
171     TestUtilsTest *tests)
172 {
173     TestUtilsTest *t;
174     gboolean success;
175
176     /* first_parse the command line */
177     while (argc > 1) {
178         if (strcmp(argv[1], "-d") == 0) {
179             tu_debugging_enabled = TRUE;
180         } else if (strcmp(argv[1], "-t") == 0) {
181             ignore_timeouts = TRUE;
182         } else if (strcmp(argv[1], "-n") == 0) {
183             skip_fork = TRUE;
184             only_one = TRUE;
185         } else if (strcmp(argv[1], "-l") == 0) {
186             loop_forever = TRUE;
187             only_one = TRUE;
188         } else if (strcmp(argv[1], "-c") == 0) {
189             char *p;
190             argv++, argc--;
191             occurrences = g_ascii_strtoull(argv[1], &p, 10);
192             if (errno == ERANGE) {
193                 g_fprintf(stderr, "%s is out of range\n", argv[1]);
194                 exit(1);
195             }
196             if (*p) {
197                 g_fprintf(stderr, "The -c option expects a positive integer "
198                     "as an argument, but \"%s\" isn't\n", argv[1]);
199                 exit(1);
200             }
201             if (occurrences == 0) {
202                 g_fprintf(stderr, "Sorry, I will not run tests 0 times\n");
203                 exit(1);
204             }
205         } else if (strcmp(argv[1], "-h") == 0) {
206             usage(tests);
207             return 1;
208         } else {
209             int found = 0;
210
211             for (t = tests; t->fn; t++) {
212                 if (strcmp(argv[1], t->name) == 0) {
213                     found = 1;
214                     t->selected = 1;
215                     break;
216                 }
217             }
218
219             if (!found) {
220                 g_fprintf(stderr, "Test '%s' not found\n", argv[1]);
221                 return 1;
222             }
223
224             run_all = FALSE;
225         }
226
227         argc--; argv++;
228     }
229
230     /*
231      * Check whether the -c option has been given. In this case, -l must not be
232      * specified at the same time.
233      */
234     if (occurrences > 1 && loop_forever) {
235         g_fprintf(stderr, "-c and -l are incompatible\n");
236         exit(1);
237     }
238
239     if (run_all) {
240         for (t = tests; t->fn; t++)
241             t->selected = 1;
242     }
243
244     /* check only_one */
245     if (only_one) {
246         int num_tests = 0;
247         for (t = tests; t->fn; t++) {
248             if (t->selected)
249                 num_tests++;
250         }
251
252         if (num_tests > 1) {
253             g_fprintf(stderr, "Only run one test with '-n'\n");
254             return 1;
255         }
256     }
257
258     /* Make sure g_critical and g_error will exit */
259     g_log_set_always_fatal(G_LOG_LEVEL_ERROR |  G_LOG_LEVEL_CRITICAL);
260
261     /* and silently drop debug messages unless we're debugging */
262     if (!tu_debugging_enabled) {
263         g_log_set_handler(NULL, G_LOG_LEVEL_DEBUG, ignore_debug_messages, NULL);
264     }
265
266     /* Now actually run the tests */
267     success = TRUE;
268     for (t = tests; t->fn; t++) {
269         if (t->selected) {
270             do {
271                 success = callinfork(t) && success;
272             } while (loop_forever);
273         }
274     }
275
276     return success ? 0 : 1;
277 }