r20757: Strip recipe functionality (should be one layer up)
[mat/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 void max_runtime_handler(int sig)
254 {
255         DEBUG(0,("maximum runtime exceeded for smbtorture - terminating\n"));
256         exit(1);
257 }
258
259 struct timeval last_suite_started;
260
261 static void simple_suite_start(struct torture_context *ctx,
262                                                            struct torture_suite *suite)
263 {
264         last_suite_started = timeval_current();
265         printf("Running %s\n", suite->name);
266 }
267
268 static void simple_suite_finish(struct torture_context *ctx,
269                                                            struct torture_suite *suite)
270 {
271
272         printf("%s took %g secs\n\n", suite->name, 
273                    timeval_elapsed(&last_suite_started));
274 }
275
276 static void simple_test_result (struct torture_context *context, 
277                                                                 enum torture_result res, const char *reason)
278 {
279         switch (res) {
280         case TORTURE_OK:
281                 if (reason)
282                         printf("OK: %s\n", reason);
283                 break;
284         case TORTURE_FAIL:
285                 printf("TEST %s FAILED! - %s\n", context->active_test->name, reason);
286                 break;
287         case TORTURE_ERROR:
288                 printf("ERROR IN TEST %s! - %s\n", context->active_test->name, reason); 
289                 break;
290         case TORTURE_SKIP:
291                 printf("SKIP: %s - %s\n", context->active_test->name, reason);
292                 break;
293         }
294 }
295
296 static void simple_comment (struct torture_context *test, 
297                                                         const char *comment)
298 {
299         printf("%s", comment);
300 }
301
302 const static struct torture_ui_ops std_ui_ops = {
303         .comment = simple_comment,
304         .suite_start = simple_suite_start,
305         .suite_finish = simple_suite_finish,
306         .test_result = simple_test_result
307 };
308
309 static void subunit_suite_start(struct torture_context *ctx,
310                                                            struct torture_suite *suite)
311 {
312         printf("testsuite: %s\n", suite->path);
313 }
314
315 static void subunit_test_start (struct torture_context *ctx, 
316                                                             struct torture_tcase *tcase,
317                                                                 struct torture_test *test)
318 {
319         printf("test: %s\n", test->name);
320 }
321
322 static void subunit_test_result (struct torture_context *context, 
323                                                                  enum torture_result res, const char *reason)
324 {
325         switch (res) {
326         case TORTURE_OK:
327                 printf("success: %s", context->active_test->name);
328                 break;
329         case TORTURE_FAIL:
330                 printf("failure: %s", context->active_test->name);
331                 break;
332         case TORTURE_ERROR:
333                 printf("error: %s", context->active_test->name);
334                 break;
335         case TORTURE_SKIP:
336                 printf("skip: %s", context->active_test->name);
337                 break;
338         }
339         if (reason)
340                 printf(" [ %s ]", reason);
341         printf("\n");
342 }
343
344 static void subunit_comment (struct torture_context *test, 
345                                                          const char *comment)
346 {
347         fprintf(stderr, "%s", comment);
348 }
349
350 const static struct torture_ui_ops subunit_ui_ops = {
351         .comment = subunit_comment,
352         .test_start = subunit_test_start,
353         .test_result = subunit_test_result,
354         .suite_start = subunit_suite_start
355 };
356
357 static void harness_test_start (struct torture_context *ctx, 
358                                                             struct torture_tcase *tcase,
359                                                                 struct torture_test *test)
360 {
361 }
362
363 static void harness_test_result (struct torture_context *context, 
364                                                                  enum torture_result res, const char *reason)
365 {
366         switch (res) {
367         case TORTURE_OK:
368                 printf("ok %s - %s\n", context->active_test->name, reason);
369                 break;
370         case TORTURE_FAIL:
371         case TORTURE_ERROR:
372                 printf("not ok %s - %s\n", context->active_test->name, reason);
373                 break;
374         case TORTURE_SKIP:
375                 printf("skip %s - %s\n", context->active_test->name, reason);
376                 break;
377         }
378 }
379
380 static void harness_comment (struct torture_context *test, 
381                                                          const char *comment)
382 {
383         printf("# %s\n", comment);
384 }
385
386 const static struct torture_ui_ops harness_ui_ops = {
387         .comment = harness_comment,
388         .test_start = harness_test_start,
389         .test_result = harness_test_result
390 };
391
392 static void quiet_suite_start(struct torture_context *ctx,
393                                                   struct torture_suite *suite)
394 {
395         int i;
396         ctx->quiet = true;
397         for (i = 1; i < ctx->level; i++) putchar('\t');
398         printf("%s: ", suite->name);
399         fflush(stdout);
400 }
401
402 static void quiet_suite_finish(struct torture_context *ctx,
403                                                   struct torture_suite *suite)
404 {
405         putchar('\n');
406 }
407
408 static void quiet_test_result (struct torture_context *context, 
409                                                            enum torture_result res, const char *reason)
410 {
411         fflush(stdout);
412         switch (res) {
413         case TORTURE_OK: putchar('.'); break;
414         case TORTURE_FAIL: putchar('F'); break;
415         case TORTURE_ERROR: putchar('E'); break;
416         case TORTURE_SKIP: putchar('I'); break;
417         }
418 }
419
420 const static struct torture_ui_ops quiet_ui_ops = {
421         .suite_start = quiet_suite_start,
422         .suite_finish = quiet_suite_finish,
423         .test_result = quiet_test_result
424 };
425
426 void run_shell(struct torture_context *tctx)
427 {
428         char *cline;
429         int argc;
430         const char **argv;
431         int ret;
432
433         while (1) {
434                 cline = smb_readline("torture> ", NULL, NULL);
435
436                 if (cline == NULL)
437                         return;
438         
439                 ret = poptParseArgvString(cline, &argc, &argv);
440                 if (ret != 0) {
441                         fprintf(stderr, "Error parsing line\n");
442                         continue;
443                 }
444
445                 if (!strcmp(argv[0], "quit")) {
446                         return;
447                 } else if (!strcmp(argv[0], "set")) {
448                         if (argc < 3) {
449                                 fprintf(stderr, "Usage: set <variable> <value>\n");
450                         } else {
451                                 char *name = talloc_asprintf(NULL, "torture:%s", argv[1]);
452                                 lp_set_cmdline(name, argv[2]);
453                                 talloc_free(name);
454                         }
455                 } else if (!strcmp(argv[0], "help")) {
456                         fprintf(stderr, "Available commands:\n"
457                                                         " help - This help command\n"
458                                                         " run - Run test\n"
459                                                         " set - Change variables\n"
460                                                         "\n");
461                 } else if (!strcmp(argv[0], "run")) {
462                         if (argc < 2) {
463                                 fprintf(stderr, "Usage: run TEST-NAME [OPTIONS...]\n");
464                         } else {
465                                 run_test(tctx, argv[1]);
466                         }
467                 }
468         }
469 }
470
471 /****************************************************************************
472   main program
473 ****************************************************************************/
474 int main(int argc,char *argv[])
475 {
476         int opt, i;
477         bool correct = true;
478         int max_runtime=0;
479         int argc_new;
480         struct torture_context *torture;
481         const struct torture_ui_ops *ui_ops;
482         char **argv_new;
483         poptContext pc;
484         static const char *target = "other";
485         const char **subunit_dir;
486         struct dcerpc_binding *binding_struct;
487         NTSTATUS status;
488         int shell = False;
489         static const char *ui_ops_name = "simple";
490         enum {OPT_LOADFILE=1000,OPT_UNCLIST,OPT_TIMELIMIT,OPT_DNS,
491               OPT_DANGEROUS,OPT_SMB_PORTS,OPT_ASYNC,OPT_NUMPROGS, OPT_BASEDIR};
492         
493         struct poptOption long_options[] = {
494                 POPT_AUTOHELP
495                 {"format", 0, POPT_ARG_STRING, &ui_ops_name, 0, "Output format (one of: simple, subunit, harness)", NULL },
496                 {"smb-ports",   'p', POPT_ARG_STRING, NULL,     OPT_SMB_PORTS,  "SMB ports",    NULL},
497                 {"basedir",       0, POPT_ARG_STRING, NULL, OPT_BASEDIR, "base directory", "BSAEDIR" },
498                 {"seed",          0, POPT_ARG_INT,  &torture_seed,      0,      "seed",         NULL},
499                 {"num-progs",     0, POPT_ARG_INT,  NULL,       OPT_NUMPROGS,   "num progs",    NULL},
500                 {"num-ops",       0, POPT_ARG_INT,  &torture_numops,    0,      "num ops",      NULL},
501                 {"entries",       0, POPT_ARG_INT,  &torture_entries,   0,      "entries",      NULL},
502                 {"loadfile",      0, POPT_ARG_STRING,   NULL,   OPT_LOADFILE,   "loadfile",     NULL},
503                 {"unclist",       0, POPT_ARG_STRING,   NULL,   OPT_UNCLIST,    "unclist",      NULL},
504                 {"timelimit",   't', POPT_ARG_INT,      NULL,   OPT_TIMELIMIT,  "timelimit",    NULL},
505                 {"failures",    'f', POPT_ARG_INT,  &torture_failures,  0,      "failures",     NULL},
506                 {"parse-dns",   'D', POPT_ARG_STRING,   NULL,   OPT_DNS,        "parse-dns",    NULL},
507                 {"dangerous",   'X', POPT_ARG_NONE,     NULL,   OPT_DANGEROUS,
508                  "run dangerous tests (eg. wiping out password database)", NULL},
509                 {"shell",               0, POPT_ARG_NONE, &shell, True, "Run shell", NULL},
510                 {"target",              'T', POPT_ARG_STRING, &target, 0, "samba3|samba4|other", NULL},
511                 {"async",       'a', POPT_ARG_NONE,     NULL,   OPT_ASYNC,
512                  "run async tests", NULL},
513                 {"num-async",    0, POPT_ARG_INT,  &torture_numasync,  0,
514                  "number of simultaneous async requests", NULL},
515                 {"maximum-runtime", 0, POPT_ARG_INT, &max_runtime, 0, 
516                  "set maximum time for smbtorture to live", "seconds"},
517                 POPT_COMMON_SAMBA
518                 POPT_COMMON_CONNECTION
519                 POPT_COMMON_CREDENTIALS
520                 POPT_COMMON_VERSION
521                 { NULL }
522         };
523
524         setlinebuf(stdout);
525
526         /* we are never interested in SIGPIPE */
527         BlockSignals(true, SIGPIPE);
528
529         pc = poptGetContext("smbtorture", argc, (const char **) argv, long_options, 
530                             POPT_CONTEXT_KEEP_FIRST);
531
532         poptSetOtherOptionHelp(pc, "<binding>|<unc> TEST1 TEST2 ...");
533
534         while((opt = poptGetNextOpt(pc)) != -1) {
535                 switch (opt) {
536                 case OPT_LOADFILE:
537                         lp_set_cmdline("torture:loadfile", poptGetOptArg(pc));
538                         break;
539                 case OPT_UNCLIST:
540                         lp_set_cmdline("torture:unclist", poptGetOptArg(pc));
541                         break;
542                 case OPT_TIMELIMIT:
543                         lp_set_cmdline("torture:timelimit", poptGetOptArg(pc));
544                         break;
545                 case OPT_NUMPROGS:
546                         lp_set_cmdline("torture:nprocs", poptGetOptArg(pc));
547                         break;
548                 case OPT_BASEDIR:
549                         lp_set_cmdline("torture:basedir", poptGetOptArg(pc));
550                         break;
551                 case OPT_DNS:
552                         parse_dns(poptGetOptArg(pc));
553                         break;
554                 case OPT_DANGEROUS:
555                         lp_set_cmdline("torture:dangerous", "Yes");
556                         break;
557                 case OPT_ASYNC:
558                         lp_set_cmdline("torture:async", "Yes");
559                         break;
560                 case OPT_SMB_PORTS:
561                         lp_set_cmdline("smb ports", poptGetOptArg(pc));
562                         break;
563                 }
564         }
565
566         if (strcmp(target, "samba3") == 0) {
567                 lp_set_cmdline("torture:samba3", "true");
568                 lp_set_cmdline("torture:knownfail", "samba3-knownfail");
569         } else if (strcmp(target, "samba4") == 0) {
570                 lp_set_cmdline("torture:samba4", "true");
571                 lp_set_cmdline("torture:knownfail", "samba4-knownfail");
572         }
573
574         if (max_runtime) {
575                 /* this will only work if nobody else uses alarm(),
576                    which means it won't work for some tests, but we
577                    can't use the event context method we use for smbd
578                    as so many tests create their own event
579                    context. This will at least catch most cases. */
580                 signal(SIGALRM, max_runtime_handler);
581                 alarm(max_runtime);
582         }
583
584         torture_init();
585         ldb_global_init();
586
587         subunit_dir = lp_parm_string_list(-1, "torture", "subunitdir", ":");
588         if (subunit_dir == NULL) 
589                 torture_subunit_load_testsuites(dyn_TORTUREDIR, true, NULL);
590         else {
591                 for (i = 0; subunit_dir[i]; i++)
592                         torture_subunit_load_testsuites(subunit_dir[i], true, NULL);
593         }
594
595         if (torture_seed == 0) {
596                 torture_seed = time(NULL);
597         } 
598         printf("Using seed %d\n", torture_seed);
599         srandom(torture_seed);
600
601         argv_new = discard_const_p(char *, poptGetArgs(pc));
602
603         argc_new = argc;
604         for (i=0; i<argc; i++) {
605                 if (argv_new[i] == NULL) {
606                         argc_new = i;
607                         break;
608                 }
609         }
610
611         if (!(argc_new >= 3 || (shell && argc_new >= 2))) {
612                 usage(pc);
613                 exit(1);
614         }
615
616         /* see if its a RPC transport specifier */
617         status = dcerpc_parse_binding(talloc_autofree_context(), argv_new[1], &binding_struct);
618         if (NT_STATUS_IS_OK(status)) {
619                 lp_set_cmdline("torture:host", binding_struct->host);
620                 lp_set_cmdline("torture:share", "IPC$");
621                 lp_set_cmdline("torture:binding", argv_new[1]);
622         } else {
623                 char *binding = NULL;
624                 char *host = NULL, *share = NULL;
625
626                 if (!smbcli_parse_unc(argv_new[1], NULL, &host, &share)) {
627                         d_printf("Invalid option: %s is not a valid torture target (share or binding string)\n\n", argv_new[1]);
628                         usage(pc);
629                 }
630
631                 lp_set_cmdline("torture:host", host);
632                 lp_set_cmdline("torture:share", share);
633                 asprintf(&binding, "ncacn_np:%s", host);
634                 lp_set_cmdline("torture:binding", binding);
635         }
636
637         if (!strcmp(ui_ops_name, "simple")) {
638                 ui_ops = &std_ui_ops;
639         } else if (!strcmp(ui_ops_name, "subunit")) {
640                 ui_ops = &subunit_ui_ops;
641         } else if (!strcmp(ui_ops_name, "harness")) {
642                 ui_ops = &harness_ui_ops;
643         } else if (!strcmp(ui_ops_name, "quiet")) {
644                 ui_ops = &quiet_ui_ops;
645         } else {
646                 printf("Unknown output format '%s'\n", ui_ops_name);
647                 exit(1);
648         }
649
650         torture = torture_context_init(talloc_autofree_context(), 
651                                 lp_parm_string(-1, "torture", "knownfail"), ui_ops);
652
653         if (argc_new == 0) {
654                 printf("You must specify a test to run, or 'ALL'\n");
655         } else if (shell) {
656                 run_shell(torture);
657         } else {
658                 int total;
659                 double rate;
660                 int unexpected_failures;
661                 for (i=2;i<argc_new;i++) {
662                         if (!run_test(torture, argv_new[i])) {
663                                 correct = false;
664                         }
665                 }
666
667                 unexpected_failures = str_list_length(torture->results.unexpected_failures);
668
669                 total = torture->results.skipped+torture->results.success+torture->results.failed+torture->results.errors;
670                 if (total == 0) {
671                         printf("No tests run.\n");
672                 } else {
673                         rate = ((total - unexpected_failures - torture->results.errors) * (100.0 / total));
674                 
675                         printf("Tests: %d, Failures: %d", total, torture->results.failed);
676                         if (torture->results.failed - unexpected_failures) {
677                                 printf(" (%d expected)", torture->results.failed - unexpected_failures);
678                         }
679                         printf(", Errors: %d, Skipped: %d. Success rate: %.2f%%\n",
680                            torture->results.errors, torture->results.skipped, rate);
681                 }
682
683                 if (unexpected_failures) {
684                         printf("The following tests failed:\n");
685                         for (i = 0; torture->results.unexpected_failures[i]; i++) {
686                                 printf("  %s\n", torture->results.unexpected_failures[i]);
687                         }
688                         printf("\n");
689                 }
690
691                 if (str_list_length(torture->results.unexpected_errors)) {
692                         printf("Errors occurred while running the following tests:\n");
693                         for (i = 0; torture->results.unexpected_errors[i]; i++) {
694                                 printf("  %s\n", torture->results.unexpected_errors[i]);
695                         }
696                         printf("\n");
697                 }
698
699                 if (str_list_length(torture->results.unexpected_successes)) {
700                         printf("The following tests were expected to fail but succeeded:\n");
701                         for (i = 0; torture->results.unexpected_successes[i]; i++) {
702                                 printf("  %s\n", torture->results.unexpected_successes[i]);
703                         }
704                         printf("\n");
705                 }
706         }
707
708         if (torture->results.returncode) {
709                 return(0);
710         } else {
711                 return(1);
712         }
713 }