Commit 935d5c7d authored by Guillaume Huard's avatar Guillaume Huard
Browse files

Limits only for student program, empty input bug fixed

parent 8d1a20cf
......@@ -84,16 +84,6 @@ sub get_value($$$) {
}
}
# Sets a named field to a given value if it is not already set
sub set_default($$$) {
my $description = shift;
my $name = shift;
my $default = shift;
if (!exists($description->{$name})) {
$description->{$name} = $default;
}
}
# Reads all the data available from a given fd and closes it
sub read_from_fd($) {
my $fd = shift;
......@@ -108,11 +98,12 @@ sub read_from_fd($) {
}
# Runs a command associated to a test with a given input, returns a hash that holds the content of standard output and error as well as exit code
# Warning : will block in case of insufficient buffers size (has to be rewritten with an asynchronous management of input, output and error)
sub run_command($$$) {
# Looks at IOlimit, timeout, debug, args and input in the test to find out how to run it (but they are not necessarily expected to exist)
sub run_command($$$$) {
my $test_name = shift;
my $command = shift;
my $test = shift;
my $has_limits = shift;
my $arguments = " ";
chmod(0755, "$command") || error("Cannot make $command executable : $!".
......@@ -124,26 +115,30 @@ sub run_command($$$) {
}
}
my $data = {};
# We build an execution wrapper to ensure the use of bash instead of /bin/sh (system's default)
# This avoids issues with ulimit
my $command_script = "#!/bin/bash\n";
$command_script .= "ulimit -f$test->{IOlimit}\n" if exists($test->{IOlimit});
$command_script .= "timeout -s9 $test->{timeout} " if exists($test->{timeout});
$command_script .= "$command$arguments >.internal_output.txt 2>.internal_error.txt";
if (exists($test->{input})) {
ensure_write(".internal_input.txt", $test->{input});
$command_script .= " <.internal_input.txt";
$test->{input} = "" unless defined($test->{input});
write_file(".internal_input.txt", $test->{input}) || error("Cannot write input file");
my $command_line = "$command$arguments >.internal_output.txt 2>.internal_error.txt <.internal_input.txt";
if ($has_limits) {
# We build an execution wrapper to ensure the use of bash instead of /bin/sh (system's default)
# This avoids issues with ulimit
my $command_script = "#!/bin/bash\n";
# $command_script .= "set -x\n";
$command_script .= "ulimit -f".get_value($test, 'IOlimit', 100)."\n";
$command_script .= "timeout -s9 ".get_value($test, 'timeout', 10)." ";
$command_script .= $command_line;
ensure_write(".execution_wrapper", $command_script);
chmod(0755, ".execution_wrapper") || error("Cannot make execution_wrapper executable");
$command_line = "./.execution_wrapper";
}
ensure_write(".execution_wrapper", $command_script);
chmod(0755, ".execution_wrapper") || error("Cannot make execution_wrapper executable");
# debug("Executing: $command_line\n");
# One should use the POSIX macros WIFEXITED, WEXITSTATUS, and so on, but it's too cumbersome
$data->{failure} = system("./.execution_wrapper");
$data->{failure} = system($command_line);
$data->{signal} = $data->{failure} & 0xFF;
$data->{code} = $data->{failure} >> 8;
$data->{output} = read_file(".internal_output.txt");
$data->{error} = read_file(".internal_error.txt");
remove(".execution_wrapper", ".internal_input.txt", ".internal_output.txt", ".internal_error.txt");
remove(".execution_wrapper") if $has_limits;
remove(".internal_input.txt", ".internal_output.txt", ".internal_error.txt");
return $data;
}
......@@ -173,19 +168,26 @@ sub dispatch_error($$$) {
}
}
sub run_special($$$$);
sub run_special($$$$) {
# Runs a named command, described by a given content and associated to a test,
# returns a hash that holds the content of standard output and error as well as exit code
# The content might be :
# - scalar : a script to be executed
# - a ref to a hash : description of the executable and execution
# The test holds input and args informations
sub run_special($$$$$);
sub run_special($$$$$) {
my $test_name = shift;
my $name = shift;
my $content = shift;
my $test = shift;
my $soft_fail = 1;
my $has_limits = shift;
my $special = $content;
if (ref($special) ne 'HASH') {
$special = { content => $content };
} else {
# Values of special are written to test to be able to overwrite intput and args in special
# This is especially usefull for a validator
my $given_test = $test;
$test = {};
add_values($test, $given_test);
......@@ -207,9 +209,9 @@ sub run_special($$$$) {
if (exists($special->{compile}) && !$special->{compiled}) {
debug("Compiling $name with @{$special->{files}}\n");
setup_env($name, $special);
my $data = run_special($name, '.compile', $special->{compile}, $special);
my $data = run_special($name, '.compile', $special->{compile}, $special, 0);
if ($data->{failure}) {
dispatch_error("Cannot compile $name", $data, $soft_fail);
dispatch_error("Cannot compile $name", $data, 1);
$data->{compilation_error} = 1;
return $data;
} else {
......@@ -222,13 +224,13 @@ sub run_special($$$$) {
if (exists($special->{execute})) {
if ($special->{execute}) {
setup_env($name, $special);
$result = run_special($test_name, '.execute', $special->{execute}, $test);
$result = run_special($test_name, '.execute', $special->{execute}, $test, $has_limits);
}
} else {
debug("Executing $name\n");
$result = run_command($test_name, "./$name", $test);
$result = run_command($test_name, "./$name", $test, $has_limits);
if ($result->{failure}) {
dispatch_error("Cannot execute $name", $result, $soft_fail);
dispatch_error("Cannot execute $name", $result, 1);
}
}
if (exists($special->{content})) {
......@@ -335,7 +337,7 @@ sub perform_tests($$) {
my $details = "Test $test_name:\n";
# runs setup, if any
if (exists($test->{setup})) {
my $setup = run_special($test_name, '.setup', $test->{setup}, $test);
my $setup = run_special($test_name, '.setup', $test->{setup}, $test, 0);
foreach my $part ('output', 'error') {
print STDERR "Setup $part :\n$setup->{$part}" if length($setup->{$part});
}
......@@ -344,11 +346,7 @@ sub perform_tests($$) {
}
# runs program
my $student_test = {};
add_values($student_test, $test);
set_default($student_test, "timeout", 10);
set_default($student_test, "IOlimit", 1000);
my $data = run_special($test_name, $program, $case, $student_test);
my $data = run_special($test_name, $program, $case, $test, 1);
if ($data->{compilation_error}) {
$details .= "Compilation errors, compiler output :\n".preformat($data->{output}.$data->{error});
......@@ -362,7 +360,7 @@ sub perform_tests($$) {
# runs a provided solution for the testcase
# this will populate test->{output,error,code} for the following test
if (exists($test->{solution})) {
my $answer = run_special($test_name, '.solution', $test->{solution}, $test);
my $answer = run_special($test_name, '.solution', $test->{solution}, $test, 0);
if ($answer->{compilation_error}) {
dispatch_error("Cannot compile provided solution", $answer, 0);
} else {
......@@ -395,7 +393,7 @@ sub perform_tests($$) {
ensure_write(".$part.txt", $data->{$part});
ensure_write(".expected_$part.txt", $test->{$part});
}
my $valid = run_special($test_name, '.validator', $test->{validator}, $test);
my $valid = run_special($test_name, '.validator', $test->{validator}, $test, 0);
$result = ($valid->{failure} == 0);
foreach my $part ('output', 'error') {
$details .= "Validator $part is:\n".preformat($valid->{$part})."\n" if length($valid->{$part});
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment