r19556: Remove unused option.
[gd/samba-autobuild/.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};
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                 {"seed",          0, POPT_ARG_INT,  &torture_seed,      0,      "seed",         NULL},
529                 {"num-ops",       0, POPT_ARG_INT,  &torture_numops,    0,      "num ops",      NULL},
530                 {"entries",       0, POPT_ARG_INT,  &torture_entries,   0,      "entries",      NULL},
531                 {"loadfile",      0, POPT_ARG_STRING,   NULL,   OPT_LOADFILE,   "loadfile",     NULL},
532                 {"unclist",       0, POPT_ARG_STRING,   NULL,   OPT_UNCLIST,    "unclist",      NULL},
533                 {"timelimit",   't', POPT_ARG_STRING,   NULL,   OPT_TIMELIMIT,  "timelimit",    NULL},
534                 {"failures",    'f', POPT_ARG_INT,  &torture_failures,  0,      "failures",     NULL},
535                 {"parse-dns",   'D', POPT_ARG_STRING,   NULL,   OPT_DNS,        "parse-dns",    NULL},
536                 {"dangerous",   'X', POPT_ARG_NONE,     NULL,   OPT_DANGEROUS,
537                  "run dangerous tests (eg. wiping out password database)", NULL},
538                 {"shell",               0, POPT_ARG_NONE, &shell, True, "Run shell", NULL},
539                 {"target",              'T', POPT_ARG_STRING, &target, 0, "samba3|samba4|other", NULL},
540                 {"async",       'a', POPT_ARG_NONE,     NULL,   OPT_ASYNC,
541                  "run async tests", NULL},
542                 {"num-async",    0, POPT_ARG_INT,  &torture_numasync,  0,
543                  "number of simultaneous async requests", NULL},
544                 {"maximum-runtime", 0, POPT_ARG_INT, &max_runtime, 0, 
545                  "set maximum time for smbtorture to live", "seconds"},
546                 POPT_COMMON_SAMBA
547                 POPT_COMMON_CONNECTION
548                 POPT_COMMON_CREDENTIALS
549                 POPT_COMMON_VERSION
550                 { NULL }
551         };
552
553         setlinebuf(stdout);
554
555         /* we are never interested in SIGPIPE */
556         BlockSignals(true, SIGPIPE);
557
558         pc = poptGetContext("smbtorture", argc, (const char **) argv, long_options, 
559                             POPT_CONTEXT_KEEP_FIRST);
560
561         poptSetOtherOptionHelp(pc, "<binding>|<unc> TEST1 TEST2 ...");
562
563         while((opt = poptGetNextOpt(pc)) != -1) {
564                 switch (opt) {
565                 case OPT_LOADFILE:
566                         lp_set_cmdline("torture:loadfile", poptGetOptArg(pc));
567                         break;
568                 case OPT_UNCLIST:
569                         lp_set_cmdline("torture:unclist", poptGetOptArg(pc));
570                         break;
571                 case OPT_TIMELIMIT:
572                         lp_set_cmdline("torture:timelimit", poptGetOptArg(pc));
573                         break;
574                 case OPT_DNS:
575                         parse_dns(poptGetOptArg(pc));
576                         break;
577                 case OPT_DANGEROUS:
578                         lp_set_cmdline("torture:dangerous", "Yes");
579                         break;
580                 case OPT_ASYNC:
581                         lp_set_cmdline("torture:async", "Yes");
582                         break;
583                 case OPT_SMB_PORTS:
584                         lp_set_cmdline("smb ports", poptGetOptArg(pc));
585                         break;
586                 }
587         }
588
589         if (strcmp(target, "samba3") == 0) {
590                 lp_set_cmdline("torture:samba3", "true");
591                 lp_set_cmdline("torture:knownfail", "samba3-knownfail");
592         } else if (strcmp(target, "samba4") == 0) {
593                 lp_set_cmdline("torture:samba4", "true");
594                 lp_set_cmdline("torture:knownfail", "samba4-knownfail");
595         }
596
597         if (max_runtime) {
598                 /* this will only work if nobody else uses alarm(),
599                    which means it won't work for some tests, but we
600                    can't use the event context method we use for smbd
601                    as so many tests create their own event
602                    context. This will at least catch most cases. */
603                 signal(SIGALRM, max_runtime_handler);
604                 alarm(max_runtime);
605         }
606
607         torture_init();
608         ldb_global_init();
609
610         subunit_dir = lp_parm_string_list(-1, "torture", "subunitdir", ":");
611         if (subunit_dir == NULL) 
612                 torture_subunit_load_testsuites(dyn_TORTUREDIR, true, NULL);
613         else {
614                 for (i = 0; subunit_dir[i]; i++)
615                         torture_subunit_load_testsuites(subunit_dir[i], true, NULL);
616         }
617
618         if (torture_seed == 0) {
619                 torture_seed = time(NULL);
620         } 
621         printf("Using seed %d\n", torture_seed);
622         srandom(torture_seed);
623
624         argv_new = discard_const_p(char *, poptGetArgs(pc));
625
626         argc_new = argc;
627         for (i=0; i<argc; i++) {
628                 if (argv_new[i] == NULL) {
629                         argc_new = i;
630                         break;
631                 }
632         }
633
634         if (!(argc_new >= 3 || (shell && argc_new >= 2))) {
635                 usage(pc);
636                 exit(1);
637         }
638
639         /* see if its a RPC transport specifier */
640         if (is_binding_string(argv_new[1])) {
641                 lp_set_cmdline("torture:binding", argv_new[1]);
642         } else {
643                 char *binding = NULL;
644                 char *host = NULL, *share = NULL;
645
646                 if (!smbcli_parse_unc(argv_new[1], NULL, &host, &share)) {
647                         d_printf("Invalid option: %s is not a valid torture target (share or binding string)\n\n", argv_new[1]);
648                         usage(pc);
649                 }
650
651                 lp_set_cmdline("torture:host", host);
652                 lp_set_cmdline("torture:share", share);
653                 asprintf(&binding, "ncacn_np:%s", host);
654                 lp_set_cmdline("torture:binding", binding);
655         }
656
657         if (!strcmp(ui_ops_name, "simple")) {
658                 ui_ops = &std_ui_ops;
659         } else if (!strcmp(ui_ops_name, "subunit")) {
660                 ui_ops = &subunit_ui_ops;
661         } else if (!strcmp(ui_ops_name, "harness")) {
662                 ui_ops = &harness_ui_ops;
663         } else if (!strcmp(ui_ops_name, "quiet")) {
664                 ui_ops = &quiet_ui_ops;
665         } else {
666                 printf("Unknown output format '%s'\n", ui_ops_name);
667                 exit(1);
668         }
669
670         torture = torture_context_init(talloc_autofree_context(), 
671                                 lp_parm_string(-1, "torture", "knownfail"), ui_ops);
672
673         if (argc_new == 0) {
674                 printf("You must specify a test to run, or 'ALL'\n");
675         } else if (shell) {
676                 run_shell(torture);
677         } else {
678                 int total;
679                 double rate;
680                 int unexpected_failures;
681                 for (i=2;i<argc_new;i++) {
682                         if (argv_new[i][0] == '@') {
683                                 run_recipe(torture, argv_new[i]+1);
684                         } else if (!run_test(torture, argv_new[i])) {
685                                 correct = false;
686                         }
687                 }
688
689                 unexpected_failures = str_list_length(torture->results.unexpected_failures);
690
691                 total = torture->results.skipped+torture->results.success+torture->results.failed+torture->results.errors;
692                 if (total == 0) {
693                         printf("No tests run.\n");
694                 } else {
695                         rate = ((total - unexpected_failures - torture->results.errors) * (100.0 / total));
696                 
697                         printf("Tests: %d, Failures: %d", total, torture->results.failed);
698                         if (torture->results.failed - unexpected_failures) {
699                                 printf(" (%d expected)", torture->results.failed - unexpected_failures);
700                         }
701                         printf(", Errors: %d, Skipped: %d. Success rate: %.2f%%\n",
702                            torture->results.errors, torture->results.skipped, rate);
703                 }
704
705                 if (unexpected_failures) {
706                         printf("The following tests failed:\n");
707                         for (i = 0; torture->results.unexpected_failures[i]; i++) {
708                                 printf("  %s\n", torture->results.unexpected_failures[i]);
709                         }
710                         printf("\n");
711                 }
712
713                 if (str_list_length(torture->results.unexpected_errors)) {
714                         printf("Errors occurred while running the following tests:\n");
715                         for (i = 0; torture->results.unexpected_errors[i]; i++) {
716                                 printf("  %s\n", torture->results.unexpected_errors[i]);
717                         }
718                         printf("\n");
719                 }
720
721                 if (str_list_length(torture->results.unexpected_successes)) {
722                         printf("The following tests were expected to fail but succeeded:\n");
723                         for (i = 0; torture->results.unexpected_successes[i]; i++) {
724                                 printf("  %s\n", torture->results.unexpected_successes[i]);
725                         }
726                         printf("\n");
727                 }
728         }
729
730         if (torture->results.returncode) {
731                 return(0);
732         } else {
733                 return(1);
734         }
735 }