r19680: Allow specifying basedir for test data.
[jra/samba/.git] / source4 / torture / smbtorture.c
1 /* 
2    Unix SMB/CIFS implementation.
3    SMB torture tester
4    Copyright (C) Andrew Tridgell 1997-2003
5    Copyright (C) Jelmer Vernooij 2006
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23 #include "lib/cmdline/popt_common.h"
24 #include "system/time.h"
25 #include "system/wait.h"
26 #include "system/filesys.h"
27 #include "system/readline.h"
28 #include "lib/smbreadline/smbreadline.h"
29 #include "libcli/libcli.h"
30 #include "lib/ldb/include/ldb.h"
31 #include "lib/events/events.h"
32 #include "dynconfig.h"
33
34 #include "torture/torture.h"
35 #include "build.h"
36 #include "lib/util/dlinklist.h"
37 #include "librpc/rpc/dcerpc.h"
38
39 static bool run_matching(struct torture_context *torture,
40                                                  const char *prefix, 
41                                                  const char *expr,
42                                                  struct torture_suite *suite,
43                                                  bool *matched)
44 {
45         bool ret = true;
46
47         if (suite == NULL) {
48                 struct torture_suite *o;
49
50                 for (o = torture_root->children; o; o = o->next) {
51                         if (gen_fnmatch(expr, o->name) == 0) {
52                                 *matched = true;
53                                 init_iconv();
54                                 ret &= torture_run_suite(torture, o);
55                                 continue;
56                         }
57
58                         ret &= run_matching(torture, o->name, expr, o, matched);
59                 }
60         } else {
61                 char *name;
62                 struct torture_suite *c;
63                 struct torture_tcase *t;
64
65                 for (c = suite->children; c; c = c->next) {
66                         asprintf(&name, "%s-%s", prefix, c->name);
67
68                         if (gen_fnmatch(expr, name) == 0) {
69                                 *matched = true;
70                                 init_iconv();
71                                 torture->active_testname = talloc_strdup(torture, prefix);
72                                 ret &= torture_run_suite(torture, c);
73                                 free(name);
74                                 continue;
75                         }
76                         
77                         ret &= run_matching(torture, name, expr, c, matched);
78
79                         free(name);
80                 }
81
82                 for (t = suite->testcases; t; t = t->next) {
83                         asprintf(&name, "%s-%s", prefix, t->name);
84                         if (gen_fnmatch(expr, name) == 0) {
85                                 *matched = true;
86                                 init_iconv();
87                                 torture->active_testname = talloc_strdup(torture, prefix);
88                                 ret &= torture_run_tcase(torture, t);
89                                 talloc_free(torture->active_testname);
90                         }
91                         free(name);
92                 }
93         }
94
95         return ret;
96 }
97
98 #define MAX_COLS 80 /* FIXME: Determine this at run-time */
99
100 /****************************************************************************
101 run a specified test or "ALL"
102 ****************************************************************************/
103 static bool run_test(struct torture_context *torture, const char *name)
104 {
105         bool ret = true;
106         bool matched = false;
107         struct torture_suite *o;
108
109         if (strequal(name, "ALL")) {
110                 for (o = torture_root->children; o; o = o->next) {
111                         ret &= torture_run_suite(torture, o);
112                 }
113                 return ret;
114         }
115
116         ret = run_matching(torture, NULL, name, NULL, &matched);
117
118         if (!matched) {
119                 printf("Unknown torture operation '%s'\n", name);
120                 return false;
121         }
122
123         return ret;
124 }
125
126 static void parse_dns(const char *dns)
127 {
128         char *userdn, *basedn, *secret;
129         char *p, *d;
130
131         /* retrievieng the userdn */
132         p = strchr_m(dns, '#');
133         if (!p) {
134                 lp_set_cmdline("torture:ldap_userdn", "");
135                 lp_set_cmdline("torture:ldap_basedn", "");
136                 lp_set_cmdline("torture:ldap_secret", "");
137                 return;
138         }
139         userdn = strndup(dns, p - dns);
140         lp_set_cmdline("torture:ldap_userdn", userdn);
141
142         /* retrieve the basedn */
143         d = p + 1;
144         p = strchr_m(d, '#');
145         if (!p) {
146                 lp_set_cmdline("torture:ldap_basedn", "");
147                 lp_set_cmdline("torture:ldap_secret", "");
148                 return;
149         }
150         basedn = strndup(d, p - d);
151         lp_set_cmdline("torture:ldap_basedn", basedn);
152
153         /* retrieve the secret */
154         p = p + 1;
155         if (!p) {
156                 lp_set_cmdline("torture:ldap_secret", "");
157                 return;
158         }
159         secret = strdup(p);
160         lp_set_cmdline("torture:ldap_secret", secret);
161
162         printf ("%s - %s - %s\n", userdn, basedn, secret);
163
164 }
165
166 static void usage(poptContext pc)
167 {
168         struct torture_suite *o;
169         struct torture_suite *s;
170         struct torture_tcase *t;
171         int i;
172
173         poptPrintUsage(pc, stdout, 0);
174         printf("\n");
175
176         printf("The binding format is:\n\n");
177
178         printf("  TRANSPORT:host[flags]\n\n");
179
180         printf("  where TRANSPORT is either ncacn_np for SMB, ncacn_ip_tcp for RPC/TCP\n");
181         printf("  or ncalrpc for local connections.\n\n");
182
183         printf("  'host' is an IP or hostname or netbios name. If the binding string\n");
184         printf("  identifies the server side of an endpoint, 'host' may be an empty\n");
185         printf("  string.\n\n");
186
187         printf("  'flags' can include a SMB pipe name if using the ncacn_np transport or\n");
188         printf("  a TCP port number if using the ncacn_ip_tcp transport, otherwise they\n");
189         printf("  will be auto-determined.\n\n");
190
191         printf("  other recognised flags are:\n\n");
192
193         printf("    sign : enable ntlmssp signing\n");
194         printf("    seal : enable ntlmssp sealing\n");
195         printf("    connect : enable rpc connect level auth (auth, but no sign or seal)\n");
196         printf("    validate: enable the NDR validator\n");
197         printf("    print: enable debugging of the packets\n");
198         printf("    bigendian: use bigendian RPC\n");
199         printf("    padcheck: check reply data for non-zero pad bytes\n\n");
200
201         printf("  For example, these all connect to the samr pipe:\n\n");
202
203         printf("    ncacn_np:myserver\n");
204         printf("    ncacn_np:myserver[samr]\n");
205         printf("    ncacn_np:myserver[\\pipe\\samr]\n");
206         printf("    ncacn_np:myserver[/pipe/samr]\n");
207         printf("    ncacn_np:myserver[samr,sign,print]\n");
208         printf("    ncacn_np:myserver[\\pipe\\samr,sign,seal,bigendian]\n");
209         printf("    ncacn_np:myserver[/pipe/samr,seal,validate]\n");
210         printf("    ncacn_np:\n");
211         printf("    ncacn_np:[/pipe/samr]\n\n");
212
213         printf("    ncacn_ip_tcp:myserver\n");
214         printf("    ncacn_ip_tcp:myserver[1024]\n");
215         printf("    ncacn_ip_tcp:myserver[1024,sign,seal]\n\n");
216
217         printf("    ncalrpc:\n\n");
218
219         printf("The UNC format is:\n\n");
220
221         printf("  //server/share\n\n");
222
223         printf("Tests are:");
224
225         for (o = torture_root->children; o; o = o->next) {
226                 printf("\n%s (%s):\n  ", o->description, o->name);
227
228                 i = 0;
229                 for (s = o->children; s; s = s->next) {
230                         if (i + strlen(o->name) + strlen(s->name) >= (MAX_COLS - 3)) {
231                                 printf("\n  ");
232                                 i = 0;
233                         }
234                         i+=printf("%s-%s ", o->name, s->name);
235                 }
236
237                 for (t = o->testcases; t; t = t->next) {
238                         if (i + strlen(o->name) + strlen(t->name) >= (MAX_COLS - 3)) {
239                                 printf("\n  ");
240                                 i = 0;
241                         }
242                         i+=printf("%s-%s ", o->name, t->name);
243                 }
244
245                 if (i) printf("\n");
246         }
247
248         printf("\nThe default test is ALL.\n");
249
250         exit(1);
251 }
252
253 static bool is_binding_string(const char *binding_string)
254 {
255         TALLOC_CTX *mem_ctx = talloc_named_const(NULL, 0, "is_binding_string");
256         struct dcerpc_binding *binding_struct;
257         NTSTATUS status;
258         
259         status = dcerpc_parse_binding(mem_ctx, binding_string, &binding_struct);
260
261         talloc_free(mem_ctx);
262         return NT_STATUS_IS_OK(status);
263 }
264
265 static void max_runtime_handler(int sig)
266 {
267         DEBUG(0,("maximum runtime exceeded for smbtorture - terminating\n"));
268         exit(1);
269 }
270
271 struct timeval last_suite_started;
272
273 static void simple_suite_start(struct torture_context *ctx,
274                                                            struct torture_suite *suite)
275 {
276         last_suite_started = timeval_current();
277         printf("Running %s\n", suite->name);
278 }
279
280 static void simple_suite_finish(struct torture_context *ctx,
281                                                            struct torture_suite *suite)
282 {
283
284         printf("%s took %g secs\n\n", suite->name, 
285                    timeval_elapsed(&last_suite_started));
286 }
287
288 static void simple_test_result (struct torture_context *context, 
289                                                                 enum torture_result res, const char *reason)
290 {
291         switch (res) {
292         case TORTURE_OK:
293                 if (reason)
294                         printf("OK: %s\n", reason);
295                 break;
296         case TORTURE_FAIL:
297                 printf("TEST %s FAILED! - %s\n", context->active_test->name, reason);
298                 break;
299         case TORTURE_ERROR:
300                 printf("ERROR IN TEST %s! - %s\n", context->active_test->name, reason); 
301                 break;
302         case TORTURE_SKIP:
303                 printf("SKIP: %s - %s\n", context->active_test->name, reason);
304                 break;
305         }
306 }
307
308 static void simple_comment (struct torture_context *test, 
309                                                         const char *comment)
310 {
311         printf("%s", comment);
312 }
313
314 const static struct torture_ui_ops std_ui_ops = {
315         .comment = simple_comment,
316         .suite_start = simple_suite_start,
317         .suite_finish = simple_suite_finish,
318         .test_result = simple_test_result
319 };
320
321
322 static void subunit_test_start (struct torture_context *ctx, 
323                                                             struct torture_tcase *tcase,
324                                                                 struct torture_test *test)
325 {
326         printf("test: %s\n", test->name);
327 }
328
329 static void subunit_test_result (struct torture_context *context, 
330                                                                  enum torture_result res, const char *reason)
331 {
332         switch (res) {
333         case TORTURE_OK:
334                 printf("success: %s", context->active_test->name);
335                 break;
336         case TORTURE_FAIL:
337                 printf("failure: %s", context->active_test->name);
338                 break;
339         case TORTURE_ERROR:
340                 printf("error: %s", context->active_test->name);
341                 break;
342         case TORTURE_SKIP:
343                 printf("skip: %s", context->active_test->name);
344                 break;
345         }
346         if (reason)
347                 printf(" [ %s ]", reason);
348         printf("\n");
349 }
350
351 static void subunit_comment (struct torture_context *test, 
352                                                          const char *comment)
353 {
354         fprintf(stderr, "%s", comment);
355 }
356
357 const static struct torture_ui_ops subunit_ui_ops = {
358         .comment = subunit_comment,
359         .test_start = subunit_test_start,
360         .test_result = subunit_test_result
361 };
362
363 static void harness_test_start (struct torture_context *ctx, 
364                                                             struct torture_tcase *tcase,
365                                                                 struct torture_test *test)
366 {
367 }
368
369 static void harness_test_result (struct torture_context *context, 
370                                                                  enum torture_result res, const char *reason)
371 {
372         switch (res) {
373         case TORTURE_OK:
374                 printf("ok %s - %s\n", context->active_test->name, reason);
375                 break;
376         case TORTURE_FAIL:
377         case TORTURE_ERROR:
378                 printf("not ok %s - %s\n", context->active_test->name, reason);
379                 break;
380         case TORTURE_SKIP:
381                 printf("skip %s - %s\n", context->active_test->name, reason);
382                 break;
383         }
384 }
385
386 static void harness_comment (struct torture_context *test, 
387                                                          const char *comment)
388 {
389         printf("# %s\n", comment);
390 }
391
392 const static struct torture_ui_ops harness_ui_ops = {
393         .comment = harness_comment,
394         .test_start = harness_test_start,
395         .test_result = harness_test_result
396 };
397
398 static void quiet_suite_start(struct torture_context *ctx,
399                                                   struct torture_suite *suite)
400 {
401         int i;
402         ctx->quiet = true;
403         for (i = 1; i < ctx->level; i++) putchar('\t');
404         printf("%s: ", suite->name);
405         fflush(stdout);
406 }
407
408 static void quiet_suite_finish(struct torture_context *ctx,
409                                                   struct torture_suite *suite)
410 {
411         putchar('\n');
412 }
413
414 static void quiet_test_result (struct torture_context *context, 
415                                                            enum torture_result res, const char *reason)
416 {
417         fflush(stdout);
418         switch (res) {
419         case TORTURE_OK: putchar('.'); break;
420         case TORTURE_FAIL: putchar('F'); break;
421         case TORTURE_ERROR: putchar('E'); break;
422         case TORTURE_SKIP: putchar('I'); break;
423         }
424 }
425
426 const static struct torture_ui_ops quiet_ui_ops = {
427         .suite_start = quiet_suite_start,
428         .suite_finish = quiet_suite_finish,
429         .test_result = quiet_test_result
430 };
431
432 void run_recipe(struct torture_context *tctx, const char *recipe)
433 {
434         int numlines, i, ret;
435         char **lines;
436
437         lines = file_lines_load(recipe, &numlines, NULL);
438         if (lines == NULL) {
439                 fprintf(stderr, "Unable to load file %s\n", recipe);
440                 return;
441         }
442
443         for (i = 0; i < numlines; i++) {
444                 int argc;
445                 const char **argv;
446
447                 ret = poptParseArgvString(lines[i], &argc, &argv);
448                 if (ret != 0) {
449                         fprintf(stderr, "Error parsing line\n");
450                         continue;
451                 }
452
453                 run_test(tctx, argv[0]);
454         }
455
456         talloc_free(lines);
457 }
458
459 void run_shell(struct torture_context *tctx)
460 {
461         char *cline;
462         int argc;
463         const char **argv;
464         int ret;
465
466         while (1) {
467                 cline = smb_readline("torture> ", NULL, NULL);
468
469                 if (cline == NULL)
470                         return;
471         
472                 ret = poptParseArgvString(cline, &argc, &argv);
473                 if (ret != 0) {
474                         fprintf(stderr, "Error parsing line\n");
475                         continue;
476                 }
477
478                 if (!strcmp(argv[0], "quit")) {
479                         return;
480                 } else if (!strcmp(argv[0], "set")) {
481                         if (argc < 3) {
482                                 fprintf(stderr, "Usage: set <variable> <value>\n");
483                         } else {
484                                 char *name = talloc_asprintf(NULL, "torture:%s", argv[1]);
485                                 lp_set_cmdline(name, argv[2]);
486                                 talloc_free(name);
487                         }
488                 } else if (!strcmp(argv[0], "help")) {
489                         fprintf(stderr, "Available commands:\n"
490                                                         " help - This help command\n"
491                                                         " run - Run test\n"
492                                                         " set - Change variables\n"
493                                                         "\n");
494                 } else if (!strcmp(argv[0], "run")) {
495                         if (argc < 2) {
496                                 fprintf(stderr, "Usage: run TEST-NAME [OPTIONS...]\n");
497                         } else {
498                                 run_test(tctx, argv[1]);
499                         }
500                 }
501         }
502 }
503
504 /****************************************************************************
505   main program
506 ****************************************************************************/
507 int main(int argc,char *argv[])
508 {
509         int opt, i;
510         bool correct = true;
511         int max_runtime=0;
512         int argc_new;
513         struct torture_context *torture;
514         const struct torture_ui_ops *ui_ops;
515         char **argv_new;
516         poptContext pc;
517         static const char *target = "other";
518         const char **subunit_dir;
519         int shell = False;
520         static const char *ui_ops_name = "simple";
521         enum {OPT_LOADFILE=1000,OPT_UNCLIST,OPT_TIMELIMIT,OPT_DNS,
522               OPT_DANGEROUS,OPT_SMB_PORTS,OPT_ASYNC,OPT_NUMPROGS, OPT_BASEDIR};
523         
524         struct poptOption long_options[] = {
525                 POPT_AUTOHELP
526                 {"format", 0, POPT_ARG_STRING, &ui_ops_name, 0, "Output format (one of: simple, subunit, harness)", NULL },
527                 {"smb-ports",   'p', POPT_ARG_STRING, NULL,     OPT_SMB_PORTS,  "SMB ports",    NULL},
528                 {"basedir",       0, POPT_ARG_STRING, NULL, OPT_BASEDIR, "base directory", "BSAEDIR" },
529                 {"seed",          0, POPT_ARG_INT,  &torture_seed,      0,      "seed",         NULL},
530                 {"num-progs",     0, POPT_ARG_INT,  NULL,       OPT_NUMPROGS,   "num progs",    NULL},
531                 {"num-ops",       0, POPT_ARG_INT,  &torture_numops,    0,      "num ops",      NULL},
532                 {"entries",       0, POPT_ARG_INT,  &torture_entries,   0,      "entries",      NULL},
533                 {"loadfile",      0, POPT_ARG_STRING,   NULL,   OPT_LOADFILE,   "loadfile",     NULL},
534                 {"unclist",       0, POPT_ARG_STRING,   NULL,   OPT_UNCLIST,    "unclist",      NULL},
535                 {"timelimit",   't', POPT_ARG_INT,      NULL,   OPT_TIMELIMIT,  "timelimit",    NULL},
536                 {"failures",    'f', POPT_ARG_INT,  &torture_failures,  0,      "failures",     NULL},
537                 {"parse-dns",   'D', POPT_ARG_STRING,   NULL,   OPT_DNS,        "parse-dns",    NULL},
538                 {"dangerous",   'X', POPT_ARG_NONE,     NULL,   OPT_DANGEROUS,
539                  "run dangerous tests (eg. wiping out password database)", NULL},
540                 {"shell",               0, POPT_ARG_NONE, &shell, True, "Run shell", NULL},
541                 {"target",              'T', POPT_ARG_STRING, &target, 0, "samba3|samba4|other", NULL},
542                 {"async",       'a', POPT_ARG_NONE,     NULL,   OPT_ASYNC,
543                  "run async tests", NULL},
544                 {"num-async",    0, POPT_ARG_INT,  &torture_numasync,  0,
545                  "number of simultaneous async requests", NULL},
546                 {"maximum-runtime", 0, POPT_ARG_INT, &max_runtime, 0, 
547                  "set maximum time for smbtorture to live", "seconds"},
548                 POPT_COMMON_SAMBA
549                 POPT_COMMON_CONNECTION
550                 POPT_COMMON_CREDENTIALS
551                 POPT_COMMON_VERSION
552                 { NULL }
553         };
554
555         setlinebuf(stdout);
556
557         /* we are never interested in SIGPIPE */
558         BlockSignals(true, SIGPIPE);
559
560         pc = poptGetContext("smbtorture", argc, (const char **) argv, long_options, 
561                             POPT_CONTEXT_KEEP_FIRST);
562
563         poptSetOtherOptionHelp(pc, "<binding>|<unc> TEST1 TEST2 ...");
564
565         while((opt = poptGetNextOpt(pc)) != -1) {
566                 switch (opt) {
567                 case OPT_LOADFILE:
568                         lp_set_cmdline("torture:loadfile", poptGetOptArg(pc));
569                         break;
570                 case OPT_UNCLIST:
571                         lp_set_cmdline("torture:unclist", poptGetOptArg(pc));
572                         break;
573                 case OPT_TIMELIMIT:
574                         lp_set_cmdline("torture:timelimit", poptGetOptArg(pc));
575                         break;
576                 case OPT_NUMPROGS:
577                         lp_set_cmdline("torture:nprocs", poptGetOptArg(pc));
578                         break;
579                 case OPT_BASEDIR:
580                         lp_set_cmdline("torture:basedir", poptGetOptArg(pc));
581                         break;
582                 case OPT_DNS:
583                         parse_dns(poptGetOptArg(pc));
584                         break;
585                 case OPT_DANGEROUS:
586                         lp_set_cmdline("torture:dangerous", "Yes");
587                         break;
588                 case OPT_ASYNC:
589                         lp_set_cmdline("torture:async", "Yes");
590                         break;
591                 case OPT_SMB_PORTS:
592                         lp_set_cmdline("smb ports", poptGetOptArg(pc));
593                         break;
594                 }
595         }
596
597         if (strcmp(target, "samba3") == 0) {
598                 lp_set_cmdline("torture:samba3", "true");
599                 lp_set_cmdline("torture:knownfail", "samba3-knownfail");
600         } else if (strcmp(target, "samba4") == 0) {
601                 lp_set_cmdline("torture:samba4", "true");
602                 lp_set_cmdline("torture:knownfail", "samba4-knownfail");
603         }
604
605         if (max_runtime) {
606                 /* this will only work if nobody else uses alarm(),
607                    which means it won't work for some tests, but we
608                    can't use the event context method we use for smbd
609                    as so many tests create their own event
610                    context. This will at least catch most cases. */
611                 signal(SIGALRM, max_runtime_handler);
612                 alarm(max_runtime);
613         }
614
615         torture_init();
616         ldb_global_init();
617
618         subunit_dir = lp_parm_string_list(-1, "torture", "subunitdir", ":");
619         if (subunit_dir == NULL) 
620                 torture_subunit_load_testsuites(dyn_TORTUREDIR, true, NULL);
621         else {
622                 for (i = 0; subunit_dir[i]; i++)
623                         torture_subunit_load_testsuites(subunit_dir[i], true, NULL);
624         }
625
626         if (torture_seed == 0) {
627                 torture_seed = time(NULL);
628         } 
629         printf("Using seed %d\n", torture_seed);
630         srandom(torture_seed);
631
632         argv_new = discard_const_p(char *, poptGetArgs(pc));
633
634         argc_new = argc;
635         for (i=0; i<argc; i++) {
636                 if (argv_new[i] == NULL) {
637                         argc_new = i;
638                         break;
639                 }
640         }
641
642         if (!(argc_new >= 3 || (shell && argc_new >= 2))) {
643                 usage(pc);
644                 exit(1);
645         }
646
647         /* see if its a RPC transport specifier */
648         if (is_binding_string(argv_new[1])) {
649                 lp_set_cmdline("torture:binding", argv_new[1]);
650         } else {
651                 char *binding = NULL;
652                 char *host = NULL, *share = NULL;
653
654                 if (!smbcli_parse_unc(argv_new[1], NULL, &host, &share)) {
655                         d_printf("Invalid option: %s is not a valid torture target (share or binding string)\n\n", argv_new[1]);
656                         usage(pc);
657                 }
658
659                 lp_set_cmdline("torture:host", host);
660                 lp_set_cmdline("torture:share", share);
661                 asprintf(&binding, "ncacn_np:%s", host);
662                 lp_set_cmdline("torture:binding", binding);
663         }
664
665         if (!strcmp(ui_ops_name, "simple")) {
666                 ui_ops = &std_ui_ops;
667         } else if (!strcmp(ui_ops_name, "subunit")) {
668                 ui_ops = &subunit_ui_ops;
669         } else if (!strcmp(ui_ops_name, "harness")) {
670                 ui_ops = &harness_ui_ops;
671         } else if (!strcmp(ui_ops_name, "quiet")) {
672                 ui_ops = &quiet_ui_ops;
673         } else {
674                 printf("Unknown output format '%s'\n", ui_ops_name);
675                 exit(1);
676         }
677
678         torture = torture_context_init(talloc_autofree_context(), 
679                                 lp_parm_string(-1, "torture", "knownfail"), ui_ops);
680
681         if (argc_new == 0) {
682                 printf("You must specify a test to run, or 'ALL'\n");
683         } else if (shell) {
684                 run_shell(torture);
685         } else {
686                 int total;
687                 double rate;
688                 int unexpected_failures;
689                 for (i=2;i<argc_new;i++) {
690                         if (argv_new[i][0] == '@') {
691                                 run_recipe(torture, argv_new[i]+1);
692                         } else if (!run_test(torture, argv_new[i])) {
693                                 correct = false;
694                         }
695                 }
696
697                 unexpected_failures = str_list_length(torture->results.unexpected_failures);
698
699                 total = torture->results.skipped+torture->results.success+torture->results.failed+torture->results.errors;
700                 if (total == 0) {
701                         printf("No tests run.\n");
702                 } else {
703                         rate = ((total - unexpected_failures - torture->results.errors) * (100.0 / total));
704                 
705                         printf("Tests: %d, Failures: %d", total, torture->results.failed);
706                         if (torture->results.failed - unexpected_failures) {
707                                 printf(" (%d expected)", torture->results.failed - unexpected_failures);
708                         }
709                         printf(", Errors: %d, Skipped: %d. Success rate: %.2f%%\n",
710                            torture->results.errors, torture->results.skipped, rate);
711                 }
712
713                 if (unexpected_failures) {
714                         printf("The following tests failed:\n");
715                         for (i = 0; torture->results.unexpected_failures[i]; i++) {
716                                 printf("  %s\n", torture->results.unexpected_failures[i]);
717                         }
718                         printf("\n");
719                 }
720
721                 if (str_list_length(torture->results.unexpected_errors)) {
722                         printf("Errors occurred while running the following tests:\n");
723                         for (i = 0; torture->results.unexpected_errors[i]; i++) {
724                                 printf("  %s\n", torture->results.unexpected_errors[i]);
725                         }
726                         printf("\n");
727                 }
728
729                 if (str_list_length(torture->results.unexpected_successes)) {
730                         printf("The following tests were expected to fail but succeeded:\n");
731                         for (i = 0; torture->results.unexpected_successes[i]; i++) {
732                                 printf("  %s\n", torture->results.unexpected_successes[i]);
733                         }
734                         printf("\n");
735                 }
736         }
737
738         if (torture->results.returncode) {
739                 return(0);
740         } else {
741                 return(1);
742         }
743 }