+use Algorithm::Diff;
+use Getopt::Long;
+
+our ($VERBOSE) = 0; # Verbosity of output
+our (@TESTS); # Tests to run.
+my ($clean) = 0;
+my ($grade) = 0;
+
+GetOptions ("v|verbose+" => \$VERBOSE,
+ "h|help" => sub { usage (0) },
+ "t|test=s" => \@TESTS,
+ "c|clean" => \$clean,
+ "g|grade" => \$grade)
+ or die "Malformed command line; use --help for help.\n";
+die "Non-option argument not supported; use --help for help.\n"
+ if @ARGV > 0;
+
+sub usage {
+ my ($exitcode) = @_;
+ print "run-tests, for grading Pintos thread projects.\n\n";
+ print "Invoke from a directory containing a student tarball named by\n";
+ print "the submit script, e.g. username.Oct.12.04.20.04.09.tar.gz.\n";
+ print "In normal usage, no options are needed.\n\n";
+ print "Output is produced in tests.out and details.out.\n\n";
+ print "Options:\n";
+ print " -c, --clean Remove old output files before starting\n";
+ print " -t, --test=TEST Execute TEST only (allowed multiple times)\n";
+ print " -g, --grade Instead of running tests, compose grade.out\n";
+ print " -v, --verbose Print commands before executing them\n";
+ print " -h, --help Print this help message\n";
+ exit $exitcode;
+}
+
+# Default set of tests.
+@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")
+ unless @TESTS > 0;
+
+# Handle final grade mode.
+if ($grade) {
+ open (OUT, ">grade.out") or die "grade.out: create: $!\n";
+
+ open (GRADE, "<grade.txt") or die "grade.txt: open: $!\n";
+ while (<GRADE>) {
+ last if /^\s*$/;
+ print OUT;
+ }
+ close (GRADE);
+
+ my (@tests) = snarf ("tests.out");
+ my ($p_got, $p_pos) = $tests[0] =~ m%\((\d+)/(\d+)\)% or die;
+
+ my (@review) = snarf ("review.txt");
+ my ($part_lost) = (0, 0);
+ for (my ($i) = $#review; $i >= 0; $i--) {
+ local ($_) = $review[$i];
+ if (my ($loss) = /^\s*([-+]\d+)/) {
+ $part_lost += $loss;
+ } elsif (my ($out_of) = m%\[\[/(\d+)\]\]%) {
+ my ($got) = $out_of + $part_lost;
+ $got = 0 if $got < 0;
+ $review[$i] =~ s%\[\[/\d+\]\]%($got/$out_of)% or die;
+ $part_lost = 0;
+
+ $p_got += $got;
+ $p_pos += $out_of;
+ }
+ }
+ die "Lost points outside a section\n" if $part_lost;
+
+ for (my ($i) = 1; $i <= $#review; $i++) {
+ if ($review[$i] =~ /^-{3,}\s*$/ && $review[$i - 1] !~ /^\s*$/) {
+ $review[$i] = '-' x (length ($review[$i - 1]));
+ }
+ }
+
+ print OUT "\nOVERALL SCORE\n";
+ print OUT "-------------\n";
+ print OUT "$p_got points out of $p_pos total\n\n";