Remove unused variable.
[pintos-anon] / src / tests / tests.pm
1 use strict;
2 use warnings;
3 use tests::Algorithm::Diff;
4 use File::Temp 'tempfile';
5
6 sub fail;
7 sub pass;
8
9 die if @ARGV != 2;
10 our ($test, $src_dir) = @ARGV;
11
12 my ($msg_file) = tempfile ();
13 select ($msg_file);
14
15 sub check_expected {
16     my ($expected) = pop @_;
17     my (@options) = @_;
18     my (@output) = read_text_file ("$test.output");
19     common_checks (@output);
20     compare_output (@options, \@output, $expected);
21 }
22
23 sub common_checks {
24     my (@output) = @_;
25
26     fail "No output at all\n" if @output == 0;
27
28     check_for_panic (@output);
29     check_for_keyword ("FAIL", @output);
30     check_for_triple_fault (@output);
31     check_for_keyword ("TIMEOUT", @output);
32
33     fail "Didn't start up properly: no \"Pintos booting\" startup message\n"
34       if !grep (/Pintos booting with.*kB RAM\.\.\./, @output);
35     fail "Didn't start up properly: no \"Boot complete\" startup message\n"
36       if !grep (/Boot complete/, @output);
37     fail "Didn't shut down properly: no \"Timer: # ticks\" shutdown message\n"
38       if !grep (/Timer: \d+ ticks/, @output);
39     fail "Didn't shut down properly: no \"Powering off\" shutdown message\n"
40       if !grep (/Powering off/, @output);
41 }
42
43 sub check_for_panic {
44     my (@output) = @_;
45
46     my ($panic) = grep (/PANIC/, @output);
47     return unless defined $panic;
48
49     print "Kernel panic: ", substr ($panic, index ($panic, "PANIC")), "\n";
50
51     my (@stack_line) = grep (/Call stack:/, @output);
52     if (@stack_line != 0) {
53         my (@addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/;
54         print "Call stack: @addrs\n";
55         print "Translation of call stack:\n";
56         print `backtrace kernel.o @addrs`;
57     }
58
59     if ($panic =~ /sec_no \< d-\>capacity/) {
60         print <<EOF;
61 \nThis assertion commonly fails when accessing a file via an inode that
62 has been closed and freed.  Freeing an inode clears all its sector
63 indexes to 0xcccccccc, which is not a valid sector number for disks
64 smaller than about 1.6 TB.
65 EOF
66     }
67
68     fail;
69 }
70
71 sub check_for_keyword {
72     my ($keyword, @output) = @_;
73     
74     my ($kw_line) = grep (/$keyword/, @output);
75     return unless defined $kw_line;
76
77     # Most output lines are prefixed by (test-name).  Eliminate this
78     # from our message for brevity.
79     $kw_line =~ s/^\([^\)]+\)\s+//;
80     print "$kw_line\n";
81
82     fail;
83 }
84
85 sub check_for_triple_fault {
86     my (@output) = @_;
87
88     return unless grep (/Pintos booting/, @output) > 1;
89
90     print <<EOF;
91 Pintos spontaneously rebooted during this test.
92 This is most often caused by unhandled page faults.
93 EOF
94
95     fail;
96 }
97
98 # Get @output without header or trailer.
99 sub get_core_output {
100     my ($p);
101     do { $p = shift; } while (defined ($p) && $p !~ /^Executing '.*':$/);
102     do { $p = pop; } while (defined ($p) && $p !~ /^Execution of '.*' complete.$/);
103     return @_;
104 }
105
106 sub compare_output {
107     my ($expected) = pop @_;
108     my ($output) = pop @_;
109     my (%options) = @_;
110
111     my (@output) = get_core_output (@$output);
112     fail "'$test' didn't run or didn't produce any output\n" if !@output;
113
114     if (exists $options{IGNORE_EXIT_CODES}) {
115         delete $options{IGNORE_EXIT_CODES};
116         @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output);
117     }
118     die "unknown option " . (keys (%options))[0] . "\n" if %options;
119
120     my ($msg);
121
122     # Compare actual output against each allowed output.
123     foreach my $exp_string (@$expected) {
124         my (@expected) = split ("\n", $exp_string);
125
126         $msg .= "Acceptable output:\n";
127         $msg .= join ('', map ("  $_\n", @expected));
128
129         # Check whether actual and expected match.
130         # If it's a perfect match, we're done.
131         if ($#output == $#expected) {
132             my ($eq) = 1;
133             for (my ($i) = 0; $i <= $#expected; $i++) {
134                 $eq = 0 if $output[$i] ne $expected[$i];
135             }
136             pass if $eq;
137         }
138
139         # They differ.  Output a diff.
140         my (@diff) = "";
141         my ($d) = Algorithm::Diff->new (\@expected, \@output);
142         while ($d->Next ()) {
143             my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2));
144             if ($d->Same ()) {
145                 push (@diff, map ("  $_\n", $d->Items (1)));
146             } else {
147                 push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1);
148                 push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2);
149             }
150         }
151
152         $msg .= "Differences in `diff -u' format:\n";
153         $msg .= join ('', @diff);
154     }
155
156     # Failed to match.  Report failure.
157     fail "Test output failed to match any acceptable form.\n\n$msg";
158 }
159
160 sub fail {
161     finish ("FAIL", @_);
162 }
163
164 sub pass {
165     finish ("PASS", @_);
166 }
167
168 sub finish {
169     my ($verdict, @rest) = @_;
170
171     my ($messages) = "";
172     seek ($msg_file, 0, 0);
173     while (<$msg_file>) {
174         $messages .= $_;
175     }
176     close ($msg_file);
177
178     my ($result_fn) = "$test.result";
179     open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n";
180     print RESULT "$verdict\n", $messages, @rest;
181     close (RESULT);
182
183     if ($verdict eq 'PASS') {
184         print STDOUT "pass $test\n";
185     } else {
186         print STDOUT "FAIL $test\n";
187     }
188     print STDOUT $messages, @rest, "\n";
189
190     exit 0;
191 }
192
193 sub read_text_file {
194     my ($file_name) = @_;
195     open (FILE, '<', $file_name) or die "$file_name: open: $!\n";
196     my (@content) = <FILE>;
197     chomp (@content);
198     close (FILE);
199     return @content;
200 }
201
202 1;