tap-driver.pl 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. #! /usr/bin/env perl
  2. # Copyright (C) 2011-2013 Free Software Foundation, Inc.
  3. # Copyright (C) 2018 Red Hat, Inc.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2, or (at your option)
  8. # any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. # As a special exception to the GNU General Public License, if you
  18. # distribute this file as part of a program that contains a
  19. # configuration script generated by Autoconf, you may include it under
  20. # the same distribution terms that you use for the rest of that program.
  21. # ---------------------------------- #
  22. # Imports, static data, and setup. #
  23. # ---------------------------------- #
  24. use warnings FATAL => 'all';
  25. use strict;
  26. use Getopt::Long ();
  27. use TAP::Parser;
  28. use Term::ANSIColor qw(:constants);
  29. my $ME = "tap-driver.pl";
  30. my $VERSION = "2018-11-30";
  31. my $USAGE = <<'END';
  32. Usage:
  33. tap-driver [--test-name=TEST] [--color={always|never|auto}]
  34. [--verbose] [--show-failures-only]
  35. END
  36. my $HELP = "$ME: TAP-aware test driver for QEMU testsuite harness." .
  37. "\n" . $USAGE;
  38. # It's important that NO_PLAN evaluates "false" as a boolean.
  39. use constant NO_PLAN => 0;
  40. use constant EARLY_PLAN => 1;
  41. use constant LATE_PLAN => 2;
  42. use constant DIAG_STRING => "#";
  43. # ------------------- #
  44. # Global variables. #
  45. # ------------------- #
  46. my $testno = 0; # Number of test results seen so far.
  47. my $bailed_out = 0; # Whether a "Bail out!" directive has been seen.
  48. my $failed = 0; # Final exit code
  49. # Whether the TAP plan has been seen or not, and if yes, which kind
  50. # it is ("early" is seen before any test result, "late" otherwise).
  51. my $plan_seen = NO_PLAN;
  52. # ----------------- #
  53. # Option parsing. #
  54. # ----------------- #
  55. my %cfg = (
  56. "color" => 0,
  57. "verbose" => 0,
  58. "show-failures-only" => 0,
  59. );
  60. my $color = "auto";
  61. my $test_name = undef;
  62. # Perl's Getopt::Long allows options to take optional arguments after a space.
  63. # Prevent --color by itself from consuming other arguments
  64. foreach (@ARGV) {
  65. if ($_ eq "--color" || $_ eq "-color") {
  66. $_ = "--color=$color";
  67. }
  68. }
  69. Getopt::Long::GetOptions
  70. (
  71. 'help' => sub { print $HELP; exit 0; },
  72. 'version' => sub { print "$ME $VERSION\n"; exit 0; },
  73. 'test-name=s' => \$test_name,
  74. 'color=s' => \$color,
  75. 'show-failures-only' => sub { $cfg{"show-failures-only"} = 1; },
  76. 'verbose' => sub { $cfg{"verbose"} = 1; },
  77. ) or exit 1;
  78. if ($color =~ /^always$/i) {
  79. $cfg{'color'} = 1;
  80. } elsif ($color =~ /^never$/i) {
  81. $cfg{'color'} = 0;
  82. } elsif ($color =~ /^auto$/i) {
  83. $cfg{'color'} = (-t STDOUT);
  84. } else {
  85. die "Invalid color mode: $color\n";
  86. }
  87. # ------------- #
  88. # Prototypes. #
  89. # ------------- #
  90. sub colored ($$);
  91. sub decorate_result ($);
  92. sub extract_tap_comment ($);
  93. sub handle_tap_bailout ($);
  94. sub handle_tap_plan ($);
  95. sub handle_tap_result ($);
  96. sub is_null_string ($);
  97. sub main ();
  98. sub report ($;$);
  99. sub stringify_result_obj ($);
  100. sub testsuite_error ($);
  101. # -------------- #
  102. # Subroutines. #
  103. # -------------- #
  104. # If the given string is undefined or empty, return true, otherwise
  105. # return false. This function is useful to avoid pitfalls like:
  106. # if ($message) { print "$message\n"; }
  107. # which wouldn't print anything if $message is the literal "0".
  108. sub is_null_string ($)
  109. {
  110. my $str = shift;
  111. return ! (defined $str and length $str);
  112. }
  113. sub stringify_result_obj ($)
  114. {
  115. my $result_obj = shift;
  116. if ($result_obj->is_unplanned || $result_obj->number != $testno)
  117. {
  118. return "ERROR";
  119. }
  120. elsif ($plan_seen == LATE_PLAN)
  121. {
  122. return "ERROR";
  123. }
  124. elsif (!$result_obj->directive)
  125. {
  126. return $result_obj->is_ok ? "PASS" : "FAIL";
  127. }
  128. elsif ($result_obj->has_todo)
  129. {
  130. return $result_obj->is_actual_ok ? "XPASS" : "XFAIL";
  131. }
  132. elsif ($result_obj->has_skip)
  133. {
  134. return $result_obj->is_ok ? "SKIP" : "FAIL";
  135. }
  136. die "$ME: INTERNAL ERROR"; # NOTREACHED
  137. }
  138. sub colored ($$)
  139. {
  140. my ($color_string, $text) = @_;
  141. return $color_string . $text . RESET;
  142. }
  143. sub decorate_result ($)
  144. {
  145. my $result = shift;
  146. return $result unless $cfg{"color"};
  147. my %color_for_result =
  148. (
  149. "ERROR" => BOLD.MAGENTA,
  150. "PASS" => GREEN,
  151. "XPASS" => BOLD.YELLOW,
  152. "FAIL" => BOLD.RED,
  153. "XFAIL" => YELLOW,
  154. "SKIP" => BLUE,
  155. );
  156. if (my $color = $color_for_result{$result})
  157. {
  158. return colored ($color, $result);
  159. }
  160. else
  161. {
  162. return $result; # Don't colorize unknown stuff.
  163. }
  164. }
  165. sub report ($;$)
  166. {
  167. my ($msg, $result, $explanation) = (undef, @_);
  168. if ($result =~ /^(?:X?(?:PASS|FAIL)|SKIP|ERROR)/)
  169. {
  170. # Output on console might be colorized.
  171. $msg = decorate_result($result);
  172. if ($result =~ /^(?:PASS|XFAIL|SKIP)/)
  173. {
  174. return if $cfg{"show-failures-only"};
  175. }
  176. else
  177. {
  178. $failed = 1;
  179. }
  180. }
  181. elsif ($result eq "#")
  182. {
  183. $msg = " ";
  184. }
  185. else
  186. {
  187. die "$ME: INTERNAL ERROR"; # NOTREACHED
  188. }
  189. $msg .= " $explanation" if defined $explanation;
  190. print $msg . "\n";
  191. }
  192. sub testsuite_error ($)
  193. {
  194. report "ERROR", "$test_name - $_[0]";
  195. }
  196. sub handle_tap_result ($)
  197. {
  198. $testno++;
  199. my $result_obj = shift;
  200. my $test_result = stringify_result_obj $result_obj;
  201. my $string = $result_obj->number;
  202. my $description = $result_obj->description;
  203. $string .= " $test_name" unless is_null_string $test_name;
  204. $string .= " $description" unless is_null_string $description;
  205. if ($plan_seen == LATE_PLAN)
  206. {
  207. $string .= " # AFTER LATE PLAN";
  208. }
  209. elsif ($result_obj->is_unplanned)
  210. {
  211. $string .= " # UNPLANNED";
  212. }
  213. elsif ($result_obj->number != $testno)
  214. {
  215. $string .= " # OUT-OF-ORDER (expecting $testno)";
  216. }
  217. elsif (my $directive = $result_obj->directive)
  218. {
  219. $string .= " # $directive";
  220. my $explanation = $result_obj->explanation;
  221. $string .= " $explanation"
  222. unless is_null_string $explanation;
  223. }
  224. report $test_result, $string;
  225. }
  226. sub handle_tap_plan ($)
  227. {
  228. my $plan = shift;
  229. if ($plan_seen)
  230. {
  231. # Error, only one plan per stream is acceptable.
  232. testsuite_error "multiple test plans";
  233. return;
  234. }
  235. # The TAP plan can come before or after *all* the TAP results; we speak
  236. # respectively of an "early" or a "late" plan. If we see the plan line
  237. # after at least one TAP result has been seen, assume we have a late
  238. # plan; in this case, any further test result seen after the plan will
  239. # be flagged as an error.
  240. $plan_seen = ($testno >= 1 ? LATE_PLAN : EARLY_PLAN);
  241. # If $testno > 0, we have an error ("too many tests run") that will be
  242. # automatically dealt with later, so don't worry about it here. If
  243. # $plan_seen is true, we have an error due to a repeated plan, and that
  244. # has already been dealt with above. Otherwise, we have a valid "plan
  245. # with SKIP" specification, and should report it as a particular kind
  246. # of SKIP result.
  247. if ($plan->directive && $testno == 0)
  248. {
  249. my $explanation = is_null_string ($plan->explanation) ?
  250. undef : "- " . $plan->explanation;
  251. report "SKIP", $explanation;
  252. }
  253. }
  254. sub handle_tap_bailout ($)
  255. {
  256. my ($bailout, $msg) = ($_[0], "Bail out!");
  257. $bailed_out = 1;
  258. $msg .= " " . $bailout->explanation
  259. unless is_null_string $bailout->explanation;
  260. testsuite_error $msg;
  261. }
  262. sub extract_tap_comment ($)
  263. {
  264. my $line = shift;
  265. if (index ($line, DIAG_STRING) == 0)
  266. {
  267. # Strip leading `DIAG_STRING' from `$line'.
  268. $line = substr ($line, length (DIAG_STRING));
  269. # And strip any leading and trailing whitespace left.
  270. $line =~ s/(?:^\s*|\s*$)//g;
  271. # Return what is left (if any).
  272. return $line;
  273. }
  274. return "";
  275. }
  276. sub main ()
  277. {
  278. my $iterator = TAP::Parser::Iterator::Stream->new(\*STDIN);
  279. my $parser = TAP::Parser->new ({iterator => $iterator });
  280. STDOUT->autoflush(1);
  281. while (defined (my $cur = $parser->next))
  282. {
  283. # Parsing of TAP input should stop after a "Bail out!" directive.
  284. next if $bailed_out;
  285. if ($cur->is_plan)
  286. {
  287. handle_tap_plan ($cur);
  288. }
  289. elsif ($cur->is_test)
  290. {
  291. handle_tap_result ($cur);
  292. }
  293. elsif ($cur->is_bailout)
  294. {
  295. handle_tap_bailout ($cur);
  296. }
  297. elsif ($cfg{"verbose"})
  298. {
  299. my $comment = extract_tap_comment ($cur->raw);
  300. report "#", "$comment" if length $comment;
  301. }
  302. }
  303. # A "Bail out!" directive should cause us to ignore any following TAP
  304. # error.
  305. if (!$bailed_out)
  306. {
  307. if (!$plan_seen)
  308. {
  309. testsuite_error "missing test plan";
  310. }
  311. elsif ($parser->tests_planned != $parser->tests_run)
  312. {
  313. my ($planned, $run) = ($parser->tests_planned, $parser->tests_run);
  314. my $bad_amount = $run > $planned ? "many" : "few";
  315. testsuite_error (sprintf "too %s tests run (expected %d, got %d)",
  316. $bad_amount, $planned, $run);
  317. }
  318. }
  319. }
  320. # ----------- #
  321. # Main code. #
  322. # ----------- #
  323. main;
  324. exit($failed);
  325. # Local Variables:
  326. # perl-indent-level: 2
  327. # perl-continued-statement-offset: 2
  328. # perl-continued-brace-offset: 0
  329. # perl-brace-offset: 0
  330. # perl-brace-imaginary-offset: 0
  331. # perl-label-offset: -2
  332. # cperl-indent-level: 2
  333. # cperl-brace-offset: 0
  334. # cperl-continued-brace-offset: 0
  335. # cperl-label-offset: -2
  336. # cperl-extra-newline-before-brace: t
  337. # cperl-merge-trailing-else: nil
  338. # cperl-continued-statement-offset: 2
  339. # End: