Description:
Combined judge project to the web project. git-svn-id: http://theory.cpe.ku.ac.th/grader/web/trunk@378 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r176:2448930b9ab8 - - 93 files changed: 4322 inserted, 0 deleted

@@ -0,0 +1,2
1 + env*.rb
2 +
@@ -0,0 +1,20
1 + #
2 + # See documentation in lib/configuration.rb
3 + #
4 + Grader::Initializer.run do |config|
5 +
6 + config.problems_dir = GRADER_ROOT + "/../ev"
7 + config.user_result_dir = GRADER_ROOT + "/../result"
8 +
9 + config.talkative = true
10 + config.logging = true
11 + config.log_dir = GRADER_ROOT + "/../log"
12 +
13 + config.report_grader = true
14 +
15 + config.test_request_input_base_dir = RAILS_ROOT + "/data/test_request/input"
16 + config.test_request_output_base_dir = RAILS_ROOT + "/data/test_request/output"
17 + config.test_request_problem_templates_dir = config.problems_dir + "/test_request"
18 +
19 + config.comment_report_style = :short
20 + end
@@ -0,0 +1,19
1 + #
2 + # See documentation in lib/configuration.rb
3 + #
4 + Grader::Initializer.run do |config|
5 + config.problems_dir = GRADER_ROOT + "/../ev"
6 + config.user_result_dir = GRADER_ROOT + "/../result"
7 +
8 + config.talkative = true
9 + config.logging = true
10 + config.log_dir = GRADER_ROOT + "/../log"
11 +
12 + config.report_grader = true
13 +
14 + config.test_request_input_base_dir = RAILS_ROOT + "/data/test_request/input"
15 + config.test_request_output_base_dir = RAILS_ROOT + "/data/test_request/output"
16 + config.test_request_problem_templates_dir = config.problems_dir + "/test_request"
17 +
18 + config.comment_report_style = :full
19 + end
@@ -0,0 +1,29
1 + #
2 + # See documentation in lib/configuration.rb
3 + #
4 + Grader::Initializer.run do |config|
5 + config.problems_dir = GRADER_ROOT + "/test/sandbox/ev"
6 + config.user_result_dir = GRADER_ROOT + "/test/sandbox/result"
7 +
8 + config.talkative = false
9 +
10 + config.report_grader = false
11 +
12 + config.rails_env = 'test'
13 +
14 + config.comment_report_style = :full
15 +
16 + config.test_request_input_base_dir = GRADER_ROOT + "/test/data/test_request/input"
17 + config.test_request_output_base_dir = GRADER_ROOT + "/test/sandbox/test_request/output"
18 + config.test_request_problem_templates_dir = GRADER_ROOT + "/test/data/test_request/problems"
19 +
20 + #
21 + # These options are for testing
22 + #
23 + class << config
24 + attr_accessor :test_data_dir, :test_sandbox_dir
25 + end
26 +
27 + config.test_data_dir = GRADER_ROOT + "/test/data"
28 + config.test_sandbox_dir = GRADER_ROOT + "/test/sandbox"
29 + end
@@ -0,0 +1,10
1 + # Rails app directory
2 + RAILS_ROOT = "/home/jittat/web_grader"
3 +
4 + GRADER_ROOT = "/home/jittat/grader/scripts"
5 +
6 + # This load all required codes
7 + require File.join(File.dirname(__FILE__),'../lib/boot')
8 +
9 + # load the required environment file
10 + require File.dirname(__FILE__) + "/env_#{GRADER_ENV}.rb"
@@ -0,0 +1,217
1 + #!/usr/bin/ruby
2 +
3 + def stop_grader(id)
4 + if id==:all
5 + File.open(File.dirname(__FILE__) + "/stop.all",'w').close
6 + else
7 + File.open(File.dirname(__FILE__) + "/stop.#{id}",'w').close
8 + end
9 + end
10 +
11 + def check_stopfile
12 + FileTest.exist?(File.dirname(__FILE__) + "/stop.all") or
13 + FileTest.exist?(File.dirname(__FILE__) + "/stop.#{Process.pid}")
14 + end
15 +
16 + def clear_stopfile
17 + if FileTest.exist?(File.dirname(__FILE__) + "/stop.#{Process.pid}")
18 + system("rm " + File.dirname(__FILE__) + "/stop.#{Process.pid}")
19 + end
20 + end
21 +
22 + def config
23 + Grader::Configuration.get_instance
24 + end
25 +
26 + def log_file_name
27 + if !File.exists?(config.log_dir)
28 + raise "Log directory does not exist: #{config.log_dir}"
29 + end
30 + config.log_dir +
31 + "/#{GRADER_ENV}_#{config.grader_mode}.#{Process.pid}"
32 + end
33 +
34 + def log(str)
35 + if config.talkative
36 + puts str
37 + end
38 + if config.logging
39 + fp = File.open(log_file_name,"a")
40 + fp.puts("GRADER: #{Time.new.strftime("%H:%M")} #{str}")
41 + fp.close
42 + end
43 + end
44 +
45 + def display_manual
46 + puts <<USAGE
47 + Grader.
48 + using: (1) grader
49 + (2) grader environment [mode]
50 + (3) grader stop [all|pids-list]
51 + (4) grader --help
52 + (1) call grader with environment = 'exam', mode = 'queue'
53 + (2) possible modes are: 'queue', 'prob', 'test_request'
54 + (3) create stop-file to stop running grader in queue mode
55 + (4) You are here.
56 + USAGE
57 + end
58 +
59 + #########################################
60 + # main program
61 + #########################################
62 +
63 + # with --help
64 + if (ARGV.length==1) and (/help/.match(ARGV[0]))
65 + display_manual
66 + exit(0)
67 + end
68 +
69 + # reading environment and options
70 + if (ARGV.length >= 1) and (ARGV[0]=='stop')
71 + if ARGV.length==1
72 + puts "you should specify pid-list or 'all'"
73 + display_manual
74 + elsif (ARGV.length==2) and (ARGV[1]=='all')
75 + stop_grader(:all)
76 + puts "A global stop file ('stop.all') created."
77 + puts "You should remove it manually later."
78 + else
79 + (1..ARGV.length-1).each do |i|
80 + stop_grader(ARGV[i])
81 + end
82 + puts "stop file(s) created"
83 + end
84 + exit(0)
85 + end
86 +
87 + if check_stopfile
88 + puts "Stop file exists. Terminated."
89 + clear_stopfile
90 + exit(0)
91 + end
92 +
93 + grader_mode = 'queue'
94 + if ARGV.length >= 1
95 + GRADER_ENV = ARGV[0]
96 + if ARGV.length >=2
97 + grader_mode = ARGV[1]
98 + end
99 + else
100 + GRADER_ENV = 'exam'
101 + end
102 +
103 + puts "environment: #{GRADER_ENV}"
104 + require File.join(File.dirname(__FILE__),'config/environment')
105 +
106 + # add grader_mode to config
107 + # this is needed because method log needs it. TODO: clean this up
108 + class << config
109 + attr_accessor :grader_mode
110 + end
111 + config.grader_mode = grader_mode
112 +
113 + # reading rails environment
114 + log 'Reading rails environment'
115 +
116 + RAILS_ENV = config.rails_env
117 + require RAILS_ROOT + '/config/environment'
118 +
119 + # register grader process
120 + if config.report_grader
121 + grader_proc = GraderProcess.register(config.grader_hostname,
122 + Process.pid,
123 + grader_mode)
124 + else
125 + grader_proc = nil
126 + end
127 +
128 + #set loggin environment
129 + ENV['GRADER_LOGGING'] = log_file_name
130 +
131 + # register exit handler to report inactive, and terminated
132 + at_exit do
133 + if grader_proc!=nil
134 + grader_proc.report_inactive
135 + grader_proc.terminate
136 + end
137 + end
138 +
139 + #
140 + # MAIN LOOP
141 + #
142 +
143 + case grader_mode
144 + when "queue", "test_request"
145 + log "Grader: #{grader_mode}"
146 + if grader_mode=="queue"
147 + engine = Grader::Engine.new
148 + else
149 + engine = Grader::Engine.new(Grader::TestRequestRoomMaker.new,
150 + Grader::TestRequestReporter.new)
151 + end
152 +
153 + runner = Grader::Runner.new(engine, grader_proc)
154 + while true
155 +
156 + if check_stopfile # created by calling grader stop
157 + clear_stopfile
158 + log "stopped (with stop file)"
159 + break
160 + end
161 +
162 + if grader_mode=="queue"
163 + task = runner.grade_oldest_task
164 + else
165 + task = runner.grade_oldest_test_request
166 + end
167 + if task==nil
168 + sleep(1)
169 + end
170 + end
171 +
172 + when "prob"
173 + engine = Grader::Engine.new
174 + runner = Grader::Runner.new(engine, grader_proc)
175 +
176 + grader_proc.report_active if grader_proc!=nil
177 +
178 + ARGV.shift
179 + ARGV.shift
180 +
181 + ARGV.each do |prob_name|
182 + prob = Problem.find_by_name(prob_name)
183 + if prob==nil
184 + puts "cannot find problem: #{prob_name}"
185 + else
186 + runner.grade_problem(prob)
187 + end
188 + end
189 +
190 + when "sub"
191 + engine = Grader::Engine.new
192 + runner = Grader::Runner.new(engine, grader_proc)
193 +
194 + grader_proc.report_active if grader_proc!=nil
195 +
196 + ARGV.shift
197 + ARGV.shift
198 +
199 + ARGV.each do |sub_id|
200 + puts "Grading #{sub_id}"
201 + begin
202 + submission = Submission.find(sub_id.to_i)
203 + rescue ActiveRecord::RecordNotFound
204 + puts "Record not found"
205 + submission = nil
206 + end
207 +
208 + if submission!=nil
209 + runner.grade_submission(submission)
210 + end
211 + end
212 +
213 + else
214 + display_manual
215 + exit(0)
216 + end
217 +
@@ -0,0 +1,110
1 + #!/usr/bin/ruby
2 +
3 + def talk(str)
4 + if TALKATIVE
5 + puts str
6 + end
7 + end
8 +
9 + def execute(command, error_message="")
10 + if not system(command)
11 + puts "ERROR: #{error_message}"
12 + exit(127)
13 + end
14 + end
15 +
16 + def save_source(submission,dir,fname)
17 + f = File.open("#{dir}/#{fname}","w")
18 + f.write(submission.source)
19 + f.close
20 + end
21 +
22 + def call_judge(problem_home,language,problem_out_dir,fname)
23 + ENV['PROBLEM_HOME'] = problem_home
24 + Dir.chdir problem_out_dir
25 + cmd = "#{problem_home}/script/judge #{language} #{fname}"
26 + # puts "CMD: #{cmd}"
27 + system(cmd)
28 + end
29 +
30 + def read_result(test_result_dir)
31 + cmp_msg_fname = "#{test_result_dir}/compiler_message"
32 + cmp_msg = File.open(cmp_msg_fname).read
33 +
34 + result_fname = "#{test_result_dir}/result"
35 + comment_fname = "#{test_result_dir}/comment"
36 + if FileTest.exist?(result_fname)
37 + result = File.open(result_fname).readline.to_i
38 + comment = File.open(comment_fname).readline.chomp
39 + return {:points => result,
40 + :comment => comment,
41 + :cmp_msg => cmp_msg}
42 + else
43 + return {:points => 0,
44 + :comment => 'compile error',
45 + :cmp_msg => cmp_msg}
46 + end
47 + end
48 +
49 + def save_result(submission,result)
50 + submission.graded_at = Time.now
51 + submission.points = result[:points]
52 + submission.grader_comment = report_comment(result[:comment])
53 + submission.compiler_message = result[:cmp_msg]
54 + submission.save
55 + end
56 +
57 + def grade(submission_id)
58 + sub = Submission.find(submission_id)
59 + user = sub.user
60 + problem = sub.problem
61 +
62 + language = sub.language.name
63 + lang_ext = sub.language.ext
64 + # FIX THIS
65 + talk 'some hack on language'
66 + if language == 'cpp'
67 + language = 'c++'
68 + end
69 +
70 + user_dir = "#{USER_RESULT_DIR}/#{user.login}"
71 + Dir.mkdir(user_dir) if !FileTest.exist?(user_dir)
72 +
73 + problem_out_dir = "#{user_dir}/#{problem.name}"
74 + Dir.mkdir(problem_out_dir) if !FileTest.exist?(problem_out_dir)
75 +
76 + problem_home = "#{PROBLEMS_DIR}/#{problem.name}"
77 + source_name = "#{problem.name}.#{lang_ext}"
78 +
79 + save_source(sub,problem_out_dir,source_name)
80 + call_judge(problem_home,language,problem_out_dir,source_name)
81 + save_result(sub,read_result("#{problem_out_dir}/test-result"))
82 + end
83 +
84 + def stop_grader
85 + File.open(File.dirname(__FILE__) + '/stop','w')
86 + end
87 +
88 + def check_stopfile
89 + FileTest.exist?(File.dirname(__FILE__) + '/stop')
90 + end
91 +
92 + def clear_stopfile
93 + system("rm " + File.dirname(__FILE__) + '/stop')
94 + end
95 +
96 + # reading environment and options
97 + GRADER_ENV = 'exam'
98 + puts "environment: #{GRADER_ENV}"
99 + require File.dirname(__FILE__) + "/environment.rb"
100 +
101 + #main program
102 + talk 'Reading rails environment'
103 +
104 + RAILS_ENV = 'development'
105 + require RAILS_APP_DIR + '/config/environment'
106 +
107 + current_dir = `pwd`
108 + grade(ARGV[0].to_i)
109 +
110 +
@@ -0,0 +1,164
1 + #!/usr/bin/ruby
2 +
3 + # import_problem:
4 + # * creates a directory for a problem in the current directory,
5 + # * copy testdata in the old format and create standard testcase config file
6 +
7 + require 'erb'
8 + require 'fileutils'
9 + require File.join(File.dirname(__FILE__),'lib/import_helper')
10 +
11 + def input_filename(dir,i)
12 + "#{dir}/input-#{i}.txt"
13 + end
14 +
15 + def answer_filename(dir,i)
16 + "#{dir}/answer-#{i}.txt"
17 + end
18 +
19 + def build_testrun_info_from_dir(num_testruns,importing_test_dir)
20 + filenames = Dir["#{importing_test_dir}/*.in"].collect do |filename|
21 + File.basename((/(.*)\.in/.match(filename))[1])
22 + end
23 + build_testrun_info(num_testruns,filenames)
24 + end
25 +
26 + def copy_testcase(importing_test_dir,fname,dir,i)
27 + system("cp #{importing_test_dir}/#{fname}.in #{input_filename(dir,i)}")
28 + system("cp #{importing_test_dir}/#{fname}.sol #{answer_filename(dir,i)}")
29 + end
30 +
31 + def process_options(options)
32 + i = 4
33 + while i<ARGV.length
34 + if ARGV[i]=='-t'
35 + options[:time_limit] = ARGV[i+1].to_f if ARGV.length>i+1
36 + i += 1
37 + end
38 + if ARGV[i]=='-m'
39 + options[:mem_limit] = ARGV[i+1].to_i if ARGV.length>i+1
40 + i += 1
41 + end
42 + i += 1
43 + end
44 + end
45 +
46 + SCRIPT_DIR = File.dirname(__FILE__)
47 +
48 + # print usage
49 + if (ARGV.length < 4) or (ARGV[3][0,1]=="-")
50 + puts <<USAGE
51 + using: import_problem name dir num check [options]
52 + where: name = problem_name (put '-' (dash) to use dir name)
53 + dir = importing testcase directory
54 + num = number of testruns
55 + check = check script, which can be
56 + 'integer', 'text' (for standard script),
57 + path_to_your_script, or
58 + 'wrapper:(path_to_your_wrapped_script)'
59 + options: -t time-limit (in seconds)
60 + -m memory-limit (in megabytes)
61 + What it does:
62 + * creates a directory for a problem in the current directory,
63 + * copies testdata in the old format and create standard testcase config file
64 + * copies a check script for grading
65 + * creates a test_request template in the current directory + '/test_request'
66 +
67 + For wrapped checked script see comment in templates/check_wrapper for
68 + information.
69 + USAGE
70 + exit(127)
71 + end
72 +
73 + # processing arguments
74 + problem = ARGV[0]
75 + testcase_dir = ARGV[1]
76 + problem = File.basename(testcase_dir) if problem=="-"
77 + num_testruns = ARGV[2].to_i
78 + check_script = ARGV[3]
79 + options = {:time_limit => 1, :mem_limit => 16}
80 + process_options(options)
81 +
82 + testrun_info = build_testrun_info_from_dir(num_testruns, testcase_dir)
83 +
84 + # start working
85 + puts "creating directories"
86 +
87 + system("mkdir #{problem}")
88 + system("mkdir #{problem}/script")
89 + system("mkdir #{problem}/test_cases")
90 + #system("cp #{GRADER_DIR}/std-script/* #{problem}/script")
91 +
92 + puts "copying testcases"
93 +
94 + tr_num = 0
95 +
96 + num_testcases = 0
97 +
98 + testrun_info.each do |testrun|
99 + tr_num += 1
100 + puts "testrun: #{tr_num}"
101 +
102 + testrun.each do |testcase_info|
103 + testcase_num, testcase_fname = testcase_info
104 +
105 + puts "copy #{testcase_fname} to #{testcase_num}"
106 +
107 + system("mkdir #{problem}/test_cases/#{testcase_num}")
108 + copy_testcase("#{testcase_dir}",testcase_fname,"#{problem}/test_cases/#{testcase_num}",testcase_num)
109 +
110 + num_testcases += 1
111 + end
112 + end
113 +
114 + # generating all_tests.cfg
115 + puts "generating testcase config file"
116 +
117 + template = File.open(SCRIPT_DIR + "/templates/all_tests.cfg.erb").read
118 + all_test_cfg = ERB.new(template)
119 +
120 + cfg_file = File.open("#{problem}/test_cases/all_tests.cfg","w")
121 + cfg_file.puts all_test_cfg.result
122 + cfg_file.close
123 +
124 +
125 + # copy check script
126 + if res = /^wrapper:(.*)$/.match(check_script)
127 + # wrapper script
128 + check_script_fname = res[1]
129 + script_name = File.basename(check_script_fname)
130 + check_wrapper_template = File.open(SCRIPT_DIR + "/templates/check_wrapper").read
131 + check_wrapper = ERB.new(check_wrapper_template)
132 +
133 + check_file = File.open("#{problem}/script/check","w")
134 + check_file.puts check_wrapper.result
135 + check_file.close
136 +
137 + File.chmod(0755,"#{problem}/script/check")
138 +
139 + system("cp #{check_script_fname} #{problem}/script/#{script_name}")
140 + else
141 + if File.exists?(SCRIPT_DIR + "/templates/check.#{check_script}")
142 + check_script_fname = SCRIPT_DIR + "/templates/check.#{check_script}"
143 + else
144 + check_script_fname = check_script
145 + end
146 + system("cp #{check_script_fname} #{problem}/script/check")
147 + end
148 +
149 + # generating test_request directory
150 + puts "generating test_request template"
151 + FileUtils.mkdir_p("test_request/#{problem}/script")
152 + FileUtils.mkdir_p("test_request/#{problem}/test_cases/1")
153 +
154 + template = File.open(SCRIPT_DIR + "/templates/test_request_all_tests.cfg.erb").read
155 + test_request_all_test_cfg = ERB.new(template)
156 +
157 + cfg_file = File.open("test_request/#{problem}/test_cases/all_tests.cfg","w")
158 + cfg_file.puts test_request_all_test_cfg.result
159 + cfg_file.close
160 +
161 + system("cp #{SCRIPT_DIR}/templates/check_empty test_request/#{problem}/script/check")
162 + system("cp #{SCRIPT_DIR}/templates/answer-1.txt test_request/#{problem}/test_cases/1")
163 +
164 + puts "done"
@@ -0,0 +1,10
1 +
2 + require File.join(File.dirname(__FILE__), 'configuration')
3 + require File.join(File.dirname(__FILE__), 'initializer')
4 +
5 + require File.join(File.dirname(__FILE__), 'submission_helper')
6 + require File.join(File.dirname(__FILE__), 'test_request_helper')
7 +
8 + require File.join(File.dirname(__FILE__), 'engine')
9 + require File.join(File.dirname(__FILE__), 'runner')
10 +
@@ -0,0 +1,84
1 + module Grader
2 +
3 + # This singleton class holds basic configurations for grader. When
4 + # running in each mode, grader uses resources from different
5 + # directories and outputs differently. Usually the attributes name
6 + # are descriptive; below we explain more on each attributes.
7 + class Configuration
8 + # Rails' environment: "development", "production"
9 + attr_accessor :rails_env
10 +
11 + # Grader looks for problem [prob] in problem_dir/[prob], and store
12 + # execution results for submission [x] of user [u] in directory
13 + # user_result_dir/[u]/[x]
14 + attr_accessor :problems_dir
15 + attr_accessor :user_result_dir
16 +
17 + # If report_grader=true, the grader would add a row in model
18 + # GraderProcess. It would report itself with grader_hostname and
19 + # process id.
20 + attr_accessor :report_grader
21 + attr_accessor :grader_hostname
22 +
23 + # If talkative=true, grader would report status to console. If
24 + # logging=true, grader would report status to a log file located
25 + # in log_dir, in a file name mode.options.pid. TODO: defined
26 + # log file naming.
27 + attr_accessor :talkative
28 + attr_accessor :logging
29 + attr_accessor :log_dir
30 +
31 + # These are directories related to the test interface.
32 + attr_accessor :test_request_input_base_dir
33 + attr_accessor :test_request_output_base_dir
34 + attr_accessor :test_request_problem_templates_dir
35 +
36 + # Comment received from the grading script will be filtered
37 + # through Configuration#report_comment. How this method behave
38 + # depends on this option; right now only two formats, :short and
39 + # :long
40 + attr_accessor :comment_report_style
41 +
42 + def report_comment(comment)
43 + case comment_report_style
44 + when :short
45 + if comment.chomp =~ /^[\[\]P]+$/ # all P's
46 + 'passed'
47 + elsif comment.chomp =~ /[Cc]ompil.*[Ee]rror/
48 + 'compilation error'
49 + else
50 + 'failed'
51 + end
52 +
53 + when :full
54 + comment.chomp
55 + end
56 + end
57 +
58 + # Codes for singleton
59 + private_class_method :new
60 +
61 + @@instance = nil
62 +
63 + def self.get_instance
64 + if @@instance==nil
65 + @@instance = new
66 + end
67 + @@instance
68 + end
69 +
70 + private
71 + def initialize
72 + @talkative = false
73 + @log_file = nil
74 + @report_grader = false
75 + @grader_hostname = `hostname`.chomp
76 +
77 + @rails_env = 'development'
78 +
79 + @comment_report_style = :full
80 + end
81 +
82 + end
83 +
84 + end
@@ -0,0 +1,138
1 + require 'fileutils'
2 +
3 + module Grader
4 +
5 + #
6 + # A grader engine grades a submission, against anything: a test
7 + # data, or a user submitted test data. It uses two helpers objects:
8 + # room_maker and reporter.
9 + #
10 + class Engine
11 +
12 + attr_writer :room_maker
13 + attr_writer :reporter
14 +
15 + def initialize(room_maker=nil, reporter=nil)
16 + @config = Grader::Configuration.get_instance
17 +
18 + @room_maker = room_maker || Grader::SubmissionRoomMaker.new
19 + @reporter = reporter || Grader::SubmissionReporter.new
20 + end
21 +
22 + # takes a submission, asks room_maker to produce grading directories,
23 + # calls grader scripts, and asks reporter to save the result
24 + def grade(submission)
25 + current_dir = `pwd`.chomp
26 +
27 + user = submission.user
28 + problem = submission.problem
29 +
30 + # TODO: will have to create real exception for this
31 + if user==nil or problem == nil
32 + @reporter.report_error(submission,"Grading error: problem with submission")
33 + #raise "engine: user or problem is nil"
34 + end
35 +
36 + # TODO: this is another hack so that output only task can be judged
37 + if submission.language!=nil
38 + language = submission.language.name
39 + lang_ext = submission.language.ext
40 + else
41 + language = 'c'
42 + lang_ext = 'c'
43 + end
44 +
45 + # FIX THIS
46 + talk 'some hack on language'
47 + if language == 'cpp'
48 + language = 'c++'
49 + end
50 +
51 + # COMMENT: should it be only source.ext?
52 + if problem!=nil
53 + source_name = "#{problem.name}.#{lang_ext}"
54 + else
55 + source_name = "source.#{lang_ext}"
56 + end
57 +
58 + begin
59 + grading_dir = @room_maker.produce_grading_room(submission)
60 + @room_maker.save_source(submission,source_name)
61 + problem_home = @room_maker.find_problem_home(submission)
62 +
63 + # puts "GRADING DIR: #{grading_dir}"
64 + # puts "PROBLEM DIR: #{problem_home}"
65 +
66 + copy_log = copy_script(problem_home)
67 +
68 + call_judge(problem_home,language,grading_dir,source_name)
69 +
70 + @reporter.report(submission,"#{grading_dir}/test-result")
71 +
72 + clear_script(copy_log,problem_home)
73 +
74 + rescue RuntimeError => msg
75 + @reporter.report_error(submission,"Grading error: #{msg}")
76 +
77 + ensure
78 + @room_maker.clean_up(submission)
79 + Dir.chdir(current_dir) # this is really important
80 + end
81 + end
82 +
83 + protected
84 +
85 + def talk(str)
86 + if @config.talkative
87 + puts str
88 + end
89 + end
90 +
91 + def call_judge(problem_home,language,grading_dir,fname)
92 + ENV['PROBLEM_HOME'] = problem_home
93 +
94 + talk grading_dir
95 + Dir.chdir grading_dir
96 + cmd = "#{problem_home}/script/judge #{language} #{fname}"
97 + talk "CMD: #{cmd}"
98 + system(cmd)
99 + end
100 +
101 + def get_std_script_dir
102 + GRADER_ROOT + '/std-script'
103 + end
104 +
105 + def copy_script(problem_home)
106 + script_dir = "#{problem_home}/script"
107 + std_script_dir = get_std_script_dir
108 +
109 + raise "std-script directory not found" if !FileTest.exist?(std_script_dir)
110 +
111 + scripts = Dir[std_script_dir + '/*']
112 +
113 + copied = []
114 +
115 + scripts.each do |s|
116 + fname = File.basename(s)
117 + if !FileTest.exist?("#{script_dir}/#{fname}")
118 + copied << fname
119 + system("cp #{s} #{script_dir}")
120 + end
121 + end
122 +
123 + return copied
124 + end
125 +
126 + def clear_script(log,problem_home)
127 + log.each do |s|
128 + system("rm #{problem_home}/script/#{s}")
129 + end
130 + end
131 +
132 + def mkdir_if_does_not_exist(dirname)
133 + Dir.mkdir(dirname) if !FileTest.exist?(dirname)
134 + end
135 +
136 + end
137 +
138 + end
@@ -0,0 +1,28
1 +
2 + def filter_filename_for_testrun(testrun, filename_list)
3 + l = []
4 + regex = Regexp.new("^(#{testrun}[a-z]*|#{testrun}-.*)$")
5 + filename_list.each do |filename|
6 + if regex.match(filename)
7 + l << filename
8 + end
9 + end
10 + l
11 + end
12 +
13 + def build_testrun_info(num_testruns, input_filename_list)
14 + info = []
15 + num_testcases = 0
16 + num_testruns.times do |i|
17 + r = i+1
18 + testrun_info = []
19 + filenames = filter_filename_for_testrun(r, input_filename_list)
20 + filenames.each do |fname|
21 + num_testcases += 1
22 + testrun_info << [num_testcases,fname]
23 + end
24 + info << testrun_info
25 + end
26 + info
27 + end
28 +
@@ -0,0 +1,13
1 +
2 + module Grader
3 +
4 + class Initializer
5 +
6 + def self.run(&block)
7 + config = Grader::Configuration.get_instance
8 + yield config
9 + end
10 +
11 + end
12 +
13 + end
@@ -0,0 +1,58
1 + #
2 + # A runner drives the engine into various tasks.
3 + #
4 +
5 + module Grader
6 +
7 + class Runner
8 +
9 + def initialize(engine, grader_process=nil)
10 + @engine = engine
11 + @grader_process = grader_process
12 + end
13 +
14 + def grade_oldest_task
15 + task = Task.get_inqueue_and_change_status(Task::STATUS_GRADING)
16 + if task!=nil
17 + @grader_process.report_active(task) if @grader_process!=nil
18 +
19 + submission = Submission.find(task.submission_id)
20 + @engine.grade(submission)
21 + task.status_complete!
22 + @grader_process.report_inactive(task) if @grader_process!=nil
23 + end
24 + return task
25 + end
26 +
27 + def grade_problem(problem)
28 + users = User.find(:all)
29 + users.each do |u|
30 + puts "user: #{u.login}"
31 + last_sub = Submission.find_last_by_user_and_problem(u.id,problem.id)
32 + if last_sub!=nil
33 + @engine.grade(last_sub)
34 + end
35 + end
36 + end
37 +
38 + def grade_submission(submission)
39 + puts "Submission: #{submission.id} by #{submission.user.full_name}"
40 + @engine.grade(submission)
41 + end
42 +
43 + def grade_oldest_test_request
44 + test_request = TestRequest.get_inqueue_and_change_status(Task::STATUS_GRADING)
45 + if test_request!=nil
46 + @grader_process.report_active(test_request) if @grader_process!=nil
47 +
48 + @engine.grade(test_request)
49 + test_request.status_complete!
50 + @grader_process.report_inactive(test_request) if @grader_process!=nil
51 + end
52 + return test_request
53 + end
54 +
55 + end
56 +
57 + end
58 +
@@ -0,0 +1,123
1 + module Grader
2 +
3 + class SubmissionRoomMaker
4 + def initialize
5 + @config = Grader::Configuration.get_instance
6 + end
7 +
8 + def produce_grading_room(submission)
9 + user = submission.user
10 + problem = submission.problem
11 + grading_room = "#{@config.user_result_dir}/" +
12 + "#{user.login}/#{problem.name}/#{submission.id}"
13 +
14 + FileUtils.mkdir_p(grading_room)
15 + grading_room
16 + end
17 +
18 + def find_problem_home(submission)
19 + problem = submission.problem
20 + "#{@config.problems_dir}/#{problem.name}"
21 + end
22 +
23 + def save_source(submission,source_name)
24 + dir = self.produce_grading_room(submission)
25 + f = File.open("#{dir}/#{source_name}","w")
26 + f.write(submission.source)
27 + f.close
28 + end
29 +
30 + def clean_up(submission)
31 + end
32 + end
33 +
34 + class SubmissionReporter
35 + def initialize
36 + @config = Grader::Configuration.get_instance
37 + end
38 +
39 + def report(sub,test_result_dir)
40 + save_result(sub,read_result(test_result_dir))
41 + end
42 +
43 + def report_error(sub,msg)
44 + save_result(sub,{:points => 0,
45 + :comment => "Grading error: #{msg}" })
46 + end
47 +
48 + protected
49 + def read_result(test_result_dir)
50 + cmp_msg_fname = "#{test_result_dir}/compiler_message"
51 + if FileTest.exist?(cmp_msg_fname)
52 + cmp_file = File.open(cmp_msg_fname)
53 + cmp_msg = cmp_file.read
54 + cmp_file.close
55 + else
56 + cmp_msg = ""
57 + end
58 +
59 + result_fname = "#{test_result_dir}/result"
60 + comment_fname = "#{test_result_dir}/comment"
61 + if FileTest.exist?(result_fname)
62 + comment = ""
63 + begin
64 + result_file = File.open(result_fname)
65 + result = result_file.readline.to_i
66 + result_file.close
67 + rescue
68 + result = 0
69 + comment = "error reading result file."
70 + end
71 +
72 + begin
73 + comment_file = File.open(comment_fname)
74 + comment += comment_file.readline.chomp
75 + comment_file.close
76 + rescue
77 + comment += ""
78 + end
79 +
80 + return {:points => result,
81 + :comment => comment,
82 + :cmp_msg => cmp_msg}
83 + else
84 + if FileTest.exist?("#{test_result_dir}/a.out")
85 + return {:points => 0,
86 + :comment => 'error during grading',
87 + :cmp_msg => cmp_msg}
88 + else
89 + return {:points => 0,
90 + :comment => 'compilation error',
91 + :cmp_msg => cmp_msg}
92 + end
93 + end
94 + end
95 +
96 + def save_result(submission,result)
97 + problem = submission.problem
98 + submission.graded_at = Time.now.gmtime
99 + points = result[:points]
100 + submission.points = points
101 + comment = @config.report_comment(result[:comment])
102 +
103 + #
104 + # TODO: FIX THIS MESSAGE
105 + #
106 + if problem == nil
107 + submission.grader_comment = 'PASSED: ' + comment + '(problem is nil)'
108 + elsif points == problem.full_score
109 + #submission.grader_comment = 'PASSED: ' + comment
110 + submission.grader_comment = comment
111 + elsif result[:comment].chomp =~ /^[\[\]P]+$/
112 + submission.grader_comment = 'PASSED: ' + comment + '(inconsistent score)'
113 + else
114 + #submission.grader_comment = 'FAILED: ' + comment
115 + submission.grader_comment = comment
116 + end
117 + submission.compiler_message = result[:cmp_msg] or ''
118 + submission.save
119 + end
120 +
121 + end
122 +
123 + end
@@ -0,0 +1,242
1 + #
2 + # This part contains various test_request helpers for interfacing
3 + # with Grader::Engine. There are TestRequestRoomMaker and
4 + # TestRequestReporter.
5 +
6 + module Grader
7 +
8 + #
9 + # A TestRequestRoomMaker is a helper object for Engine
10 + # - finds grading room: in user_result_dir/(user)/test_request/ ...
11 + # - prepare problem configuration for grading --- basically it copy
12 + # all config files, and copy user's input into the testcase
13 + # directory. First, it finds the template from problem template
14 + # directory; if it can't find a template, it'll use the template
15 + # from default template.
16 + class TestRequestRoomMaker
17 + def initialize
18 + @config = Grader::Configuration.get_instance
19 + end
20 +
21 + def produce_grading_room(test_request)
22 + grading_room = grading_room_dir(test_request)
23 + FileUtils.mkdir_p(grading_room)
24 +
25 + #
26 + # Also copy additional submitted file to this directory as well.
27 + # The program would see this file only if it is copied
28 + # to the sandbox directory later. The run script should do it.
29 + #
30 + if FileTest.exists?("#{test_request.input_file_name}.files")
31 + cmd = "cp #{test_request.input_file_name}.files/* #{grading_room}"
32 + system(cmd)
33 + end
34 +
35 + grading_room
36 + end
37 +
38 + def find_problem_home(test_request)
39 + problem_name = test_request.problem_name
40 +
41 + template_dir = "#{@config.test_request_problem_templates_dir}/" + problem_name
42 +
43 + raise "Test Request: error template not found" if !File.exists?(template_dir)
44 +
45 + problem_home = problem_home_dir(test_request)
46 + FileUtils.mkdir_p(problem_home)
47 +
48 + copy_problem_template(template_dir,problem_home)
49 + link_input_file(test_request,problem_home)
50 +
51 + problem_home
52 + end
53 +
54 + def save_source(test_request,source_name)
55 + dir = self.produce_grading_room(test_request)
56 + submission = test_request.submission
57 + f = File.open("#{dir}/#{source_name}","w")
58 + f.write(submission.source)
59 + f.close
60 + end
61 +
62 + def clean_up(test_request)
63 + problem_home = problem_home_dir(test_request)
64 + remove_data_files(problem_home)
65 + end
66 +
67 + protected
68 + def grading_room_dir(test_request)
69 + problem_name = test_request.problem_name
70 + user = test_request.user
71 + grading_room = "#{@config.user_result_dir}" +
72 + "/#{user.login}/test_request" +
73 + "/#{problem_name}/#{test_request.id}"
74 + grading_room
75 + end
76 +
77 + def problem_home_dir(test_request)
78 + problem_name = test_request.problem_name
79 + user = test_request.user
80 + "#{@config.user_result_dir}" +
81 + "/#{user.login}/test_request/#{problem_name}"
82 + end
83 +
84 + def copy_problem_template(template_dir,problem_home)
85 + cmd = "cp -R #{template_dir}/* #{problem_home}"
86 + system_and_raise_when_fail(cmd,"Test Request: cannot copy problem template")
87 + end
88 +
89 + def link_input_file(test_request,problem_home)
90 + input_fname = "#{test_request.input_file_name}"
91 + if !File.exists?(input_fname)
92 + raise "Test Request: input file not found."
93 + end
94 +
95 + input_fname_problem_home = "#{problem_home}/test_cases/1/input-1.txt"
96 + if File.exists?(input_fname_problem_home)
97 + FileUtils.rm([input_fname_problem_home], :force => true)
98 + end
99 +
100 + cmd = "ln -s #{input_fname} #{input_fname_problem_home}"
101 + system_and_raise_when_fail(cmd,"Test Request: cannot link input file")
102 + end
103 +
104 + def remove_data_files(problem_home)
105 + if File.exists?("#{problem_home}/test_cases/1/input-1.txt")
106 + cmd = "rm #{problem_home}/test_cases/1/*"
107 + system_and_raise_when_fail(cmd,"Test Request: cannot remove data files")
108 + end
109 + end
110 +
111 + def system_and_raise_when_fail(cmd,msg)
112 + if !system(cmd)
113 + raise msg
114 + end
115 + end
116 +
117 + end
118 +
119 + class TestRequestReporter
120 + def initialize
121 + @config = Grader::Configuration.get_instance
122 + end
123 +
124 + def report(test_request,test_result_dir)
125 + save_result(test_request,read_result(test_result_dir))
126 + end
127 +
128 + def report_error(test_request, msg)
129 + save_result(test_request, {:running_stat => {
130 + :msg => "#{msg}",
131 + :running_time => nil,
132 + :exit_status => "Some error occured. Program did not run",
133 + :memory_usage => nil
134 + }})
135 + end
136 +
137 + protected
138 + def read_result(test_result_dir)
139 + # TODO:
140 + cmp_msg_fname = "#{test_result_dir}/compiler_message"
141 + cmp_file = File.open(cmp_msg_fname)
142 + cmp_msg = cmp_file.read
143 + cmp_file.close
144 +
145 + result_file_name = "#{test_result_dir}/1/result"
146 +
147 + if File.exists?(result_file_name)
148 + output_file_name = "#{test_result_dir}/1/output.txt"
149 + results = File.open("#{test_result_dir}/1/result").readlines
150 + stat = extract_running_stat(results)
151 +
152 + return {
153 + :output_file_name => output_file_name,
154 + :running_stat => stat,
155 + :comment => "",
156 + :cmp_msg => cmp_msg}
157 + else
158 + return {
159 + :running_stat => nil,
160 + :comment => "Compilation error",
161 + :cmp_msg => cmp_msg}
162 + end
163 + end
164 +
165 + def extract_running_stat(results)
166 + running_stat_line = results[-1]
167 +
168 + # extract exit status line
169 + run_stat = ""
170 + if !(/[Cc]orrect/.match(results[0]))
171 + run_stat = results[0].chomp
172 + else
173 + run_stat = 'Program exited normally'
174 + end
175 +
176 + # extract running time
177 + if res = /r(.*)u(.*)s/.match(running_stat_line)
178 + seconds = (res[1].to_f + res[2].to_f)
179 + time_stat = "Time used: #{seconds} sec."
180 + else
181 + seconds = nil
182 + time_stat = "Time used: n/a sec."
183 + end
184 +
185 + # extract memory usage
186 + if res = /s(.*)m/.match(running_stat_line)
187 + memory_used = res[1].to_i
188 + else
189 + memory_used = -1
190 + end
191 +
192 + return {
193 + :msg => "#{run_stat}\n#{time_stat}",
194 + :running_time => seconds,
195 + :exit_status => run_stat,
196 + :memory_usage => memory_used
197 + }
198 + end
199 +
200 + def save_result(test_request,result)
201 + if result[:output_file_name]!=nil
202 + test_request.output_file_name = link_output_file(test_request,
203 + result[:output_file_name])
204 + end
205 + test_request.graded_at = Time.now
206 + test_request.compiler_message = (result[:cmp_msg] or '')
207 + test_request.grader_comment = (result[:comment] or '')
208 + if result[:running_stat]!=nil
209 + test_request.running_stat = (result[:running_stat][:msg] or '')
210 + test_request.running_time = (result[:running_stat][:running_time] or nil)
211 + test_request.exit_status = result[:running_stat][:exit_status]
212 + test_request.memory_usage = result[:running_stat][:memory_usage]
213 + else
214 + test_request.running_stat = ''
215 + end
216 + test_request.save
217 + end
218 +
219 + protected
220 + def link_output_file(test_request, fname)
221 + target_file_name = random_output_file_name(test_request.user,
222 + test_request.problem)
223 + FileUtils.mkdir_p(File.dirname(target_file_name))
224 + cmd = "ln -s #{fname} #{target_file_name}"
225 + if !system(cmd)
226 + raise "TestRequestReporter: cannot move output file"
227 + end
228 + return target_file_name
229 + end
230 +
231 + def random_output_file_name(user,problem)
232 + problem_name = TestRequest.name_of(problem)
233 + begin
234 + tmpname = "#{@config.test_request_output_base_dir}" +
235 + "/#{user.login}/#{problem_name}/#{rand(10000)}"
236 + end while File.exists?(tmpname)
237 + tmpname
238 + end
239 +
240 + end
241 +
242 + end
@@ -0,0 +1,73
1 + #!/usr/bin/ruby
2 +
3 + # new_problem:
4 + # * creates a directory for a problem in the current directory,
5 + # * create standard testcase config file
6 +
7 + require 'erb'
8 +
9 + def process_options(options)
10 + i = 2
11 + while i<ARGV.length
12 + if ARGV[i]=='-t'
13 + options[:time_limit] = ARGV[i+1].to_i if ARGV.length>i+1
14 + i += 1
15 + end
16 + if ARGV[i]=='-m'
17 + options[:mem_limit] = ARGV[i+1].to_i if ARGV.length>i+1
18 + i += 1
19 + end
20 + i += 1
21 + end
22 + end
23 +
24 +
25 + puts "This script is out of dated, shall be fixed soon"
26 + puts "Right now, you can create raw_ev and import"
27 + exit(0)
28 +
29 + GRADER_DIR = File.dirname(__FILE__)
30 +
31 + # print usage
32 + if ARGV.length < 2
33 + puts <<USAGE
34 + using: new_problem problem number_of_testcase [options]
35 + * creates a directory for a problem in the current directory,
36 + * create standard testcase config file
37 + * options: -t time-limit (in seconds)
38 + -m mem-limit (in MB)
39 + USAGE
40 + exit(127)
41 + end
42 +
43 + # processing arguments
44 + problem = ARGV[0]
45 + num_testcases = ARGV[1].to_i
46 +
47 + options = {:time_limit => 1, :mem_limit => 16}
48 + process_options(options)
49 +
50 + # start working
51 + puts "creating directories"
52 +
53 + system("mkdir #{problem}")
54 + system("mkdir #{problem}/script")
55 + system("mkdir #{problem}/test_cases")
56 +
57 + puts "creating testcases directories"
58 +
59 + 1.upto(num_testcases) do |i|
60 + system("mkdir #{problem}/test_cases/#{i}")
61 + end
62 +
63 + # generating all_tests.cfg
64 + puts "generating testcase config file"
65 +
66 + template = File.open(File.dirname(__FILE__) + "/templates/all_tests.cfg.erb").read
67 + all_test_cfg = ERB.new(template)
68 +
69 + cfg_file = File.open("#{problem}/test_cases/all_tests.cfg","w")
70 + cfg_file.puts all_test_cfg.result
71 + cfg_file.close
72 +
73 + puts "done"
This diff has been collapsed as it changes many lines, (684 lines changed) Show them Hide them
@@ -0,0 +1,684
1 + /*
2 + * A Simple Testing Sandbox
3 + *
4 + * (c) 2001--2004 Martin Mares <mj@ucw.cz>
5 + */
6 +
7 + #define _LARGEFILE64_SOURCE
8 + //#define _GNU_SOURCE
9 +
10 + #include <errno.h>
11 + #include <stdio.h>
12 + #include <fcntl.h>
13 + #include <stdlib.h>
14 + #include <string.h>
15 + #include <stdarg.h>
16 + #include <unistd.h>
17 + #include <getopt.h>
18 + #include <time.h>
19 + #include <sys/wait.h>
20 + #include <sys/user.h>
21 + #include <sys/time.h>
22 + #include <sys/ptrace.h>
23 + #include <sys/signal.h>
24 + #include <sys/sysinfo.h>
25 + #include <sys/syscall.h>
26 + #include <sys/resource.h>
27 +
28 + #define NONRET __attribute__((noreturn))
29 + #define UNUSED __attribute__((unused))
30 +
31 + static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */
32 + static double timeout;
33 + static int pass_environ;
34 + static int use_wall_clock;
35 + static int file_access;
36 + static int verbose;
37 + static int memory_limit;
38 + static int allow_times;
39 + static char *redir_stdin, *redir_stdout;
40 + static char *set_cwd;
41 +
42 + static pid_t box_pid;
43 + static int is_ptraced;
44 + static volatile int timer_tick;
45 + static time_t start_time;
46 + static int ticks_per_sec;
47 + static int page_size;
48 +
49 + #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0
50 + /* glibc 2.1 or newer -> has lseek64 */
51 + #define long_seek(f,o,w) lseek64(f,o,w)
52 + #else
53 + /* Touching clandestine places in glibc */
54 + extern loff_t llseek(int fd, loff_t pos, int whence);
55 + #define long_seek(f,o,w) llseek(f,o,w)
56 + #endif
57 +
58 + int max_mem_used = 0;
59 +
60 + void print_running_stat(double wall_time,
61 + double user_time,
62 + double system_time,
63 + int mem_usage)
64 + {
65 + fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dm\n",
66 + wall_time, user_time, system_time, mem_usage);
67 + }
68 +
69 + static void NONRET
70 + box_exit(void)
71 + {
72 + if (box_pid > 0) {
73 + if (is_ptraced)
74 + ptrace(PTRACE_KILL, box_pid);
75 + kill(-box_pid, SIGKILL);
76 + kill(box_pid, SIGKILL);
77 + }
78 +
79 + struct timeval total;
80 + int wall;
81 + struct rusage rus;
82 + int stat;
83 + pid_t p;
84 +
85 + // wait so that we can get information
86 + p = wait4(box_pid, &stat, WUNTRACED, &rus);
87 + if (p < 0) {
88 + fprintf(stderr,"wait4: error\n");
89 + print_running_stat(0,0,0,max_mem_used);
90 + } else if (p != box_pid) {
91 + fprintf(stderr,"wait4: unknown pid %d exited!\n", p);
92 + print_running_stat(0,0,0,max_mem_used);
93 + } else {
94 + if (!WIFEXITED(stat))
95 + fprintf(stderr,"wait4: unknown status\n");
96 + struct timeval total;
97 + int wall;
98 + wall = time(NULL) - start_time;
99 + timeradd(&rus.ru_utime, &rus.ru_stime, &total);
100 +
101 + print_running_stat((double)wall,
102 + (double) rus.ru_utime.tv_sec +
103 + ((double) rus.ru_utime.tv_usec/1000000.0),
104 + (double) rus.ru_stime.tv_sec +
105 + ((double) rus.ru_stime.tv_usec/1000000.0),
106 + max_mem_used);
107 + }
108 + exit(1);
109 + }
110 +
111 + static void NONRET __attribute__((format(printf,1,2)))
112 + die(char *msg, ...)
113 + {
114 + va_list args;
115 + va_start(args, msg);
116 + vfprintf(stderr, msg, args);
117 + fputc('\n', stderr);
118 + box_exit();
119 + }
120 +
121 + static void __attribute__((format(printf,1,2)))
122 + log(char *msg, ...)
123 + {
124 + va_list args;
125 + va_start(args, msg);
126 + if (verbose)
127 + {
128 + vfprintf(stderr, msg, args);
129 + fflush(stderr);
130 + }
131 + va_end(args);
132 + }
133 +
134 + static void
135 + valid_filename(unsigned long addr)
136 + {
137 + char namebuf[4096], *p, *end;
138 + static int mem_fd;
139 +
140 + if (!file_access)
141 + die("File access forbidden.");
142 + if (file_access >= 9)
143 + return;
144 +
145 + if (!mem_fd)
146 + {
147 + sprintf(namebuf, "/proc/%d/mem", (int) box_pid);
148 + mem_fd = open(namebuf, O_RDONLY);
149 + if (mem_fd < 0)
150 + die("open(%s): %m", namebuf);
151 + }
152 + p = end = namebuf;
153 + do
154 + {
155 + if (p >= end)
156 + {
157 + int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
158 + int l = namebuf + sizeof(namebuf) - end;
159 + if (l > remains)
160 + l = remains;
161 + if (!l)
162 + die("Access to file with name too long.");
163 + if (long_seek(mem_fd, addr, SEEK_SET) < 0)
164 + die("long_seek(mem): %m");
165 + remains = read(mem_fd, end, l);
166 + if (remains < 0)
167 + die("read(mem): %m");
168 + if (!remains)
169 + die("Access to file with name out of memory.");
170 + end += l;
171 + addr += l;
172 + }
173 + }
174 + while (*p++);
175 +
176 + log("[%s] ", namebuf);
177 + if (file_access >= 3)
178 + return;
179 + if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
180 + return;
181 + if (file_access >= 2)
182 + {
183 + if ((!strncmp(namebuf, "/etc/", 5) ||
184 + !strncmp(namebuf, "/lib/", 5) ||
185 + !strncmp(namebuf, "/usr/lib/", 9))
186 + && !strstr(namebuf, ".."))
187 + return;
188 + if (!strcmp(namebuf, "/dev/null") ||
189 + !strcmp(namebuf, "/dev/zero") ||
190 + !strcmp(namebuf, "/proc/meminfo") ||
191 + !strcmp(namebuf, "/proc/self/stat") ||
192 + !strncmp(namebuf, "/usr/share/zoneinfo/", 20))
193 + return;
194 + }
195 + die("Forbidden access to file `%s'.", namebuf);
196 + }
197 +
198 + static int
199 + valid_syscall(struct user *u)
200 + {
201 + switch (u->regs.orig_eax)
202 + {
203 + case __NR_execve:
204 + {
205 + static int exec_counter;
206 + return !exec_counter++;
207 + }
208 + case __NR_open:
209 + case __NR_creat:
210 + case __NR_unlink:
211 + case __NR_oldstat:
212 + case __NR_access:
213 + case __NR_oldlstat:
214 + case __NR_truncate:
215 + case __NR_stat:
216 + case __NR_lstat:
217 + case __NR_truncate64:
218 + case __NR_stat64:
219 + case __NR_lstat64:
220 + valid_filename(u->regs.ebx);
221 + return 1;
222 + case __NR_exit:
223 + case __NR_read:
224 + case __NR_write:
225 + case __NR_close:
226 + case __NR_lseek:
227 + case __NR_getpid:
228 + case __NR_getuid:
229 + case __NR_oldfstat:
230 + case __NR_dup:
231 + case __NR_brk:
232 + case __NR_getgid:
233 + case __NR_geteuid:
234 + case __NR_getegid:
235 + case __NR_dup2:
236 + case __NR_ftruncate:
237 + case __NR_fstat:
238 + case __NR_personality:
239 + case __NR__llseek:
240 + case __NR_readv:
241 + case __NR_writev:
242 + case __NR_getresuid:
243 + #ifdef __NR_pread64
244 + case __NR_pread64:
245 + case __NR_pwrite64:
246 + #else
247 + case __NR_pread:
248 + case __NR_pwrite:
249 + #endif
250 + case __NR_ftruncate64:
251 + case __NR_fstat64:
252 + case __NR_fcntl:
253 + case __NR_fcntl64:
254 + case __NR_mmap:
255 + case __NR_munmap:
256 + case __NR_ioctl:
257 + case __NR_uname:
258 + case 252:
259 + case 243:
260 + // added for free pascal
261 + case __NR_ugetrlimit:
262 + case __NR_readlink:
263 + return 1;
264 + // case __NR_time:
265 + case __NR_alarm:
266 + // case __NR_pause:
267 + case __NR_signal:
268 + case __NR_fchmod:
269 + case __NR_sigaction:
270 + case __NR_sgetmask:
271 + case __NR_ssetmask:
272 + case __NR_sigsuspend:
273 + case __NR_sigpending:
274 + case __NR_getrlimit:
275 + case __NR_getrusage:
276 + case __NR_gettimeofday:
277 + case __NR_select:
278 + case __NR_readdir:
279 + case __NR_setitimer:
280 + case __NR_getitimer:
281 + case __NR_sigreturn:
282 + case __NR_mprotect:
283 + case __NR_sigprocmask:
284 + case __NR_getdents:
285 + case __NR_getdents64:
286 + case __NR__newselect:
287 + case __NR_fdatasync:
288 + case __NR_mremap:
289 + case __NR_poll:
290 + case __NR_getcwd:
291 + case __NR_nanosleep:
292 + case __NR_rt_sigreturn:
293 + case __NR_rt_sigaction:
294 + case __NR_rt_sigprocmask:
295 + case __NR_rt_sigpending:
296 + case __NR_rt_sigtimedwait:
297 + case __NR_rt_sigqueueinfo:
298 + case __NR_rt_sigsuspend:
299 + case __NR_mmap2:
300 + case __NR__sysctl:
301 + return (filter_syscalls == 1);
302 + case __NR_times:
303 + case __NR_time:
304 + return allow_times;
305 + case __NR_kill:
306 + if (u->regs.ebx == box_pid)
307 + die("Commited suicide by signal %d.", (int)u->regs.ecx);
308 + return 0;
309 + default:
310 + return 0;
311 + }
312 + }
313 +
314 + static void
315 + signal_alarm(int unused UNUSED)
316 + {
317 + /* Time limit checks are synchronous, so we only schedule them there. */
318 + timer_tick = 1;
319 +
320 + //NOTE: do not use alarm, changed to setitimer for precision
321 + // alarm(1);
322 + }
323 +
324 + static void
325 + signal_int(int unused UNUSED)
326 + {
327 + /* Interrupts are fatal, so no synchronization requirements. */
328 + die("Interrupted.");
329 + }
330 +
331 + static void
332 + check_timeout(void)
333 + {
334 + double sec;
335 +
336 + if (use_wall_clock)
337 + sec = (double)(time(NULL) - start_time);
338 + else
339 + {
340 + char buf[4096], *x;
341 + int c, utime, stime;
342 + static int proc_status_fd;
343 + if (!proc_status_fd)
344 + {
345 + sprintf(buf, "/proc/%d/stat", (int) box_pid);
346 + proc_status_fd = open(buf, O_RDONLY);
347 + if (proc_status_fd < 0)
348 + die("open(%s): %m", buf);
349 + }
350 + lseek(proc_status_fd, 0, SEEK_SET);
351 + if ((c = read(proc_status_fd, buf, sizeof(buf)-1)) < 0)
352 + die("read on /proc/$pid/stat: %m");
353 + if (c >= (int) sizeof(buf) - 1)
354 + die("/proc/$pid/stat too long");
355 + buf[c] = 0;
356 + x = buf;
357 + while (*x && *x != ' ')
358 + x++;
359 + while (*x == ' ')
360 + x++;
361 + if (*x++ != '(')
362 + die("proc syntax error 1");
363 + while (*x && (*x != ')' || x[1] != ' '))
364 + x++;
365 + while (*x == ')' || *x == ' ')
366 + x++;
367 + if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
368 + die("proc syntax error 2");
369 + //printf("%s - %d\n",x,ticks_per_sec);
370 + sec = ((double)(utime + stime))/(double)ticks_per_sec;
371 + }
372 + if (verbose > 1)
373 + fprintf(stderr, "[timecheck: %d seconds]\n", sec);
374 + if (sec > timeout) {
375 + die("Time limit exceeded.",sec,timeout);
376 + }
377 + }
378 +
379 + static void
380 + check_memory_usage()
381 + {
382 + char proc_fname[100];
383 + sprintf(proc_fname,"/proc/%d/statm",box_pid);
384 + //printf("proc fname: %s\n",proc_fname);
385 + FILE *fp = fopen(proc_fname,"r");
386 + if(fp!=NULL) {
387 + char line[1000];
388 + fgets(line,999,fp);
389 + //printf("%s\n",line);
390 + int m;
391 +
392 + if(sscanf(line,"%d",&m)==1) {
393 + m = (m*page_size+1023)/1024;
394 + if(m>max_mem_used)
395 + max_mem_used = m;
396 + }
397 +
398 + fclose(fp);
399 + }
400 + }
401 +
402 + static void
403 + boxkeeper(void)
404 + {
405 + int syscall_count = 0;
406 + struct sigaction sa;
407 +
408 + is_ptraced = 1;
409 + bzero(&sa, sizeof(sa));
410 + sa.sa_handler = signal_int;
411 + sigaction(SIGINT, &sa, NULL);
412 + start_time = time(NULL);
413 + ticks_per_sec = sysconf(_SC_CLK_TCK);
414 + page_size = getpagesize();
415 + if (ticks_per_sec <= 0)
416 + die("Invalid ticks_per_sec!");
417 +
418 + check_memory_usage();
419 +
420 + sa.sa_handler = signal_alarm;
421 + sigaction(SIGALRM, &sa, NULL);
422 + //alarm(1);
423 +
424 + struct itimerval val;
425 + val.it_interval.tv_sec = 0;
426 + val.it_interval.tv_usec = 50000;
427 + val.it_value.tv_sec = 0;
428 + val.it_value.tv_usec = 50000;
429 + setitimer(ITIMER_REAL,&val,NULL);
430 +
431 + /*
432 + --- add alarm handler no matter what..
433 + if (timeout)
434 + {
435 + sa.sa_handler = signal_alarm;
436 + sigaction(SIGALRM, &sa, NULL);
437 + alarm(1);
438 + }
439 + */
440 +
441 + for(;;)
442 + {
443 + struct rusage rus;
444 + int stat;
445 + pid_t p;
446 +
447 + if (timer_tick)
448 + {
449 + check_timeout();
450 + check_memory_usage();
451 + timer_tick = 0;
452 + }
453 + p = wait4(box_pid, &stat, WUNTRACED, &rus);
454 +
455 + if (p < 0)
456 + {
457 + if (errno == EINTR)
458 + continue;
459 + die("wait4: %m");
460 + }
461 + if (p != box_pid)
462 + die("wait4: unknown pid %d exited!", p);
463 + if (WIFEXITED(stat))
464 + {
465 + struct timeval total;
466 + int wall;
467 + wall = time(NULL) - start_time;
468 + timeradd(&rus.ru_utime, &rus.ru_stime, &total);
469 +
470 + box_pid = 0;
471 + if (WEXITSTATUS(stat))
472 + fprintf(stderr,"Exited with error status %d.\n", WEXITSTATUS(stat));
473 + else if ((use_wall_clock ?
474 + wall :
475 + (double) total.tv_sec +
476 + ((double) total.tv_usec/1000000.0)) > timeout)
477 + fprintf(stderr,"Time limit exceeded.\n");
478 + else
479 + // report OK and statistics
480 + fprintf(stderr,"OK\n");
481 +
482 + print_running_stat((double) wall,
483 + (double) rus.ru_utime.tv_sec +
484 + ((double) rus.ru_utime.tv_usec/1000000.0),
485 + (double) rus.ru_stime.tv_sec +
486 + ((double) rus.ru_stime.tv_usec/1000000.0),
487 + max_mem_used);
488 + /*
489 + (%.4lf sec real (%d), %d sec wall, %d syscalls, %d kb)\n",
490 + (double) total.tv_sec + ((double)total.tv_usec / 1000000.0),
491 + (int) total.tv_usec,
492 + wall,
493 + syscall_count,
494 + max_mem_used);
495 + */
496 + exit(0);
497 + }
498 + if (WIFSIGNALED(stat))
499 + {
500 + box_pid = 0;
501 + fprintf(stderr,"Caught fatal signal %d.\n", WTERMSIG(stat));
502 +
503 + struct timeval total;
504 + int wall;
505 + wall = time(NULL) - start_time;
506 + timeradd(&rus.ru_utime, &rus.ru_stime, &total);
507 + print_running_stat((double) wall,
508 + (double) rus.ru_utime.tv_sec +
509 + ((double) rus.ru_utime.tv_usec/1000000.0),
510 + (double) rus.ru_stime.tv_sec +
511 + ((double) rus.ru_stime.tv_usec/1000000.0),
512 + max_mem_used);
513 + exit(0);
514 + }
515 + if (WIFSTOPPED(stat))
516 + {
517 + int sig = WSTOPSIG(stat);
518 + if (sig == SIGTRAP)
519 + {
520 + struct user u;
521 + static int stop_count = -1;
522 + if (ptrace(PTRACE_GETREGS, box_pid, NULL, &u) < 0)
523 + die("ptrace(PTRACE_GETREGS): %m");
524 + stop_count++;
525 + if (!stop_count) /* Traceme request */
526 + log(">> Traceme request caught\n");
527 + else if (stop_count & 1) /* Syscall entry */
528 + {
529 + log(">> Syscall %3ld (%08lx,%08lx,%08lx) ", u.regs.orig_eax, u.regs.ebx, u.regs.ecx, u.regs.edx);
530 + syscall_count++;
531 + if (!valid_syscall(&u))
532 + {
533 + /*
534 + * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
535 + * so we have to change it to something harmless (e.g., an undefined
536 + * syscall) and make the program continue.
537 + */
538 + unsigned int sys = u.regs.orig_eax;
539 + u.regs.orig_eax = 0xffffffff;
540 + if (ptrace(PTRACE_SETREGS, box_pid, NULL, &u) < 0)
541 + die("ptrace(PTRACE_SETREGS): %m");
542 + die("Forbidden syscall %d.", sys);
543 + }
544 + }
545 + else /* Syscall return */
546 + log("= %ld\n", u.regs.eax);
547 + ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
548 + }
549 + else if (sig != SIGSTOP && sig != SIGXCPU && sig != SIGXFSZ)
550 + {
551 + log(">> Signal %d\n", sig);
552 + ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
553 + }
554 + else
555 + die("Received signal %d.", sig);
556 + }
557 + else
558 + die("wait4: unknown status %x, giving up!", stat);
559 + }
560 + }
561 +
562 + static void
563 + box_inside(int argc, char **argv)
564 + {
565 + struct rlimit rl;
566 + char *args[argc+1];
567 + char *env[1] = { NULL };
568 +
569 + memcpy(args, argv, argc * sizeof(char *));
570 + args[argc] = NULL;
571 + if (set_cwd && chdir(set_cwd))
572 + die("chdir: %m");
573 + if (redir_stdin)
574 + {
575 + close(0);
576 + if (open(redir_stdin, O_RDONLY) != 0)
577 + die("open(\"%s\"): %m", redir_stdin);
578 + }
579 + if (redir_stdout)
580 + {
581 + close(1);
582 + if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
583 + die("open(\"%s\"): %m", redir_stdout);
584 + }
585 + dup2(1, 2);
586 + setpgrp();
587 + if (memory_limit)
588 + {
589 + rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
590 + if (setrlimit(RLIMIT_AS, &rl) < 0)
591 + die("setrlimit: %m");
592 + }
593 + rl.rlim_cur = rl.rlim_max = 64;
594 + if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
595 + die("setrlimit: %m");
596 + if (filter_syscalls && ptrace(PTRACE_TRACEME) < 0)
597 + die("ptrace(PTRACE_TRACEME): %m");
598 + execve(args[0], args, (pass_environ ? environ : env));
599 + die("execve(\"%s\"): %m", args[0]);
600 + }
601 +
602 + static void
603 + usage(void)
604 + {
605 + fprintf(stderr, "Invalid arguments!\n");
606 + printf("\
607 + Usage: box [<options>] -- <command> <arguments>\n\
608 + \n\
609 + Options:\n\
610 + -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
611 + -c <dir>\tChange directory to <dir> first\n\
612 + -e\t\tPass full environment of parent process\n\
613 + -f\t\tFilter system calls (-ff=very restricted)\n\
614 + -i <file>\tRedirect stdin from <file>\n\
615 + -m <size>\tLimit address space to <size> KB\n\
616 + -o <file>\tRedirect stdout to <file>\n\
617 + -t <time>\tStop after <time> seconds\n\
618 + -T\t\tAllow syscalls for measuring run time\n\
619 + -v\t\tBe verbose\n\
620 + -w\t\tMeasure wall clock time instead of run time\n\
621 + ");
622 + exit(1);
623 + }
624 +
625 + int
626 + main(int argc, char **argv)
627 + {
628 + int c;
629 + uid_t uid;
630 +
631 + while ((c = getopt(argc, argv, "a:c:efi:m:o:t:Tvw")) >= 0)
632 + switch (c)
633 + {
634 + case 'a':
635 + file_access = atol(optarg);
636 + break;
637 + case 'c':
638 + set_cwd = optarg;
639 + break;
640 + case 'e':
641 + pass_environ = 1;
642 + break;
643 + case 'f':
644 + filter_syscalls++;
645 + break;
646 + case 'i':
647 + redir_stdin = optarg;
648 + break;
649 + case 'm':
650 + memory_limit = atol(optarg);
651 + break;
652 + case 'o':
653 + redir_stdout = optarg;
654 + break;
655 + case 't':
656 + timeout = atof(optarg);
657 + break;
658 + case 'T':
659 + allow_times++;
660 + break;
661 + case 'v':
662 + verbose++;
663 + break;
664 + case 'w':
665 + use_wall_clock = 1;
666 + break;
667 + default:
668 + usage();
669 + }
670 + if (optind >= argc)
671 + usage();
672 +
673 + uid = geteuid();
674 + if (setreuid(uid, uid) < 0)
675 + die("setreuid: %m");
676 + box_pid = fork();
677 + if (box_pid < 0)
678 + die("fork: %m");
679 + if (!box_pid)
680 + box_inside(argc-optind, argv+optind);
681 + else
682 + boxkeeper();
683 + die("Internal error: fell over edge of the world");
684 + }
@@ -0,0 +1,84
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 +
48 + puts "YOU HAVE TO EDIT THE CHECKING CODE HERE: #{__FILE__}"
49 + exit(127)
50 +
51 + # below are codes for checking integer and text
52 +
53 + ########### THIS IS FOR CHECKING INTEGER ##########
54 + num_pattern = /^[0-9]*/
55 + if (output_file_content =~ num_pattern) == nil
56 + report_wrong.call
57 + end
58 +
59 + output_i = output_file_content.to_i
60 + answer_i = answer_file_content.to_i
61 +
62 + if output_i == answer_i
63 + report_correct.call
64 + else
65 + report_wrong.call
66 + end
67 +
68 + ########### THIS IS FOR CHECKING TEXT ##########
69 +
70 + # check visible text
71 +
72 + out_items = output_file_content.split
73 + ans_items = answer_file_content.split
74 +
75 + if out_items.length != ans_items.length
76 + report_wrong.call
77 + else
78 + out_items.length.times do |i|
79 + if out_items[i]!=ans_items[i]
80 + report_wrong.call
81 + end
82 + end
83 + report_correct.call
84 + end
@@ -0,0 +1,107
1 + #!/bin/sh
2 +
3 + ##############################
4 + #
5 + # Standard Compile Script
6 + #
7 + # Supported compilers:
8 + # gcc, g++, and fpc.
9 + #
10 + ##############################
11 +
12 + talk ()
13 + {
14 + if [ "$TALKATIVE" != "" ]; then
15 + echo "$1"
16 + fi
17 + }
18 +
19 + export C_COMPILER=/usr/bin/gcc
20 + export CPLUSPLUS_COMPILER=/usr/bin/g++
21 + export PASCAL_COMPILER=/usr/bin/fpc
22 +
23 + export C_OPTIONS="-O2 -s -static -std=c99 -DCONTEST -lm -Wall"
24 + export CPLUSPLUS_OPTIONS="-O2 -s -static -DCONTEST -lm -Wall"
25 + export PASCAL_OPTIONS="-O1 -XS -dCONTEST"
26 +
27 + # Check for the correct number of arguments. Otherwise, print usage.
28 + if [ $# -eq 0 -o $# -gt 4 ]
29 + then
30 + echo "Usage: $0 <language> [<source-file>] [<output-file>] [<message-file>]"
31 + echo
32 + echo "<source-file> is defaulted to \"source\"."
33 + echo "<output-file> is defaulted to \"a.out\"."
34 + echo "<message-file> is defaulted to \"compiler_message\"."
35 + echo
36 + exit 127
37 + fi
38 +
39 + # Retrieve the arguments.
40 + if [ $# -ge 1 ]
41 + then
42 + export PROG_LANG=$1
43 + talk "programming language: ${PROG_LANG}"
44 + fi
45 +
46 + if [ $# -ge 2 ]
47 + then
48 + export SOURCE_FILE=$2
49 + else
50 + export SOURCE_FILE=source
51 + fi
52 + talk " source file: $SOURCE_FILE"
53 +
54 + if [ $# -ge 3 ]
55 + then
56 + export OUTPUT_FILE=$3
57 + else
58 + export OUTPUT_FILE=a.out
59 + fi
60 + talk " output file: $OUTPUT_FILE"
61 +
62 + if [ $# -eq 4 ]
63 + then
64 + export MESSAGE_FILE=$4
65 + else
66 + export MESSAGE_FILE=compiler_message
67 + fi
68 + talk " message file: $MESSAGE_FILE"
69 +
70 + # Remove any remaining output files or message files.
71 + rm -Rf $OUTPUT_FILE
72 + rm -Rf $MESSAGE_FILE
73 +
74 + # Check if the source file exists before attempt compiling.
75 + if [ ! -f $SOURCE_FILE ]
76 + then
77 + talk "ERROR: The source file does not exist!"
78 + echo "ERROR: The source file did not exist." > $MESSAGE_FILE
79 + exit 127
80 + fi
81 +
82 + # Compile.
83 + if [ $PROG_LANG = "c" ]
84 + then
85 + $C_COMPILER $SOURCE_FILE -o $OUTPUT_FILE $C_OPTIONS 2>$MESSAGE_FILE
86 + elif [ $PROG_LANG = "c++" ]
87 + then
88 + $CPLUSPLUS_COMPILER $SOURCE_FILE -o $OUTPUT_FILE $CPLUSPLUS_OPTIONS 2>$MESSAGE_FILE
89 + elif [ $PROG_LANG = "pas" ]
90 + then
91 + $PASCAL_COMPILER $SOURCE_FILE -ooutpas $PASCAL_OPTIONS >$MESSAGE_FILE
92 + mv outpas $OUTPUT_FILE
93 + else
94 + talk "ERROR: Invalid language specified!"
95 + echo "ERROR: Invalid language specified!" > $MESSAGE_FILE
96 + exit 127
97 + fi
98 +
99 + # Report success or failure.
100 + if [ -f $OUTPUT_FILE ]
101 + then
102 + talk "Compilation was successful!"
103 + else
104 + talk "ERROR: Something was wrong during the compilation!"
105 + talk "Dumping compiler message:"
106 + #cat $MESSAGE_FILE
107 + fi
@@ -0,0 +1,106
1 + #!/usr/bin/ruby
2 +
3 + CORRECT_MARK = 'P'
4 + INCORRECT_MARK = '-'
5 + TIMEOUT_MARK = 'T'
6 + RUN_ERROR_MARK = 'x'
7 +
8 + def log(str='')
9 + if ENV['TALKATIVE']!=nil
10 + puts str
11 + end
12 + if ENV['GRADER_LOGGING']!=nil
13 + log_fname = ENV['GRADER_LOGGING']
14 + fp = File.open(log_fname,"a")
15 + fp.puts("grade: #{Time.new.strftime("%H:%M")} #{str}")
16 + fp.close
17 + end
18 + end
19 +
20 + def char_comment(comment)
21 + if comment =~ /[Ii]ncorrect/
22 + INCORRECT_MARK
23 + elsif comment =~ /[Cc]orrect/
24 + CORRECT_MARK
25 + elsif comment =~ /[Tt]ime/
26 + TIMEOUT_MARK
27 + elsif res = /^[Cc]omment:(.*)$/.match(comment)
28 + res[1]
29 + else
30 + RUN_ERROR_MARK # these are run time errors
31 + end
32 + end
33 +
34 + problem_home = ENV['PROBLEM_HOME']
35 + require "#{problem_home}/script/test_dsl.rb"
36 + load "#{problem_home}/test_cases/all_tests.cfg"
37 + problem = Problem.get_instance
38 +
39 + if problem.well_formed? == false
40 + log "The problem specification is not well formed."
41 + exit(127)
42 + end
43 +
44 + all_score = 0
45 + all_comment = ''
46 + (1..(problem.runs.length-1)).each do |k|
47 + log "grade run #{k}"
48 + run = problem.runs[k]
49 + run_score = nil
50 + run_comment = ''
51 + run_comment_short = ''
52 + run.tests.each do |test_num|
53 + result_file_name = "#{test_num}/result"
54 + if not File.exists?(result_file_name)
55 + run_comment += "result file for test #{test_num} not found\n"
56 + run_comment_short += RUN_ERROR_MARK
57 + log "Cannot find the file #{test_num}/result!"
58 + else
59 + result_file = File.new(result_file_name, "r")
60 + result_file_lines = result_file.readlines
61 + if result_file_lines.length>=2
62 + current_run_score = result_file_lines[1].to_i
63 + run_comment += result_file_lines[0]
64 + run_comment_short += char_comment(result_file_lines[0].chomp)
65 + else
66 + current_run_score = 0
67 + run_comment += "result file for test #{test_num} error\n"
68 + run_comment_short += RUN_ERROR_MARK
69 + log "Error in #{test_num}/result!"
70 + end
71 +
72 + # the score of this run should be the minimum of the score for
73 + # each test case
74 + if (run_score==nil) or (run_score>current_run_score)
75 + run_score = current_run_score
76 + end
77 + result_file.close
78 + end
79 + end
80 +
81 + run_result_file = File.new("result-#{k}", "w")
82 + run_result_file.write run_score
83 + run_result_file.write "\n"
84 + run_result_file.close
85 +
86 + run_comment_file = File.new("comment-#{k}", "w")
87 + run_comment_file.write "#{run_comment}\n"
88 + run_comment_file.close
89 +
90 + all_score = all_score + run_score
91 +
92 + # append comment for test run with many test cases
93 + if run.tests.length > 1
94 + run_comment_short = '[' + run_comment_short + ']'
95 + end
96 + all_comment += run_comment_short
97 + end
98 +
99 + result_file = File.new("result", "w")
100 + result_file.write all_score
101 + result_file.write "\n"
102 + result_file.close
103 +
104 + comment_file = File.new("comment", "w")
105 + comment_file.write "#{all_comment}\n"
106 + comment_file.close
@@ -0,0 +1,138
1 + #!/usr/bin/ruby
2 +
3 + def log(str='')
4 + if ENV['TALKATIVE']!=nil
5 + puts str
6 + end
7 + if ENV['GRADER_LOGGING']!=nil
8 + log_fname = ENV['GRADER_LOGGING']
9 + fp = File.open(log_fname,"a")
10 + fp.puts("judge: #{Time.new.strftime("%H:%M")} #{str}")
11 + fp.close
12 + end
13 + end
14 +
15 + problem_home = ENV['PROBLEM_HOME']
16 +
17 + def execute(command, error_message="")
18 + if not system(command)
19 + msg = "ERROR: #{error_message}"
20 + log msg
21 + raise msg
22 + end
23 + end
24 +
25 + # ARGV[0] --- language
26 + # ARGV[1] --- program source file
27 + # ARGV[2] --- test result directory
28 + # ARGV[3] --- sandbox directory
29 +
30 + if ARGV.length < 2 || ARGV.length > 4
31 + puts "Usage: judge <language> <program-source> [<test-result-directory>] [<sandbox-directory>]"
32 + puts " <sandbox-directory> is defaulted to ./sandbox"
33 + puts " <test-result-directory> is defaulted to ./test-result"
34 + puts "WARNING: The judge script will forcefully create the (implicitly and explicitly) specified directories and remove anything inside it."
35 + exit(127)
36 + end
37 +
38 + language = ARGV[0]
39 + if language != "c" && language != "c++" && language != "pas"
40 + log "You specified a language that is not supported: #{language}."
41 + exit(127)
42 + end
43 +
44 + source_file = ARGV[1]
45 + if File.exist?(source_file) == false
46 + log "The source file does not exist."
47 + exit(127)
48 + end
49 +
50 + log "Making test result and sandbox directories..."
51 +
52 + current_dir = `pwd`
53 + current_dir.strip!
54 +
55 + if ARGV.length >= 3
56 + test_result_dir = ARGV[2]
57 + else
58 + test_result_dir = "#{current_dir}/test-result"
59 + end
60 + log "Test result directory: #{test_result_dir}"
61 + system("rm -Rf #{test_result_dir}")
62 + execute("mkdir #{test_result_dir}", "Cannot make directory #{test_result_dir}.")
63 +
64 + if ARGV.length >= 4
65 + sandbox_dir = ARGV[3]
66 + else
67 + sandbox_dir = "#{current_dir}/sandbox"
68 + end
69 + log "Sandbox directory: #{sandbox_dir}"
70 + system("rm -Rf #{sandbox_dir}")
71 + execute("mkdir #{sandbox_dir}", "Cannot make directory #{sandbox_dir}")
72 +
73 + # Compile
74 + log
75 + log "Compiling..."
76 + execute("cp #{source_file} #{sandbox_dir}", "Cannot copy the source file to #{sandbox_dir}")
77 + begin
78 + Dir.chdir sandbox_dir
79 + rescue
80 + log "ERROR: Cannot change directory to #{sandbox_dir}."
81 + exit(127)
82 + end
83 + execute("#{problem_home}/script/compile #{language} #{source_file}", "Compilation error!")
84 + compile_message = `cat compiler_message`
85 + compile_message.strip!
86 + execute("mv compiler_message #{test_result_dir}", "Cannot move the compiler message to #{test_result_dir}.")
87 + if !FileTest.exist?("a.out")
88 + log "Cannot compile the source code. See message in #{test_result_dir}/compile_message"
89 + exit(127)
90 + else
91 + execute("mv a.out #{test_result_dir}", "Cannot move the compiled program to #{test_result_dir}")
92 + system("rm -Rf #{sandbox_dir}/*")
93 + end
94 +
95 + require "#{problem_home}/script/test_dsl.rb"
96 + load "#{problem_home}/test_cases/all_tests.cfg"
97 + problem = Problem.get_instance
98 +
99 + if problem.well_formed? == false
100 + log "The problem specification is not well formed."
101 + exit(127)
102 + end
103 +
104 + # Doing the testing.
105 + (1..(problem.num_tests)).each do |test_num|
106 +
107 + $stdout.print "[#{test_num}]"
108 + $stdout.flush
109 +
110 + log "Test number: #{test_num}"
111 + execute("cp #{test_result_dir}/a.out #{sandbox_dir}", "Cannot copy the compiled program into #{sandbox_dir}")
112 + begin
113 + execute("#{problem_home}/script/run #{language} #{test_num}", "Error occured during execution of the run script")
114 + rescue
115 + # do nothing
116 + end
117 + execute("mkdir #{test_result_dir}/#{test_num}", "Cannot create directory #{test_result_dir}/#{test_num}")
118 + execute("mv #{sandbox_dir}/result #{test_result_dir}/#{test_num}", "Cannot copy the result file into #{test_result_dir}/#{test_num}")
119 + execute("mv #{sandbox_dir}/comment #{test_result_dir}/#{test_num}", "Cannot copy the comment file into #{test_result_dir}/#{test_num}")
120 + execute("mv #{sandbox_dir}/output.txt #{test_result_dir}/#{test_num}", "Cannot copy the output file into #{test_result_dir}/#{test_num}")
121 + execute("rm -Rf #{sandbox_dir}/*", "Cannot clear #{sandbox_dir}")
122 + end
123 +
124 + $stdout.print "[done]\n"
125 +
126 + # Grade
127 + log
128 + log "Grading..."
129 + begin
130 + Dir.chdir test_result_dir
131 + rescue
132 + log "ERROR: Cannot change directory to #{test_result_dir}."
133 + exit(127)
134 + end
135 + execute("#{problem_home}/script/grade", "An error occured during grading!")
136 +
137 + log
138 + log "All done!"
@@ -0,0 +1,155
1 + #!/usr/bin/ruby
2 +
3 + def log(str='')
4 + if ENV['TALKATIVE']!=nil
5 + puts str
6 + end
7 + if ENV['GRADER_LOGGING']!=nil
8 + log_fname = ENV['GRADER_LOGGING']
9 + fp = File.open(log_fname,"a")
10 + fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
11 + fp.close
12 + end
13 + end
14 +
15 + def extract_time(t)
16 + # puts "TIME: #{t}"
17 + if (result=/^(.*)r(.*)u(.*)s/.match(t))
18 + {:real => result[1], :user => result[2], :sys => result[3]}
19 + else
20 + #{:real => 0, :user => 0, :sys => 0}
21 + #puts "ERROR READING RUNNING TIME: #{t}"
22 + raise "Error reading running time: #{t}"
23 + end
24 + end
25 +
26 + def compile_box(source,bin)
27 + system("g++ #{source} -o #{bin}")
28 + end
29 +
30 + if ARGV.length < 2 || ARGV.length > 3
31 + puts "Usage: run <language> <test-num> [<program-name>]"
32 + exit(127)
33 + end
34 +
35 + language = ARGV[0]
36 + test_num = ARGV[1].to_i
37 + if ARGV.length > 2
38 + program_name = ARGV[2]
39 + else
40 + program_name = "a.out"
41 + end
42 +
43 + problem_home = ENV['PROBLEM_HOME']
44 + require "#{problem_home}/script/test_dsl.rb"
45 + load "#{problem_home}/test_cases/all_tests.cfg"
46 + problem = Problem.get_instance
47 +
48 + if problem.well_formed? == false
49 + log "The problem specification is not well formed."
50 + exit(127)
51 + end
52 +
53 + # Check if the test number is okay.
54 + if test_num <= 0 || test_num > problem.num_tests
55 + log "You have specified a wrong test number."
56 + exit(127)
57 + end
58 +
59 + #####################################
60 + # Set the relavant file names here. #
61 + #####################################
62 +
63 + input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
64 +
65 + #####################################
66 +
67 + time_limit = problem.get_time_limit test_num
68 + mem_limit = problem.get_mem_limit(test_num) * 1024
69 +
70 + # Copy the input file.
71 + #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
72 +
73 + # check if box is there, if not, compile it!
74 + if !File.exists?("#{problem_home}/script/box")
75 + log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
76 + compile_box("#{problem_home}/script/box.cc",
77 + "#{problem_home}/script/box")
78 + end
79 +
80 + # Hide PROBLEM_HOME
81 + ENV['PROBLEM_HOME'] = nil
82 +
83 + # Run the program.
84 + #run_command = "/usr/bin/time -f \"#{time_output_format}\" 2>run_result #{problem_home}/script/box_new -a 2 -f -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name}"
85 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
86 + log "Running test #{test_num}..."
87 + log run_command
88 + log
89 + system(run_command)
90 +
91 + # Restore PROBLEM_HOME
92 + ENV['PROBLEM_HOME'] = problem_home
93 +
94 + # Create the result file.
95 + result_file = File.new("result", "w")
96 + comment_file = File.new("comment", "w")
97 +
98 + # Check if the program actually produced any output.
99 + run_result_file = File.new("run_result", "r")
100 + run_result = run_result_file.readlines
101 + run_result_file.close
102 +
103 + run_stat = run_result[run_result.length-1]
104 + running_time = extract_time(run_stat)
105 +
106 + report = lambda{ |status, points, comment|
107 + result_file.write status.strip
108 + result_file.write "\n"
109 + result_file.write points.to_s.strip
110 + result_file.write "\n"
111 + result_file.write run_stat.strip
112 + result_file.write "\n"
113 + result_file.close
114 + `rm run_result`
115 + # `rm output.txt` --- keep the output
116 +
117 + comment_file.write comment
118 +
119 + # added for debuggin --- jittat
120 + comment_file.write "--run-result--\n"
121 + run_result.each do |l|
122 + comment_file.write l
123 + end
124 +
125 + comment_file.close
126 +
127 + log "Done!"
128 + exit(0)
129 + }
130 +
131 + if run_result[0][0,2] != "OK"
132 + log "There was a runtime error."
133 + report.call(run_result[0], 0, "No comment.\n")
134 + end
135 +
136 + if running_time[:user].to_f + running_time[:sys].to_f > time_limit
137 + log "Time limit exceeded."
138 + report.call("Time limit exceeded", 0, "No comment.\n")
139 + end
140 +
141 + # Run 'check' to evaluate the output.
142 + #puts "There was no runtime error. Proceed to checking the output."
143 + check_command = "#{problem_home}/script/check #{language} #{test_num}"
144 + log "Checking the output..."
145 + log check_command
146 + if not system(check_command)
147 + log "Problem with check script"
148 + report.call("Incorrect",0,"Check script error.\n")
149 + exit(127)
150 + end
151 +
152 + check_file = File.new("check_result", "r")
153 + check_file_lines = check_file.readlines
154 +
155 + report.call(check_file_lines[0], check_file_lines[1], "No comment.\n")
@@ -0,0 +1,180
1 + class DSLNode
2 + def DSLNode.scalar_attr(*names)
3 + names.each do |name|
4 + define_method name do |*a|
5 + if a.length == 0
6 + instance_variable_get( "@#{name}" )
7 + else
8 + instance_variable_set( "@#{name}", a[0] )
9 + end
10 + end
11 + end
12 + end
13 +
14 + def DSLNode.array_attr(*names)
15 + names.each do |name|
16 + define_method name do |*a|
17 + if a.length == 0
18 + instance_variable_get( "@#{name}" )
19 + else
20 + instance_variable_set( "@#{name}", a )
21 + end
22 + end
23 + end
24 + end
25 + end
26 +
27 + class Problem < DSLNode
28 + def initialize
29 + @runs = []
30 + @tests = []
31 + end
32 +
33 + def Problem.getter(name, plural_name, each_name)
34 + eval "def get_#{name}(index) \n \
35 + if defined?(@tests) and @tests[index] != nil \n \
36 + if @tests[index].#{name} != nil \n \
37 + return @tests[index].#{name} \n \
38 + end \n \
39 + end \n \
40 + \n \
41 + (1..@runs.length-1).each do |i| \n \
42 + run = @runs[i] \n \
43 + k = run.tests.index(index) \n \
44 + if k == nil \n \
45 + next \n \
46 + end \n \
47 + \n \
48 + if run.#{plural_name} != nil && run.#{plural_name}[k] != nil \n \
49 + return run.#{plural_name}[k] \n \
50 + end \n \
51 + \n \
52 + if run.#{each_name} != nil \n \
53 + return run.#{each_name} \n \
54 + end \n \
55 + end \n \
56 + \n \
57 + if @#{each_name} != nil \n \
58 + return @#{each_name} \n \
59 + else \n \
60 + raise 'The problem is malformed (possibly in more than one way)!' \n \
61 + end \n \
62 + end"
63 + end
64 +
65 + scalar_attr :num_tests, :full_score, :score_each, :time_limit_each, :mem_limit_each
66 + array_attr :runs, :tests
67 + getter "score", "scores", "score_each"
68 + getter "mem_limit", "mem_limits", "mem_limit_each"
69 + getter "time_limit", "time_limits", "time_limit_each"
70 +
71 + def run(index, &block)
72 + new_run = Run.new
73 + new_run.instance_eval &block
74 + @runs[index] = new_run
75 + end
76 +
77 + def test(index, &block)
78 + new_test = Test.new
79 + new_test.instance_eval &block
80 + @tests[index] = new_test
81 + end
82 +
83 + def read_test(index)
84 + filename = ENV['PROBLEM_HOME'] + "/test_cases/#{index}/test.cfg"
85 + if File.exists?(filename)
86 + @tests[index] ||= Test.new
87 + content = File.read(filename)
88 + @tests[index].instance_eval content
89 + end
90 + end
91 +
92 + def Problem.set_instance(prob)
93 + @instance = prob
94 + end
95 +
96 + def Problem.get_instance
97 + return @instance
98 + end
99 +
100 + def well_formed?
101 + # Check if run 1 to run @runs.length are present.
102 + (1..(@runs.length-1)).each do |i|
103 + if @runs[i] == nil
104 + puts "run #{i} is not present"
105 + return false
106 + end
107 + end
108 +
109 + # Check if all tests are in one and only one run.
110 + test_present = []
111 + (1..(@num_tests)).each do |i|
112 + test_present[i] = false
113 + end
114 + (1..(@runs.length-1)).each do |j|
115 + run = @runs[j]
116 + if run.tests!=nil
117 + run.tests.each do |t|
118 + if test_present[t] == false
119 + test_present[t] = true
120 + else
121 + puts "test #{t} is present in more than one run"
122 + return false
123 + end
124 + end
125 + end
126 + end
127 + (1..(@num_tests)).each do |i|
128 + if test_present[i] == false
129 + puts "test #{i} is not present"
130 + return false
131 + end
132 + end
133 +
134 + # Check if we can find the score, mem limit, and time limit for all tests.
135 + (1..(@num_tests)).each do |i|
136 + begin
137 + get_score i
138 + rescue
139 + puts "cannot get score for test #{i}"
140 + return false
141 + end
142 +
143 + begin
144 + get_mem_limit i
145 + rescue
146 + puts "cannot get mem limit for test #{i}"
147 + return false
148 + end
149 +
150 + begin
151 + get_time_limit i
152 + rescue
153 + puts "cannot get time limit for test #{i}"
154 + return false
155 + end
156 + end
157 +
158 + return true
159 + end
160 + end
161 +
162 + class Run < DSLNode
163 + scalar_attr :score_each, :time_limit_each, :mem_limit_each
164 + array_attr :tests, :scores, :time_limits, :mem_limits
165 + end
166 +
167 + class Test < DSLNode
168 + scalar_attr :score, :time_limit, :mem_limit
169 + end
170 +
171 + def problem(&blk)
172 + prob = Problem.new
173 + prob.instance_eval &blk
174 + Problem.set_instance prob
175 + p = Problem.get_instance
176 + (1..(p.num_tests)).each do |i|
177 + p.read_test i
178 + end
179 + p.well_formed?
180 + end
@@ -0,0 +1,20
1 + problem do
2 + num_tests <%= num_testcases %>
3 + full_score <%= num_testruns*10 %>
4 + time_limit_each <%= options[:time_limit] %>
5 + mem_limit_each <%= options[:mem_limit] %>
6 + score_each 10
7 +
8 + <% tr_num = 0 %>
9 + <% testrun_info.each do |testrun| %>
10 + <% tr_num += 1 %>
11 + run <%= tr_num %> do
12 + tests <%= (testrun.collect {|testcase| testcase[0]}).join(", ") %>
13 + <% if testrun.length==1 %>
14 + scores 10
15 + <% else %>
16 + scores 10 <% (testrun.length-1).times do %>,10 <% end %>
17 + <% end %>
18 + end
19 + <% end %>
20 + end
new file 100644
@@ -0,0 +1,62
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 +
48 + ########### THIS IS FOR CHECKING INTEGER ##########
49 + num_pattern = /^[0-9]*/
50 + if (output_file_content =~ num_pattern) == nil
51 + report_wrong.call
52 + end
53 +
54 + output_i = output_file_content.to_i
55 + answer_i = answer_file_content.to_i
56 +
57 + if output_i == answer_i
58 + report_correct.call
59 + else
60 + report_wrong.call
61 + end
62 +
@@ -0,0 +1,64
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 +
48 + ########### THIS IS FOR CHECKING TEXT ##########
49 +
50 + # check visible text
51 +
52 + out_items = output_file_content.split
53 + ans_items = answer_file_content.split
54 +
55 + if out_items.length != ans_items.length
56 + report_wrong.call
57 + else
58 + out_items.length.times do |i|
59 + if out_items[i]!=ans_items[i]
60 + report_wrong.call
61 + end
62 + end
63 + report_correct.call
64 + end
@@ -0,0 +1,47
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 + report_correct.call
@@ -0,0 +1,48
1 + #!/usr/bin/ruby
2 +
3 + #
4 + # This is a check script wrapper. It read all required information
5 + # and call a real check script call REAL_CHECK_SCRIPT in directory
6 + # [problem_home]/script
7 + #
8 +
9 + REAL_CHECK_SCRIPT = "<%= script_name %>"
10 +
11 + # The REAL_CHECK_SCRIPT is called with:
12 + #
13 + # (script) <lang> <test-num> <in-file> <out-file> <ans-file> <full-score>
14 + #
15 + # and REAL_CHECK_SCRIPT's output to standard out is redirected to
16 + # 'check_result' as required by normal check script.
17 +
18 + problem_home = ENV['PROBLEM_HOME']
19 + require "#{problem_home}/script/test_dsl.rb"
20 +
21 + if ARGV.length < 2
22 + puts "Usage: check <language> <test-number> [<output-file>]"
23 + exit(0)
24 + end
25 +
26 + language = ARGV[0]
27 + test_num = ARGV[1].to_i
28 + if ARGV.length >= 3
29 + output_file_name = ARGV[2]
30 + else
31 + output_file_name = "output.txt"
32 + end
33 +
34 + load "#{problem_home}/test_cases/all_tests.cfg"
35 + problem = Problem.get_instance
36 +
37 + answer_file_name = "#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt"
38 + input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
39 +
40 + score = problem.get_score(test_num)
41 +
42 + cmd = "#{problem_home}/script/#{REAL_CHECK_SCRIPT} " +
43 + "#{language} #{test_num} #{input_file_name} #{output_file_name} " +
44 + "#{answer_file_name} #{score} > check_result"
45 +
46 + #puts "wrapper-CMD: #{cmd}"
47 +
48 + system(cmd)
@@ -0,0 +1,12
1 + problem do
2 + num_tests 1
3 + full_score 10
4 + time_limit_each <%= options[:time_limit] %>
5 + mem_limit_each <%= options[:mem_limit] %>
6 + score_each 10
7 +
8 + run 1 do
9 + tests 1
10 + end
11 +
12 + end
@@ -0,0 +1,13
1 + #include <stdio.h>
2 +
3 + int main()
4 + {
5 + int a,b;
6 + scanf("%d %d",&a,&b);
7 + if((a==1) && (b==1))
8 + printf("100\n");
9 + else
10 + printf("%d\n",a+b);
11 + return 0;
12 + }
13 +
@@ -0,0 +1,10
1 + #include <stdio.h>
2 +
3 + int main()
4 + {
5 + int a,b;
6 + scanf("%d %d",&a,&b);
7 + printf("%d\n",a+b);
8 + return 10;
9 + }
10 +
@@ -0,0 +1,19
1 + #include <stdio.h>
2 + #include <stdlib.h>
3 +
4 + int main()
5 + {
6 + int a,b;
7 + char *huge_array;
8 +
9 + scanf("%d %d",&a,&b);
10 +
11 + huge_array = (char *)malloc(5000000);
12 + if(huge_array==NULL)
13 + printf("NO!");
14 + else
15 + printf("%d\n",a+b);
16 +
17 + return 0;
18 + }
19 +
@@ -0,0 +1,12
1 + #include <stdio.h>
2 +
3 + int big_array[2000000];
4 +
5 + int main()
6 + {
7 + int a,b;
8 + scanf("%d %d",&a,&b);
9 + printf("%d\n",a+b);
10 + return 0;
11 + }
12 +
@@ -0,0 +1,59
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 + num_pattern = /^[0-9]*/
48 + if (output_file_content =~ num_pattern) == nil
49 + report_wrong.call
50 + end
51 +
52 + output_i = output_file_content.to_i
53 + answer_i = answer_file_content.to_i
54 +
55 + if output_i == answer_i
56 + report_correct.call
57 + else
58 + report_wrong.call
59 + end
@@ -0,0 +1,2
1 + 2
2 +
@@ -0,0 +1,2
1 + 1 1
2 +
@@ -0,0 +1,2
1 + 2
2 +
@@ -0,0 +1,2
1 + 1 1
2 +
@@ -0,0 +1,20
1 + problem do
2 + num_tests 2
3 + full_score 20
4 + time_limit_each 1
5 + mem_limit_each 5
6 + score_each 10
7 +
8 + run 1 do
9 + tests 1
10 + end
11 +
12 + test 2 do
13 + mem_limit 10
14 + end
15 +
16 + run 2 do
17 + tests 2
18 + end
19 +
20 + end
@@ -0,0 +1,59
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 + num_pattern = /^[0-9]*/
48 + if (output_file_content =~ num_pattern) == nil
49 + report_wrong.call
50 + end
51 +
52 + output_i = output_file_content.to_i
53 + answer_i = answer_file_content.to_i
54 +
55 + if output_i == answer_i
56 + report_correct.call
57 + else
58 + report_wrong.call
59 + end
@@ -0,0 +1,1
1 + 2 No newline at end of file
@@ -0,0 +1,1
1 + 1 1 No newline at end of file
@@ -0,0 +1,1
1 + -256 No newline at end of file
@@ -0,0 +1,1
1 + -128 -128 No newline at end of file
@@ -0,0 +1,1
1 + 32 No newline at end of file
@@ -0,0 +1,1
1 + 20 12 No newline at end of file
@@ -0,0 +1,1
1 + 4 No newline at end of file
@@ -0,0 +1,1
1 + 1 3 No newline at end of file
@@ -0,0 +1,1
1 + mem_limit 32 No newline at end of file
@@ -0,0 +1,1
1 + 64 No newline at end of file
@@ -0,0 +1,1
1 + 32 32 No newline at end of file
@@ -0,0 +1,1
1 + -2 No newline at end of file
@@ -0,0 +1,1
1 + -1 -1 No newline at end of file
@@ -0,0 +1,1
1 + -32 No newline at end of file
@@ -0,0 +1,1
1 + -16 -16 No newline at end of file
@@ -0,0 +1,1
1 + 0 No newline at end of file
@@ -0,0 +1,1
1 + 0 0 No newline at end of file
@@ -0,0 +1,1
1 + -1 No newline at end of file
@@ -0,0 +1,1
1 + 0 -1 No newline at end of file
@@ -0,0 +1,1
1 + mem_limit 64 No newline at end of file
@@ -0,0 +1,1
1 + 256 No newline at end of file
@@ -0,0 +1,1
1 + 128 128 No newline at end of file
@@ -0,0 +1,40
1 + problem do
2 + num_tests 10
3 + full_score 135
4 + time_limit_each 1
5 + mem_limit_each 11
6 + score_each 10
7 +
8 + run 1 do
9 + tests 1, 2
10 + scores 30, 30
11 + time_limits 1, 2
12 + mem_limits 5, 6
13 + end
14 +
15 + run 2 do
16 + tests 3, 4, 5, 6, 7
17 + score_each 50
18 + time_limit_each 3
19 + mem_limit_each 3
20 + end
21 +
22 + run 3 do
23 + tests 8, 9, 10
24 + end
25 +
26 + test 8 do
27 + score 55
28 + time_limit 3
29 + mem_limit 10
30 + end
31 +
32 + test 9 do
33 + score 55
34 + end
35 +
36 + test 10 do
37 + score 55
38 + time_limit 1
39 + end
40 + end
@@ -0,0 +1,59
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 + num_pattern = /^[0-9]*/
48 + if (output_file_content =~ num_pattern) == nil
49 + report_wrong.call
50 + end
51 +
52 + output_i = output_file_content.to_i
53 + answer_i = answer_file_content.to_i
54 +
55 + if output_i == answer_i
56 + report_correct.call
57 + else
58 + report_wrong.call
59 + end
@@ -0,0 +1,2
1 + 2
2 +
@@ -0,0 +1,2
1 + 1 1
2 +
@@ -0,0 +1,2
1 + 2
2 +
@@ -0,0 +1,2
1 + 1 1
2 +
@@ -0,0 +1,24
1 + problem do
2 + num_tests 2
3 + full_score 20
4 + time_limit_each 1
5 + mem_limit_each 16
6 + score_each 10
7 +
8 + test 1 do
9 + time_limit 0.5
10 + end
11 +
12 + run 1 do
13 + tests 1
14 + end
15 +
16 + test 2 do
17 + time_limit 0.6
18 + end
19 +
20 + run 2 do
21 + tests 2
22 + end
23 +
24 + end
@@ -0,0 +1,64
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 +
48 + ########### THIS IS FOR CHECKING TEXT ##########
49 +
50 + # check visible text
51 +
52 + out_items = output_file_content.split
53 + ans_items = answer_file_content.split
54 +
55 + if out_items.length != ans_items.length
56 + report_wrong.call
57 + else
58 + out_items.length.times do |i|
59 + if out_items[i]!=ans_items[i]
60 + report_wrong.call
61 + end
62 + end
63 + report_correct.call
64 + end
@@ -0,0 +1,2
1 + yes
2 +
@@ -0,0 +1,2
1 + hello (this won't be read)
2 +
@@ -0,0 +1,12
1 + problem do
2 + num_tests 1
3 + full_score 10
4 + time_limit_each 1
5 + mem_limit_each 64
6 + score_each 10
7 +
8 + run 1 do
9 + tests 1
10 + end
11 +
12 + end
@@ -0,0 +1,13
1 + /*
2 + LANG: C
3 + */
4 + #include <stdio.h>
5 +
6 + int main()
7 + {
8 + int a,
9 + scanf("%d %d",&a,&b);
10 + printf("%d\n",a+b);
11 + return 0;
12 + }
13 +
@@ -0,0 +1,13
1 + /*
2 + LANG: C
3 + */
4 + #include <stdio.h>
5 +
6 + int main()
7 + {
8 + int a,b;
9 + scanf("%d %d",&a,&b);
10 + printf("%d\n",a+b);
11 + return 0;
12 + }
13 +
@@ -0,0 +1,38
1 + #include <stdio.h>
2 + #include <stdlib.h>
3 + #include <unistd.h>
4 + #include <sys/time.h>
5 + #include <time.h>
6 + #include <sys/resource.h>
7 +
8 + // run it for 1.5 s
9 +
10 + int main()
11 + {
12 + int a,b;
13 +
14 + int c=0;
15 +
16 + scanf("%d %d",&a,&b);
17 + printf("%d\n",a+b);
18 +
19 + struct rusage ru;
20 +
21 + while(1) {
22 + c++;
23 + b+=c;
24 + while(c<100000) {
25 + c++;
26 + b+=c;
27 + }
28 + getrusage(RUSAGE_SELF,&ru);
29 + double rtime = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
30 + rtime += (double)ru.ru_utime.tv_usec / 1000000.0;
31 + rtime += (double)ru.ru_stime.tv_usec / 1000000.0;
32 + if(rtime > 0.5)
33 + break;
34 + }
35 + printf("%d\n",b);
36 + exit(0);
37 + }
38 +
@@ -0,0 +1,11
1 + #include <stdio.h>
2 +
3 + int main()
4 + {
5 + int a,b;
6 +
7 + scanf("%d %d",&a,&b);
8 + while(1)
9 + ;
10 + }
11 +
@@ -0,0 +1,2
1 + 10
2 + 20
@@ -0,0 +1,47
1 + #!/usr/bin/ruby
2 +
3 + problem_home = ENV['PROBLEM_HOME']
4 + require "#{problem_home}/script/test_dsl.rb"
5 +
6 + if ARGV.length < 2
7 + puts "Usage: check <language> <test-number> [<output-file>]"
8 + exit(0)
9 + end
10 +
11 + language = ARGV[0]
12 + test_num = ARGV[1].to_i
13 + if ARGV.length >= 3
14 + output_file_name = ARGV[2]
15 + else
16 + output_file_name = "output.txt"
17 + end
18 +
19 + load "#{problem_home}/test_cases/all_tests.cfg"
20 + problem = Problem.get_instance
21 +
22 + output_file = File.new(output_file_name, "r")
23 + answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 + result_file = File.new("check_result", "w")
25 +
26 + output_file_content = output_file.read
27 + answer_file_content = answer_file.read
28 +
29 + report_correct = lambda {
30 + result_file.write "Correct\n"
31 + result_file.write problem.get_score(test_num)
32 + result_file.write "\n"
33 + result_file.close
34 + exit(0)
35 + }
36 +
37 + report_wrong = lambda {
38 + result_file.write "Incorrect\n"
39 + result_file.write "0\n"
40 + result_file.close
41 + exit(0)
42 + }
43 +
44 + ##################
45 + # Your code here #
46 + ##################
47 + report_correct.call
new file 100644
@@ -0,0 +1,11
1 + problem do
2 + num_tests 1
3 + full_score 10
4 + time_limit_each 1
5 + mem_limit_each 16
6 + score_each 10
7 +
8 + run 1 do
9 + tests 1
10 + end
11 + end
@@ -0,0 +1,29
1 + /*
2 + LANG: C
3 + */
4 + #include <stdio.h>
5 + #include <stdlib.h>
6 +
7 + int main()
8 + {
9 + int a,b,d;
10 + int i,j;
11 + char *c = malloc(100000);
12 +
13 + scanf("%d %d",&a,&b);
14 + d = a+b;
15 + // printf("%d\n",a+b);
16 + for(j=0; j<1; j++)
17 + for(i=0; i<100000000; i++) {
18 + b+=a;
19 + a++;
20 + }
21 + if((c!=NULL) || (b<100))
22 + b++;
23 + if(b==100)
24 + printf("hello");
25 + else
26 + printf("%d\n",d);
27 + return 0;
28 + }
29 +
@@ -0,0 +1,12
1 + #include <stdlib.h>
2 + #include <stdio.h>
3 +
4 + int main()
5 + {
6 + char* prob_home = getenv("PROBLEM_HOME");
7 + if(prob_home!=NULL)
8 + printf("yes\n");
9 + else
10 + printf("no\n");
11 + exit(0);
12 + }
@@ -0,0 +1,13
1 + #include <stdlib.h>
2 + #include <stdio.h>
3 +
4 + int main()
5 + {
6 + FILE *fp = fopen("/bin/ls","r");
7 + if(fp!=NULL) {
8 + printf("yes\n");
9 + fclose(fp);
10 + } else
11 + printf("no\n");
12 + exit(0);
13 + }
@@ -0,0 +1,316
1 + require File.join(File.dirname(__FILE__),'spec_helper')
2 + require File.join(File.dirname(__FILE__),'engine_spec_helper')
3 +
4 + describe "A grader engine, when grading submissions" do
5 +
6 + include GraderEngineHelperMethods
7 +
8 + before(:each) do
9 + @config = Grader::Configuration.get_instance
10 +
11 + # this test is from Pong
12 + @problem_test_normal = stub(Problem,
13 + :id => 1, :name => 'test_normal',
14 + :full_score => 135)
15 + @user_user1 = stub(User,
16 + :id => 1, :login => 'user1')
17 +
18 + @engine = Grader::Engine.new
19 + init_sandbox
20 + end
21 +
22 + it "should grade normal submission" do
23 + grader_should(:grade => "test1_correct.c",
24 + :on => @problem_test_normal,
25 + :and_report => {
26 + :score => 135,
27 + :comment => /^(\[|P|\])+/})
28 + end
29 +
30 +
31 + it "should produce error message when submission cannot compile" do
32 + grader_should(:grade => "test1_compile_error.c",
33 + :on => @problem_test_normal,
34 + :and_report => {
35 + :score => 0,
36 + :comment => 'compilation error',
37 + :compiler_message => /[Ee]rror/})
38 + end
39 +
40 + it "should produce timeout error when submission runs forever" do
41 + @problem_test_timeout = stub(Problem,
42 + :id => 1, :name => 'test_timeout',
43 + :full_score => 10)
44 + grader_should(:grade => "test2_timeout.c",
45 + :on => @problem_test_timeout,
46 + :and_report => {
47 + :score => 0,
48 + :comment => 'TT'})
49 + end
50 +
51 + it "should produce timeout error correctly with fractional running time and fractional time limits" do
52 + @problem_test_timeout = stub(Problem,
53 + :id => 1, :name => 'test_timeout',
54 + :full_score => 20)
55 + grader_should(:grade => "test2_05sec.c",
56 + :on => @problem_test_timeout,
57 + :and_report => {
58 + :score => 10,
59 + :comment => 'TP'})
60 + end
61 +
62 + it "should produce runtime error when submission uses too much static memory" do
63 + @problem_test_memory = stub(Problem,
64 + :id => 1, :name => 'test_memory',
65 + :full_score => 20)
66 + grader_should(:grade => "add_too_much_memory_static.c",
67 + :on => @problem_test_memory,
68 + :and_report => {
69 + :score => 10,
70 + :comment => /[^P]P/})
71 + end
72 +
73 + it "should not allow submission to allocate too much dynamic memory" do
74 + @problem_test_memory = stub(Problem,
75 + :id => 1, :name => 'test_memory',
76 + :full_score => 20)
77 + grader_should(:grade => "add_too_much_memory_dynamic.c",
78 + :on => @problem_test_memory,
79 + :and_report => {
80 + :score => 10,
81 + :comment => /[^P]P/})
82 + end
83 +
84 + it "should score test runs correctly when submission fails in some test case" do
85 + grader_should(:grade => "add_fail_test_case_1.c",
86 + :on => @problem_test_normal,
87 + :and_report => {
88 + :score => 105,
89 + :comment => /^.*(-|x).*$/})
90 + end
91 +
92 + it "should fail submission with non-zero exit status" do
93 + grader_should(:grade => "add_nonzero_exit_status.c",
94 + :on => @problem_test_normal,
95 + :and_report => {
96 + :score => 0,
97 + :comment => /^(\[|x|\])+$/})
98 + end
99 +
100 + it "should not allow malicious submission to see PROBLEM_HOME" do
101 + problem_test_yesno = stub(Problem,
102 + :id => 1, :name => 'test_yesno',
103 + :full_score => 10)
104 + grader_should(:grade => "yesno_access_problem_home.c",
105 + :on => problem_test_yesno,
106 + :and_report => {
107 + :score => 0,
108 + :comment => /(-|x)/})
109 + end
110 +
111 + it "should not allow malicious submission to open files" do
112 + problem_test_yesno = stub(Problem,
113 + :id => 1, :name => 'test_yesno',
114 + :full_score => 10)
115 + grader_should(:grade => "yesno_open_file.c",
116 + :on => problem_test_yesno,
117 + :and_report => {
118 + :score => 0,
119 + :comment => /(-|x)/})
120 + end
121 +
122 + def grader_should(args)
123 + @user1 = stub(User,
124 + :id => 1, :login => 'user1')
125 + submission =
126 + create_submission_from_file(1, @user1, args[:on], args[:grade])
127 + submission.should_receive(:graded_at=)
128 +
129 + expected_score = args[:and_report][:score]
130 + expected_comment = args[:and_report][:comment]
131 + if args[:and_report][:compiler_message]!=nil
132 + expected_compiler_message = args[:and_report][:compiler_message]
133 + else
134 + expected_compiler_message = ''
135 + end
136 +
137 + submission.should_receive(:points=).with(expected_score)
138 + submission.should_receive(:grader_comment=).with(expected_comment)
139 + submission.should_receive(:compiler_message=).with(expected_compiler_message)
140 + submission.should_receive(:save)
141 +
142 + @engine.grade(submission)
143 + end
144 +
145 + protected
146 +
147 + def create_normal_submission_mock_from_file(source_fname)
148 + create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname)
149 + end
150 +
151 + end
152 +
153 + describe "A grader engine, when grading test requests" do
154 +
155 + include GraderEngineHelperMethods
156 +
157 + before(:each) do
158 + @config = Grader::Configuration.get_instance
159 + @engine = Grader::Engine.new(Grader::TestRequestRoomMaker.new,
160 + Grader::TestRequestReporter.new)
161 + init_sandbox
162 + end
163 +
164 + it "should report error if there is no problem template" do
165 + problem = stub(Problem,
166 + :id => 1, :name => 'nothing')
167 + grader_should(:grade => 'test1_correct.c',
168 + :on => problem,
169 + :with => 'in1.txt',
170 + :and_report => {
171 + :graded_at= => nil,
172 + :compiler_message= => '',
173 + :grader_comment= => '',
174 + :running_stat= => /template not found/,
175 + :running_time= => nil,
176 + :exit_status= => nil,
177 + :memory_usage= => nil,
178 + :save => nil})
179 + end
180 +
181 + it "should run test request and produce output file" do
182 + problem = stub(Problem,
183 + :id => 1, :name => 'test_normal')
184 + grader_should(:grade => 'test1_correct.c',
185 + :on => problem,
186 + :with => 'in1.txt',
187 + :and_report => {
188 + :graded_at= => nil,
189 + :compiler_message= => '',
190 + :grader_comment= => '',
191 + :running_stat= => /0.0\d* sec./,
192 + :output_file_name= => lambda { |fname|
193 + File.exists?(fname).should be_true
194 + },
195 + :running_time= => nil,
196 + :exit_status= => nil,
197 + :memory_usage= => nil,
198 + :save => nil})
199 + end
200 +
201 + it "should clean up problem directory after running test request" do
202 + problem = stub(Problem,
203 + :id => 1, :name => 'test_normal')
204 + grader_should(:grade => 'test1_correct.c',
205 + :on => problem,
206 + :with => 'in1.txt',
207 + :and_report => {
208 + :graded_at= => nil,
209 + :compiler_message= => '',
210 + :grader_comment= => '',
211 + :running_stat= => nil,
212 + :output_file_name= => nil,
213 + :running_time= => nil,
214 + :exit_status= => nil,
215 + :memory_usage= => nil,
216 + :save => nil})
217 + File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be_false
218 + end
219 +
220 + it "should compile test request with error and report compilation error" do
221 + problem = stub(Problem,
222 + :id => 1, :name => 'test_normal')
223 + grader_should(:grade => 'test1_compile_error.c',
224 + :on => problem,
225 + :with => 'in1.txt',
226 + :and_report => {
227 + :graded_at= => nil,
228 + :compiler_message= => /.+/,
229 + :grader_comment= => /[Cc]ompil.*error/,
230 + :running_stat= => '',
231 + :save => nil})
232 + end
233 +
234 + it "should report exit status" do
235 + problem = stub(Problem,
236 + :id => 1, :name => 'test_normal')
237 + grader_should(:grade => 'add_nonzero_exit_status.c',
238 + :on => problem,
239 + :with => 'in1.txt',
240 + :and_report => {
241 + :graded_at= => nil,
242 + :compiler_message= => '',
243 + :grader_comment= => '',
244 + :running_stat= => /[Ee]xit.*status.*10.*0\.0\d* sec/m,
245 + :output_file_name= => lambda { |fname|
246 + File.exists?(fname).should be_true
247 + },
248 + :running_time= => nil,
249 + :exit_status= => /10/,
250 + :memory_usage= => nil,
251 + :save => nil})
252 + end
253 +
254 + it "should produce running statistics for normal submission" do
255 + problem = stub(Problem,
256 + :id => 1, :name => 'test_normal')
257 + grader_should(:grade => 'test_run_stat.c',
258 + :on => problem,
259 + :with => 'in1.txt',
260 + :and_report => {
261 + :graded_at= => nil,
262 + :compiler_message= => '',
263 + :grader_comment= => '',
264 + :running_stat= => nil,
265 + :output_file_name= => lambda { |fname|
266 + File.exists?(fname).should be_true
267 + },
268 + :running_time= => lambda { |t|
269 + (t>=0.14) and (t<=0.16)
270 + },
271 + :exit_status= => nil,
272 + :memory_usage= => lambda { |s|
273 + (s>=500) and (s<=1000)
274 + },
275 + :save => nil})
276 + end
277 +
278 + protected
279 + def grader_should(args)
280 + @user1 = stub(User,
281 + :id => 1, :login => 'user1')
282 +
283 + problem = args[:on]
284 + input_file = @config.test_request_input_base_dir + "/" + args[:with]
285 +
286 + submission =
287 + create_submission_from_file(1, @user1, args[:on], args[:grade])
288 +
289 + test_request = stub(TestRequest,
290 + :id => 1,
291 + :user => @user1,
292 + :problem => problem,
293 + :submission => submission,
294 + :input_file_name => input_file,
295 + :language => submission.language,
296 + :problem_name => problem.name)
297 +
298 + expectations = args[:and_report]
299 +
300 + expectations.each do |key,val|
301 + if val==nil
302 + test_request.should_receive(key)
303 + elsif val.class == Proc
304 + test_request.should_receive(key) { |fname|
305 + val.call(fname)
306 + }
307 + else
308 + test_request.should_receive(key).with(val)
309 + end
310 + end
311 +
312 + @engine.grade(test_request)
313 + end
314 +
315 + end
316 +
@@ -0,0 +1,30
1 + module GraderEngineHelperMethods
2 +
3 + def clear_sandbox
4 + config = Grader::Configuration.get_instance
5 + clear_cmd = "rm -rf #{config.test_sandbox_dir}/*"
6 + system(clear_cmd)
7 + end
8 +
9 + def init_sandbox
10 + config = Grader::Configuration.get_instance
11 + clear_sandbox
12 + FileUtils.mkdir_p config.user_result_dir
13 + cp_cmd = "cp -R #{config.test_data_dir}/ev #{config.test_sandbox_dir}"
14 + system(cp_cmd)
15 + end
16 +
17 + def create_submission_from_file(id, user, problem,
18 + source_fname, language=nil)
19 +
20 + language = stub(Language, :name => 'c', :ext => 'c') if language==nil
21 +
22 + config = Grader::Configuration.get_instance
23 + source = File.open(config.test_data_dir + "/" + source_fname).read
24 + stub(Submission,
25 + :id => id, :user => user, :problem => problem,
26 + :source => source, :language => language)
27 + end
28 +
29 + end
30 +
@@ -0,0 +1,152
1 + require 'test/unit'
2 + require 'rubygems'
3 + require 'mocha'
4 +
5 + require File.join(File.dirname(__FILE__),'test_helper')
6 +
7 + class GraderEngineTest < UnitTest.TestCase
8 +
9 + def setup
10 + @@lang_c = stub(:name => 'c', :ext => 'c')
11 + @@lang_cpp = stub(:name => 'cpp', :ext => 'cpp')
12 + @@lang_pas = stub(:name => 'pas', :ext => 'pas')
13 +
14 + @config = Grader::Configuration.get_instance
15 +
16 + @problem_test1 = stub(:id => 1, :name => 'test1', :full_score => 135)
17 + @user_user1 = stub(:id => 1, :login => 'user1')
18 +
19 + @engine = Grader::Engine.new
20 +
21 + init_sandbox
22 + end
23 +
24 + def teardown
25 + end
26 +
27 + def test_grade_oldest_task_with_no_submission
28 + Task.expects(:get_inqueue_and_change_status).returns(nil)
29 + assert_equal nil, @engine.grade_oldest_task, 'should return nil when there is no task'
30 + end
31 +
32 + def test_normal_submission
33 + submission = create_test1_submission_mock_from_file("test1_correct.c")
34 +
35 + submission.expects(:graded_at=)
36 + submission.expects(:points=).with(135)
37 + submission.expects(:grader_comment=).with do |value|
38 + /^PASSED/.match(value)
39 + end
40 + submission.expects(:compiler_message=).with('')
41 + submission.expects(:save)
42 +
43 + @engine.grade(submission)
44 + end
45 +
46 + def test_compile_error_submission
47 + submission = create_test1_submission_mock_from_file("test1_compile_error.c")
48 +
49 + submission.expects(:graded_at=)
50 + submission.expects(:points=).with(0)
51 + submission.expects(:grader_comment=).with('FAILED: compile error')
52 + submission.expects(:compiler_message=) do |value|
53 + /[Ee]rror/.match value
54 + end
55 + submission.expects(:save)
56 +
57 + @engine.grade(submission)
58 + end
59 +
60 + def test_timeout_submission
61 + @problem_test2 = stub(:id => 1, :name => 'test2', :full_score => 10)
62 + @user_user1 = stub(:id => 1, :login => 'user1')
63 +
64 + submission = create_submission_from_file(1, @user_user1, @problem_test2,
65 + "test2_timeout.c")
66 +
67 + submission.expects(:graded_at=)
68 + submission.expects(:points=).with(0)
69 + submission.expects(:grader_comment=).with do |value|
70 + /^FAILED: TT$/.match value
71 + end
72 + submission.expects(:compiler_message=).with('')
73 + submission.expects(:save)
74 +
75 + @engine.grade(submission)
76 + end
77 +
78 + def test_timeout_submission_running_one_and_a_half_second
79 + @problem_test2 = stub(:id => 1, :name => 'test2', :full_score => 20)
80 + @user_user1 = stub(:id => 1, :login => 'user1')
81 +
82 + submission = create_submission_from_file(1, @user_user1, @problem_test2,
83 + "test2_1-5sec.c")
84 +
85 + submission.expects(:graded_at=)
86 + submission.expects(:points=).with(10)
87 + submission.expects(:grader_comment=).with do |value|
88 + /^FAILED: TP$/.match value
89 + end
90 + submission.expects(:compiler_message=).with('')
91 + submission.expects(:save)
92 +
93 + @engine.grade(submission)
94 + end
95 +
96 + def test_grade_oldest_task
97 + # mock submission
98 + submission = create_test1_submission_mock_from_file("test1_correct.c")
99 +
100 + submission.expects(:graded_at=)
101 + submission.expects(:points=).with(135)
102 + submission.expects(:grader_comment=).with do |value|
103 + /^PASSED/.match(value)
104 + end
105 + submission.expects(:compiler_message=).with('')
106 + submission.expects(:save)
107 +
108 + # mock task
109 + task = stub(:id => 1, :submission_id => submission.id)
110 + Task.expects(:get_inqueue_and_change_status).returns(task)
111 + task.expects(:status_complete!)
112 +
113 + # mock Submission
114 + Submission.expects(:find).with(task.submission_id).returns(submission)
115 +
116 + @engine.grade_oldest_task
117 + end
118 +
119 + def test_grade_oldest_task_with_grader_process
120 + grader_process = stub
121 + grader_process.expects(:report_active)
122 +
123 + @engine = Grader::Engine.new(grader_process)
124 +
125 + test_grade_oldest_task
126 + end
127 +
128 + protected
129 +
130 + def clear_sandbox
131 + clear_cmd = "rm -rf #{@config.test_sandbox_dir}/*"
132 + system(clear_cmd)
133 + end
134 +
135 + def init_sandbox
136 + clear_sandbox
137 + Dir.mkdir @config.user_result_dir
138 + cp_cmd = "cp -R #{@config.test_data_dir}/ev #{@config.test_sandbox_dir}"
139 + system(cp_cmd)
140 + end
141 +
142 + def create_submission_from_file(id, user, problem, source_fname, language = @@lang_c)
143 + source = File.open(@config.test_data_dir + "/" + source_fname).read
144 + stub(:id => id, :user => user, :problem => problem,
145 + :source => source, :language => language)
146 + end
147 +
148 + def create_test1_submission_mock_from_file(source_fname)
149 + create_submission_from_file(1, @user_user1, @problem_test1, source_fname)
150 + end
151 +
152 + end
@@ -0,0 +1,40
1 + require 'test/unit'
2 + require 'rubygems'
3 + require 'mocha'
4 +
5 + require File.join(File.dirname(__FILE__),'../lib/import_helper')
6 +
7 + class ImportHelperTest < Test::Unit::TestCase
8 +
9 + def setup
10 + end
11 +
12 + def teardown
13 + end
14 +
15 + def test_build_only_singleton_testruns
16 + testrun_info = build_testrun_info(3,['1','2','3'])
17 + assert_equal [[[1,'1']],[[2,'2']],[[3,'3']]], testrun_info, 'should build singleton testruns'
18 + end
19 +
20 + def test_build_only_singleton_testruns2
21 + testrun_info = build_testrun_info(4,['1','2','3','4'])
22 + assert_equal [[[1,'1']],[[2,'2']],[[3,'3']],[[4,'4']]], testrun_info, 'should build singleton testruns'
23 + end
24 +
25 + def test_build_testruns_when_testcases_defined_by_appending_alphabets
26 + testrun_info = build_testrun_info(4,['1a','1b','2','3a','3b','4'])
27 + assert_equal [[[1,'1a'],[2,'1b']],
28 + [[3,'2']],
29 + [[4,'3a'],[5,'3b']],
30 + [[6,'4']]], testrun_info
31 + end
32 +
33 + def test_build_testruns_when_testcases_defined_by_appending_dashed_numbers
34 + testrun_info = build_testrun_info(4,['1-1','1-2','2','3-1','3-2','4'])
35 + assert_equal [[[1,'1-1'],[2,'1-2']],
36 + [[3,'2']],
37 + [[4,'3-1'],[5,'3-2']],
38 + [[6,'4']]], testrun_info
39 + end
40 + end
@@ -0,0 +1,65
1 + require File.join(File.dirname(__FILE__),'spec_helper')
2 + require File.join(File.dirname(__FILE__),'engine_spec_helper')
3 +
4 + describe "A grader runner, when grade task" do
5 +
6 + include GraderEngineHelperMethods
7 +
8 + before(:each) do
9 + @config = Grader::Configuration.get_instance
10 + @problem_test_normal = stub(Problem,
11 + :id => 1, :name => 'test_normal',
12 + :full_score => 135)
13 + @user_user1 = stub(User,
14 + :id => 1, :login => 'user1')
15 +
16 + @engine = Grader::Engine.new
17 + @runner = Grader::Runner.new(@engine)
18 + init_sandbox
19 + end
20 +
21 + it "should just return nil when there is no submission" do
22 + Task.should_receive(:get_inqueue_and_change_status).and_return(nil)
23 + @runner.grade_oldest_task.should be_nil
24 + end
25 +
26 + it "should grade oldest task in queue" do
27 + submission = create_normal_submission_mock_from_file("test1_correct.c")
28 +
29 + submission.should_receive(:graded_at=)
30 + submission.should_receive(:points=).with(135)
31 + submission.should_receive(:grader_comment=).with(/^PASSED/)
32 + submission.should_receive(:compiler_message=).with('')
33 + submission.should_receive(:save)
34 +
35 + # mock task
36 + task = stub(Task,:id => 1, :submission_id => submission.id)
37 + Task.should_receive(:get_inqueue_and_change_status).and_return(task)
38 + task.should_receive(:status_complete!)
39 +
40 + # mock Submission
41 + Submission.should_receive(:find).
42 + with(task.submission_id).
43 + and_return(submission)
44 +
45 + @runner.grade_oldest_task
46 + end
47 +
48 + # to be converted
49 + def test_grade_oldest_task_with_grader_process
50 + grader_process = stub
51 + grader_process.expects(:report_active)
52 +
53 + @runner = Grader::Runner.new(@engine,grader_process)
54 +
55 + test_grade_oldest_task
56 + end
57 +
58 + protected
59 +
60 + def create_normal_submission_mock_from_file(source_fname)
61 + create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname)
62 + end
63 +
64 + end
65 +
@@ -0,0 +1,25
1 +
2 + # This test helper loads the grader's environment and rails environment
3 +
4 + GRADER_ENV = 'test'
5 + require File.join(File.dirname(__FILE__),'../config/environment')
6 +
7 +
8 + # this shall be removed soon
9 + RAILS_ENV = Grader::Configuration.get_instance.rails_env
10 + require RAILS_ROOT + '/config/environment'
11 +
12 + # make sure not to access real database!
13 + # taken from http://blog.jayfields.com/2006/06/ruby-on-rails-unit-tests.html
14 +
15 + class UnitTest
16 + def self.TestCase
17 + class << ActiveRecord::Base
18 + def connection
19 + raise 'You cannot access the database from a unit test'
20 + # raise InvalidActionError, 'You cannot access the database from a unit test', caller
21 + end
22 + end
23 + Test::Unit::TestCase
24 + end
25 + end
@@ -0,0 +1,25
1 +
2 + # This test helper loads the grader's environment and rails environment
3 +
4 + GRADER_ENV = 'test'
5 + require File.join(File.dirname(__FILE__),'../config/environment')
6 +
7 +
8 + # this shall be removed soon
9 + RAILS_ENV = Grader::Configuration.get_instance.rails_env
10 + require RAILS_ROOT + '/config/environment'
11 +
12 + # make sure not to access real database!
13 + # taken from http://blog.jayfields.com/2006/06/ruby-on-rails-unit-tests.html
14 +
15 + class UnitTest
16 + def self.TestCase
17 + class << ActiveRecord::Base
18 + def connection
19 + raise 'You cannot access the database from a unit test'
20 + # raise InvalidActionError, 'You cannot access the database from a unit test', caller
21 + end
22 + end
23 + Test::Unit::TestCase
24 + end
25 + end
You need to be logged in to leave comments. Login now