+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 {