diff --git a/lib/kgrader/assignment.rb b/lib/kgrader/assignment.rb index 04bf0ce..cf12807 100644 --- a/lib/kgrader/assignment.rb +++ b/lib/kgrader/assignment.rb @@ -30,7 +30,8 @@ module KGrader def tests @tests ||= @config['grade'].map do |it| - { :name => it.keys.first, :max => it.values.first } + script = File.join @root, it.keys.first + ".rb" + { :name => it.keys.first, :script => script, :max => it.values.first } end end diff --git a/lib/kgrader/backend/svn.rb b/lib/kgrader/backend/svn.rb index 935de1c..4af767f 100644 --- a/lib/kgrader/backend/svn.rb +++ b/lib/kgrader/backend/svn.rb @@ -36,6 +36,7 @@ module KGrader::Backend def commit(repo, message, paths = nil) # TODO + # run 'commit', '-m', message, *paths.map { |fn| File.join repo, fn } end def commit_date(repo) diff --git a/lib/kgrader/jail.rb b/lib/kgrader/jail.rb index 4e7f729..84c841e 100644 --- a/lib/kgrader/jail.rb +++ b/lib/kgrader/jail.rb @@ -3,6 +3,7 @@ module KGrader def initialize(root) @root = root + @salt = nil end def reset @@ -11,6 +12,7 @@ module KGrader def init FileUtils.mkdir_p @root + @salt = rand 100000000 end def stage(source, target) @@ -18,15 +20,43 @@ module KGrader end def exec(command, logpath) - pid = Process.fork do - fp = File.open(logpath, 'w+') - Dir.chdir @root - # TODO: rlimit in exec, umask? - Process.exec command, :in => :close, :out => fp, :err => fp, - :close_others => true - end + pid = execute command, logpath Process.waitpid pid, 0 $?.exited? && $?.exitstatus == 0 end + + def run_test(script, logpath) + grade_rd, grade_wr = IO.pipe + cmt_rd, cmt_wr = IO.pipe + + command = ['ruby', '-r', '../lib/kgrader/runtime.rb', script, @salt] + pid = execute command, logpath do |options| + [grade_rd, cmt_rd].each &:close + options[3] = grade_wr + options[4] = cmt_wr + end + + [grade_wr, cmt_wr].each &:close + Process.waitpid pid, 0 + + cmt_rd.read.split("\n").each { |cmt| yield cmt } if block_given? + grade = grade_rd.read.strip.to_i + [grade_rd, cmt_rd].each &:close + grade + end + + private + def execute(command, logpath) + Process.fork do + fp = File.open(logpath, 'a') + Dir.chdir @root + options = { + :in => :close, :out => fp, :err => fp, :close_others => true, + :rlimit_nproc => 32 + } + yield options if block_given? + Process.exec *command, options + end + end end end diff --git a/lib/kgrader/submission.rb b/lib/kgrader/submission.rb index bf3ceab..921c890 100644 --- a/lib/kgrader/submission.rb +++ b/lib/kgrader/submission.rb @@ -60,10 +60,9 @@ module KGrader def commit if status == :graded && File.exists?(pendingfile) - target = File.join(repo, @assignment.report) message = @assignment.commit_message @student - FileUtils.cp gradefile, target - @course.backend.commit repo, message, target + FileUtils.cp gradefile, File.join(repo, @assignment.report) + @course.backend.commit repo, message, @assignment.report FileUtils.rm pendingfile end end @@ -113,16 +112,12 @@ module KGrader @failure = false @comments = [] @summary = nil - @tests = [] + @tests = @assignment.tests.clone.each { |test| test[:score] = 0 } self.status = :ungraded FileUtils.rm_f [buildlog, testlog] @fs.jail.reset @fs.jail.init - - @assignment.tests.each do |test| - @tests.push({ :name => test[:name], :max => test[:max], :score => 0 }) - end end def stage @@ -142,8 +137,10 @@ module KGrader def test return if @failure - @assignment.tests.each do |test| - # TODO: execute script in jail and update @test/@comments; out testlog + @tests.each do |test| + test[:score] = @fs.jail.run_test test[:script], testlog do |comment| + @comments.push "#{test[:name]}: #{comment}" + end end end @@ -199,7 +196,7 @@ module KGrader end def generate_summary - tests = @tests.each do |test| + tests = @tests.map do |test| "#{test[:score].to_s.rjust get_span(test[:max])}/#{test[:max]}" end.join ', ' "#{format_points score, max_score}: #{tests}"