1
# LSB Test Execution Framework
2
# Report Generation Routines (Report.pm)
4
# Copyright (C) 2007-2009 The Linux Foundation. All rights reserved.
6
# This program has been developed by ISP RAS for LF.
7
# The ptyshell tool is originally written by Jiri Dluhos <jdluhos@suse.cz>
8
# Copyright (C) 2005-2007 SuSE Linux Products GmbH
10
# This program is free software; you can redistribute it and/or
11
# modify it under the terms of the GNU General Public License
12
# version 2 as published by the Free Software Foundation.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with this program; if not, write to the Free Software
21
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
31
our @ISA = qw(Exporter);
35
#----------------------------------------------------------------------
40
# KIND => TET|SIMPLE|TCL|PYTHON, # kind of the journal
41
# STATS => { PASSED, WARNINGS, TESTS_TOTAL, NOTICES, FIPS, TESTS_EXPECTED,
42
# TESTSUITE_ERRORS, UNTESTED, FAILURES, QUELLED }, # statistical data
44
# # @ARR elements can be of two types.
45
# # Passed and untested test cases are stored as scalars (to save memory),
46
# # and their format is "$severity:$test_name",
47
# # where $severity is an empty line "" for passed test cases.
48
# # Hash type is for other test result types and can contain various details.
49
# ARR => [ $testcase_ok |
50
# {TEST_NAME, TEST_POINT, TEST_NUM, SEVERITY, MESSAGE, LINE_NUM, COMMENT}
52
# TESTS_EXPECTED, # expected number of test cases
53
# INFO_TABLES, # information from journal header
56
# Map of TET result codes to human-readable severity names.
57
our %result_code_map = (
69
103 => "Unimplemented",
73
#----------------------------------------------------------------------
79
"unresolved" => 'failed',
80
"uninitiated" => 'failed',
81
"unreported" => 'failed',
82
"time expired" => 'failed',
84
"unused" => 'untested',
85
"unsupported" => 'untested',
86
"untested" => 'untested',
87
"unimplemented" => 'untested',
88
"unapproved" => 'untested',
92
"warning" => 'warning',
93
"testsuite_warning" => 'warning',
96
"testsuite_notice" => 'notice',
98
"testsuite_error" => 'testsuite_error',
101
#----------------------------------------------------------------------
103
# HTML colors used in the report.
104
my $dark_gray = "#555555";
105
my $gray = "#C8C8C8";
106
my $neutral_gray = "#888888";
107
my $light_gray = "#F0F0F0";
108
my $dark_blue = "#000080";
109
my $dark_green = "#006600";
110
my $fluorescent_green = "#40FF40";
111
my $fluorescent_yellow = "#FFFF40";
112
my $fluorescent_red = "#FF6060";
113
my $green = "#00A000";
115
#----------------------------------------------------------------------
117
# Problem database records
118
our $problem_database;
120
my $box_id = 0; # To generate unique IDs for HTML elements.
122
our $cert_mode = 0; # For TRIAL-USE tests
124
our $result_dir = undef;
126
#----------------------------------------------------------------------
128
# Loads the problem database which is used for automatic identification
129
# of known test suite failures, warnings and false positives.
130
sub load_problem_database {
131
my ($file_name) = @_;
133
# Open the database file.
134
open FILE, $file_name
135
or return error "Failed to open problem database '$file_name': $!";
137
# The problem record being read.
140
while ( my $line = <FILE> ) {
142
$line =~ s/^\s+//; # Remove spaces in the beginning of the line
143
$line =~ s/\s+$//; # Remove trailing spaces
145
next if $line eq ""; # Skip empty lines
146
next if $line =~ /^#/; # Skip comments
149
# The 'test' line starts a new test record.
150
# It contains the name of the test and the test point number.
151
if ($line =~ /^test\s+(.*)$/) {
154
# Clear the old record and prepare a clean one.
156
'test_name' => $test_name,
162
warning "problem_db: $.:"
163
." Unexpected text beyound a test block: '$line'";
167
# The 'expected_severity' line indicates the severity
168
# expected to be reported by the test suite.
169
if ( $line =~ /^expected_severity\s+(.*)/ ) {
170
my $expected_severity = lc($1);
172
# Check if the expected severity is one of the predefined values.
173
if ( !defined $severity_map{$expected_severity} ) {
174
warning "problem_db: $.:"
175
.($problem ? " ".$problem->{'test_name'}.":" : "")
176
." unrecognized expected_severity '$expected_severity'."
177
." Allowed values are: ".(join ", ", keys %severity_map);
180
$problem->{'expected_severity'} = $expected_severity;
182
# The 'marker' line contains a marker which can be found
183
# to identify the particular problem from the messages.
184
# There may be (and typically are) more markers, each on its own
186
elsif ( $line =~ /^marker\s+(.*)/ ) {
188
push @{$problem->{'markers'}}, $marker;
190
# The 'resolution' line contains the resolution of the problem.
191
elsif ($line =~ /^resolution\s+(.*)/) {
193
$problem->{'resolution'} = $resolution;
195
# If the 'quell' line is present, the problem is considered harmless
196
# regardless of severity. This is used for already solved bugs
197
# and false positives.
198
elsif ($line =~ /^quell(\s+(.*))?/) {
199
my $force_severity = $2;
200
$force_severity = 1 if !$force_severity;
202
$problem->{'quell'} = $force_severity;
204
# The 'comment' line holds a comment for human reader
205
# which is copied to the problem report. There can be more
207
elsif ($line =~ /^comment\s+(.*)/) {
210
$problem->{'comment'} .= "\n" if $problem->{'comment'};
211
$problem->{'comment'} .= $text;
213
# The 'lsb_report_id' contains the LSB problem report ID.
214
elsif ($line =~ /^lsb_report_id\s+(\S+)$/) {
215
$problem->{'lsb_report_id'} = $1;
217
# The 'lsb_resolution_id' contains the LSB resolution ID.
218
elsif ($line =~ /^lsb_resolution_id\s+(\S+)$/) {
219
$problem->{'lsb_resolution_id'} = $1;
221
# 'dup' means that problem description should be copied for
223
# NOTE: 'dup' should go AFTER all records to be copied.
224
# In other words, if you put something below 'dup', it won't be copied.
225
elsif ($line =~ /^dup\s+(.*)$/) {
226
my $dup_test_name = $1;
228
my $copy = { %$problem };
229
$copy->{'test_name'} = $dup_test_name;
231
# Add the same problem record to the database with another key.
232
push @{$problem_database->{$dup_test_name}}, $copy;
234
# The 'end' line finishes one problem entry.
235
elsif ($line =~ /^end/) {
237
# Check if this problem report has all required fields.
238
unless ( $problem->{'expected_severity'} ) {
239
warning "problem_db: $.:"
240
.($problem ? " ".$problem->{'test_name'}.":" : "")
241
." expected_severity field is required.";
244
# The problem record database is indexed by the test name.
245
# Add the problem record to the database.
246
push @{$problem_database->{$problem->{'test_name'}}}, $problem;
248
$problem = undef; # Close the problem record.
250
# Anything else is a syntax error.
252
warning "problem_db: $.:"
253
.($problem ? " ".$problem->{'test_name'}.":" : "")
254
." Unrecognized line: '$line'";
261
#---------------------------------------------------------------------
263
# Finds problem record in the database.
264
sub find_problem_record {
267
my $test_name = $data{TEST_NAME};
268
my $test_point = $data{TEST_POINT};
269
my $severity = $data{SEVERITY};
270
my $test_messages = $data{MESSAGE};
272
# Some automatically generated problem reports have no test name or point;
273
# these are not meant to be searched in the database but directly reported
275
return undef if !$test_name;
277
# TODO: bind each problem record to version(s) of test suite.
278
# TODO: regexp in matcher.
280
# The basic record key is the name of the test and the test point number.
281
my $record_key = $test_name;
282
$record_key .= " " . $test_point if defined $test_point && $test_point ne "";
284
# Get all records matching the key
285
( $problem_database ) or return undef;
286
my $found_records = $problem_database->{$record_key};
288
return undef if !$found_records || !@$found_records; # Nothing found;
290
# Try finding the problem record.
292
foreach my $problem_record ( @$found_records ) {
294
# Check if the severity matches.
295
my $expected_severity = $problem_record->{'expected_severity'};
296
next PROBLEM_RECORD unless lc($severity) eq lc($expected_severity);
298
# Look which distinguishing markers the problem should have.
299
my $markers = $problem_record->{'markers'};
301
# Check if the markers are found in the test messages.
302
foreach my $marker ( @$markers ) {
303
unless ( index($test_messages, $marker) >= 0 ) {
304
# The message doesn't match to marker.
305
next PROBLEM_RECORD; # Try another problem record.
309
# All the markes match.
310
# Yup, this is the problem report we looked for.
311
return $problem_record;
317
#---------------------------------------------------------------------
319
# Escape HTML entities
323
( defined $text ) or return error "Undefined parameter";
325
$text =~ s/&/&/g;
332
#----------------------------------------------------------------------
336
my ($text, $max) = @_;
338
$max = 95 unless $max;
340
my $res = ""; my $line = ""; my $q = 0;
341
my $text_len = length $text;
343
for ( my $i=0; $i < $text_len; $i++ ) {
344
my $c = substr( $text, $i, 1 );
345
$line .= $c; $q++; # increase the line
347
$res .= $line; # flush the line
350
elsif ( $q >= $max && $i+1 < $text_len ) {
352
for ( ; $q > 0; $q-- ) {
353
last if ( substr($line, $q-1, 1) =~ /^\s+$/ );
357
$res .= substr( $line, 0, $q )."\n";
358
$line = substr( $line, $q ); $q = length $line;
365
#----------------------------------------------------------------------
372
$res = '<pre class="errmsg">'.html(pre_wrap($message, 80)).'</pre>';
376
#----------------------------------------------------------------------
378
# Makes a hyperlink to a file in the "results" subdir.
380
my ($href, $content, $more) = @_;
382
( defined $href ) or return error "Undefined parameter";
384
$content = $href if !defined $href;
386
if ( !defined $more ) {
387
$more = 'target="_blank"';
393
if ( $link !~ /^\// && defined $result_dir ) {
394
$link = $result_dir.'/'.$link;
396
if ( $link =~ /^\// ) {
397
$res .= '<a class="view_srv" '.$more.' href="'.html('/get.pl?file='.$link).'">'
401
if ( defined $result_dir && $href =~ /^\// ) {
402
if ( substr($href,0,length($result_dir)) eq $result_dir ) {
403
$href = substr($href,length($result_dir));
407
if ( $href !~ /^\.*\// ) {
410
$href =~ s/\?.*//; # remove get parameters if any
411
$res .= '<a class="view_as_is" '.$more.' href="'.html($href).'">'.$content.'</a>';
415
#----------------------------------------------------------------------
420
( defined $jstruct ) or return error "Undefined parameter";
421
( ref($jstruct) eq 'HASH' ) or return error "Wrong parameter";
423
my $kind = $jstruct->{KIND} or return error "Journal kind undefined";
424
my $file = $jstruct->{FILE} or return error "Journal file undefined";
426
if ( $kind eq 'TET' ) {
427
is_ok ParseTetJournal( $jstruct )
428
or return error "Failed to parse TET journal '$file'", $Error::Last;
430
elsif ( $kind eq 'SIMPLE' ) {
431
is_ok ParseSimpleJournal( $jstruct )
432
or return error "Failed to parse Simple journal '$file'", $Error::Last;
434
elsif ( $kind eq 'TCL' ) {
435
is_ok ParseTclJournal( $jstruct )
436
or return error "Failed to parse TCL journal '$file'", $Error::Last;
438
elsif ( $kind eq 'PYTHON' ) {
439
is_ok ParsePythonJournal( $jstruct )
440
or return error "Failed to parse Python journal '$file'", $Error::Last;
443
return error "Unknown journal kind: '$kind'";
446
# Calculate statistics
456
TESTS_EXPECTED => undef,
457
TESTSUITE_ERRORS => 0,
460
foreach my $obj ( @{$jstruct->{ARR}} ) {
461
$stats->{TESTS_TOTAL}++;
463
if ( ref($obj) eq "" ) {
466
if ( $obj =~ /^([^:]*):(.*)$/ ) {
468
if ( $severity eq "" ) { $severity = "passed" }
472
warning "Wrong jstruct entry: '$obj'";
476
my $gen_severity = $severity;
477
$gen_severity = $severity_map{$severity} if defined $severity_map{$severity};
479
if ( $gen_severity eq 'passed' ) {
482
elsif ( $gen_severity eq 'untested' ) {
483
$stats->{UNTESTED}++;
486
warning "Unknown severity code: '$severity'";
494
if ( ref($obj) ne "HASH" ) {
495
warning "Wrong entry type in jstruct";
499
my $severity = $obj->{SEVERITY};
501
my $gen_severity = $severity;
502
$gen_severity = $severity_map{$severity} if defined $severity_map{$severity};
504
$stats->{TESTS_TOTAL}-- if $severity =~ /^testsuite_/; # Not a real test case
506
if ( $severity =~ /(quelled|waived)/ ) { # Contains "quelled" or "waived"
507
$stats->{QUELLED}++; # Increment the quelled tests count statistic.
509
elsif ( $gen_severity eq "testsuite_error" ) {
510
$stats->{TESTSUITE_ERRORS}++;
512
elsif ( $gen_severity eq "failed" ) {
513
$stats->{FAILURES}++; # Increment the number of failures reported.
515
elsif ( $gen_severity eq "warning" ) {
516
$stats->{WARNINGS}++; # Increment the number of warnings reported.
518
elsif ( $gen_severity eq "fip" ) {
519
$stats->{FIPS}++; # Increment the number of FIPs reported.
521
elsif ( $gen_severity eq "notice" ) {
522
$stats->{NOTICES}++; # Increment the number of notices reported.
525
warning "Unhandled severity: '$severity' {$gen_severity}";
529
if ( defined $jstruct->{TESTS_EXPECTED} ) {
530
$stats->{TESTS_EXPECTED} = $jstruct->{TESTS_EXPECTED};
532
if ( $stats->{TESTS_EXPECTED} < 0 ) {
533
$stats->{TESTS_EXPECTED} = $stats->{TESTS_TOTAL} + (-$stats->{TESTS_EXPECTED});
537
if ( $stats->{TESTS_TOTAL} == 0 ) {
538
wrong_structure ( { JS => $jstruct }, "No tests in journal",
539
"The test journal contains no test records.", "testsuite_error" );
540
$stats->{TESTSUITE_ERRORS}++;
543
$jstruct->{STATS} = $stats;
547
#----------------------------------------------------------------------
549
sub ParseTetJournal {
552
my $file = $jstruct->{FILE} or return error "Journal file undefined";
554
open JOURNAL_FH, $file
555
or return error "Can't open file for reading: '$file': $!";
557
# Flags indicating that we are inside a some block.
559
my $inside_test_point = 0;
561
my $test_comment = undef;
562
my $test_messages = undef;
563
my $test_points_in_this_case;
566
my $context = { JS => $jstruct };
568
$context->{TEST_NAME} = undef;
570
my %config_vars = (); # For lines with Code 30 -- Configuration Variables.
572
# Read the journal line by line.
573
while ( my $line = <JOURNAL_FH> ) {
578
#$context->{LINE_TEXT} = $line;
579
$context->{LINE_NUM} = $line_number;
581
# Each journal line consists of three parts, separated by
584
# 1. A numeric line code, determining the meaning of the line.
585
# 2. Additional data, format depends on line code.
586
# 3. Additional text (message for the user, or some comment).
587
# e.g. '400|0 1 1 20:04:00|IC Start'
589
my ($line_code, $line_data, $line_text);
590
if ( $line =~ /^(\d*)\|([^\|]*)\|(.*)$/ ) {
596
# Lines that do not conform to this format are reported
597
# to the user and skipped.
598
wrong_structure ( $context, -NAME, "Wrong syntax",
599
"Unrecognized entry in journal." );
603
## Determine the type of the line and process it appropriately.
605
if ($line_code == 0) {
606
# Code 0 -- Test Case Controller Start.
607
# 0|version time date|who
608
my ($test_engine_version, $test_start_time, $test_start_date);
609
if ( $line_data =~ /^(\S*)\s+(\S*)\s+(\S*)$/ ) {
610
$test_engine_version = $1;
611
$test_start_time = $2;
612
$test_start_date = $3;
615
wrong_structure ( $context, -NAME, "Wrong syntax",
616
"Unrecognized entry in journal." );
620
# Print the test engine version, time and date to the output.
622
$info_table->{TITLE} = "Information about the test suit from the journal";
623
push @{$info_table->{ROWS}}, {LEFT => "Test Engine Version", RIGHT => $test_engine_version};
624
push @{$info_table->{ROWS}}, {LEFT => "Test Started At", RIGHT => $test_start_time." ".$test_start_date};
625
push @{$info_table->{ROWS}}, {LEFT => "Test Command Line", RIGHT => $line_text};
627
push @{$jstruct->{INFO_TABLES}}, $info_table;
629
elsif ($line_code == 5) {
630
# Code 5 -- Local System Information.
631
# The line structure is:
632
# 5|sysname nodename release date machine|text
633
my ($system_name, $node_name, $release, $date, $machine);
634
if ( $line =~ /^5\|(\S+)\s+(\S+)\s+(\S+)\s+#(.+)\s+(\S+)\|/ ) {
642
wrong_structure ( $context, -NAME, "Wrong syntax",
643
"Unrecognized entry in journal." );
647
# Save the local system data.
649
$info_table->{TITLE} = "Information about the system under test from the journal";
650
push @{$info_table->{ROWS}}, {LEFT => "System Name", RIGHT => $system_name};
651
push @{$info_table->{ROWS}}, {LEFT => "Node Name", RIGHT => $node_name};
652
push @{$info_table->{ROWS}}, {LEFT => "Release", RIGHT => $release.' #'.$date};
653
push @{$info_table->{ROWS}}, {LEFT => "Machine", RIGHT => $machine};
655
push @{$jstruct->{INFO_TABLES}}, $info_table;
657
elsif ($line_code == 10) {
658
# Code 10 -- Test Case Start.
659
# The line structure is:
660
# 10|activitynum test_name hh:mm:ss|list of invocable components
661
# * Timestamp is omitted in perl test reports. (Bug?)
662
my ($activitynum, $testname_param, $timestamp);
663
if ($line_data =~ /^(\d+)\s+(\S+)\s*(\S*)$/) {
665
$testname_param = $2;
669
wrong_structure ( $context, -NAME, "Wrong syntax",
670
"Unrecognized entry in journal." );
674
if ( defined $context->{TEST_NAME} ) {
675
# The previous test case was not properly finished;
677
wrong_structure ( $context, -NAME, "Previous TC hasn't finished",
678
"Test case ($testname_param) starts before"
679
." the previous one (".$context->{TEST_NAME}.") has finished"
683
# Set the test name and number.
684
$context->{TEST_NUM} = $activitynum;
685
$context->{TEST_NAME} = $testname_param;
687
# Reset the test point number and counter.
688
$context->{TEST_POINT} = 0;
689
$test_points_in_this_case = 0;
691
elsif ($line_code == 15) {
692
# Code 15 -- Test Case Manager Start.
694
elsif ($line_code == 20) {
695
# Code 20 -- Local/Remote System Configuration Start.
697
elsif ($line_code == 30) {
698
# Code 30 -- Configuration Variables.
699
# Format: 30||VARIABLE=value
700
if ($line =~ /^30\|\|([^=]+)=(.*)/) {
704
$config_vars{$varname} = $value;
707
elsif ($line_code == 40) {
708
# Code 40 -- Configuration End.
710
elsif ($line_code == 50) {
711
# Code 50 -- Test Case Controller Message.
712
# Format: 50||Message
713
process_test_point_result ( %$context,
714
SEVERITY => "testsuite_error",
715
COMMENT => "Test case controller reports a problem:",
716
MESSAGE => $line_text, );
718
elsif ($line_code == 70) {
719
# Code 70 -- Scenario Information.
721
# LSB-runtime-test only:
722
# for the .os sections need to double expected counts to allow
723
# for the macro versions.
724
if ( $line =~ /^70\|.*total tests in (ANSI|POSIX|LSB|PTHR).os ([0-9]+)/ ) {
725
$jstruct->{TESTS_EXPECTED} += $2 * 2;
727
elsif ( $line =~ /^70\|.*total tests in ([A-Za-z.0-9_-]+) ([0-9]+)/ ) {
728
$jstruct->{TESTS_EXPECTED} += $2;
730
elsif ( $line =~ /total\s+tests\s+in/ ) {
731
wrong_structure ( $context, "Wrong syntax",
732
"Unrecognized entry in journal." );
735
elsif ($line_code == 80) {
736
# Code 80 -- Test Case End.
737
if ( !defined $context->{TEST_NAME} ) {
738
# Test case end found when no test case was started;
740
wrong_structure ( $context, "Unexpected TC End",
741
"A Test Case End entry found when no test case seems to be running" );
744
# Warn if the test case seems to be empty.
745
if ( $test_points_in_this_case == 0 ) {
746
wrong_structure ( $context, "No test points",
747
"The test case contains no test points" );
750
# Check the test point returned any result
751
if ( $context->{TEST_POINT} > 0 ) {
752
wrong_structure ( $context, "No result",
753
"The test case returned no result", "unreported" );
756
# Clear the test name, number, messages, and others.
757
$context->{TEST_NAME} = undef;
758
$context->{TEST_NUM} = 0;
759
$test_comment = undef;
760
$test_messages = undef;
761
$test_points_in_this_case = 0;
763
elsif ($line_code == 90) {
764
# Code 90 -- User Abort.
765
process_test_point_result ( %$context,
766
SEVERITY => "testsuite_error",
767
RESOLUTION => "Interrupted by the user",
768
COMMENT => "Test suite execution was interrupted by the user"
771
elsif ($line_code == 100) {
772
# Code 100 -- Captured Output.
774
elsif ($line_code == 110) {
775
# Code 110 -- Build Start.
777
elsif ($line_code == 130) {
778
# Code 130 -- Build End.
780
elsif ($line_code == 200) {
781
# Code 200 -- Test Purpose Start.
782
# The structure of the line is as follows:
783
# 200|activitynum pointnum hh:mm:ss|comment
784
if ($line =~ /^\d*\|(\d*)\s*(\d*)/) {
787
wrong_structure ( $context, "Wrong syntax",
788
"Unrecognized entry in journal." );
792
# Check if the previous test point was properly finished.
793
if ( $context->{TEST_POINT} > 0 ) {
794
if ( $context->{TEST_POINT} == $2 ) {
795
# Some test suites have a strange habit of re-starting
800
wrong_structure ( $context, "Restarted in the middle",
801
"The test was restarted in the middle", "testsuite_notice");
805
# Notify about a disappeared test.
806
wrong_structure ( $context, "Previous test point hasn't finished",
807
"A test point starts before the previous one"
808
." (".$context->{TEST_POINT}.") has finished" );
812
if ( $line_text ne "" && $line_text ne "TP Start" ) {
813
$test_comment = "Test Purpose Comment: ".$line_text;
815
$test_comment = undef;
818
# Clear the messages.
819
$test_messages = undef;
821
# Change the test point number.
822
$context->{TEST_POINT} = $2;
824
# Increment the number of test points.
825
$test_points_in_this_case++;
827
elsif ($line_code == 220) {
828
# Code 220 -- Test Purpose Result.
829
# The structure of the line is as follows:
830
# 220|activitynum pointnum result time|comment
831
my ($activitynum, $pointnum, $result_code, $timestamp);
832
if ($line_data =~ /^(\d+)\s+(\d+)?\s+(\d+)\s+(\S+)$/) {
839
wrong_structure ( $context, "Wrong syntax",
840
"Unrecognized entry in journal." );
844
# Print a warning if the test purpose seems to never been started.
845
if ( $context->{TEST_POINT} == 0 ) {
846
wrong_structure ( $context, "Unexpected test result entry",
847
"A test result entry found but the test point had not started" );
850
# Print a warning if the test purpose number is different from
852
if ( defined $pointnum && $context->{TEST_POINT} != $pointnum) {
853
wrong_structure ( $context, "Unexpected TP number",
854
"The test purpose number ($2) differs"
855
." from the one (".$context->{TEST_POINT}.") at the beginning of the test." );
858
# Find the meaning of the result code (severity).
859
if ( !defined $result_code_map{$result_code} ) {
860
wrong_structure ( $context, "Unknown result code",
861
"Unrecognized test result code ($result_code)" );
865
my $severity = $result_code_map{$result_code};
867
# Process the result.
868
process_test_point_result ( %$context,
869
SEVERITY => lc $severity,
870
MESSAGE => $test_messages,
871
COMMENT => $test_comment,
874
# Clear the test point number.
875
$context->{TEST_POINT} = 0;
877
elsif ($line_code == 310) {
878
# 310 -- Clean Start.
880
elsif ($line_code == 320) {
883
elsif ($line_code == 400) {
884
# 400 -- Invocable Component Start.
886
elsif ($line_code == 410) {
887
# 410 -- Invocable Component End.
889
# Check the test point returned any result
890
if ( $context->{TEST_POINT} > 0 ) {
891
wrong_structure ( $context, "No result",
892
"The test case returned no result", "unreported" );
893
$context->{TEST_POINT} = 0;
896
elsif ($line_code == 510) {
897
# 510 -- Error reported by the underlying test suite.
898
# Format: 510|testnum|Message
899
process_test_point_result ( %$context,
900
SEVERITY => "testsuite_error",
901
COMMENT => "Error reported by the test suite:",
902
MESSAGE => $line_text, );
904
elsif ($line_code == 520) {
905
# 520 -- Message from the underlying test suite.
907
# Coalesce consequent rows of spaces in the message
908
# to a single space. This improves matching with markers.
909
my $message = $line_text;
910
$message =~ s/\s+/ /g;
912
# Add the message to the accumulated messages.
913
$test_messages .= $message."\n";
915
elsif ($line_code == 600) {
916
# 600 -- Parallel Start.
918
elsif ($line_code == 620) {
919
# 620 -- Parallel End.
921
elsif ($line_code == 630) {
922
# 630 -- Implied Sequential Start.
924
elsif ($line_code == 640) {
925
# 640 -- Implied Sequential End.
927
elsif ($line_code == 700) {
928
# 700 -- Repeat Start.
930
elsif ($line_code == 720) {
933
elsif ($line_code == 730) {
934
# 730 -- Timed Loop Start.
936
elsif ($line_code == 740) {
937
# 740 -- Timed Loop End.
939
elsif ($line_code == 750) {
940
# 750 -- Random Start.
942
elsif ($line_code == 760) {
945
elsif ($line_code == 800) {
946
# 800 -- Remote Start.
948
elsif ($line_code == 820) {
951
elsif ($line_code == 830) {
952
# 830 -- Distributed Start.
954
elsif ($line_code == 840) {
955
# 840 -- Distributed End.
957
elsif ($line_code == 900) {
958
# 900 -- Test Case Controller End.
961
# Other lines are notified to the user and otherwise ignored.
962
wrong_structure ( $context, "Unrecognized entry",
963
"Unrecognized journal entry" );
970
# Save the configuration info.
971
if ( %config_vars ) {
973
$info_table->{TITLE} = "Configuration information from the journal";
975
for my $varname qw(VSX_NAME LSB_VERSION LSB_MODULES TET_VERSION XT_FONTPATH) {
976
if ( defined $config_vars{$varname} ) {
977
push @{$info_table->{ROWS}},
978
{ LEFT => $varname, RIGHT => $config_vars{$varname} };
979
delete $config_vars{$varname};
983
push @{$jstruct->{INFO_TABLES}}, $info_table;
986
#----------------------------------------------------------------------
988
sub ParseSimpleJournal {
991
my $file = $jstruct->{FILE} or return error "Journal file undefined";
993
open JOURNAL_FH, $file
994
or return error "Can't open file for reading: '$file': $!";
996
# Flags indicating that we are inside a some block.
999
my $test_description = undef;
1000
my $test_comment = undef;
1001
my $test_message = undef;
1003
my $line_number = 0;
1005
my $context = { JS => $jstruct };
1007
$context->{TEST_NAME} = undef;
1009
# Read the journal line by line.
1010
while ( my $line = <JOURNAL_FH> ) {
1015
$line =~ s/^\s+//; # Trim spaces
1018
next if $line eq ""; # Skip empty lines
1019
next if $line =~ /^#/; # Skip comments
1021
$context->{LINE_NUM} = $line_number;
1022
#$context->{LINE_TEXT} = $line;
1024
my ($linetype, $value);
1026
# Format: "LINETYPE: some text"
1027
if ( $line =~ /^([A-Z_\-]+)\s*(:\s*(.*))?$/ ) {
1032
# Lines that do not conform to this format are reported
1033
# to the user and skipped.
1034
wrong_structure( $context, -NAME, "Wrong syntax",
1035
"Unrecognized entry in journal." );
1039
if ( $linetype eq 'TEST') {
1042
elsif ( $linetype eq 'TESTS_EXPECTED' ) {
1043
$jstruct->{TESTS_EXPECTED} = $value;
1045
elsif ( $linetype eq 'TESTCASE' ) {
1046
if ( defined $context->{TEST_NAME} ) {
1047
# Previous test case wasn't closed
1048
wrong_structure_line( $context, "Unclosed test case",
1049
"New testcase '$value' was started"
1050
." before the previous one has closed: '".$context->{TEST_NAME}."'." );
1053
$context->{TEST_NAME} = $value;
1054
$test_description = undef;
1055
$test_comment = undef;
1056
$test_message = undef;
1058
elsif ( $linetype =~ /^(DESCR|COMMENT|MESSAGE)$/ ) {
1059
if ( !defined $context->{TEST_NAME} ) {
1060
wrong_structure ( $context, "Unexpected line",
1061
"$linetype beyond a test block." );
1065
if ( $linetype eq 'DESCR' ) {
1066
$test_description .= $value."<br />\n";
1068
elsif ( $linetype eq 'COMMENT' ) {
1069
$test_comment .= $value."<br />\n";
1071
elsif ( $linetype eq 'MESSAGE' ) {
1072
$test_message .= $value."\n";
1075
wrong_structure ( $context, "Missed line type",
1076
"Missed line type: '$linetype'." );
1079
elsif ( $linetype eq 'RESULT' ) {
1080
# Process the result.
1081
process_test_point_result ( %$context,
1082
SEVERITY => lc $value,
1083
DESCRIPTION => $test_description,
1084
COMMENT => $test_comment,
1085
MESSAGE => $test_message,
1088
$context->{TEST_NAME} = undef;
1089
$test_description = undef;
1090
$test_comment = undef;
1091
$test_message = undef;
1093
elsif ( $linetype eq 'FINISHED' ) {
1094
# This record indicates the journal was closed correctly
1098
wrong_structure( $context, "Wrong syntax",
1099
"Unhandled line type: '$linetype'." );
1103
if ( defined $context->{TEST_NAME} ) {
1104
# Unclosed test case
1105
wrong_structure ( $context, "Unclosed test case",
1106
"Unclosed test case: '".$context->{TEST_NAME}."'." );
1110
# Unclosed test case
1111
wrong_structure ( $context, -NAME, "Incomplete journal",
1112
"The journal wasn't closed correctly." );
1115
#----------------------------------------------------------------------
1117
# The Tcl-style journal parser function.
1118
sub ParseTclJournal {
1121
my $file = $jstruct->{FILE} or return error "Journal file undefined";
1123
open JOURNAL_FH, $file
1124
or return error "Can't open file for reading: '$file': $!";
1126
# Flags indicating that we are inside a some block.
1129
my $test_messages = undef;
1131
my $line_number = 0;
1133
my $context = { JS => $jstruct };
1135
$context->{TEST_NAME} = undef;
1137
# Read the journal line by line.
1138
while ( my $line = <JOURNAL_FH> ) {
1143
# Do not trim spaces at the beginning!
1144
$line =~ s/\s+$//; # Trim spaces at the end of the line.
1146
# Don't skip empty lines
1148
$context->{LINE_NUM} = $line_number;
1149
#$context->{LINE_TEXT} = $line;
1151
# A test record message ends with a line with a rough format
1152
# '==== testname result'
1153
if ( $line =~ /^=+\s*([a-zA-Z0-9_\-\.]+)\s*FAILED/ ) {
1155
if ( !defined $context->{TEST_NAME} ) {
1156
# Oops, we have missed something...
1157
wrong_structure ( $context, "Failure",
1158
"End of a test record found but no start was seen." );
1161
# Process the result.
1162
process_test_point_result ( %$context,
1163
SEVERITY => "failed",
1164
MESSAGE => $test_messages,
1167
# Clear the previous test messages.
1168
$test_messages = undef;
1170
# Finish the test record.
1171
$context->{TEST_NAME} = undef;
1173
# A failure report starts with a line with a rough format
1174
# '==== testname errormessage result'.
1175
elsif ($line =~ /^=+\s*([a-zA-Z0-9_\-\.]+)\s*(.+)\s*FAILED/) {
1177
# Load the test name.
1178
$context->{TEST_NAME} = $1;
1180
# Add the error message to the test messages.
1181
$test_messages .= $2."\n";
1183
# A line that contains only the name of the test (which ends
1184
# with '.test', marks a passed test.
1185
# There could be some garbage at the beginning of the line (from a previous test).
1186
elsif ($line =~ /^(.*\s+)?([a-zA-Z0-9_\-]+)\.test$/) {
1187
$context->{TEST_NAME} = $2;
1188
$test_messages = undef;
1190
# Process the result.
1191
process_test_point_result ( %$context,
1192
SEVERITY => "passed",
1194
$context->{TEST_NAME} = undef;
1198
if ( defined $context->{TEST_NAME} ) {
1199
# We are inside a test point result record, so it will be
1201
$test_messages .= $line."\n";
1204
# Anything else is better ignored (there is too much noise
1205
# for considering unrecognized lines to be errors).
1211
#----------------------------------------------------------------------
1213
# The Python-style journal parser function.
1214
sub ParsePythonJournal {
1217
my $file = $jstruct->{FILE} or return error "Journal file undefined";
1219
open JOURNAL_FH, $file
1220
or return error "Can't open file for reading: '$file': $!";
1222
my $severity = undef;
1223
my $test_messages = undef;
1224
my $line_number = 0;
1227
my $context = { JS => $jstruct };
1229
local *check_prev_tc = sub {
1230
my ($new_test_name) = @_;
1232
if ( defined $context->{TEST_NAME} && $context->{TEST_NAME} ne $new_test_name ) {
1233
# Save the previous test case
1234
process_test_point_result ( %$context,
1235
SEVERITY => $severity,
1236
MESSAGE => $test_messages
1241
# Read the journal line by line.
1242
while (my $line = <JOURNAL_FH>) {
1247
# Do not trim spaces at the beginning!
1248
$line =~ s/\s+$//; # Trim spaces at the end of the line.
1250
# Don't skip empty lines
1252
#$context->{LINE_TEXT} = $line;
1253
$context->{LINE_NUM} = $line_number;
1255
if ( $line =~ /^(test_[a-zA-Z0-9_]+)$/ ) {
1256
# Looks like a test name
1259
$context->{TEST_NAME} = $1;
1260
$severity = "passed";
1261
$test_messages = undef;
1263
# A line in form 'testname skipped -- ...' marks a test
1264
# that was skipped, and the end of the line contains
1265
# the description why.
1266
elsif ( $line =~ /^(test_[a-zA-Z0-9_]+)\s*skipped\s*--\s*(.*)$/ ) {
1269
$context->{TEST_NAME} = $1;
1270
$severity = "unsupported";
1271
$test_messages = $2;
1273
# A line in form 'test testname failed -- ...' marks a failed test.
1274
elsif ( $line =~ /^test\s*([a-zA-Z0-9_]+)\s*(failed|crashed)\s*--\s*(.*)$/ ) {
1277
$context->{TEST_NAME} = $1;
1279
$severity = "failed" if $severity eq "crashed";
1280
$test_messages = $3;
1283
if ( $line =~ /^(\d+) tests OK/ ) {
1284
# End of the journal
1287
$context->{TEST_NAME} = undef;
1292
if ( defined $context->{TEST_NAME} ) {
1293
# Just append this line to the test record messages
1294
# and continue by the next line.
1295
$test_messages .= $line."\n";
1299
# This should be lines with some statistics at the end of the journal.
1300
if ( $line =~ /^(\d+)\s+skips\s+unexpected/ ) {
1301
$jstruct->{TESTS_EXPECTED} = -$1; # Negative value means SKIPS_UNEXPECTED
1302
# TESTS_EXPECTED = TESTS_TOTAL + SKIPS_UNEXPECTED;
1305
# Other lines are ignored.
1312
wrong_structure ($context, -NAME, "Journal wasn't finished");
1315
#----------------------------------------------------------------------
1317
sub wrong_structure {
1318
my $context = shift;
1319
my %data = %$context;
1320
if ( defined $_[0] && ref($_[0]) eq '' && $_[0] eq '-NAME' ) {
1321
delete $data{TEST_NAME};
1322
delete $data{TEST_POINT};
1325
my $resolution = shift || "Wrong structure";
1326
my $comment = shift || $resolution;
1327
my $severity = shift || "testsuite_warning";
1329
process_test_point_result ( %data,
1330
SEVERITY => lc $severity,
1331
RESOLUTION => $resolution,
1332
COMMENT => $comment,
1335
#----------------------------------------------------------------------
1337
sub process_test_point_result {
1340
( defined $data{SEVERITY} ) or return error "undefined severity";
1342
my $gen_severity = ( $severity_map{$data{SEVERITY}} or $data{SEVERITY} );
1343
my $severity_ok = ( $gen_severity eq "passed" || $gen_severity eq "untested" );
1345
my $problem_record = undef;
1347
unless ( $severity_ok ) {
1348
# Search for the problem record in the database.
1349
$problem_record = find_problem_record( %data );
1350
if ( !is_ok($problem_record) ) {
1351
warning $Error::Last;
1352
$problem_record = undef;
1356
if ( $problem_record ) {
1358
$data{PROBLEM_INFO} = $problem_record;
1360
if ( $problem_record->{'quell'} ) {
1361
if ( defined $severity_map{ $problem_record->{'quell'} } ) {
1362
# If 'quell' contains severity name, that severity will be forced.
1363
$data{SEVERITY} = lc $problem_record->{'quell'};
1365
# Add the "(quelled)" keyword to the severity if the problem is quelled.
1366
# This means it can be safely ignored.
1367
if ( $problem_record->{'lsb_report_id'} ) {
1368
$data{SEVERITY} .= " (waived)";
1370
$data{SEVERITY} .= " (quelled)";
1375
$data{RESOLUTION} = $problem_record->{"resolution"} if $problem_record->{"resolution"};
1377
if ( $data{RESOLUTION} =~ /^\s*$/ ) {
1378
# If resolution is empty, put "..." in its place to indicate,
1379
# that some information about the problem is available.
1380
$data{RESOLUTION} = "...";
1384
my $testcase = $data{TEST_NAME};
1385
$testcase = "***" if !defined $testcase; # testsuite error
1387
$testcase .= " ".$data{TEST_POINT} if defined $data{TEST_POINT} && $data{TEST_POINT} ne "";
1389
if ( $severity_ok ) {
1390
# For severity_ok save the testcase only (scalar)
1391
if ( $data{SEVERITY} eq "passed" ) {
1392
push @{$data{JS}->{ARR}}, ":".$testcase;
1394
push @{$data{JS}->{ARR}}, $data{SEVERITY}.":".$testcase;
1397
# For problems save the entire record (hashref)
1398
my $record = { %data };
1399
delete $record->{JS};
1401
push @{$data{JS}->{ARR}}, $record;
1404
#----------------------------------------------------------------------
1406
sub print_info_table {
1407
my ($info_table) = @_;
1411
# Print the local system data.
1412
$res .= '<table class="info">'."\n";
1413
if ( $info_table->{TITLE} ) { # Table title
1414
$res .= '<tr><th colspan="2" class="title">'.$info_table->{TITLE}.'</th></tr>'."\n";
1416
foreach my $row ( @{$info_table->{ROWS}} ) {
1417
my $first_column = $row->{LEFT};
1418
my $second_column = $row->{RIGHT};
1419
my $background_color = $row->{BG_COLOR};
1420
my $foreground_color = $row->{FG_COLOR};
1421
$res .= info_line( $first_column, $second_column, $background_color, $foreground_color );
1427
#----------------------------------------------------------------------
1429
# Prints a single line of a two-column table.
1432
# $1 - The first column.
1433
# $2 - The second column.
1434
# $3 (optional) - The foreground color of the second column.
1435
# $4 (optional) - The background color of the second column.
1437
my ($first_column, $second_column, $background_color, $foreground_color ) = @_;
1439
if ( !defined $second_column ) {
1440
$second_column = '<i style="color:'.$neutral_gray.'">(undef)</i>';
1443
# Compile the style of the first column
1444
my $style1 = "white-space:nowrap;";
1446
$style1 = ' style="'.$style1.'"';
1448
# Compile the style of the second column
1451
$style2 .= "text-align:right;";
1453
if ( $foreground_color ) {
1454
$style2 .= "color:$foreground_color;";
1456
if ($background_color) {
1457
$style2 .= "background-color:$background_color;";
1460
$style2 = ' style="'.$style2.'"';
1462
# Print the line with appropriate decorations.
1463
my $res = "<tr><th".$style1.">".$first_column."</th>\n"
1464
."<td".$style2.">".$second_column."</td>\n"
1469
#----------------------------------------------------------------------
1474
my $no_problems = 1;
1478
$pt .= problem_table_header();
1480
foreach my $obj ( @{$jstruct->{ARR}} ) {
1481
next if ref($obj) eq "";
1483
if ( ref($obj) ne "HASH" ) {
1484
warning "Wrong jstruct element: '$obj'";
1488
$no_problems = 0 if $no_problems;
1490
$pt .= problem_table_line( $obj, $jstruct );
1493
$pt .= "</table>\n";
1497
if ( defined $jstruct->{INFO_TABLES} ) {
1498
$res .= print_info_table($_) foreach ( @{$jstruct->{INFO_TABLES}} );
1501
if ( $no_problems ) {
1502
$res .= '<h4>Problem summary:</h4>';
1503
$res .= '<p style="font-weight:bold;color:'.$dark_green.'">'
1504
.'No problems were detected.</p>';
1513
#----------------------------------------------------------------------
1515
# Prints a header of the problem table.
1516
sub problem_table_header {
1517
my $res = <<HERETEXT;
1518
<h4>Problem Summary</h4>
1520
<p>Click on lines in the table to see the details about each problem.</p>
1522
<table width="100%" class="problem_table">
1523
<thead><tr style="background-color:$gray">
1524
<th>Test Name</th><th>Severity</th><th>Resolution</th>
1530
#---------------------------------------------------------------------
1532
sub problem_table_line {
1533
my ( $data, $jstruct ) = @_;
1535
my $severity = $data->{SEVERITY};
1536
my $gen_severity = ( $severity_map{$severity} or $severity );
1539
if ( $severity =~ /(quelled|waived)/i ) {
1540
# Anything what is quelled is harmless, so use neutral gray
1541
# for all quelled problems.
1544
elsif ( $gen_severity eq "failed" || $gen_severity eq "testsuite_error" ) {
1545
$color = "red"; # Use fluorescent red for various failures.
1547
elsif ( $gen_severity eq "warning" ) {
1548
$color = "yellow"; # Use fluorescent yellow for warnings.
1550
elsif ( $gen_severity eq "fip" ) {
1551
$color = "green"; # Use fluorescent green for FIPs.
1553
$severity .= '<span class="view_srv"> '
1554
.'<a href="tests_help.pl#FIP" title="Help">' # HELP icon
1555
.'<img src="images/question.png" width="16" height="17" alt="[?]" />'
1558
elsif ( $gen_severity eq "notice" || $gen_severity eq "untested" ) {
1559
# Actually, untested test cases shouldn't displayed
1560
$color = "gray"; # Use neutral gray for harmless messages.
1563
warning "Unrecognized severity: '$severity'";
1564
$severity = "Unrecognized severity: '$severity'";
1569
# Give the box a unique ID.
1570
my $message_box_id = "m".(++$box_id);
1573
# (with attached script that opens the detail block on clicking).
1574
$res .= '<tr class="ptl_'.$color.'" title="Click here!"'
1575
.' onclick="flip(\''.$message_box_id.'\');">';
1577
# Print the test name and test point (if available).
1579
if ( defined $data->{TEST_NAME} ) {
1580
$res .= html( $data->{TEST_NAME} );
1582
$res .= "<i>(none)</i>";
1585
if ( defined $data->{TEST_POINT} ) {
1586
$res .= ' <span style="color:'.$dark_blue.';">'
1587
.html($data->{TEST_POINT}).'</span>';
1592
$severity = ($data->{SEVERITY} or "");
1593
$severity =~ s/ +/ /g; # Make spaces nonbreakable
1594
$res .= '<td class="fw1">'.$severity."</td>\n";
1597
$res .= '<td class="fw2">'.($data->{RESOLUTION} or "")."</td>\n";
1602
# Print the detail block, which is initially hidden and unrolls when
1603
# the table row is clicked (via the javascript).
1604
$res .= '<tr>'."\n";
1605
$res .= '<td colspan="3" class="pdu">';
1606
$res .= '<div class="ptd_'.$color.'" id="'.$message_box_id.'"'
1607
.' style="display:none">';
1609
if ( defined $data->{DESCRIPTION} ) { # Test description
1610
$res .= '<h4>Test description:</h4>'."\n";
1611
#$res .= '<pre>'.html(pre_wrap($data->{DESCRIPTION}, 100)).'</pre>'."\n";
1612
$res .= '<p>'.$data->{DESCRIPTION}.'</p>'."\n";
1616
if ( defined $data->{MESSAGE} ) {
1617
$res .= '<pre>'.html(pre_wrap($data->{MESSAGE}, 100)).'</pre>'."\n";
1621
if ( defined $data->{COMMENT} && $data->{COMMENT} ne "" ) {
1622
$res .= '<p>'.$data->{COMMENT}.'</p>'."\n";
1626
$res .= '<h4>Open the journal:</h4>'."\n";
1629
my $href = $jstruct->{FILE};
1630
$href .= '&line='.$data->{LINE_NUM}.'#line'.$data->{LINE_NUM} if defined $data->{LINE_NUM};
1632
my $text = $jstruct->{FILE};
1633
$text .= ':'.$data->{LINE_NUM} if defined $data->{LINE_NUM};
1635
$res .= local_href($href, $text);
1637
$res .= '</p>'."\n";
1641
# Problem info from problem DB
1642
my $pr_info = $data->{PROBLEM_INFO};
1644
$res .= '<h4>Problem info:</h4>'."\n";
1646
if ( $pr_info->{'comment'} ) {
1647
$res .= '<p>'.html($pr_info->{'comment'}).'</p>'."\n";
1649
if ( $pr_info->{'lsb_report_id'} ) {
1650
my $pr_id = $pr_info->{'lsb_report_id'};
1651
my $link = "https://www.linuxfoundation.org/lsbprs/pr/".$pr_id;
1652
$res .= '<p>Reported to the LSB Committee as'
1653
.' <a href="'.$link.'" target="_blank">PR '.$pr_id.'</a>.</p>'."\n";
1659
# "Report bug in test" button.
1660
if ( $color eq "red" # If a bug
1661
&& (!$pr_info || !$pr_info->{'lsb_report_id'} ) # And not yet reported
1671
my $test_suite = quot($jstruct->{TESTSUITE} or "undef");
1673
my $test_case = ( $data->{TEST_NAME} or "..." );
1674
if ( defined $data->{TEST_POINT} && $data->{TEST_POINT} ne "" ) {
1675
$test_case .= ' '.$data->{TEST_POINT};
1677
$test_case = quot($test_case);
1679
$severity = quot($data->{SEVERITY} or "");
1680
my $file = quot($jstruct->{FILE} or "");
1684
<input type="button" value="Report Bug in the Test"
1685
onclick="javascript:bug_report('$test_suite', '$test_case', '$severity', '$file');"
1686
title="Press this button if you believe the problem is in the test, not in your system." />
1692
# Finish the table line
1694
$res .= "</td></tr>\n";
1698
#----------------------------------------------------------------------
1701
# Read the runconfig file
1702
my $runconfig = read_config( $result_dir."/runconfig" );
1703
is_ok($runconfig) or return error "Failed to read runconfig", $Error::Last;
1705
my $tests = $runconfig->{SECTIONS};
1707
# Information in the 'runconfig' file is for non-executed tests
1708
# For executed tests information is obtained from <test>.info files.
1710
# Read tests' .info files
1711
my @info_files = glob($result_dir."/results/*.info");
1712
foreach my $info_file ( @info_files ) {
1713
my $test_info = read_config( $info_file );
1714
my $test_name = $test_info->{NAME} or next;
1715
$tests->{$test_name} = $test_info;
1718
for my $test_name ( keys %$tests ) {
1719
my $test_info = $tests->{$test_name};
1721
if ( !$test_info->{VERDICT} ) { $test_info->{VERDICT} = "incomplete"; }
1723
$test_info->{REPORT} = "";
1726
$test_info->{REPORT} .= '<h2>';
1728
$test_info->{REPORT} .= '<a name="'.$test_info->{NAME}.'"></a>'
1729
.'<font color="#336699">'.html($test_info->{DISPLAYNAME}).'</font>'
1732
if ( defined $test_info->{VERSION} ) {
1733
$test_info->{REPORT} .= '<font size="-1" color="'.$neutral_gray.'">'
1734
.'v. '.html($test_info->{VERSION}).'</font>';
1737
if ( $test_info->{TRIAL_USE} && $cert_mode ) {
1738
$test_info->{REPORT} .= ' <span style="color:'.$fluorescent_green.'">'
1739
.'TRIAL USE</span>';
1742
$test_info->{REPORT} .= '</h2>'."\n";
1744
# Read .report.part1 file
1745
my $report_filename = $result_dir."/results/".$test_info->{NAME}.".report.part1";
1746
my $report_part = read_file( $report_filename );
1747
if ( is_ok($report_part) ) {
1748
$test_info->{REPORT} .= $report_part;
1752
if ( !$test_info->{STARTED} ) {
1753
if ( $test_info->{MANUAL} ) {
1754
$test_info->{REPORT} .= "<p>The test hasn't been prepared.</p>\n";
1756
$test_info->{REPORT} .= "<p>The test hasn't been executed.</p>\n";
1759
$test_info->{REPORT} .= print_testinfo_table( $test_info );
1762
# Add .report.part2 file
1763
$report_filename = $result_dir."/results/".$test_info->{NAME}.".report.part2";
1764
$report_part = read_file( $report_filename );
1765
if ( is_ok($report_part) ) {
1766
$test_info->{REPORT} .= $report_part;
1769
$test_info->{REPORT} .= "\n<hr />\n\n";
1772
my @auto_tests = grep {!$_->{MANUAL} && !($cert_mode && $_->{TRIAL_USE})} values %$tests;
1774
if ( @auto_tests ) {
1775
$verdict = overall_verdict( map {$_->{VERDICT}} @auto_tests );
1777
my @man_tests = grep {$_->{MANUAL} && !($cert_mode && $_->{TRIAL_USE})} values %$tests;
1778
my $verdict_man = "";
1780
$verdict_man = overall_verdict( map {$_->{VERDICT}} @man_tests );
1785
$res .= Head( $runconfig );
1787
my @sort_order = ();
1788
$res .= Tree_toc( $tests, \@sort_order );
1790
$res .= "\n<hr />\n\n";
1792
my @tests = sort { in_array($a, @sort_order) <=> in_array($b, @sort_order) } keys %$tests;
1794
foreach my $test_name ( @tests ) {
1795
if ( !defined $tests->{$test_name}->{REPORT} ) {
1796
warning "No report for '$test_name'";
1799
$res .= $tests->{$test_name}{REPORT};
1804
# Write the report to file
1805
my $report_file = $result_dir."/report.htm";
1806
is_ok write_string_as_file( $report_file, $res)
1807
or complain "Failed to write the report to '$report_file'", $Error::Last;
1809
return $verdict, $verdict_man;
1810
} # End of Compile()
1811
#-----------------------------------------------------------------------
1813
sub print_testinfo_table {
1814
my ($test_info) = @_;
1819
my $start_time = $test_info->{START_TIME};
1820
my $finish_time = $test_info->{FINISH_TIME};
1822
$res .= '<table class="info">'."\n";
1824
if ( $start_time ) {
1825
$res .= info_line('Started At', format_time($start_time) );
1826
if ( $finish_time ) {
1827
$res .= info_line('Execution Time', format_duration($finish_time - $start_time) );
1830
if ( !$test_info->{MANUAL} ) {
1831
$res .= info_line('Started At', "NOT STARTED", $fluorescent_red );
1835
$res .= info_line( 'Verdict:',
1836
verdict_text( $test_info->{VERDICT} ),
1837
verdict_color( $test_info->{VERDICT} )
1840
$res .= '</table>'."\n";
1848
# Deduce the verdict
1849
my $verdict = undef;
1850
if ( $stats->{FAILURES} || $stats->{TESTSUITE_ERRORS} ) {
1851
$verdict = "failed";
1853
elsif ( $stats->{WARNINGS} ) {
1854
$verdict = "warning";
1856
elsif ( defined $stats->{TESTS_EXPECTED} && defined $stats->{TESTS_TOTAL}
1857
&& $stats->{TESTS_EXPECTED} != $stats->{TESTS_TOTAL} )
1859
$verdict = "warning";
1862
$verdict = "passed";
1866
#-----------------------------------------------------------------------
1868
my %verdict_sort = ( 'undefined' => 0, 'incomplete' => 1, 'run_problems' => 2, 'failed' => 3,
1869
'warning' => 4, 'passed' => 5 , '' => 6 );
1871
sub overall_verdict {
1874
foreach my $verdict ( @_ ) {
1875
$verdict = "" if !defined $verdict;
1876
if ( $verdict_sort{$verdict} < $verdict_sort{$res} ) {
1881
if ( $res eq 'undefined' ) { warning "Undefined verdict"; }
1885
#----------------------------------------------------------------------
1888
my ($runconfig) = @_;
1892
# TODO: read from file ../webui/public_html/css/report.css
1897
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1898
<title>Test Results</title>
1900
<style type="text/css">
1903
font-family: sans-serif;
1907
h1 { border-bottom: 1px solid #aaa; }
1908
h2 { border-bottom: 1px dashed #aaa; }
1911
text-decoration: none;
1916
text-decoration: underline;
1925
background: #f9ffaa;
1926
border: dashed 1px red;
1928
margin-bottom: 15px;
1935
margin: 1em 1em 6pt 0;
1936
background: #f9f9f9;
1937
border: 1px #aaaaaa solid;
1938
border-collapse: collapse;
1940
table.info th, table.info td {
1941
border: 1px #aaaaaa solid;
1943
padding-left: 0.4em;
1944
padding-right: 0.4em;
1948
border-collapse: collapse;
1949
background: #E6E6E6;
1951
table.toc th, table.toc td {
1952
border: 1px #FFFFFF solid;
1954
padding-left: 0.4em;
1955
padding-right: 0.4em;
1958
table.info th, table.toc th {
1959
background: #f2f2f2;
1962
table.info th.title {
1963
background: #E5E5E5;
1966
table.problem_table {
1968
border-collapse: collapse;
1970
table.problem_table thead th {
1971
background-color: $gray;
1974
table.problem_table tr td {
1975
border: 1px solid white;
1978
td.pd, td.pdu { /* Problem details */
1987
.fw1 { width:12%; text-align: center; }
1990
.ptl_gray { background: #E6E6E6;}
1991
.ptl_gray:hover { background: #D6D6F0;}
1993
.ptl_green { background: #CDFFCD;}
1994
.ptl_green:hover { background: #A0FFA0;}
1996
.ptl_yellow { background: #FFFFCD;}
1997
.ptl_yellow:hover { background: #FFFF90;}
1999
.ptl_red { background: #FFCDCD;}
2000
.ptl_red:hover { background: #FFA0A0;}
2002
.ptl_gray, .ptl_green, .ptl_yellow, .ptl_red { cursor: pointer }
2004
.ptd_gray, .ptd_green, .ptd_yellow, .ptd_red {
2005
padding: 1pt 16pt 5pt 16pt;
2006
margin: 3pt 5pt 10pt 5pt;
2008
.ptd_gray { border-left: 5pt solid #D6D6F0 }
2009
.ptd_green { border-left: 5pt solid #A0FFA0 }
2010
.ptd_yellow { border-left: 5pt solid #FFFF90 }
2011
.ptd_red { border-left: 5pt solid #FFA0A0 }
2013
.view_srv { display: none; }
2015
.legal-text { width: 100%; font-size: xx-small; text-align:center; color: gray; }
2021
my $rep_platform = "All";
2022
if ( $runconfig->{'HOST_architecture'} ) {
2023
$rep_platform = $runconfig->{'HOST_architecture'};
2024
if ( $rep_platform eq 'x86' ) { $rep_platform = 'IA32'; }
2025
if ( $rep_platform eq 'x86-64' ) { $rep_platform = 'AMD64'; }
2026
if ( !in_array($rep_platform, "IA32", "AMD64", "IA64", "PPC32", "PPC64", "S390", "S390X") ) {
2027
$rep_platform = "All";
2031
if ( $runconfig->{'HOST_OS'} ) {
2032
for my $s ( "Asianux", "BOSS", "Booyo", "Debian", "Fedora", "Linpus", "Mandriva",
2033
"OpenSuse", "Red Flag", "Red Hat", "Sun Wah", "SuSE", "ThizLinux", "Turbolinux",
2034
"Ubuntu", "United Linux", "Xandros" )
2038
if ( $runconfig->{'HOST_OS'} =~ /$re/ ) {
2047
<!-- Bugreport form. Used when one clicks "Report bug" button. -->
2048
<form name="bugreport" style="display:none" method="get" action="http://bugs.linuxbase.org/enter_bug.cgi" target="_blank">
2049
<input type="hidden" name="product" value="Distribution Tests" />
2050
<input type="hidden" name="format" value="problemreport" />
2051
<input type="hidden" name="rep_platform" value="$rep_platform" />
2052
<input type="hidden" name="op_sys" value="$op_sys" />
2053
<input type="hidden" name="short_desc" value="" />
2054
<input type="hidden" name="comment" value="" />
2057
<script language="javascript" type="text/javascript">
2059
function flip (message_box_id) {
2060
msgbox = document.getElementById(message_box_id);
2061
if (msgbox.style.display != 'none') {
2062
msgbox.style.display = 'none';
2065
msgbox.style.display = '';
2068
function bug_report(test_suite, test_case, severity, file) {
2069
document.bugreport.short_desc.value = test_suite+": "+test_case+" "+severity;
2070
document.bugreport.comment.value = "Enter a description of the problem here\\n\\n---\\nDTK Manager: Please use the 'Add an attachment' button below to attach the following test journal:\\n "+file;
2071
document.bugreport.submit();
2079
$res .= '<h1>Automatic test results for '.html($runconfig->{'HOST_OS'})
2080
.' on '.html($runconfig->{'HOST_architecture'}).'</h1>';
2082
if ( -f "$result_dir/log" ) {
2083
$res .= '<p>The complete console output log is available '
2084
.local_href( "log", 'here' ).'.</p>';
2086
if ( -f "$result_dir/verbose_log" ) {
2087
$res .= '<p>The '.local_href( "verbose_log", 'verbose log' )
2088
.' is also available.</p>';
2091
$res .= '<table class="info">';
2092
$res .= info_line("Standard", $runconfig->{'STANDARD'});
2093
$res .= info_line("Machine", $runconfig->{'HOST_machine'}." (".$runconfig->{'HOST_architecture'}.")");
2094
$res .= info_line("Package Manager", $runconfig->{'HOST_package_manager'});
2095
$res .= info_line("Kernel", $runconfig->{'HOST_kernel'});
2096
$res .= info_line("Operation System", $runconfig->{'HOST_OS'});
2097
$res .= info_line("/etc/issue", $runconfig->{'HOST_etc_issue'});
2114
#----------------------------------------------------------------------
2115
my @toc_groups = ("Certification Tests", "Command Tests", "Static Interface Tests",
2116
"Runtime Interface Tests", "Automated Application Battery Tests", "Manual Tests" );
2119
my ($tests, $sort_arr) = @_;
2123
# Building a tree structure;
2126
foreach my $test_info ( values %$tests ) {
2129
$test_info->{TREE} = "Undefined" if !defined $test_info->{TREE};
2132
foreach my $group_name ( split "/", $test_info->{TREE} ) {
2133
if ( $group_name =~ s/^<([^>]*)>// ) {
2136
( in_array($group_name, @toc_groups) )
2137
or warning "Wrong group name: '$group_name'";
2139
$tmp->{CHILDREN}->{$group_name} = {} if !$tmp->{CHILDREN}->{$group_name};
2140
$tmp = $tmp->{CHILDREN}->{$group_name};
2142
# If this test has subtests, we shouldn't lose them.
2143
if ( defined $tmp->{CHILDREN}->{$test_info->{DISPLAYNAME}}->{CHILDREN} ) {
2144
$test_info->{CHILDREN} = $tmp->{CHILDREN}->{$test_info->{NAME}}->{CHILDREN};
2147
$tmp->{CHILDREN}->{$test_info->{DISPLAYNAME}} = $test_info;
2151
$res .= '<table class="toc">'."\n";
2153
if ( $tree->{CHILDREN} ) {
2154
$res .= print_toc_subtree( $tree, 0, $sort_arr );
2157
$res .= '</table>'."\n";
2162
sub print_toc_subtree {
2163
my ($subtree, $deep, $sort_arr) = @_;
2167
if ( $subtree->{CHILDREN} ) {
2168
my @keys = keys %{$subtree->{CHILDREN}};
2171
my $ap = in_array($a, @toc_groups);
2172
my $bp = in_array($b, @toc_groups);
2174
return $ap <=> $bp if ( $ap && $bp );
2175
return $a cmp $b if ( !$ap && !$bp );
2180
foreach my $node ( @keys ) {
2181
my $child = $subtree->{CHILDREN}->{$node};
2182
push @$sort_arr, $child->{NAME} if $child->{NAME};
2183
$res .= print_toc_node( $node, $child, $deep, $sort_arr );
2190
sub print_toc_node {
2191
my ($name, $subtree, $deep, $sort_arr) = @_;
2195
my $padding = 5 + $deep * 6;
2196
my $style_padding = "padding-left:$padding pt";
2198
my $color = $deep <= 3 ? 160 + 30 * $deep : 250;
2199
$color = sprintf ('#%02X%02X%02X', $color, $color, 250);
2200
my $style_color = "background:$color";
2202
if ( $subtree->{NAME} ) {
2203
my $test_info = $subtree;
2205
# Test name as a link to the corresponding part of the report.
2206
$res .= '<tr style="'.$style_color.'">';
2207
$res .= '<td style="'.$style_padding.'"'
2208
.(defined $test_info->{VERSION} ? '' : ' colspan="2"').'>'."\n";
2209
$res .= '<a href="#'.$test_info->{NAME}.'" class="toclink">'
2210
.html($test_info->{DISPLAYNAME}).'</a>';
2214
if ( defined $test_info->{VERSION} ) {
2215
$res .= '<td style="color:'.$dark_gray.'">'
2216
.'v '.html($test_info->{VERSION})
2220
$res .= '<td style="background-color:'.verdict_color($test_info->{VERDICT}, $color).'">'
2221
.verdict_text($test_info->{VERDICT}, "Undefined").'</td>'."\n";
2226
$res .= '<tr><th style="'.$style_color.";".$style_padding.'" colspan="3">'."\n";
2227
$res .= html($name);
2228
$res .= '</th></tr>';
2230
if ( $subtree->{CHILDREN} ) {
2231
$res .= print_toc_subtree( $subtree, $deep + 1, $sort_arr );
2236
#----------------------------------------------------------------------
2239
my ($verdict, $default_color) = @_;
2241
if ( !defined $verdict || $verdict eq "undefined" ) { # undef
2242
return $default_color;
2244
elsif ( $verdict eq "run_problems" ) {
2245
return $fluorescent_red;
2247
elsif ( $verdict eq "passed" ) {
2248
return $fluorescent_green;
2250
elsif ( $verdict eq "warning" || $verdict eq "incomplete" ) {
2251
return $fluorescent_yellow;
2253
elsif ( $verdict eq "failed" ) {
2254
return $fluorescent_red;
2257
warning "Unhandled verdict: '$verdict'";
2262
my ($verdict, $default_text) = @_;
2263
$default_text = "" if !defined $default_text;
2265
if ( !$verdict || $verdict eq "undefined" ) {
2266
return $default_text;
2268
elsif ( $verdict eq "run_problems" ) {
2269
return "There are problems";
2271
elsif ( $verdict =~ m/^(passed|warning|incomplete|failed)$/ ) {
2272
return ucfirst $verdict;
2275
warning "Unhandled verdict: '$verdict'";
2282
my $tests_total = $stats->{TESTS_TOTAL};
2283
my $tests_passed = $stats->{PASSED};
2284
my $failures = $stats->{FAILURES};
2285
my $warnings = $stats->{WARNINGS};
2286
my $quelled = $stats->{QUELLED};
2287
my $notices = $stats->{NOTICES};
2288
my $untested = $stats->{UNTESTED};
2289
my $fips = $stats->{FIPS};
2290
my $tests_expected = $stats->{TESTS_EXPECTED};
2291
my $testsuite_errors = $stats->{TESTSUITE_ERRORS};
2295
$res .= '<h4>Journal statistics:</h4>'."\n";
2296
$res .= '<table class="info">'."\n";
2298
if ( defined $tests_expected && defined $tests_total
2299
&& $tests_expected != $tests_total )
2301
$res .= info_line('Tests Expected', $tests_expected, $fluorescent_yellow );
2304
$res .= info_line('Tests Total', $tests_total ) if defined $tests_total;
2306
if ( defined $tests_passed ) { # zero value is printed too
2307
$res .= info_line('Tests Passed', $tests_passed, $fluorescent_green )
2309
$res .= info_line('Tests Failed', $failures, $fluorescent_red ) if $failures;
2310
$res .= info_line('Test suite errors', $testsuite_errors, $fluorescent_red ) if $testsuite_errors;
2311
$res .= info_line('Warnings', $warnings, $fluorescent_yellow ) if $warnings;
2312
$res .= info_line('Quelled problems', $quelled ) if $quelled;
2313
$res .= info_line('Tests not relevant/not applicable', $untested ) if $untested;
2314
$res .= info_line('Notices', $notices ) if $notices;
2315
$res .= info_line('FIPs', $fips ) if $fips;
2317
$res .= '</table>'."\n";
2322
#----------------------------------------------------------------------