First stab at grading advice.
[pintos-anon] / ta-advice / run-tests
1 #! /usr/bin/perl -w
2
3 use strict;
4 use Getopt::Long;
5 use POSIX;
6
7 our ($PINTOSDIR) = "/usr/class/cs140/pintos/pintos";
8
9 our ($verbose) = 0;
10 our ($start) = -d 'pintos/src' ? 4 : 1;
11 our ($stop) = 7;
12
13 GetOptions ("v|verbose+" => \$verbose,
14             "h|help" => sub { usage (0) },
15             "r|replace" => sub {
16                 die "Can't start from step 2: pintos/src does not exist\n"
17                   if ! -d 'pintos/src';
18                 $start = 2;
19             },
20             "x|extract" => sub { $stop = 2 },
21             "c|clean" => sub { $stop = 3 },
22             "b|build" => sub { $stop = 4 },
23             "t|test" => sub { $stop = 6 })
24   or die "Malformed command line; use --help for help.\n";
25
26 die "Exactly one non-option argument required; use --help for help.\n"
27   if @ARGV != 1;
28
29 my (@valid_projects) = ('threads', 'userprog', 'vm', 'filesys');
30 my ($project) = $ARGV[0];
31 $project = $valid_projects[$project - 1] if $project =~ /^[1234]$/;
32 die "Unknown project \"$project\"; use --help for help.\n"
33   if !grep ($_ eq $project, @valid_projects);
34
35 sub usage {
36     my ($exitcode) = @_;
37     print <<EOF;
38 run-tests, runs tests for grading a single submitted project
39
40 usage: run-tests PROJECT
41 where PROJECT is a project name (threads, userprog, vm, filesys)
42 or number (1-4).
43
44 Invoke from a directory containing a student tarball named by
45 the submit script, e.g. username.MMM.DD.YY.hh.mm.ss.tar.gz,
46 or a pintos/src directory containing a student submission.
47
48 Workflow:
49   1. Extracts the source tree into pintos/src.
50   2. Replaces the existing pintos/src/tests directory by a pristine
51      copy, which must be available in $PINTOSDIR.
52   3. Cleans the source tree.
53   4. Builds the source tree.
54   5. Runs the tests on the source tree and grades them.
55   6. Copies the grade report to tests.out.
56   7. Cleans the source tree again.
57
58 Note:
59   If pintos/src already exists, run-tests starts from step 4.
60   To force it to start from step 1, remove the pintos directory.
61   To force it to start from step 2, use --replace.
62
63 Options:
64   -r, --replace      Start at step 2.
65   -x, --extract      Stop after step 2.
66   -c, --clean        Stop after step 3.
67   -b, --build        Stop after step 4.
68   -t, --test         Stop after step 6.
69   -v, --verbose      Print command lines of subcommands before executing them.
70   -h, --help         Print this help message.
71 EOF
72     exit $exitcode;
73 }
74
75 if (do_step (1)) {
76     my ($tarball) = choose_tarball ();
77
78     print "Extracting $tarball...\n";
79     xsystem ("tar xzf $tarball", DIE => "extraction failed\n");
80 }
81
82 if (do_step (2)) {
83     print "Replacing tests with pristine copy...\n";
84     xsystem ("rm -rf pintos/src/tests",
85              DIE => "removal of old tests failed\n");
86     xsystem ("cp -pR $PINTOSDIR/src/tests pintos/src/tests",
87              DIE => "replacement of tests failed\n");
88 }
89
90 if (do_step (3)) {
91     print "Cleaning...\n";
92     xsystem ("cd pintos/src && make clean", DIE => "clean failed");
93 }
94
95 if (do_step (4)) {
96     print "Building...\n";
97     xsystem ("cd pintos/src/$project && make", DIE => "build failed");
98 }
99
100 if (do_step (5)) {
101     print "Grading...\n";
102     xsystem ("cd pintos/src/$project && make grade", DIE => "grade failed");
103 }
104
105 if (do_step (6)) {
106     print "Saving grade report to tests.out...\n";
107     xsystem ("cp pintos/src/$project/build/grade tests.out",
108              DIE => "copy failed");
109 }
110
111 if (do_step (7)) {
112     print "Cleaning...\n";
113     xsystem ("cd pintos/src && make clean", DIE => "clean failed");
114 }
115
116 # do_step ($N)
117 #
118 # Returns true if step $N should be executed.
119 sub do_step {
120     my ($n) = @_;
121     return $n >= $start && $n <= $stop;
122 }
123
124 # Returns the name of the tarball to extract.
125 sub choose_tarball {
126     my (@tarballs)
127         = grep (/^[a-z0-9]+\.[A-Za-z]+\.\d+\.\d+\.\d+\.\d+.\d+\.tar\.gz$/,
128                 glob ("*.tar.gz"));
129     die "no pintos dir, no files matching username.MMM.DD.YY.hh.mm.ss.tar.gz\n"
130         if scalar (@tarballs) == 0;
131
132     if (@tarballs > 1) {
133         # Sort tarballs in order by time.
134         @tarballs = sort { ext_mdyHMS ($a) cmp ext_mdyHMS ($b) } @tarballs;
135
136         print "Multiple tarballs:\n";
137         print "\t$_ submitted ", ext_mdyHMS ($_), "\n" foreach @tarballs;
138         print "Choosing $tarballs[$#tarballs]\n";
139     }
140     return $tarballs[$#tarballs];
141 }
142
143 # Extract the date within a tarball name into a string that compares
144 # lexicographically in chronological order.
145 sub ext_mdyHMS {
146     my ($s) = @_;
147     my ($ms, $d, $y, $H, $M, $S) =
148         $s =~ /.([A-Za-z]+)\.(\d+)\.(\d+)\.(\d+)\.(\d+).(\d+)\.tar\.gz$/
149         or die;
150     my ($m) = index ("janfebmaraprmayjunjulaugsepoctnovdec", lc $ms) / 3
151         or die;
152     return sprintf "%02d-%02d-%02d %02d:%02d:%02d", $y, $m, $d, $H, $M, $S;
153 }
154
155 sub xsystem {
156     my ($command, %options) = @_;
157     print "$command\n" if $verbose || $options{VERBOSE};
158
159     my ($log) = $options{LOG};
160
161     my ($pid, $status);
162     eval {
163         local $SIG{ALRM} = sub { die "alarm\n" };
164         alarm $options{TIMEOUT} if defined $options{TIMEOUT};
165         $pid = fork;
166         die "fork: $!\n" if !defined $pid;
167         if (!$pid) {
168             if (defined $log) {
169                 open (STDOUT, ">output/$log.out");
170                 open (STDERR, ">output/$log.err");
171             }
172             exec ($command);
173             exit (-1);
174         }
175         waitpid ($pid, 0);
176         $status = $?;
177         alarm 0;
178     };
179
180     my ($result);
181     if ($@) {
182         die unless $@ eq "alarm\n";   # propagate unexpected errors
183         my ($i);
184         for ($i = 0; $i < 10; $i++) {
185             kill ('SIGTERM', $pid);
186             sleep (1);
187             my ($retval) = waitpid ($pid, WNOHANG);
188             last if $retval == $pid || $retval == -1;
189             print "Timed out - Waiting for $pid to die" if $i == 0;
190             print ".";
191         }
192         print "\n" if $i;
193         $result = 'timeout';
194     } else {
195         if (WIFSIGNALED ($status)) {
196             my ($signal) = WTERMSIG ($status);
197             die "Interrupted\n" if $signal == SIGINT;
198             print "Child terminated with signal $signal\n";
199         }
200
201         my ($exp_status) = !defined ($options{EXPECT}) ? 0 : $options{EXPECT};
202         $result = WIFEXITED ($status) && WEXITSTATUS ($status) == $exp_status
203             ? "ok" : "error";
204     }
205
206
207     if ($result eq 'error' && defined $options{DIE}) {
208         my ($msg) = $options{DIE};
209         if (defined ($log)) {
210             chomp ($msg);
211             $msg .= "; see output/$log.err and output/$log.out for details\n";
212         }
213         die $msg;
214     } elsif ($result ne 'error' && defined ($log)) {
215         unlink ("output/$log.err");
216     }
217
218     return $result;
219 }