|
|
require File.join(File.dirname(__FILE__),'spec_helper')
|
|
|
require File.join(File.dirname(__FILE__),'engine_spec_helper')
|
|
|
|
|
|
describe "A grader engine, when grading submissions" do
|
|
|
|
|
|
include GraderEngineHelperMethods
|
|
|
|
|
|
before(:each) do
|
|
|
@config = Grader::Configuration.get_instance
|
|
|
|
|
|
# this test is from Pong
|
|
|
@problem_test_normal = stub(Problem,
|
|
|
:id => 1, :name => 'test_normal',
|
|
|
:full_score => 135)
|
|
|
@user_user1 = stub(User,
|
|
|
:id => 1, :login => 'user1')
|
|
|
|
|
|
@engine = Grader::Engine.new
|
|
|
init_sandbox
|
|
|
end
|
|
|
|
|
|
it "should grade normal submission" do
|
|
|
grader_should(:grade => "test1_correct.c",
|
|
|
:on => @problem_test_normal,
|
|
|
:and_report => {
|
|
|
:score => 135,
|
|
|
:comment => /^PASSED/})
|
|
|
end
|
|
|
|
|
|
|
|
|
it "should produce error message when submission cannot compile" do
|
|
|
grader_should(:grade => "test1_compile_error.c",
|
|
|
:on => @problem_test_normal,
|
|
|
:and_report => {
|
|
|
:score => 0,
|
|
|
:comment => 'FAILED: compilation error',
|
|
|
:compiler_message => /[Ee]rror/})
|
|
|
end
|
|
|
|
|
|
it "should produce timeout error when submission runs forever" do
|
|
|
@problem_test_timeout = stub(Problem,
|
|
|
:id => 1, :name => 'test_timeout',
|
|
|
:full_score => 10)
|
|
|
grader_should(:grade => "test2_timeout.c",
|
|
|
:on => @problem_test_timeout,
|
|
|
:and_report => {
|
|
|
:score => 0,
|
|
|
:comment => 'FAILED: TT'})
|
|
|
end
|
|
|
|
|
|
it "should produce timeout error correctly with fractional running time and fractional time limits" do
|
|
|
@problem_test_timeout = stub(Problem,
|
|
|
:id => 1, :name => 'test_timeout',
|
|
|
:full_score => 20)
|
|
|
grader_should(:grade => "test2_05sec.c",
|
|
|
:on => @problem_test_timeout,
|
|
|
:and_report => {
|
|
|
:score => 10,
|
|
|
:comment => 'FAILED: TP'})
|
|
|
end
|
|
|
|
|
|
it "should produce runtime error when submission uses too much static memory" do
|
|
|
@problem_test_memory = stub(Problem,
|
|
|
:id => 1, :name => 'test_memory',
|
|
|
:full_score => 20)
|
|
|
grader_should(:grade => "add_too_much_memory_static.c",
|
|
|
:on => @problem_test_memory,
|
|
|
:and_report => {
|
|
|
:score => 10,
|
|
|
:comment => /FAILED: [^P]P/})
|
|
|
end
|
|
|
|
|
|
it "should not allow submission to allocate too much dynamic memory" do
|
|
|
@problem_test_memory = stub(Problem,
|
|
|
:id => 1, :name => 'test_memory',
|
|
|
:full_score => 20)
|
|
|
grader_should(:grade => "add_too_much_memory_dynamic.c",
|
|
|
:on => @problem_test_memory,
|
|
|
:and_report => {
|
|
|
:score => 10,
|
|
|
:comment => /FAILED: [^P]P/})
|
|
|
end
|
|
|
|
|
|
it "should score test runs correctly when submission fails in some test case" do
|
|
|
grader_should(:grade => "add_fail_test_case_1.c",
|
|
|
:on => @problem_test_normal,
|
|
|
:and_report => {
|
|
|
:score => 105,
|
|
|
:comment => /^FAILED:/})
|
|
|
end
|
|
|
|
|
|
it "should fail submission with non-zero exit status" do
|
|
|
grader_should(:grade => "add_nonzero_exit_status.c",
|
|
|
:on => @problem_test_normal,
|
|
|
:and_report => {
|
|
|
:score => 0,
|
|
|
:comment => /^FAILED:/})
|
|
|
end
|
|
|
|
|
|
it "should not allow malicious submission to see PROBLEM_HOME" do
|
|
|
problem_test_yesno = stub(Problem,
|
|
|
:id => 1, :name => 'test_yesno',
|
|
|
:full_score => 10)
|
|
|
grader_should(:grade => "yesno_access_problem_home.c",
|
|
|
:on => problem_test_yesno,
|
|
|
:and_report => {
|
|
|
:score => 0,
|
|
|
:comment => /^FAILED:/})
|
|
|
end
|
|
|
|
|
|
it "should not allow malicious submission to open files" do
|
|
|
problem_test_yesno = stub(Problem,
|
|
|
:id => 1, :name => 'test_yesno',
|
|
|
:full_score => 10)
|
|
|
grader_should(:grade => "yesno_open_file.c",
|
|
|
:on => problem_test_yesno,
|
|
|
:and_report => {
|
|
|
:score => 0,
|
|
|
:comment => /^FAILED:/})
|
|
|
end
|
|
|
|
|
|
def grader_should(args)
|
|
|
@user1 = stub(User,
|
|
|
:id => 1, :login => 'user1')
|
|
|
submission =
|
|
|
create_submission_from_file(1, @user1, args[:on], args[:grade])
|
|
|
submission.should_receive(:graded_at=)
|
|
|
|
|
|
expected_score = args[:and_report][:score]
|
|
|
expected_comment = args[:and_report][:comment]
|
|
|
if args[:and_report][:compiler_message]!=nil
|
|
|
expected_compiler_message = args[:and_report][:compiler_message]
|
|
|
else
|
|
|
expected_compiler_message = ''
|
|
|
end
|
|
|
|
|
|
submission.should_receive(:points=).with(expected_score)
|
|
|
submission.should_receive(:grader_comment=).with(expected_comment)
|
|
|
submission.should_receive(:compiler_message=).with(expected_compiler_message)
|
|
|
submission.should_receive(:save)
|
|
|
|
|
|
@engine.grade(submission)
|
|
|
end
|
|
|
|
|
|
protected
|
|
|
|
|
|
def create_normal_submission_mock_from_file(source_fname)
|
|
|
create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname)
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
describe "A grader engine, when grading test requests" do
|
|
|
|
|
|
include GraderEngineHelperMethods
|
|
|
|
|
|
before(:each) do
|
|
|
@config = Grader::Configuration.get_instance
|
|
|
@engine = Grader::Engine.new(Grader::TestRequestRoomMaker.new,
|
|
|
Grader::TestRequestReporter.new)
|
|
|
init_sandbox
|
|
|
end
|
|
|
|
|
|
it "should report error if there is no problem template" do
|
|
|
problem = stub(Problem,
|
|
|
:id => 1, :name => 'nothing')
|
|
|
grader_should(:grade => 'test1_correct.c',
|
|
|
:on => problem,
|
|
|
:with => 'in1.txt',
|
|
|
:and_report => {
|
|
|
:graded_at= => nil,
|
|
|
:compiler_message= => '',
|
|
|
:grader_comment= => '',
|
|
|
:running_stat= => /template not found/,
|
|
|
:running_time= => nil,
|
|
|
:exit_status= => nil,
|
|
|
:memory_usage= => nil,
|
|
|
:save => nil})
|
|
|
end
|
|
|
|
|
|
it "should run test request and produce output file" do
|
|
|
problem = stub(Problem,
|
|
|
:id => 1, :name => 'test_normal')
|
|
|
grader_should(:grade => 'test1_correct.c',
|
|
|
:on => problem,
|
|
|
:with => 'in1.txt',
|
|
|
:and_report => {
|
|
|
:graded_at= => nil,
|
|
|
:compiler_message= => '',
|
|
|
:grader_comment= => '',
|
|
|
:running_stat= => /0.0\d* sec./,
|
|
|
:output_file_name= => lambda { |fname|
|
|
|
File.exists?(fname).should be_true
|
|
|
},
|
|
|
:running_time= => nil,
|
|
|
:exit_status= => nil,
|
|
|
:memory_usage= => nil,
|
|
|
:save => nil})
|
|
|
end
|
|
|
|
|
|
it "should clean up problem directory after running test request" do
|
|
|
problem = stub(Problem,
|
|
|
:id => 1, :name => 'test_normal')
|
|
|
grader_should(:grade => 'test1_correct.c',
|
|
|
:on => problem,
|
|
|
:with => 'in1.txt',
|
|
|
:and_report => {
|
|
|
:graded_at= => nil,
|
|
|
:compiler_message= => '',
|
|
|
:grader_comment= => '',
|
|
|
:running_stat= => nil,
|
|
|
:output_file_name= => nil,
|
|
|
:running_time= => nil,
|
|
|
:exit_status= => nil,
|
|
|
:memory_usage= => nil,
|
|
|
:save => nil})
|
|
|
File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be_false
|
|
|
end
|
|
|
|
|
|
it "should compile test request with error and report compilation error" do
|
|
|
problem = stub(Problem,
|
|
|
:id => 1, :name => 'test_normal')
|
|
|
grader_should(:grade => 'test1_compile_error.c',
|
|
|
:on => problem,
|
|
|
:with => 'in1.txt',
|
|
|
:and_report => {
|
|
|
:graded_at= => nil,
|
|
|
:compiler_message= => /.+/,
|
|
|
:grader_comment= => /[Cc]ompil.*error/,
|
|
|
:running_stat= => '',
|
|
|
:save => nil})
|
|
|
end
|
|
|
|
|
|
it "should report exit status" do
|
|
|
problem = stub(Problem,
|
|
|
:id => 1, :name => 'test_normal')
|
|
|
grader_should(:grade => 'add_nonzero_exit_status.c',
|
|
|
:on => problem,
|
|
|
:with => 'in1.txt',
|
|
|
:and_report => {
|
|
|
:graded_at= => nil,
|
|
|
:compiler_message= => '',
|
|
|
:grader_comment= => '',
|
|
|
:running_stat= => /[Ee]xit.*status.*10.*0\.0\d* sec/m,
|
|
|
:output_file_name= => lambda { |fname|
|
|
|
File.exists?(fname).should be_true
|
|
|
},
|
|
|
:running_time= => nil,
|
|
|
:exit_status= => /10/,
|
|
|
:memory_usage= => nil,
|
|
|
:save => nil})
|
|
|
end
|
|
|
|
|
|
it "should produce running statistics for normal submission" do
|
|
|
problem = stub(Problem,
|
|
|
:id => 1, :name => 'test_normal')
|
|
|
grader_should(:grade => 'test_run_stat.c',
|
|
|
:on => problem,
|
|
|
:with => 'in1.txt',
|
|
|
:and_report => {
|
|
|
:graded_at= => nil,
|
|
|
:compiler_message= => '',
|
|
|
:grader_comment= => '',
|
|
|
:running_stat= => nil,
|
|
|
:output_file_name= => lambda { |fname|
|
|
|
File.exists?(fname).should be_true
|
|
|
},
|
|
|
:running_time= => lambda { |t|
|
|
|
(t>=0.14) and (t<=0.16)
|
|
|
},
|
|
|
:exit_status= => nil,
|
|
|
:memory_usage= => lambda { |s|
|
|
|
(s>=500) and (s<=1000)
|
|
|
},
|
|
|
:save => nil})
|
|
|
end
|
|
|
|
|
|
protected
|
|
|
def grader_should(args)
|
|
|
@user1 = stub(User,
|
|
|
:id => 1, :login => 'user1')
|
|
|
|
|
|
problem = args[:on]
|
|
|
input_file = @config.test_request_input_base_dir + "/" + args[:with]
|
|
|
|
|
|
submission =
|
|
|
create_submission_from_file(1, @user1, args[:on], args[:grade])
|
|
|
|
|
|
test_request = stub(TestRequest,
|
|
|
:id => 1,
|
|
|
:user => @user1,
|
|
|
:problem => problem,
|
|
|
:submission => submission,
|
|
|
:input_file_name => input_file,
|
|
|
:language => submission.language,
|
|
|
:problem_name => problem.name)
|
|
|
|
|
|
expectations = args[:and_report]
|
|
|
|
|
|
expectations.each do |key,val|
|
|
|
if val==nil
|
|
|
test_request.should_receive(key)
|
|
|
elsif val.class == Proc
|
|
|
test_request.should_receive(key) { |fname|
|
|
|
val.call(fname)
|
|
|
}
|
|
|
else
|
|
|
test_request.should_receive(key).with(val)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
@engine.grade(test_request)
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|