}
# Default set of tests.
-@TESTS = qw (create-normal create-empty create-null create-bad-ptr
+@TESTS = qw (args-argc args-argv0 args-argvn args-single args-multiple
+ args-dbl-space
+ sc-bad-sp sc-bad-arg sc-boundary
+ halt exit
+ create-normal create-empty create-null create-bad-ptr
create-long create-exists create-bound
- args-argc args-argv0 args-argvn args-single args-multiple
- args-dbl-space)
- unless @TESTS > 0;
+ open-normal open-missing open-boundary open-empty open-null
+ open-bad-ptr open-twice
+ close-normal close-twice close-stdin close-stdout close-bad-fd
+ read-normal read-bad-ptr read-boundary read-zero read-stdout
+ read-bad-fd
+ write-normal write-bad-ptr write-boundary write-zero write-stdin
+ write-bad-fd
+ exec-once exec-arg exec-multiple exec-missing exec-bad-ptr
+ join-simple join-twice join-killed join-bad-pid
+ multi-recurse multi-oom multi-child-fd
+ ) unless @TESTS > 0;
our (%args);
for my $key ('args-argc', 'args-argv0', 'args-argvn', 'args-multiple') {
}
$args{'args-single'} = "onearg";
$args{'args-dbl-space'} = "two args";
+$args{'multi-recurse'} = "15";
+$args{'multi-oom'} = "0";
# Handle final grade mode.
if ($grade) {
xsystem ("cd pintos/src && tar xzf ../../$tarball",
DIE => "extraction failed\n");
+ if (-e "fixme.sh") {
+ print "Running fixme.sh...\n";
+ xsystem ("sh -e fixme.sh", DIE => "fix script failed\n");
+ }
+
print "Patching...\n";
xsystem ("patch -fs pintos/src/lib/debug.c < $GRADES_DIR/panic.diff",
LOG => "patch",
DIE => "patch failed\n");
+ xsystem ("patch -fs pintos/src/lib/kernel/bitmap.c "
+ . "< $GRADES_DIR/random.diff",
+ LOG => "patch",
+ DIE => "patch failed\n");
+
+ open (CONSTANTS, ">pintos/src/constants.h")
+ or die "constants.h: create: $!\n";
+ print CONSTANTS "#define THREAD_JOIN_IMPLEMENTED 1\n";
+ close CONSTANTS;
}
sub ext_mdyHMS {
# Run.
my ($timeout) = 10;
+ $timeout = 600 if $test =~ /^multi-/;
my ($testargs) = defined ($args{$test}) ? " $args{$test}" : "";
xsystem ("pintos "
. "--os-disk=pintos/src/userprog/build/os.dsk "
my (@output) = snarf ("output/$test/run.out");
- if (-e "$GRADES_DIR/$test.exp") {
+ my ($grade_func) = "grade_$test";
+ $grade_func =~ s/-/_/g;
+ if (-e "$GRADES_DIR/$test.exp" && !defined (&$grade_func)) {
eval {
verify_common (@output);
compare_output ("$GRADES_DIR/$test.exp", @output);
}
} else {
- my ($grade_func);
- ($grade_func = $test) =~ s/-/_/g;
- eval "grade_$grade_func (\@output)";
+ eval "$grade_func (\@output)";
}
if ($@) {
die $@ if $@ =~ /at \S+ line \d+$/;
return "ok";
}
\f
-sub grade_alarm_multiple {
- verify_alarm (7, @_);
-}
-
-sub verify_alarm {
- my ($iterations, @output) = @_;
-
+sub grade_write_normal {
+ my (@output) = @_;
verify_common (@output);
-
- my (@products);
- for (my ($i) = 0; $i < $iterations; $i++) {
- for (my ($t) = 0; $t < 5; $t++) {
- push (@products, ($i + 1) * ($t + 1) * 10);
+ compare_output ("$GRADES_DIR/write-normal.exp", @output);
+ my ($test_txt) = "output/$test/test.txt";
+ get_file ("test.txt", $test_txt) if ! -e $test_txt;
+
+ my (@actual) = snarf ($test_txt);
+ my (@expected) = snarf ("$GRADES_DIR/sample.txt");
+
+ my ($eq);
+ if ($#actual == $#expected) {
+ $eq = 1;
+ for my $i (0...$#actual) {
+ $eq = 0 if $actual[$i] ne $expected[$i];
}
+ } else {
+ $eq = 0;
}
- @products = sort {$a <=> $b} @products;
-
- local ($_);
- foreach (@output) {
- die $_ if /Out of order/;
-
- my ($p) = /product=(\d+)$/;
- next if !defined $p;
+ if (!$eq) {
+ my ($details);
+ $details = "Expected file content:\n";
+ $details .= join ('', map (" $_\n", @expected));
+ $details .= "Actual file content:\n";
+ $details .= join ('', map (" $_\n", @actual));
+ $extra{$test} = $details;
- my ($q) = shift (@products);
- die "Too many wakeups.\n" if !defined $q;
- die "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME
+ die "File written didn't have expected content.\n";
}
- die scalar (@products) . " fewer wakeups than expected.\n"
- if @products != 0;
-}
-
-sub grade_alarm_zero {
- my (@output) = @_;
- verify_common (@output);
- die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
}
-sub grade_alarm_negative {
+sub grade_multi_oom {
my (@output) = @_;
verify_common (@output);
- die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
-}
-
-sub grade_join_invalid {
- my (@output) = @_;
- verify_common (@output);
- grep (/Testing invalid join/, @output) or die "Test didn't start\n";
- grep (/Invalid join test done/, @output) or die "Test didn't complete\n";
-}
-sub grade_join_no {
- my (@output) = @_;
- verify_common (@output);
- grep (/Testing no join/, @output) or die "Test didn't start\n";
- grep (/No join test done/, @output) or die "Test didn't complete\n";
-}
-
-sub grade_join_multiple {
- my (@output) = @_;
-
- verify_common (@output);
- my (@t);
- $t[4] = $t[5] = $t[6] = -1;
- local ($_);
- foreach (@output) {
- my ($idx) = /^Thread (\d+)/ or next;
- my ($iter) = /iteration (\d+)$/;
- $iter = 5 if /done!$/;
- die "Malformed output\n" if !defined $iter;
- if ($idx == 6) {
- die "Thread 6 started before either other thread finished\n"
- if $t[4] < 5 && $t[5] < 5;
- die "Thread 6 started before thread 4 finished\n"
- if $t[4] < 5;
- die "Thread 6 started before thread 5 finished\n"
- if $t[5] < 5;
- }
- die "Thread $idx out of order output\n" if $t[$idx] != $iter - 1;
- $t[$idx] = $iter;
+ @output = fix_exit_codes (get_core_output (@output));
+ my ($n) = 0;
+ while (my ($m) = $output[0] =~ /^\(multi-oom\) begin (\d+)$/) {
+ die "Child process $m started out of order.\n" if $m != $n;
+ $n = $m + 1;
+ shift @output;
}
-
- my ($err) = "";
- for my $idx (4, 5, 6) {
- if ($t[$idx] == -1) {
- $err .= "Thread $idx did not run at all\n";
- } elsif ($t[$idx] != 5) {
- $err .= "Thread $idx only completed $t[$idx] iterations\n";
- }
+ die "Only $n child process(es) started.\n" if $n < 15;
+
+ # There could be a death notice for a process that didn't get
+ # fully loaded, and/or notices from the loader.
+ while (@output > 0
+ && ($output[0] =~ /^multi-oom: exit\(-1\)$/
+ || $output[0] =~ /^load: /)) {
+ shift @output;
}
- die $err if $err ne '';
-}
-sub grade_priority_fifo {
- my (@output) = @_;
+ while (--$n >= 0) {
+ die "Output ended unexpectedly before process $n finished.\n"
+ if @output < 2;
- verify_common (@output);
- my ($thread_cnt) = 10;
- my ($iter_cnt) = 5;
- my (@order);
- my (@t) = (-1) x $thread_cnt;
- local ($_);
- foreach (@output) {
- my ($idx) = /^Thread (\d+)/ or next;
- my ($iter) = /iteration (\d+)$/;
- $iter = $iter_cnt if /done!$/;
- die "Malformed output\n" if !defined $iter;
- if (@order < $thread_cnt) {
- push (@order, $idx);
- die "Thread $idx repeated within first $thread_cnt iterations: "
- . join (' ', @order) . ".\n"
- if grep ($_ == $idx, @order) != 1;
- } else {
- die "Thread $idx ran when $order[0] should have.\n"
- if $idx != $order[0];
- push (@order, shift @order);
- }
- die "Thread $idx out of order output.\n" if $t[$idx] != $iter - 1;
- $t[$idx] = $iter;
- }
+ local ($_);
+ chomp ($_ = shift @output);
+ die "Found '$_' expecting 'end' message.\n" if !/^\(multi-oom\) end/;
+ die "Child process $n ended out of order.\n"
+ if !/^\(multi-oom\) end $n$/;
- my ($err) = "";
- for my $idx (0..$#t) {
- if ($t[$idx] == -1) {
- $err .= "Thread $idx did not run at all.\n";
- } elsif ($t[$idx] != $iter_cnt) {
- $err .= "Thread $idx only completed $t[$idx] iterations.\n";
- }
+ chomp ($_ = shift @output);
+ die "Kernel didn't print proper exit message for process $n.\n"
+ if !/^multi-oom: exit\($n\)$/;
}
- die $err if $err ne '';
-}
-
-sub grade_mlfqs_on {
- my (@output) = @_;
- verify_common (@output);
- our (@mlfqs_on_stats) = mlfqs_stats (@output);
+ die "Spurious output at end: '$output[0]'.\n" if @output;
}
-sub grade_mlfqs_off {
- my (@output) = @_;
- verify_common (@output);
- our (@mlfqs_off_stats) = mlfqs_stats (@output);
-}
-
-sub grade_mlfqs_speedup {
- our (@mlfqs_off_stats);
- our (@mlfqs_on_stats);
- eval {
- check_mlfqs ();
- my ($off_ticks) = $mlfqs_off_stats[1];
- my ($on_ticks) = $mlfqs_on_stats[1];
- die "$off_ticks ticks without MLFQS, $on_ticks with MLFQS\n"
- if $on_ticks >= $off_ticks;
- die "ok\n";
- };
- chomp $@;
- $result{'mlfqs-speedup'} = $@;
-}
-
-sub grade_mlfqs_priority {
- our (@mlfqs_off_stats);
- our (@mlfqs_on_stats);
- eval {
- check_mlfqs () if !defined (@mlfqs_on_stats);
- for my $cat qw (CPU IO MIX) {
- die "Priority changed away from PRI_DEFAULT (29) without MLFQS\n"
- if $mlfqs_off_stats[0]{$cat}{MIN} != 29
- || $mlfqs_off_stats[0]{$cat}{MAX} != 29;
- die "Minimum priority never changed from PRI_DEFAULT (29) "
- . "with MLFQS\n"
- if $mlfqs_on_stats[0]{$cat}{MIN} == 29;
- die "Maximum priority never changed from PRI_DEFAULT (29) "
- . "with MLFQS\n"
- if $mlfqs_on_stats[0]{$cat}{MAX} == 29;
- }
- die "ok\n";
- };
- chomp $@;
- $result{'mlfqs-priority'} = $@;
-}
-
-sub check_mlfqs {
- our (@mlfqs_off_stats);
- our (@mlfqs_on_stats);
- die "p1-4 didn't finish with MLFQS on or off\n"
- if !defined (@mlfqs_off_stats) && !defined (@mlfqs_on_stats);
- die "p1-4 didn't finish with MLFQS on\n"
- if !defined (@mlfqs_on_stats);
- die "p1-4 didn't finish with MLFQS off\n"
- if !defined (@mlfqs_off_stats);
+sub get_file {
+ my ($guest_fn, $host_fn) = @_;
+ xsystem ("pintos "
+ . "--os-disk=pintos/src/userprog/build/os.dsk "
+ . "--fs-disk=output/$test/fs.dsk "
+ . "-v get $guest_fn $host_fn",
+ LOG => "$test/get-$guest_fn",
+ TIMEOUT => 10)
+ or die "get $guest_fn failed\n";
}
-sub mlfqs_stats {
- my (@output) = @_;
- my (%stats) = (CPU => {}, IO => {}, MIX => {});
- my (%map) = ("CPU intensive" => 'CPU',
- "IO intensive" => 'IO',
- "Alternating IO/CPU" => 'MIX');
- my (%rmap) = reverse %map;
- my ($ticks);
- local ($_);
- foreach (@output) {
- $ticks = $1 if /Timer: (\d+) ticks/;
- my ($thread, $pri) = /^([A-Za-z\/ ]+): (\d+)$/ or next;
- my ($t) = $map{$thread} or next;
-
- my ($s) = $stats{$t};
- $$s{N}++;
- $$s{SUM} += $pri;
- $$s{SUM2} += $pri * $pri;
- $$s{MIN} = $pri if !defined ($$s{MIN}) || $pri < $$s{MIN};
- $$s{MAX} = $pri if !defined ($$s{MAX}) || $pri > $$s{MAX};
- }
-
- my (%expect_n) = (CPU => 5000, IO => 1000, MIX => 12000);
- for my $cat (values (%map)) {
- my ($s) = $stats{$cat};
- die "$rmap{$cat} printed $$s{N} times, not $expect_n{$cat}\n"
- if $$s{N} != $expect_n{$cat};
- die "$rmap{$cat} priority dropped to $$s{MIN}, below PRI_MIN (0)\n"
- if $$s{MIN} < 0;
- die "$rmap{$cat} priority rose to $$s{MAX}, above PRI_MAX (59)\n"
- if $$s{MAX} > 59;
- $$s{MEAN} = $$s{SUM} / $$s{N};
- }
-
- return (\%stats, $ticks);
-}
\f
sub verify_common {
my (@output) = @_;
} else {
$A2L = "i386-elf-addr2line";
}
- open (A2L, "$A2L -fe output/$test/kernel.o @addrs|");
+ open (A2L, "$A2L -fe pintos/src/userprog/build/kernel.o @addrs|");
for (;;) {
my ($function, $line);
last unless defined ($function = <A2L>);
die "Kernel panic. Details at end of file.\n"
}
+ if (grep (/Pintos booting/, @output) > 1) {
+ my ($details);
+
+ $details = "Pintos spontaneously rebooted during this test.\n";
+ $details .= "This is most often due to unhandled page faults.\n";
+ $details .= "Here's the output from the initial boot through the\n";
+ $details .= "first reboot:\n\n";
+
+ my ($i) = 0;
+ local ($_);
+ for (@output) {
+ $details .= " $_\n";
+ last if /Pintos booting/ && ++$i > 1;
+ }
+ $details{$test} = $details;
+ die "Triple-fault caused spontaneous reboot(s). "
+ . "Details at end of file.\n";
+ }
+
die "No output at all\n" if @output == 0;
die "Didn't start up properly: no \"Pintos booting\" startup message\n"
if !grep (/Pintos booting with.*kB RAM\.\.\./, @output);
return @output[$first ... $last];
}
-sub compare_output {
- my ($exp, @actual) = @_;
- @actual = get_core_output (map ("$_\n", @actual));
+sub fix_exit_codes {
+ my (@output) = @_;
# Fix up lines that look like exit codes.
- for my $i (0...$#actual) {
- if (my ($process, $code)
- = $actual[$i] =~ /^([-a-zA-Z0-9 ]+):.*[ \(](-?\d+)\b\)?$/) {
+ # Exit codes are supposed to be printed in the form "process: exit(code)"
+ # but people get unfortunately creative with it.
+ for my $i (0...$#output) {
+ local ($_) = $output[$i];
+
+ my ($process, $code);
+ if ((($process, $code) = /^([-a-zA-Z0-9 ]+):.*[ \(](-?\d+)\b\)?$/)
+ || (($process, $code) = /^([-a-zA-Z0-9 ]+) exit\((-?\d+)\)$/)
+ || (($process, $code)
+ = /^([-a-zA-Z0-9 ]+) \(.*\): exit\((-?\d+)\)$/)
+ || (($process, $code) = /^([-a-zA-Z0-9 ]+):\( (-?\d+) \) $/)
+) {
$process = substr ($process, 0, 15);
$process =~ s/\s.*//;
- $actual[$i] = "$process: exit($code)\n";
+ $output[$i] = "$process: exit($code)\n";
}
}
+ return @output;
+}
+
+sub compare_output {
+ my ($exp, @actual) = @_;
+ @actual = fix_exit_codes (get_core_output (map ("$_\n", @actual)));
+
my ($details) = "";
$details .= "$test actual output:\n";
$details .= join ('', map (" $_", @actual));
# They differ. Output a diff.
my (@diff) = "";
my ($d) = Algorithm::Diff->new (\@expected, \@actual);
- my ($all_additions) = 1;
+ my ($not_fuzzy_match) = 0;
while ($d->Next ()) {
my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2));
if ($d->Same ()) {
} else {
push (@diff, map ("- $_", $d->Items (1))) if $d->Items (1);
push (@diff, map ("+ $_", $d->Items (2))) if $d->Items (2);
- $all_additions = 0 if $d->Items (1);
+ if ($d->Items (1)
+ || grep (/\($test\)|exit\(-?\d+\)/, $d->Items (2))) {
+ $not_fuzzy_match = 1;
+ }
}
}
-
- $fuzzy_match = 1 if $all_additions;
+ $fuzzy_match = 1 if !$not_fuzzy_match;
$details .= "Differences in `diff -u' format:\n";
$details .= join ('', @diff);
- $details .= "(This is considered a `fuzzy match'.)\n" if $fuzzy_match;
+ $details .= "(This is considered a `fuzzy match'.)\n"
+ if !$not_fuzzy_match;
+ }
+
+ if ($fuzzy_match) {
+ $details =
+ "This test passed, but with extra, unexpected output.\n"
+ . "Please inspect your code to make sure that it does not\n"
+ . "produce output other than as specified in the project\n"
+ . "description.\n\n"
+ . "$details";
+ } else {
+ $details =
+ "This test failed because its output did not match any\n"
+ . "of the acceptable form(s).\n\n"
+ . "$details";
}
$details{$test} = $details;
my ($ploss) = 0;
my ($tloss) = 0;
my ($total) = 0;
+ my ($warnings) = 0;
for (my ($i) = 0; $i <= $#summary; $i++) {
local ($_) = $summary[$i];
if (my ($loss, $test) = /^ -(\d+) ([-a-zA-Z0-9]+):/) {
my ($result) = $result{$test} || "Not tested.";
if ($result eq 'ok') {
- splice (@summary, $i, 1);
- $i--;
+ if (!defined $details{$test}) {
+ # Test successful and no warnings.
+ splice (@summary, $i, 1);
+ $i--;
+ } else {
+ # Test successful with warnings.
+ s/-(\d+) //;
+ $summary[$i] = $_;
+ splice (@summary, $i + 1, 0,
+ " Test passed with warnings. "
+ . "Details at end of file.");
+ $warnings++;
+ }
} else {
$ploss += $loss;
$tloss += $loss;
} elsif (my ($ptotal) = /^Score: \/(\d+)$/) {
$total += $ptotal;
$summary[$i] = "Score: " . ($ptotal - $ploss) . "/$ptotal";
- splice (@summary, $i, 0, " All tests passed.") if $ploss == 0;
+ splice (@summary, $i, 0, " All tests passed.")
+ if $ploss == 0 && !$warnings;
$ploss = 0;
+ $warnings = 0;
$i++;
}
}
my ($details) = $details{$test};
next if !defined ($details) && ! -e "output/$test/run.out";
+ my ($banner);
+ if ($result{$test} ne 'ok') {
+ $banner = "$test failure details";
+ } else {
+ $banner = "$test warnings";
+ }
+
print DETAILS "\n" if $n++;
- print DETAILS "--- $test details ", '-' x (50 - length ($test));
+ print DETAILS "--- $banner ", '-' x (50 - length ($banner));
print DETAILS "\n\n";
if (!defined $details) {