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