X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=grading%2Fuserprog%2Frun-tests;h=dd7cd218b901d2e0fc889f4fe6916aa2e280f255;hb=cfbade23277061d4ab0d26a2fb350b98657e7302;hp=ddc5b6edcdf0be0a4b7786c2ac813cb514148b0f;hpb=064e19becdc8969ddb605223b340606706a33f86;p=pintos-anon diff --git a/grading/userprog/run-tests b/grading/userprog/run-tests index ddc5b6e..dd7cd21 100755 --- a/grading/userprog/run-tests +++ b/grading/userprog/run-tests @@ -37,9 +37,32 @@ sub usage { } # Default set of tests. -@TESTS = qw (create-normal create-empty create-null - create-long) - unless @TESTS > 0; +@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 + 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{$key} = "some arguments for you!"; +} +$args{'args-single'} = "onearg"; +$args{'args-dbl-space'} = "two args"; +$args{'multi-recurse'} = "15"; +$args{'multi-oom'} = "0"; # Handle final grade mode. if ($grade) { @@ -166,10 +189,24 @@ sub extract_tarball { 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 { @@ -238,10 +275,12 @@ sub really_run_test { # 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 " . "--fs-disk=output/$test/fs.dsk " - . "-v run -q -ex \"$test\"", + . "-v run -q -ex \"$test$testargs\"", LOG => "$test/run", TIMEOUT => $timeout) or return "Bochs error"; @@ -254,15 +293,15 @@ sub grade_test { 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+$/; @@ -271,241 +310,86 @@ sub grade_test { return "ok"; } -sub grade_create_empty { +sub grade_write_normal { my (@output) = @_; - verify_common (@_); - compare_output (["(create-empty) begin"], @output); - -} - -sub grade_alarm_multiple { - verify_alarm (7, @_); -} - -sub verify_alarm { - my ($iterations, @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 { - my (@output) = @_; - verify_common (@output); - die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output); } -sub grade_join_invalid { +sub grade_multi_oom { 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 ''; + die "Spurious output at end: '$output[0]'.\n" if @output; } -sub grade_mlfqs_on { - my (@output) = @_; - verify_common (@output); - our (@mlfqs_on_stats) = mlfqs_stats (@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); -} sub verify_common { my (@output) = @_; @@ -527,7 +411,7 @@ sub verify_common { } 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 = ); @@ -541,6 +425,25 @@ sub verify_common { 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); @@ -552,57 +455,73 @@ sub verify_common { if !grep (/Powering off/, @output); } -sub eq_lines { - my ($actual, $expected) = @_; - return $actual eq $expected; -} - -sub compare_output { - my ($exp, @actual) = @_; - @actual = map ("$_\n", @actual); +# Get @output without header or trailer. +sub get_core_output { + my (@output) = @_; - # Trim header and trailer from @actual. our ($test); my ($first); - for ($first = 0; $first <= $#actual; $first++) { - $first++, last if $actual[$first] =~ /^Executing '$test':$/; + for ($first = 0; $first <= $#output; $first++) { + $first++, last if $output[$first] =~ /^Executing '$test.*':$/; } my ($last); - for ($last = $#actual; $last >= 0; $last--) { - $last--, last if $actual[$last] =~ /^Timer: \d+ ticks$/; + for ($last = $#output; $last >= 0; $last--) { + $last--, last if $output[$last] =~ /^Timer: \d+ ticks$/; } if ($last < $first) { - my ($no_first) = $first > $#actual; - my ($no_last) = $last < $#actual; + my ($no_first) = $first > $#output; + my ($no_last) = $last < $#output; die "Couldn't locate output.\n"; } - @actual = @actual[$first ... $last]; + return @output[$first ... $last]; +} + +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); - $actual[$i] = "$process: exit($code)\n"; + $process =~ s/\s.*//; + $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)); + my (@exp) = map ("$_\n", snarf ($exp)); + my ($fuzzy_match) = 0; - for (my ($i) = 0; ; $i++) { - my ($fn) = $exp; - $fn .= $i if $i; - if (! -e $fn) { - die "$exp: stat: $!\n" if !$i; - last; + while (@exp != 0) { + my (@expected); + while (@exp != 0) { + my ($s) = shift (@exp); + last if $s eq "--OR--\n"; + push (@expected, $s); } - my (@expected) = map ("$_\n", snarf ($fn)); $details .= "\n$test acceptable output:\n"; $details .= join ('', map (" $_", @expected)); @@ -611,7 +530,7 @@ sub compare_output { if ($#actual == $#expected) { my ($eq) = 1; for (my ($i) = 0; $i <= $#expected; $i++) { - $eq = 0 if !eq_lines ($actual[$i], $expected[$i]); + $eq = 0 if $actual[$i] ne $expected[$i]; } return if $eq; } @@ -619,7 +538,7 @@ sub compare_output { # 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 ()) { @@ -627,15 +546,32 @@ sub compare_output { } 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; @@ -649,14 +585,26 @@ sub write_grades { 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; @@ -666,8 +614,10 @@ sub write_grades { } 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++; } } @@ -688,8 +638,15 @@ sub write_details { 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) {