diff --git a/lib/kgrader/assignment.rb b/lib/kgrader/assignment.rb index 66c227a..f66dd20 100644 --- a/lib/kgrader/assignment.rb +++ b/lib/kgrader/assignment.rb @@ -25,7 +25,9 @@ module KGrader end def tests - @config['grade'] + @tests ||= @config['grade'].map do |it| + { :name => it.keys.first, :max => it.values.first } + end end def report @@ -33,12 +35,16 @@ module KGrader end def commit_message(student) - default = "adding grade report for #{name}: {student}" - template = @config['commit']['message'].clone || default + default = "Adding grade report for #{title}: {student}" + template = (@config['commit']['message'] || default).clone template['{student}'] = student template end + def extra_comments + @config['commit']['comments'] || [] + end + private def verify_config %w(stage build grade).each do |key| @@ -59,5 +65,9 @@ module KGrader { :provided => provided, :graded => graded } end + + def title + @config['title'] || @name + end end end diff --git a/lib/kgrader/filesystem.rb b/lib/kgrader/filesystem.rb index 0d8f3a8..ca277e8 100644 --- a/lib/kgrader/filesystem.rb +++ b/lib/kgrader/filesystem.rb @@ -77,7 +77,8 @@ module KGrader Dir[File.join desk_dir, '*', '*', '*', '*', 'status.txt'].each do |fn| File.write fn, "ungraded" if File.read(fn) == "graded" end - FileUtils.rm_rf Dir[File.join desk_dir, '*', '*', '*', '*', 'pending'] + FileUtils.rm_f Dir[File.join desk_dir, '*', '*', '*', '*', 'pending'] + FileUtils.rm_f Dir[File.join desk_dir, '*', '*', '*', '*', '*.log'] end # ------------------------------------------------------------------------- diff --git a/lib/kgrader/jail.rb b/lib/kgrader/jail.rb index 98207f5..4e7f729 100644 --- a/lib/kgrader/jail.rb +++ b/lib/kgrader/jail.rb @@ -17,13 +17,16 @@ module KGrader FileUtils.cp source, File.join(@root, target) end - def exec(command) + def exec(command, logpath) pid = Process.fork do + fp = File.open(logpath, 'w+') Dir.chdir @root # TODO: rlimit in exec, umask? - Process.exec command + Process.exec command, :in => :close, :out => fp, :err => fp, + :close_others => true end Process.waitpid pid, 0 + $?.exited? && $?.exitstatus == 0 end end end diff --git a/lib/kgrader/submission.rb b/lib/kgrader/submission.rb index edad216..43d2841 100644 --- a/lib/kgrader/submission.rb +++ b/lib/kgrader/submission.rb @@ -48,14 +48,13 @@ module KGrader end def grade - self.status = :ungraded + grade_prep stage build test save - # self.status = :graded # UNCOMMENT - # @fs.jail.reset # UNCOMMENT - # TODO: return grade summary string + grade_post + @summary end def commit @@ -85,6 +84,14 @@ module KGrader File.join @root, 'pending' end + def buildlog + File.join @root, 'build.log' + end + + def testlog + File.join @root, 'test.log' + end + def revision @course.backend.revision repo end @@ -101,10 +108,23 @@ module KGrader rev end - def stage + def grade_prep + @failure = false + @comments = [] + @summary = nil + @tests = [] + + 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 @assignment.manifest[:provided].each do |entry| @fs.jail.stage entry[:path], entry[:name] end @@ -115,19 +135,63 @@ module KGrader def build @assignment.build_steps.each do |command| - @fs.jail.exec command + return build_failure unless @fs.jail.exec command, buildlog end end def test - @assignment.tests.each do |script| - # TODO: execute script in jail + return if @failure + @assignment.tests.each do |test| + # TODO: execute script in jail and update @test/@comments; out testlog end end def save - # TODO: save gradefile + File.write gradefile, generate_report FileUtils.touch pendingfile end + + def grade_post + # self.status = :graded # TODO: uncomment + # @fs.jail.reset # TODO: uncomment + @summary = generate_summary unless @summary + end + + def build_failure + @failure = true + @comments.push "failed to compile" + @summary = "#{format_points 0, max_score}: failed to compile" + end + + def generate_report + # TODO + @tests + @assignment.extra_comments + end + + def generate_summary + tests = @tests.each do |test| + "#{test[:score].to_s.rjust get_span(test[:max])}/#{test[:max]}" + end.join ', ' + "#{format_points score, max_score}: #{tests}" + end + + def score + @tests.reduce(0) { |sum, t| sum + t[:score] } + end + + def max_score + @tests.reduce(0) { |sum, t| sum + t[:max] } + end + + def format_points(score, max) + percent = (score.to_f * 100 / max).round.to_s.rjust 3 + span = get_span max + "#{percent}% (#{score.to_s.rjust span}/#{max.to_s.rjust span})" + end + + def get_span(max) + (Math.log10(max) + 1).to_i + end end end