quiet = true;
- check (argc == 2, "argc must be 2, actually %d", argc);
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
child_idx = atoi (argv[1]);
random_init (0);
random_bytes (buf, sizeof buf);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
for (i = 0; i < sizeof buf; i++)
{
char c;
- check (read (fd, &c, 1) > 0, "read \"%s\"", filename);
+ CHECK (read (fd, &c, 1) > 0, "read \"%s\"", filename);
compare_bytes (&c, buf + i, 1, i, filename);
}
close (fd);
quiet = true;
- check (argc == 2, "argc must be 2, actually %d", argc);
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
child_idx = atoi (argv[1]);
random_init (0);
random_bytes (buf1, sizeof buf1);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
ofs = 0;
while (ofs < sizeof buf2)
{
int bytes_read = read (fd, buf2 + ofs, sizeof buf2 - ofs);
- check (bytes_read >= -1 && bytes_read <= (int) (sizeof buf2 - ofs),
+ CHECK (bytes_read >= -1 && bytes_read <= (int) (sizeof buf2 - ofs),
"%zu-byte read on \"%s\" returned invalid value of %d",
sizeof buf2 - ofs, filename, bytes_read);
if (bytes_read > 0)
quiet = true;
- check (argc == 2, "argc must be 2, actually %d", argc);
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
child_idx = atoi (argv[1]);
random_init (0);
random_bytes (buf, sizeof buf);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
seek (fd, CHUNK_SIZE * child_idx);
- check (write (fd, buf + CHUNK_SIZE * child_idx, CHUNK_SIZE) > 0,
+ CHECK (write (fd, buf + CHUNK_SIZE * child_idx, CHUNK_SIZE) > 0,
"write \"%s\"", filename);
msg ("close \"%s\"", filename);
close (fd);
void
test_main (void)
{
- check (create ("testfile", TEST_SIZE), "create testfile");
- check_file ("testfile", buf, TEST_SIZE);
+ const char *filename = "blargle";
+ CHECK (create (filename, TEST_SIZE), "create \"%s\"", filename);
+ check_file (filename, buf, TEST_SIZE);
}
void
test_main (void)
{
- check (!create ("", 0), "create \"\" (must return false)");
+ CHECK (!create ("", 0), "create \"\" (must return false)");
}
dir[1] = '\0';
for (dir[0] = '0'; dir[0] <= '9'; dir[0]++)
{
- check (mkdir (dir), "mkdir \"%s\"", dir);
- check (chdir (dir), "chdir \"%s\"", dir);
+ CHECK (mkdir (dir), "mkdir \"%s\"", dir);
+ CHECK (chdir (dir), "chdir \"%s\"", dir);
}
- check (create ("test", 512), "create \"test\"");
- check (chdir ("/"), "chdir \"/\"");
- check (open (filename) > 1, "open \"%s\"", filename);
+ CHECK (create ("test", 512), "create \"test\"");
+ CHECK (chdir ("/"), "chdir \"/\"");
+ CHECK (open (filename) > 1, "open \"%s\"", filename);
}
void
test_main (void)
{
- check (mkdir ("a"), "mkdir \"a\"");
- check (create ("a/b", 512), "create \"a/b\"");
- check (chdir ("a"), "chdir \"a\"");
- check (open ("b") > 1, "open \"b\"");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (create ("a/b", 512), "create \"a/b\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
+ CHECK (open ("b") > 1, "open \"b\"");
}
{
int fd;
- check (mkdir ("xyzzy"), "mkdir \"xyzzy\"");
+ CHECK (mkdir ("xyzzy"), "mkdir \"xyzzy\"");
msg ("open \"xyzzy\"");
fd = open ("xyzzy");
if (fd == -1)
else
{
int retval = write (fd, "foobar", 6);
- check (retval == -1, "write \"xyzzy\" (must return -1, actually %d)",
+ CHECK (retval == -1, "write \"xyzzy\" (must return -1, actually %d)",
retval);
}
}
void
test_main (void)
{
- check (mkdir ("abc"), "mkdir \"abc\"");
- check (!create ("abc", 0), "create \"abc\" (must return false)");
+ CHECK (mkdir ("abc"), "mkdir \"abc\"");
+ CHECK (!create ("abc", 0), "create \"abc\" (must return false)");
}
void
test_main (void)
{
- check (mkdir ("a"), "mkdir \"a\"");
- check (chdir ("a"), "chdir \"a\"");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
msg ("remove \"/a\" (must not crash)");
if (remove ("/a"))
- check (!chdir ("/a"),
+ CHECK (!chdir ("/a"),
"chdir \"/a\" (remove succeeded so this must return false)");
else
- check (chdir ("/a"), "chdir \"/a\" (remove failed so this must succeed)");
+ CHECK (chdir ("/a"), "chdir \"/a\" (remove failed so this must succeed)");
}
void
test_main (void)
{
- check (mkdir ("a"), "mkdir \"a\"");
- check (chdir ("a"), "chdir \"a\"");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
msg ("remove \"/a\" (must not crash)");
remove ("/a");
msg ("create \"b\" (must not crash)");
void
test_main (void)
{
- check (mkdir ("a"), "mkdir \"a\"");
- check (chdir ("a"), "chdir \"a\"");
- check (mkdir ("b"), "mkdir \"b\"");
- check (chdir ("b"), "chdir \"b\"");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
+ CHECK (mkdir ("b"), "mkdir \"b\"");
+ CHECK (chdir ("b"), "chdir \"b\"");
msg ("remove \"/b\" (must not crash)");
remove ("/b");
msg ("remove \"/a\" (must not crash)");
void
test_main (void)
{
- check (!remove ("/"), "remove \"/\" (must return false)");
- check (create ("/a", 243), "create \"/a\"");
+ CHECK (!remove ("/"), "remove \"/\" (must return false)");
+ CHECK (create ("/a", 243), "create \"/a\"");
}
quiet = false;
snprintf (try, sizeof (try), "/%d/%d/%d/%d", at - 1, 0, ct - 1, 0);
- check (open (try) == -1, "open \"%s\" (must return -1)", try);
+ CHECK (open (try) == -1, "open \"%s\" (must return -1)", try);
}
static void
vsnprintf (name, sizeof name, format, args);
va_end (args);
- check (remove (name), "remove \"%s\"", name);
+ CHECK (remove (name), "remove \"%s\"", name);
}
tmp[1] = '\0';
for (tmp[0] = '0'; tmp[0] <= '9'; tmp[0]++)
{
- check (mkdir (tmp), "mkdir \"%s\"", tmp);
- check (chdir (tmp), "chdir \"%s\"", tmp);
+ CHECK (mkdir (tmp), "mkdir \"%s\"", tmp);
+ CHECK (chdir (tmp), "chdir \"%s\"", tmp);
}
- check (create ("test", 512), "create \"test\"");
+ CHECK (create ("test", 512), "create \"test\"");
- check (chdir ("/"), "chdir \"/\"");
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (chdir ("/"), "chdir \"/\"");
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
msg ("close \"%s\"", filename);
close (fd);
strlcpy (tmp, filename, sizeof tmp);
while (strlen (tmp) > 0)
{
- check (remove (tmp), "remove \"%s\"", tmp);
+ CHECK (remove (tmp), "remove \"%s\"", tmp);
*strrchr (tmp, '/') = 0;
}
- check (open (filename) == -1, "open \"%s\" (must return -1)", filename);
+ CHECK (open (filename) == -1, "open \"%s\" (must return -1)", filename);
}
void
test_main (void)
{
- check (mkdir ("a"), "mkdir \"a\"");
- check (remove ("a"), "rmdir \"a\"");
- check (!chdir ("a"), "chdir \"a\" (must return false)");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (remove ("a"), "rmdir \"a\"");
+ CHECK (!chdir ("a"), "chdir \"a\" (must return false)");
}
void
test_main (void)
{
- check (create ("abc", 0), "create \"abc\"");
- check (!mkdir ("abc"), "mkdir \"abc\" (must return false)");
+ CHECK (create ("abc", 0), "create \"abc\"");
+ CHECK (!mkdir ("abc"), "mkdir \"abc\" (must return false)");
}
int fd;
random_bytes (buf, size);
- check (create (filename, initial_size), "create \"%s\"", filename);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (create (filename, initial_size), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
ofs = 0;
msg ("writing \"%s\"", filename);
char block[512];
int fd;
- check ((fd = open (filename)) > 1, "open \"%s\" for verification", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\" for verification", filename);
ofs = 0;
while (ofs < size)
fail ("%zu bytes read starting at offset %zu in \"%s\" differ "
"from expected", j - i, ofs, filename);
}
+
+void
+exec_children (const char *child_name, pid_t pids[], size_t child_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < child_cnt; i++)
+ {
+ char cmd_line[128];
+ snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i);
+ CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR,
+ "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line);
+ }
+}
+
+void
+join_children (pid_t pids[], size_t child_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < child_cnt; i++)
+ {
+ int status = join (pids[i]);
+ CHECK (status == (int) i,
+ "join child %zu of %zu returned %d (expected %d)",
+ i + 1, child_cnt, status, i);
+ }
+}
#include <debug.h>
#include <stdbool.h>
#include <stddef.h>
+#include <syscall.h>
extern const char test_name[];
extern bool quiet;
void msg (const char *, ...) PRINTF_FORMAT (1, 2);
void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN;
-#define check(SUCCESS, ...) \
+/* Takes an expression to test for SUCCESS and a message, which
+ may include printf-style arguments. Logs the message, then
+ tests the expression. If it is zero, indicating failure,
+ emits the message as a failure.
+
+ Somewhat tricky to use:
+
+ - SUCCESS must not have side effects that affect the
+ message, because that will cause the original message and
+ the failure message to differ.
+
+ - The message must not have side effects of its own, because
+ it will be printed twice on failure, or zero times on
+ success if quiet is set. */
+#define CHECK(SUCCESS, ...) \
do \
{ \
msg (__VA_ARGS__); \
void compare_bytes (const void *read_data, const void *expected_data,
size_t size, size_t ofs, const char *filename);
+void exec_children (const char *child_name, pid_t pids[], size_t child_cnt);
+void join_children (pid_t pids[], size_t child_cnt);
+
void test_main (void);
#endif /* fslib.h */
(grow-create) begin
-(grow-create) create testfile
-(grow-create) open "testfile" for verification
-(grow-create) close "testfile"
+(grow-create) create "blargle"
+(grow-create) open "blargle" for verification
+(grow-create) close "blargle"
(grow-create) end
size_t i;
#ifdef DIRECTORY
- check (mkdir (DIRECTORY), "mkdir %s", DIRECTORY);
+ CHECK (mkdir (DIRECTORY), "mkdir %s", DIRECTORY);
#define DIR_PREFIX DIRECTORY "/"
#else
#define DIR_PREFIX ""
void
test_main (void)
{
+ const char *filename = "testfile";
char zero = 0;
int fd;
- check (create ("testfile", 0), "create \"testfile\"");
- check ((fd = open ("testfile")) > 1, "open \"testfile\"");
- msg ("seek \"testfile\"");
+ CHECK (create (filename, 0), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ msg ("seek \"%s\"", filename);
seek (fd, sizeof buf - 1);
- check (write (fd, &zero, 1) > 0, "write \"testfile\"");
- msg ("close \"testfile\"");
+ CHECK (write (fd, &zero, 1) > 0, "write \"%s\"", filename);
+ msg ("close \"%s\"", filename);
close (fd);
- check_file ("testfile", buf, sizeof buf);
+ check_file (filename, buf, sizeof buf);
}
void
test_main (void)
{
+ const char *filename = "fumble";
char zero = 0;
int fd;
- check (create ("testfile", 0), "create \"testfile\"");
- check ((fd = open ("testfile")) > 1, "open \"testfile\"");
- msg ("seek \"testfile\"");
+ CHECK (create (filename, 0), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ msg ("seek \"%s\"", filename);
seek (fd, UINT_MAX);
- check (write (fd, &zero, 1) > 0, "write \"testfile\"");
- msg ("close \"testfile\"");
+ CHECK (write (fd, &zero, 1) > 0, "write \"%s\"", filename);
+ msg ("close \"%s\"", filename);
close (fd);
}
random_bytes (buf_a, sizeof buf_a);
random_bytes (buf_b, sizeof buf_b);
- check (create ("a", 0), "create \"a\"");
- check (create ("b", 0), "create \"b\"");
+ CHECK (create ("a", 0), "create \"a\"");
+ CHECK (create ("b", 0), "create \"b\"");
- check ((fd_a = open ("a")) > 1, "open \"a\"");
- check ((fd_b = open ("b")) > 1, "open \"b\"");
+ CHECK ((fd_a = open ("a")) > 1, "open \"a\"");
+ CHECK ((fd_b = open ("b")) > 1, "open \"b\"");
msg ("write \"a\" and \"b\" alternately");
while (ofs_a < FILE_SIZE || ofs_b < FILE_SIZE)
quiet = false;
snprintf (try, sizeof try, "/%d/%d/%d/%d", 0, bt - 1, 0, dt - 1);
- check ((fd = open (try)) > 1, "open \"%s\"", try);
+ CHECK ((fd = open (try)) > 1, "open \"%s\"", try);
msg ("close \"%s\"", try);
close (fd);
}
vsnprintf (dir, sizeof dir, format, args);
va_end (args);
- check (mkdir (dir), "mkdir \"%s\"", dir);
+ CHECK (mkdir (dir), "mkdir \"%s\"", dir);
}
static void
vsnprintf (file, sizeof file, format, args);
va_end (args);
- check (create (file, 0), "create \"%s\"", file);
+ CHECK (create (file, 0), "create \"%s\"", file);
}
for (i = 0; i < BLOCK_CNT; i++)
order[i] = i;
- check (create (filename, TEST_SIZE), "create \"%s\"", filename);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (create (filename, TEST_SIZE), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
msg ("write \"%s\" in random order", filename);
shuffle (order, BLOCK_CNT, sizeof *order);
#! /usr/bin/perl
# Find the directory that contains the grading files.
-our ($GRADES_DIR);
+use vars qw($GRADES_DIR);
# Add our Perl library directory to the include path.
BEGIN {
use POSIX;
use Algorithm::Diff;
use Getopt::Long;
+use Pintos::Grading;
+our ($test);
our ($verbose) = 0; # Verbosity of output
our (@TESTS); # Tests to run.
my ($clean) = 0;
-d ("output") || mkdir ("output") or die "output: mkdir: $!\n";
# Extract submission.
-extract_tarball () if ! -d "pintos";
+obtain_sources ();
# Compile submission.
compile ();
-d "pintos/src/threads" or die "pintos/src/threads: stat: $!\n";
# Run and grade the tests.
-our $test;
our %result;
our %details;
our %extra;
for $test (@TESTS) {
print "$test: ";
- my ($result) = run_test ($test);
+ my ($result) = get_test_result ();
if ($result eq 'ok') {
$result = grade_test ($test);
- $result =~ s/\n$//;
+ } elsif ($result =~ /^Timed out/) {
+ $result = "$result - " . grade_test ($test);
}
+ chomp ($result);
print "$result";
print " - with warnings" if $result eq 'ok' && defined $details{$test};
print "\n";
write_grades ();
write_details ();
\f
-sub choose_tarball {
- my (@tarballs)
- = 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;
-
- print "Multiple tarballs: choosing $tarballs[0]\n"
- if scalar (@tarballs) > 1;
- return $tarballs[0];
-}
-
-sub extract_tarball {
- my ($tarball) = choose_tarball ();
-
- mkdir "pintos" or die "pintos: mkdir: $!\n";
- mkdir "pintos/src" or die "pintos: mkdir: $!\n";
-
- print "Extracting $tarball...\n";
- 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");
-
- 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 {
- 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", $y, $m, $d, $H, $M, $S;
-}
-\f
-sub test_source {
- my ($test) = @_;
- my ($src) = "$GRADES_DIR/$test.c";
- -e $src or die "$src: stat: $!\n";
- return $src;
-}
-
-sub test_constants {
- my ($defines) = "";
- return $defines;
- }
-
-sub run_test {
- my ($test) = @_;
-
- # Reuse older results if any
- if (open (DONE, "<output/$test/done")) {
- my ($status);
- $status = <DONE>;
- chomp $status;
- close (DONE);
- return $status;
- }
-
- # Really run the test.
- my ($status) = really_run_test ($test);
-
- # Save the results for later.
- open (DONE, ">output/$test/done") or die "output/$test/done: create: $!\n";
- print DONE "$status\n";
- close (DONE);
-
- return $status;
-}
-
-sub compile {
- print "Compiling...\n";
- xsystem ("cd pintos/src/filesys && make", LOG => "make")
- or return "compile error";
-}
-
-sub really_run_test {
- # Need to run it.
- # If there's residue from an earlier test, move it to .old.
- # If there's already a .old, delete it.
- xsystem ("rm -rf output/$test.old", VERBOSE => 1) if -d "output/$test.old";
- rename "output/$test", "output/$test.old" or die "rename: $!\n"
- if -d "output/$test";
-
- # Make output directory.
- mkdir "output/$test";
- my ($fs_size) = $test ne 'grow-too-big' ? 2 : .25;
- xsystem ("pintos make-disk output/$test/fs.dsk $fs_size >/dev/null 2>&1",
- DIE => "failed to create file system disk");
- xsystem ("pintos make-disk output/$test/swap.dsk 2 >/dev/null 2>&1",
- DIE => "failed to create swap disk");
-
- # Format disk, install test.
- my ($pintos_base_cmd) =
- "pintos "
- . "--os-disk=pintos/src/filesys/build/os.dsk "
- . "--fs-disk=output/$test/fs.dsk "
- . "--swap-disk=output/$test/swap.dsk "
- . "-v";
- unlink ("output/$test/fs.dsk", "output/$test/swap.dsk"),
- return "format/put error"
- if !xsystem ("$pintos_base_cmd put -f $GRADES_DIR/$test $test",
- LOG => "$test/put", TIMEOUT => 60, EXPECT => 1);
-
- my (@extra_files);
- push (@extra_files, "child-syn-read") if $test eq 'syn-read';
- push (@extra_files, "child-syn-wrt") if $test eq 'syn-write';
- push (@extra_files, "child-syn-rw") if $test eq 'syn-rw';
- for my $fn (@extra_files) {
- return "format/put error"
- if !xsystem ("$pintos_base_cmd put $GRADES_DIR/$fn $fn",
- LOG => "$test/put-$fn", TIMEOUT => 60, EXPECT => 1);
- }
-
- # Run.
- my ($timeout) = 60;
- my ($testargs) = defined ($args{$test}) ? " $args{$test}" : "";
- my ($result) =
- xsystem ("$pintos_base_cmd run -q -ex \"$test$testargs\"",
- LOG => "$test/run", TIMEOUT => $timeout, EXPECT => 1)
- ? "ok" : "Bochs error";
- unlink ("output/$test/fs.dsk", "output/$test/swap.dsk");
- return $result;
-}
-
-sub grade_test {
- my ($test) = @_;
-
- my (@output) = snarf ("output/$test/run.out");
-
- 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 {
- eval "$grade_func (\@output)";
- }
- if ($@) {
- die $@ if $@ =~ /at \S+ line \d+$/;
- return $@;
- }
- return "ok";
-}
\f
sub grade_process_death {
my ($proc_name, @output) = @_;
my (@failure) = grep (/FAIL/, @output);
if (@failure != 0) {
- die "Test failed: \"$failure[0]\"\n";
+ eval {
+ my (@core) = get_core_output (@output);
+ $details{$test} = "Program output:\n\n" . join ('', map ("$_\n", @core));
+ };
+ my ($failure) = $failure[0];
+ $failure =~ s/^\([^)]+\)\s+//;
+ die "Failed with message \"$failure\"\n";
}
if (grep (/Pintos booting/, @output) > 1) {
sub get_core_output {
my (@output) = @_;
- our ($test);
my ($first);
for ($first = 0; $first <= $#output; $first++) {
$first++, last if $output[$first] =~ /^Executing '$test.*':$/;
sub write_details {
open (DETAILS, ">details.out");
my ($n) = 0;
- for my $test (@TESTS) {
+ for $test (@TESTS) {
next if $result{$test} eq 'ok' && !defined $details{$test};
my ($details) = $details{$test};
if (/PANIC/ && $panic++ > 0) {
@output = @output[0...$i];
push (@output,
- "[...details of recursive panic omitted...]");
+ "[...details of recursive panic(s) omitted...]");
last;
}
}
}
\f
-sub xsystem {
- my ($command, %options) = @_;
- print "$command\n" if $verbose || $options{VERBOSE};
-
- my ($log) = $options{LOG};
-
- my ($pid, $status);
- eval {
- local $SIG{ALRM} = sub { die "alarm\n" };
- alarm $options{TIMEOUT} if defined $options{TIMEOUT};
- $pid = fork;
- die "fork: $!\n" if !defined $pid;
- if (!$pid) {
- if (defined $log) {
- open (STDOUT, ">output/$log.out");
- open (STDERR, ">output/$log.err");
- }
- exec ($command);
- exit (-1);
- }
- waitpid ($pid, 0);
- $status = $?;
- alarm 0;
- };
-
- my ($ok);
- if ($@) {
- die unless $@ eq "alarm\n"; # propagate unexpected errors
- print "Timed out: ";
- for (my ($i) = 0; $i < 10; $i++) {
- kill ('SIGTERM', $pid);
- sleep (1);
- my ($retval) = waitpid ($pid, WNOHANG);
- last if $retval == $pid || $retval == -1;
- print "Waiting for $pid to die" if $i == 0;
- print ".";
- }
- $ok = 1;
- } else {
- if (WIFSIGNALED ($status)) {
- my ($signal) = WTERMSIG ($status);
- die "Interrupted\n" if $signal == SIGINT;
- print "Child terminated with signal $signal\n";
- }
-
- my ($exp_status) = !defined ($options{EXPECT}) ? 0 : $options{EXPECT};
- $ok = WIFEXITED ($status) && WEXITSTATUS ($status) == $exp_status;
- }
-
-
- if (!$ok && defined $options{DIE}) {
- my ($msg) = $options{DIE};
- if (defined ($log)) {
- chomp ($msg);
- $msg .= "; see output/$log.err and output/$log.out for details\n";
- }
- die $msg;
- } elsif (defined ($log) && $ok) {
- unlink ("output/$log.err");
- }
-
- return $ok;
-}
-
sub snarf {
my ($file) = @_;
open (OUTPUT, $file) or die "$file: open: $!\n";
(sm-create) begin
-(sm-create) create testfile
-(sm-create) open "testfile" for verification
-(sm-create) close "testfile"
+(sm-create) create "blargle"
+(sm-create) open "blargle" for verification
+(sm-create) close "blargle"
(sm-create) end
(sm-random) write "bazzle" in random order
(sm-random) read "bazzle" in random order
(sm-random) close "bazzle"
+(sm-random) end
{
pid_t children[CHILD_CNT];
int fd;
- int i;
- check (create (filename, sizeof buf), "create \"%s\"", filename);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (create (filename, sizeof buf), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
random_bytes (buf, sizeof buf);
- check (write (fd, buf, sizeof buf) > 0, "write \"%s\"", filename);
+ CHECK (write (fd, buf, sizeof buf) > 0, "write \"%s\"", filename);
msg ("close \"%s\"", filename);
close (fd);
- for (i = 0; i < CHILD_CNT; i++)
- {
- char cmd_line[128];
- snprintf (cmd_line, sizeof cmd_line, "child-syn-read %d", i);
- check ((children[i] = exec (cmd_line)) != PID_ERROR,
- "exec child %d of %d: \"%s\"", i + 1, (int) CHILD_CNT, cmd_line);
- }
-
- for (i = 0; i < CHILD_CNT; i++)
- {
- int status = join (children[i]);
- check (status == i, "join child %d of %d", i + 1, (int) CHILD_CNT);
- }
+ exec_children ("child-syn-read", children, CHILD_CNT);
+ join_children (children, CHILD_CNT);
}
--- /dev/null
+(syn-read) begin
+(syn-read) create "data"
+(syn-read) open "data"
+(syn-read) write "data"
+(syn-read) close "data"
+(syn-read) exec child 1 of 10: "child-syn-read 0"
+(syn-read) exec child 2 of 10: "child-syn-read 1"
+(syn-read) exec child 3 of 10: "child-syn-read 2"
+(syn-read) exec child 4 of 10: "child-syn-read 3"
+(syn-read) exec child 5 of 10: "child-syn-read 4"
+(syn-read) exec child 6 of 10: "child-syn-read 5"
+(syn-read) exec child 7 of 10: "child-syn-read 6"
+(syn-read) exec child 8 of 10: "child-syn-read 7"
+(syn-read) exec child 9 of 10: "child-syn-read 8"
+(syn-read) exec child 10 of 10: "child-syn-read 9"
+(syn-read) join child 1 of 10 returned 0 (expected 0)
+(syn-read) join child 2 of 10 returned 1 (expected 1)
+(syn-read) join child 3 of 10 returned 2 (expected 2)
+(syn-read) join child 4 of 10 returned 3 (expected 3)
+(syn-read) join child 5 of 10 returned 4 (expected 4)
+(syn-read) join child 6 of 10 returned 5 (expected 5)
+(syn-read) join child 7 of 10 returned 6 (expected 6)
+(syn-read) join child 8 of 10 returned 7 (expected 7)
+(syn-read) join child 9 of 10 returned 8 (expected 8)
+(syn-read) join child 10 of 10 returned 9 (expected 9)
+(syn-read) end
const char *filename = "deleteme";
int fd;
- check (create (filename, 0), "create \"%s\"", filename);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
- check (remove (filename), "remove \"%s\"", filename);
+ CHECK (create (filename, 0), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (remove (filename), "remove \"%s\"", filename);
random_bytes (buf1, sizeof buf1);
- check (write (fd, buf1, sizeof buf1) > 0, "write \"%s\"", filename);
+ CHECK (write (fd, buf1, sizeof buf1) > 0, "write \"%s\"", filename);
msg ("seek \"%s\" to 0", filename);
seek (fd, 0);
- check (read (fd, buf2, sizeof buf2) > 0, "read \"%s\"", filename);
+ CHECK (read (fd, buf2, sizeof buf2) > 0, "read \"%s\"", filename);
compare_bytes (buf2, buf1, sizeof buf1, 0, filename);
msg ("close \"%s\"", filename);
close (fd);
pid_t children[CHILD_CNT];
size_t ofs;
int fd;
- int i;
- check (create (filename, 0), "create \"%s\"", filename);
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (create (filename, 0), "create \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
- for (i = 0; i < CHILD_CNT; i++)
- {
- char cmd_line[128];
- snprintf (cmd_line, sizeof cmd_line, "child-syn-rw %d", i);
- check ((children[i] = exec (cmd_line)) != PID_ERROR,
- "exec child %d of %d: \"%s\"", i + 1, (int) CHILD_CNT, cmd_line);
- }
+ exec_children ("child-syn-rw", children, CHILD_CNT);
random_bytes (buf, sizeof buf);
quiet = true;
for (ofs = 0; ofs < BUF_SIZE; ofs += CHUNK_SIZE)
- check (write (fd, buf + ofs, CHUNK_SIZE) > 0,
+ CHECK (write (fd, buf + ofs, CHUNK_SIZE) > 0,
"write %d bytes at offset %zu in \"%s\"",
(int) CHUNK_SIZE, ofs, filename);
quiet = false;
- for (i = 0; i < CHILD_CNT; i++)
- {
- int status = join (children[i]);
- check (status == i, "join child %d of %d", i + 1, (int) CHILD_CNT);
- }
+ join_children (children, CHILD_CNT);
}
{
pid_t children[CHILD_CNT];
int fd;
- int i;
- check (create (filename, sizeof buf1), "create \"%s\"", filename);
+ CHECK (create (filename, sizeof buf1), "create \"%s\"", filename);
- for (i = 0; i < CHILD_CNT; i++)
- {
- char cmd_line[128];
- snprintf (cmd_line, sizeof cmd_line, "child-syn-wrt %d", i);
- check ((children[i] = exec (cmd_line)) != PID_ERROR,
- "exec child %d of %d: \"%s\"", i + 1, (int) CHILD_CNT, cmd_line);
- }
+ exec_children ("child-syn-wrt", children, CHILD_CNT);
+ join_children (children, CHILD_CNT);
- for (i = 0; i < CHILD_CNT; i++)
- {
- int status = join (children[i]);
- check (status == i, "join child %d of %d", i + 1, (int) CHILD_CNT);
- }
-
- check ((fd = open (filename)) > 1, "open \"%s\"", filename);
- check (read (fd, buf1, sizeof buf1) > 0, "read \"%s\"", filename);
+ CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename);
+ CHECK (read (fd, buf1, sizeof buf1) > 0, "read \"%s\"", filename);
random_bytes (buf2, sizeof buf2);
compare_bytes (buf1, buf2, sizeof buf1, 0, filename);
}
(syn-write) exec child 8 of 10: "child-syn-wrt 7"
(syn-write) exec child 9 of 10: "child-syn-wrt 8"
(syn-write) exec child 10 of 10: "child-syn-wrt 9"
-(syn-write) join child 1 of 10
-(syn-write) join child 2 of 10
-(syn-write) join child 3 of 10
-(syn-write) join child 4 of 10
-(syn-write) join child 5 of 10
-(syn-write) join child 6 of 10
-(syn-write) join child 7 of 10
-(syn-write) join child 8 of 10
-(syn-write) join child 9 of 10
-(syn-write) join child 10 of 10
+(syn-write) join child 1 of 10 returned 0 (expected 0)
+(syn-write) join child 2 of 10 returned 1 (expected 1)
+(syn-write) join child 3 of 10 returned 2 (expected 2)
+(syn-write) join child 4 of 10 returned 3 (expected 3)
+(syn-write) join child 5 of 10 returned 4 (expected 4)
+(syn-write) join child 6 of 10 returned 5 (expected 5)
+(syn-write) join child 7 of 10 returned 6 (expected 6)
+(syn-write) join child 8 of 10 returned 7 (expected 7)
+(syn-write) join child 9 of 10 returned 8 (expected 8)
+(syn-write) join child 10 of 10 returned 9 (expected 9)
(syn-write) open "stuff"
(syn-write) read "stuff"
(syn-write) end
-package Pintos::Grading;
-use Exporter 'import';
+use strict;
+use warnings;
-@EXPORT = qw($verbose $action);
+our ($test);
+
+our ($GRADES_DIR);
+our ($verbose);
+our (%args);
-use warnings;
-use strict;
use Getopt::Long;
+use POSIX;
+\f
+# Source tarballs.
+
+# Extracts the group's source files into pintos/src,
+# applies any patches providing in the grading directory,
+# and installs a default pintos/src/constants.h
+sub obtain_sources {
+ # Nothing to do if we already have a source tree.
+ return if -d "pintos";
+
+ my ($tarball) = choose_tarball ();
+
+ # Extract sources.
+ print "Creating pintos/src...\n";
+ mkdir "pintos" or die "pintos: mkdir: $!\n";
+ mkdir "pintos/src" or die "pintos/src: mkdir: $!\n";
+
+ print "Extracting $tarball into pintos/src...\n";
+ xsystem ("cd pintos/src && tar xzf ../../$tarball",
+ DIE => "extraction failed\n");
+
+ # Run custom script for this submission, if provided.
+ if (-e "fixme.sh") {
+ print "Running fixme.sh...\n";
+ xsystem ("sh -e fixme.sh", DIE => "fix script failed\n");
+ } else {
+ print "No fixme.sh, assuming no custom changes needed.\n";
+ }
+
+ # Apply patches from grading directory.
+ # Patches are applied in lexicographic order, so they should
+ # probably be named 00-debug.patch, 01-bitmap.patch, etc.
+ # Filenames in patches should be in the format pintos/src/...
+ print "Patching...\n";
+ for my $patch (glob ("$GRADES_DIR/patches/*.patch")) {
+ my ($stem);
+ ($stem = $patch) =~ s%^$GRADES_DIR/patches/%% or die;
+ xsystem ("patch -fs -p0 < $patch",
+ LOG => $stem, DIE => "applying patch $stem failed\n");
+ }
+
+ # Install default pintos/src/constants.h.
+ open (CONSTANTS, ">pintos/src/constants.h")
+ or die "constants.h: create: $!\n";
+ print CONSTANTS "#define THREAD_JOIN_IMPLEMENTED 1\n";
+ close CONSTANTS;
+}
+
+# Returns the name of the tarball to extract.
+sub choose_tarball {
+ my (@tarballs)
+ = grep (/^[a-z0-9]+\.[A-Za-z]+\.\d+\.\d+\.\d+\.\d+.\d+\.tar\.gz$/,
+ glob ("*.tar.gz"));
+ die "no pintos dir, no files matching username.MMM.DD.YY.hh.mm.ss.tar.gz\n"
+ if scalar (@tarballs) == 0;
+
+ if (@tarballs > 1) {
+ # Sort tarballs in order by time.
+ @tarballs = sort { ext_mdyHMS ($a) cmp ext_mdyHMS ($b) } @tarballs;
+
+ print "Multiple tarballs:";
+ print "\t$_ submitted ", ext_mdyHMS ($_), "\n" foreach @tarballs;
+ print "Choosing $tarballs[$#tarballs]\n";
+ }
+ return $tarballs[$#tarballs];
+}
+
+# Extract the date within a tarball name into a string that compares
+# lexicographically in chronological order.
+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", $y, $m, $d, $H, $M, $S;
+}
+\f
+# Compiling.
+
+sub compile {
+ print "Compiling...\n";
+ xsystem ("cd pintos/src/filesys && make", LOG => "make")
+ or return "compile error";
+}
+\f
+
+sub xsystem {
+ my ($command, %options) = @_;
+ print "$command\n" if $verbose || $options{VERBOSE};
+
+ my ($log) = $options{LOG};
+
+ my ($pid, $status);
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n" };
+ alarm $options{TIMEOUT} if defined $options{TIMEOUT};
+ $pid = fork;
+ die "fork: $!\n" if !defined $pid;
+ if (!$pid) {
+ if (defined $log) {
+ open (STDOUT, ">output/$log.out");
+ open (STDERR, ">output/$log.err");
+ }
+ exec ($command);
+ exit (-1);
+ }
+ waitpid ($pid, 0);
+ $status = $?;
+ alarm 0;
+ };
+
+ my ($result);
+ if ($@) {
+ die unless $@ eq "alarm\n"; # propagate unexpected errors
+ my ($i);
+ for ($i = 0; $i < 10; $i++) {
+ kill ('SIGTERM', $pid);
+ sleep (1);
+ my ($retval) = waitpid ($pid, WNOHANG);
+ last if $retval == $pid || $retval == -1;
+ print "Timed out - Waiting for $pid to die" if $i == 0;
+ print ".";
+ }
+ print "\n" if $i;
+ $result = 'timeout';
+ } else {
+ if (WIFSIGNALED ($status)) {
+ my ($signal) = WTERMSIG ($status);
+ die "Interrupted\n" if $signal == SIGINT;
+ print "Child terminated with signal $signal\n";
+ }
+
+ my ($exp_status) = !defined ($options{EXPECT}) ? 0 : $options{EXPECT};
+ $result = WIFEXITED ($status) && WEXITSTATUS ($status) == $exp_status
+ ? "ok" : "error";
+ }
+
+
+ if ($result eq 'error' && defined $options{DIE}) {
+ my ($msg) = $options{DIE};
+ if (defined ($log)) {
+ chomp ($msg);
+ $msg .= "; see output/$log.err and output/$log.out for details\n";
+ }
+ die $msg;
+ } elsif ($result ne 'error' && defined ($log)) {
+ unlink ("output/$log.err");
+ }
+
+ return $result;
+}
+\f
+sub get_test_result {
+ my ($cache_file) = "output/$test/run.result";
+ # Reuse older results if any.
+ if (open (RESULT, "<$cache_file")) {
+ my ($result);
+ $result = <RESULT>;
+ chomp $result;
+ close (RESULT);
+ return $result;
+ }
+
+ # If there's residue from an earlier test, move it to .old.
+ # If there's already a .old, delete it.
+ xsystem ("rm -rf output/$test.old", VERBOSE => 1) if -d "output/$test.old";
+ rename "output/$test", "output/$test.old" or die "rename: $!\n"
+ if -d "output/$test";
+
+ # Run the test.
+ my ($result) = run_test ($test);
+
+ # Save the results for later.
+ open (DONE, ">$cache_file") or die "$cache_file: create: $!\n";
+ print DONE "$result\n";
+ close (DONE);
+
+ return $result;
+}
+
+# Creates an output directory for the test,
+# creates all the files needed
+sub run_test {
+ # Make output directory.
+ mkdir "output/$test";
+
+ my ($fs_size) = $test ne 'grow-too-big' ? 2 : .25;
+ xsystem ("pintos make-disk output/$test/fs.dsk $fs_size >/dev/null 2>&1",
+ DIE => "failed to create file system disk");
+ xsystem ("pintos make-disk output/$test/swap.dsk 2 >/dev/null 2>&1",
+ DIE => "failed to create swap disk");
+
+ # Format disk, install test.
+ my ($pintos_base_cmd) =
+ "pintos "
+ . "--os-disk=pintos/src/filesys/build/os.dsk "
+ . "--fs-disk=output/$test/fs.dsk "
+ . "--swap-disk=output/$test/swap.dsk "
+ . "-v";
+ unlink ("output/$test/fs.dsk", "output/$test/swap.dsk"),
+ return "format/put error"
+ if xsystem ("$pintos_base_cmd put -f $GRADES_DIR/$test $test",
+ LOG => "$test/put", TIMEOUT => 60, EXPECT => 1) ne 'ok';
+
+ my (@extra_files);
+ push (@extra_files, "child-syn-read") if $test eq 'syn-read';
+ push (@extra_files, "child-syn-wrt") if $test eq 'syn-write';
+ push (@extra_files, "child-syn-rw") if $test eq 'syn-rw';
+ for my $fn (@extra_files) {
+ return "format/put error"
+ if xsystem ("$pintos_base_cmd put $GRADES_DIR/$fn $fn",
+ LOG => "$test/put-$fn", TIMEOUT => 60, EXPECT => 1)
+ ne 'ok';
+ }
+
+ # Run.
+ my ($timeout) = 120;
+ my ($testargs) = defined ($args{$test}) ? " $args{$test}" : "";
+ my ($retval) =
+ xsystem ("$pintos_base_cmd run -q -ex \"$test$testargs\"",
+ LOG => "$test/run", TIMEOUT => $timeout, EXPECT => 1);
+ my ($result);
+ if ($retval eq 'ok') {
+ $result = "ok";
+ } elsif ($retval eq 'timeout') {
+ $result = "Timed out after $timeout seconds";
+ } elsif ($retval eq 'error') {
+ $result = "Bochs error";
+ } else {
+ die;
+ }
+ unlink ("output/$test/fs.dsk", "output/$test/swap.dsk");
+ return $result;
+}
+
+# Grade the test.
+sub grade_test {
+ # Read test output.
+ my (@output) = snarf ("output/$test/run.out");
+ # If there's a function "grade_$test", use it to evaluate the output.
+ # If there's a file "$GRADES_DIR/$test.exp", compare its contents
+ # against the output.
+ # (If both exist, prefer the function.)
+ 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 {
+ eval "$grade_func (\@output)";
+ }
+ if ($@) {
+ die $@ if $@ =~ /at \S+ line \d+$/;
+ return $@;
+ }
+ return "ok";
+}
+sub c {
+ print "$test\n";
+}
1;