Work on grading.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 17 Oct 2004 07:11:17 +0000 (07:11 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 17 Oct 2004 07:11:17 +0000 (07:11 +0000)
15 files changed:
grading/threads/join-dummy.c
grading/threads/join-dummy.exp [new file with mode: 0644]
grading/threads/join-nested.c
grading/threads/join-nested.exp [new file with mode: 0644]
grading/threads/join-no.c
grading/threads/join-quick.exp [new file with mode: 0644]
grading/threads/panic.diff [new file with mode: 0644]
grading/threads/priority-donate-multiple.exp [new file with mode: 0644]
grading/threads/priority-donate-nest.c
grading/threads/priority-donate-nest.exp [new file with mode: 0644]
grading/threads/priority-donate-one.c
grading/threads/priority-donate-one.exp [new file with mode: 0644]
grading/threads/priority-preempt.exp [new file with mode: 0644]
grading/threads/run-tests
grading/threads/tests.txt

index 202b6e4460680d322b604dc4398303a72cc47495..01733e26117468c49922ec8c50b22a3fa88bd00d 100644 (file)
@@ -34,7 +34,7 @@ dummy_test (void)
   thread_join (tid0);
   simple_thread_func ("1");
   thread_join (tid0);
-  printf ("Simple join test done.\n");
+  printf ("Dummy join test done.\n");
 }
 
 void 
diff --git a/grading/threads/join-dummy.exp b/grading/threads/join-dummy.exp
new file mode 100644 (file)
index 0000000..b60afa5
--- /dev/null
@@ -0,0 +1,15 @@
+Testing dummy join.
+Thread 0 should finish before thread 1 starts.
+Thread 0 iteration 0
+Thread 0 iteration 1
+Thread 0 iteration 2
+Thread 0 iteration 3
+Thread 0 iteration 4
+Thread 0 done!
+Thread 1 iteration 0
+Thread 1 iteration 1
+Thread 1 iteration 2
+Thread 1 iteration 3
+Thread 1 iteration 4
+Thread 1 done!
+Dummy join test done.
index eb8651f1a3e101a6104e2f170eae6ad211952433..fc94c440f6506ed626b30545495ba943c94a843a 100644 (file)
@@ -32,7 +32,7 @@ nested_test (void)
           "and finish in reverse order.\n");
   tid0 = thread_create ("0", PRI_DEFAULT, nested_thread_func, &zero);
   thread_join (tid0);
-  printf ("Simple join test done.\n");
+  printf ("Nested join test done.\n");
 }
 
 void 
diff --git a/grading/threads/join-nested.exp b/grading/threads/join-nested.exp
new file mode 100644 (file)
index 0000000..17ebd14
--- /dev/null
@@ -0,0 +1,20 @@
+Testing nested join.
+Threads 0 to 7 should start in numerical order
+and finish in reverse order.
+Thread 0 starting.
+Thread 1 starting.
+Thread 2 starting.
+Thread 3 starting.
+Thread 4 starting.
+Thread 5 starting.
+Thread 6 starting.
+Thread 7 starting.
+Thread 7 done.
+Thread 6 done.
+Thread 5 done.
+Thread 4 done.
+Thread 3 done.
+Thread 2 done.
+Thread 1 done.
+Thread 0 done.
+Nested join test done.
index 32623e31d9a34bb7e2c87fcb5962f7000c1220cb..7cecf34c65b543ae527836398a6b50232907e712 100644 (file)
@@ -31,7 +31,7 @@ no_test (void)
           "Should just not crash.\n");
   tid0 = thread_create ("0", PRI_DEFAULT, simple_thread_func, "0");
   simple_thread_func ("1");
-  printf ("Simple join test done.\n");
+  printf ("No join test done.\n");
 }
 
 void 
diff --git a/grading/threads/join-quick.exp b/grading/threads/join-quick.exp
new file mode 100644 (file)
index 0000000..80690bb
--- /dev/null
@@ -0,0 +1,15 @@
+Testing quick join.
+Thread 2 should finish before thread 3 starts.
+Thread 2 iteration 0
+Thread 2 iteration 1
+Thread 2 iteration 2
+Thread 2 iteration 3
+Thread 2 iteration 4
+Thread 2 done!
+Thread 3 iteration 0
+Thread 3 iteration 1
+Thread 3 iteration 2
+Thread 3 iteration 3
+Thread 3 iteration 4
+Thread 3 done!
+Quick join test done.
diff --git a/grading/threads/panic.diff b/grading/threads/panic.diff
new file mode 100644 (file)
index 0000000..6134d47
--- /dev/null
@@ -0,0 +1,20 @@
+diff -up /home/blp/cs140/pintos/src/lib/debug.c.\~1.8.\~ /home/blp/cs140/pintos/src/lib/debug.c
+--- /home/blp/cs140/pintos/src/lib/debug.c.~1.8.~      2004-09-12 13:14:11.000000000 -0700
++++ /home/blp/cs140/pintos/src/lib/debug.c     2004-10-17 00:02:32.000000000 -0700
+@@ -5,6 +5,7 @@
+ #include <stdio.h>
+ #include <string.h>
+ #ifdef KERNEL
++#include "threads/init.h"
+ #include "threads/interrupt.h"
+ #include "devices/serial.h"
+ #else
+@@ -83,7 +84,7 @@ debug_panic (const char *file, int line,
+ #ifdef KERNEL
+   serial_flush ();
+-  for (;;);
++  power_off ();
+ #else
+   exit (1);
+ #endif
diff --git a/grading/threads/priority-donate-multiple.exp b/grading/threads/priority-donate-multiple.exp
new file mode 100644 (file)
index 0000000..0afb6ae
--- /dev/null
@@ -0,0 +1,13 @@
+Testing multiple priority donation.
+If the statements printed below are all true, you pass.
+ 1. Main thread should have priority 30.  Actual priority: 30.
+ 2. Main thread should have priority 31.  Actual priority: 31.
+ 3. Thread b acquired lock b.
+ 5. Thread b should have just finished.
+ 4. Thread b finished.
+ 6. Main thread should have priority 30.  Actual priority: 31.
+ 7. Thread a acquired lock a.
+ 8. Thread b finished.
+ 9. Thread a should have just finished.
+10. Main thread should have priority 29.  Actual priority: 29.
+Multiple priority priority donation test finished.
index d391233dc0c3b58cc8689752734beeaed2d2a603..1d145f393f720a0dd593378814ebc17dafa20979 100644 (file)
@@ -78,7 +78,7 @@ medium_thread_func (void *locks_)
   lock_acquire (locks->b);
   lock_acquire (locks->a);
 
-  printf (" 3. Medium thread should have priority %d.  Actual priority: %d\n",
+  printf (" 3. Medium thread should have priority %d.  Actual priority: %d.\n",
           PRI_DEFAULT + 2, thread_get_priority ());
   printf (" 4. Medium thread got the lock.\n");
 
diff --git a/grading/threads/priority-donate-nest.exp b/grading/threads/priority-donate-nest.exp
new file mode 100644 (file)
index 0000000..66de83f
--- /dev/null
@@ -0,0 +1,13 @@
+Testing nested priority donation.
+If the statements printed below are all true, you pass.
+ 1. Low thread should have priority 30.  Actual priority: 30.
+ 2. Low thread should have priority 31.  Actual priority: 31.
+ 3. Medium thread should have priority 31.  Actual priority: 31.
+ 4. Medium thread got the lock.
+ 5. High thread got the lock.
+ 6. High thread finished.
+ 7. High thread should have just finished.
+ 8. Middle thread finished.
+ 9. Medium thread should just have finished.
+10. Low thread should have priority 29.  Actual priority: 29.
+Nested priority priority donation test finished.
index e6382bdf9c911fd3786b0d0a49cbea6c5aa1fa9a..0f84e098566b87f7720298adc05b9e52789ac16c 100644 (file)
@@ -26,7 +26,8 @@ test (void)
   test_donate_return ();
 }
 \f
-static thread_func acquire_thread_func;
+static thread_func acquire1_thread_func;
+static thread_func acquire2_thread_func;
 
 static void
 test_donate_return (void) 
@@ -39,25 +40,36 @@ test_donate_return (void)
 
   lock_init (&lock, "donor");
   lock_acquire (&lock);
-  thread_create ("acquire1", PRI_DEFAULT + 1, acquire_thread_func, &lock);
-  printf ("This thread should have priority %d.  Actual priority: %d.\n",
+  thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock);
+  printf ("1. This thread should have priority %d.  Actual priority: %d.\n",
           PRI_DEFAULT + 1, thread_get_priority ());
-  thread_create ("acquire2", PRI_DEFAULT + 2, acquire_thread_func, &lock);
-  printf ("This thread should have priority %d.  Actual priority: %d.\n",
+  thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock);
+  printf ("2. This thread should have priority %d.  Actual priority: %d.\n",
           PRI_DEFAULT + 2, thread_get_priority ());
   lock_release (&lock);
-  printf ("acquire2 and acquire1 must already have finished, in that order.\n"
-          "This should be the last line before finishing this test.\n"
+  printf ("7. acquire2, acquire1 must already have finished, in that order.\n"
+          "8. This should be the last line before finishing this test.\n"
           "Priority donation test done.\n");
 }
 
 static void
-acquire_thread_func (void *lock_) 
+acquire1_thread_func (void *lock_) 
 {
   struct lock *lock = lock_;
 
   lock_acquire (lock);
-  printf ("%s: got the lock\n", thread_name ());
+  printf ("5. acquire1: got the lock\n");
   lock_release (lock);
-  printf ("%s: done\n", thread_name ());
+  printf ("6. acquire1: done\n");
+}
+
+static void
+acquire2_thread_func (void *lock_) 
+{
+  struct lock *lock = lock_;
+
+  lock_acquire (lock);
+  printf ("3. acquire2: got the lock\n");
+  lock_release (lock);
+  printf ("4. acquire2: done\n");
 }
diff --git a/grading/threads/priority-donate-one.exp b/grading/threads/priority-donate-one.exp
new file mode 100644 (file)
index 0000000..01f86d5
--- /dev/null
@@ -0,0 +1,11 @@
+Testing priority donation.
+If the statements printed below are all true, you pass.
+1. This thread should have priority 30.  Actual priority: 30.
+2. This thread should have priority 31.  Actual priority: 31.
+3. acquire2: got the lock
+4. acquire2: done
+5. acquire1: got the lock
+6. acquire1: done
+7. acquire2, acquire1 must already have finished, in that order.
+8. This should be the last line before finishing this test.
+Priority donation test done.
diff --git a/grading/threads/priority-preempt.exp b/grading/threads/priority-preempt.exp
new file mode 100644 (file)
index 0000000..0004b9c
--- /dev/null
@@ -0,0 +1,9 @@
+Testing priority preemption.
+Thread high-priority iteration 0
+Thread high-priority iteration 1
+Thread high-priority iteration 2
+Thread high-priority iteration 3
+Thread high-priority iteration 4
+Thread high-priority done!
+The high-priority thread should have already completed.
+Priority preemption test done.
index 451b6cac0d57c2393623a1c93e663e2178d3aee5..f40e0d814a0a3d80181f311070d8c7dc61b695ac 100755 (executable)
@@ -14,44 +14,74 @@ if (! -d "pintos") {
        = grep (/^[a-z0-9]+\.[A-Za-z]+\.\d+\.\d+\.\d+\.\d+.\d+\.tar\.gz$/,
                glob ("*.tar.gz"));
     die "no pintos dir and no source tarball\n" if scalar (@tarballs) == 0;
+
+    # Sort tarballs in reverse order by time.
+    @tarballs = sort { ext_mdyHMS ($b) cmp ext_mdyHMS ($a) } @tarballs;
+       
     die "no pintos dir and multiple tarballs\n" if scalar (@tarballs) > 1;
     mkdir "pintos" or die "pintos: mkdir: $!\n";
     mkdir "pintos/src" or die "pintos: mkdir: $!\n";
+
     print "Extracting $tarballs[0]...\n";
     xsystem ("", "cd pintos/src && tar xzf ../../$tarballs[0]")
        or die "extraction failed\n";
+
+    print "Patching...\n";
+    xsystem ("panic.diff",
+            "patch -fs pintos/src/lib/debug.c < $GRADES_DIR/panic.diff")
+       or die "patch failed\n";
 }
 -d "pintos/src/threads" or die "pintos/src/threads: stat: $!\n";
 
-print "Compiling initial tree...\n";
-xsystem ("make", "cd pintos/src/threads && make") or die;
-
-for my $test ("alarm-single", "alarm-multiple", "alarm-zero", "alarm-negative",
-             "join-simple"
-             #"join-quick", "join-multiple", "join-nested",
-             #"join-dummy", "join-invalid", "join-no",
-             #"priority-preempt", "priority-fifo", "priority-donate-one",
-             #"priority-donate-multiple", "priority-donate-nest",
-             #"mlfqs"
-             ) {
-    print "Testing $test: ";
-    my ($result) = run_test ($test);
-    print "$result\n";
-
-    if ($result eq 'ok') {
-       print "Grading $test: ";
-       $result = grade_test ($test);
+sub ext_mdyHMS {
+    my ($s) = $_;
+    my ($ms, $d, $y, $H, $M, $S) =
+       $s =~ /.([A-Za-z]+)\.(\d+)\.(\d+)\.(\d+)\.(\d+).(\d+)\.tar\.gz$/
+       or die;
+    my ($m) = index ("janfebmaraprmayjunjulaugsepoctnovdec", lc $ms) / 3
+       or die;
+    return sprintf "%02d-%02d-%02d %02d:%02d:%02d", $m, $d, $y, $H, $M, $S;
+}
+
+@TESTS = ("alarm-single", "alarm-multiple", "alarm-zero", "alarm-negative",
+         "join-simple",
+         "join-quick", "join-multiple", "join-nested",
+         "join-dummy", "join-invalid", "join-no",
+         "priority-preempt", "priority-fifo", "priority-donate-one",
+         "priority-donate-multiple", "priority-donate-nest",
+         "mlfqs-on", "mlfqs-off");
+
+for $test (@TESTS) {
+    my ($result);
+    do {
+       print "$test: ";
+       $result = run_test ($test);
+       if ($result eq 'ok') {
+           $result = grade_test ($test);
+           $result =~ s/\n$//;
+       }
        print "$result\n";
-    }
+    } while ($result eq 'rerun');
+    
+    $result{$test} = $result;
 }
 
+make_summary ();
+
 sub grade_test {
     my ($test) = @_;
 
     my (@output) = snarf ("output/$test.run.out");
-    
-    ($grade_func = $test) =~ s/-/_/g;
-    eval "grade_$grade_func(\@output)";
+
+    if (-e "$GRADES_DIR/$test.exp") {
+       eval {
+           verify_common (@output);
+           compare_output ("$GRADES_DIR/$test.exp", @output);
+       }
+    } else {    
+       ($grade_func = $test) =~ s/-/_/g;
+       eval "grade_$grade_func(\@output)";
+    }
     if ($@) {
        die $@ if $@ =~ /at \S+ line \d+$/;
        return $@;
@@ -69,26 +99,198 @@ sub grade_alarm_multiple {
 
 sub grade_alarm_zero {
     my (@output) = @_;
-    #verify_common (@output);
+    verify_common (@output);
     die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
 }
 
 sub grade_alarm_negative {
     my (@output) = @_;
-    #verify_common (@output);
+    verify_common (@output);
     die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output);
 }
 
-sub grade_join_simple {
+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;
+    for $_ (@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;
+    }
+
+    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 $err if $err ne '';
+}
+
+sub grade_priority_fifo {
+    my (@output) = @_;
+
+    verify_common (@output);
+    my ($thread_cnt) = 10;
+    my ($iter_cnt) = 5;
+    my (@order);
+    my (@t) = (-1) x $thread_cnt;
+    for $_ (@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;
+    }
+
+    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";
+       }
+    }
+    die $err if $err ne '';
+}
+
+sub grade_mlfqs_on {
+    my (@output) = @_;
+    verify_common (@output);
+    mlfqs_stats (@output);
+}
+
+sub grade_mlfqs_off {
+    my (@output) = @_;
+    verify_common (@output);
+    mlfqs_stats (@output);
+}
+
+sub mlfqs_stats {
+    my (@output) = @_;
+    my (%stats) = ("io" => {}, "cpu" => {}, "mix" => {});
+    my (%map) = ("CPU intensive" => "cpu",
+                "IO intensive" => "io",
+                "Alternating IO/CPU" => "mix");
+    for $_ (@output) {
+       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"};
+    }
+    for my $t (keys %stats) {
+       my ($s) = $stats{$t};
+       print "$t: n=$$s{'n'}, min=$$s{'min'}, max=$$s{'max'}, avg=", int ($$s{'sum'} / $$s{'n'}), "\n";
+    }
+}
+
+sub get_binaries {
+    if (!files_equal ("pintos/src/threads/test.c", test_source ($test))
+       || !file_contains ("pintos/src/constants.h",
+                          test_constants ($test))) {
+       unlink ("output/$test.run.out")
+           or die "output/$test.run.out: unlink: $!\n";
+       die "rerun\n";
+    }
+}
+
+sub verify_common {
     my (@output) = @_;
-    #verify_common (@output);
-    compare_output ("$GRADES_DIR/join-simple.exp", @output);
+
+    my (@assertion) = grep (/PANIC/, @output);
+    if (@assertion != 0) {
+       my ($details) = "Kernel panic:\n  $assertion[0]\n";
+
+       my (@stack_line) = grep (/Call stack:/, @output);
+       if (@stack_line != 0) {
+           get_binaries ();
+           $details .= "  $stack_line[0]\n\n";
+           $details .= "Translation of backtrace:\n";
+           my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/;
+
+           my ($A2L);
+           if (`uname -m`
+               =~ /i.86|pentium.*|[pk][56]|nexgen|viac3|6x86|athlon.*/) {
+               $A2L = "addr2line";
+           } else {
+               $A2L = "i386-elf-addr2line";
+           }
+           open (A2L, "$A2L -fe pintos/src/threads/build/kernel.o $addrs|");
+           while ($function = <A2L>) {
+               $line = <A2L>;
+               chomp $function;
+               chomp $line;
+               $details .= "  $function ($line)\n";
+           }
+       }
+       $extra{$test} = $details;
+       die "Kernel panic.  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);
+    die "Didn't start up properly: no \"Boot complete\" startup message\n"
+       if !grep (/Boot complete/, @output);
+    die "Didn't shut down properly: no \"Timer: # ticks\" shutdown message\n"
+        if !grep (/Timer: \d+ ticks/, @output);
+    die "Didn't shut down properly: no \"Powering off\" shutdown message\n"
+       if !grep (/Powering off/, @output);
 }
 
 sub compare_output {
     my ($exp_file, @actual) = @_;
     my (@expected) = snarf ($exp_file);
 
+    @actual = map ("$_\n", @actual);
+    @expected = map ("$_\n", @expected);
+
     # Trim header and trailer from @actual.
     while (scalar (@actual) && $actual[0] ne $expected[0]) {
        shift (@actual);
@@ -109,26 +311,51 @@ sub compare_output {
     }
 
     # They differ.  Output a diff.
-    my (@diff) = split ("\n", diff (\@expected, \@actual, {CONTEXT => 0}));
-    for (my ($i) = 0; $i < $#diff; ) {
-       if ($diff[$i] =~ /^@@/) {
-           if ($i == 0) {
-               shift (@diff);
+    my ($diff) = "";
+    my ($d) = Algorithm::Diff->new (\@expected, \@actual);
+    $d->Base (1);
+    while ($d->Next ()) {
+       my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2));
+       if ($d->Same ()) {
+           if ($af != $al) {
+               $diff .= "Actual lines $af...$al match expected lines "
+                   . "$ef...$el.\n";
            } else {
-               $diff[$i++] = "";
+               $diff .= "Actual line $af matches expected line $ef.\n";
            }
        } else {
-           $i++;
+           my (@i1) = $d->Items (1);
+           my (@i2) = $d->Items (2);
+           if (!@i1) {
+               $diff .= "Extra or misplaced line(s) $af...$al "
+                   . "in actual output:\n";
+               $diff .= number_lines ($af, \@i2);
+           } elsif (!$d->Items (2)) {
+               $diff .= "Expected line(s) $ef...$el missing or misplaced:\n";
+               $diff .= number_lines ($ef, \@i1);
+           } else {
+               $diff .= "The following expected line(s) $ef...$el:\n";
+               $diff .= number_lines ($ef, \@i1);
+               $diff .= "became actual line(s) $af...$al:\n";
+               $diff .= number_lines ($af, \@i2);
+           }
        }
     }
-    my ($diff) = join ("\n", @diff);
-    die "Output differs from expected:\n$diff\n";
+
+    my ($details) = "";
+    $details .= "$test actual output (line numbers added):\n";
+    $details .= number_lines (1, \@actual);
+    $details .= "\n$test expected output (line numbers added):\n";
+    $details .= number_lines (1, \@expected);
+    $details .= "\n$diff\n";
+    $details{$test} = $details;
+    die "Output differs from expected.  Details at end of file.\n";
 }
 
 sub verify_alarm {
     my ($iterations, @output) = @_;
 
-    #verify_common (@output);
+    verify_common (@output);
 
     my (@products);
     for (my ($i) = 0; $i < $iterations; $i++) {
@@ -152,12 +379,32 @@ sub verify_alarm {
        if @products != 0;
 }
 
-sub run_test {
+sub test_source {
     my ($test) = @_;
-    return "ok" if -f "output/$test.run.out";
-    
     my ($src) = "$GRADES_DIR/$test.c";
+    $src = "$GRADES_DIR/mlfqs.c" if $test =~ /^mlfqs/;
     -e $src or die "$src: stat: $!\n";
+    return $src;
+}
+
+sub test_constants {
+   my ($defines) = "";
+   $defines .= "#define MLFQS 1\n" if $test eq 'mlfqs-on';
+   return $defines;
+ }
+
+sub run_test {
+    my ($test) = @_;
+    return "ok" if -f "output/$test.run.out";
+
+    my ($defines) = test_constants ($test);
+    if ($defines ne snarf ("pintos/src/constants.h")) {
+       open (CONSTANTS, ">pintos/src/constants.h");
+       print CONSTANTS $defines;
+       close (CONSTANTS);
+    }
+
+    $src = test_source ($test);
     xsystem ("", "cp $src pintos/src/threads/test.c") or die;
     unlink ("pintos/src/threads/build/threads/test.o");
     unlink ("pintos/src/threads/build/kernel.o");
@@ -165,21 +412,28 @@ sub run_test {
     unlink ("pintos/src/threads/build/os.dsk");
     xsystem ("$test.make", "cd pintos/src/threads && make")
        or return "compile error";
-    xsystem ("$test.run", "cd pintos/src/threads/build && pintos -v run -q")
+
+    my ($timeout) = 10;
+    $timeout = 600 if $test =~ /^mlfqs/;
+    xsystem ("$test.run", "cd pintos/src/threads/build && pintos -v run -q",
+            $timeout)
        or return "Bochs error";
     return "ok";
 }
 
 sub xsystem {
-    my ($log, $command) = @_;
+    my ($log, $command, $timeout) = @_;
     print "$command\n" if $verbose;
 
+    $timeout = 0 if !defined $timeout;
+
     my ($status);
     if ($log ne '') {
-       $status = systimeout ("($command) >output/$log.out 2>output/$log.err");
+       $status = systimeout ("($command) >output/$log.out 2>output/$log.err",
+                             $timeout);
        unlink ("output/$log.err") unless $status != 0;
     } else {
-       $status = systimeout ($command);
+       $status = systimeout ($command, $timeout);
     }
 
     die "Interrupted\n"
@@ -189,18 +443,23 @@ sub xsystem {
 }
 
 sub systimeout {
-    my ($command) = @_;
-    my ($status);
+    my ($command, $timeout) = @_;
+    my ($pid, $status);
     eval {
        local $SIG{ALRM} = sub { die "alarm\n" };
-       alarm 10;
-       $status = system ($command);
+       alarm $timeout;
+       $pid = fork;
+       die "fork: $!\n" if !defined $pid;
+       exec ($command), die "$command: exec: $!\n" if !$pid;
+       waitpid ($pid, 0);
+       $status = $?;
        alarm 0;
     };
     if ($@) {
        die unless $@ eq "alarm\n";   # propagate unexpected errors
        print "Timed out.\n";
-       $status = -1;
+       kill SIGTERM, $pid;
+       $status = 0;
     }
     return $status;
 }
@@ -209,6 +468,107 @@ sub snarf {
     my ($file) = @_;
     open (OUTPUT, $file) or die "$file: open: $!\n";
     my (@lines) = <OUTPUT>;
+    chomp (@lines);
     close (OUTPUT);
-    return @lines;
+    return wantarray ? @lines : join ('', map ("$_\n", @lines));
+}
+
+sub make_summary {
+    @summary = snarf ("$GRADES_DIR/tests.txt");
+
+    my ($ploss) = 0;
+    my ($tloss) = 0;
+    my ($total) = 0;
+    for (my ($i) = 0; $i <= $#summary; $i++) {
+       $_ = $summary[$i];
+       if (my ($loss, $test) = /^  -(\d+) ([-a-zA-Z0-9]+):/) {
+           my ($result) = $result{$test} or die "missing results for $test\n";
+
+           if ($result eq 'ok') {
+               splice (@summary, $i, 1);
+               $i--;
+           } else {
+               $ploss += $loss;
+               $tloss += $loss;
+               splice (@summary, $i + 1, 0,
+                       map ("     $_", split ("\n", $result)));
+           }
+       } elsif (my ($ptotal) = /^Score: \/(\d+)$/) {
+           $total += $ptotal;
+           $summary[$i] = "Score: " . ($ptotal - $ploss) . "/$ptotal";
+           splice (@summary, $i, 0, "  All tests passed.") if $ploss == 0;
+           $ploss = 0;
+           $i++;
+       }
+    }
+    my ($ts) = "(" . ($total - $tloss) . "/" . $total . ")";
+    $summary[0] =~ s/\[\[total\]\]/$ts/;
+
+    open (SUMMARY, ">tests.out");
+    print SUMMARY map ("$_\n", @summary);
+    close (SUMMARY);
+
+    open (DETAILS, ">details.out");
+    my ($n) = 0;
+    for my $test (@TESTS) {
+       next if $result{$test} eq 'ok';
+       
+       my ($details) = $details{$test};
+       next if !defined ($details) && ! -e "output/$test.run.out";
+
+       print DETAILS "\n" if $n++;
+       print DETAILS "--- $test details ", '-' x (50 - length ($test));
+       print DETAILS "\n\n";
+
+       if (!defined $details) {
+           $details = "Output:\n\n" . snarf ("output/$test.run.out");
+       }
+       print DETAILS $details;
+
+       print DETAILS "\n", "-" x 10, "\n\n$extra{$test}"
+           if defined $extra{$test};
+    }
+    close (DETAILS);
+
+}
+
+sub files_equal {
+    my ($a, $b) = @_;
+    my ($equal);
+    open (A, "<$a") or die "$a: open: $!\n";
+    open (B, "<$b") or die "$b: open: $!\n";
+    if (-s A != -s B) {
+       $equal = 0;
+    } else {
+       my ($sa, $sb);
+       for (;;) {
+           sysread (A, $sa, 1024);
+           sysread (B, $sb, 1024);
+           $equal = 0, last if $sa ne $sb;
+           $equal = 1, last if $sa eq '';
+       }
+    }
+    close (A);
+    close (B);
+    return $equal;
+}
+
+sub file_contains {
+    my ($file, $expected) = @_;
+    open (FILE, "<$file") or die "$file: open: $!\n";
+    my ($actual);
+    sysread (FILE, $actual, -s FILE);
+    my ($equal) = $actual eq $expected;
+    close (FILE);
+    return $equal;
+}
+
+sub number_lines {
+    my ($ln, $lines) = @_;
+    my ($out);
+    for my $line (@$lines) {
+       chomp $line;
+       $out .= sprintf "%4d  %s\n", $ln++, $line;
+    }
+    return $out;
 }
index 6a882928470b2443a60d82a846c8699c3726912f..3a37405f70238dcd9fbdad5c34b0d12ec7dbd718 100644 (file)
@@ -1,32 +1,36 @@
-CORRECTNESS [[/40]]
+CORRECTNESS [[total]]
 -------------------
 
 Points are taken off for tests that failed.  Only failing tests are
 listed.
 
-Problem 1-1: Alarm Clock [[/8]]
-[[-3]] alarm-single: Multiple threads each sleep once (public)
-[[-3]] alarm-multiple: Multiple threads each sleep many times (public)
-[[-1]] alarm-zero: Zero wait time must not crash or hang
-[[-1]] alarm-negative: Negative wait time must not crash or hang
+Problem 1-1: Alarm Clock
+  -3 alarm-single: Multiple threads each sleep once (public)
+  -3 alarm-multiple: Multiple threads each sleep many times (public)
+  -1 alarm-zero: Zero wait time must not crash or hang
+  -1 alarm-negative: Negative wait time must not crash or hang
+Score: /8
 
-Problem 1-2: Join [[/14]]
-[[-2]] join-simple: A creates B, A joins B (public)
-[[-2]] join-quick: A creates B, A joins B, with different details (public)
-[[-2]] join-multiple: A creates B and C, A joins B, A joins C (public)
-[[-2]] join-nested: A creates B, B creates C, ..., B joins C, A joins B
-[[-2]] join-dummy: A creates B, A joins B, A joins B
-[[-2]] join-invalid: Joining an invalid tid must not crash or hang
-[[-2]] join-no: Creating a thread and never joining it must not crash or hang
+Problem 1-2: Join
+  -2 join-simple: A creates B, A joins B (public)
+  -2 join-quick: A creates B, A joins B, with different details (public)
+  -2 join-multiple: A creates B and C, A joins B, A joins C (public)
+  -2 join-nested: A creates B, B creates C, ..., B joins C, A joins B
+  -2 join-dummy: A creates B, A joins B, A joins B
+  -2 join-invalid: Joining an invalid tid must not crash or hang
+  -2 join-no: Creating a thread and never joining it must not crash or hang
+Score: /14
 
-Problem 1-3: Priority Scheduler [[/10]]
-[[-2]] priority-preempt: Higher-priority thread preempts others (public)
-[[-2]] priority-fifo: Threads of equal priority run round-robin (public)
-[[-2]] priority-donate-one: Priority donation with single lock (public)
-[[-2]] priority-donate-multiple: Priority donation with multiple locks
-[[-2]] priority-donate-nest: Nested priority donation with single lock
+Problem 1-3: Priority Scheduler
+  -2 priority-preempt: Higher-priority thread preempts others (public)
+  -2 priority-fifo: Threads of equal priority run round-robin (public)
+  -2 priority-donate-one: Priority donation with single lock (public)
+  -2 priority-donate-multiple: Priority donation with multiple locks
+  -2 priority-donate-nest: Nested priority donation with single lock
+Score: /10
 
-Problem 1-4: Advanced Scheduler [[/8]
-[[-2]] Public testcase doesn't run faster with MLFQS
-[[-2]] Group's own testcase doesn't run faster with MLFQS
-[[-4]] Priorities don't change properly
+Problem 1-4: Advanced Scheduler
+  -2 Public testcase doesn't run faster with MLFQS
+  -2 Group's own testcase doesn't run faster with MLFQS
+  -4 Priorities don't change properly
+Score: /8