├── .gitignore ├── MIT-LICENSE ├── check_similar ├── config ├── .gitignore ├── env_exam.rb.SAMPLE ├── env_grading.rb.SAMPLE ├── env_test.rb.SAMPLE └── environment.rb.SAMPLE ├── grader ├── grader-process-check.SAMPLE ├── grader_id ├── import_problem ├── installer ├── install-opensuse.sh └── install.sh ├── isolate ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── cg.c ├── config.c ├── default.cf ├── isolate-check-environment ├── isolate.1.txt ├── isolate.c ├── isolate.h ├── rules.c └── util.c ├── lib ├── boot.rb ├── configuration.rb ├── dir_init.rb ├── engine.rb ├── import_helper.rb ├── initializer.rb ├── runner.rb ├── submission_helper.rb └── test_request_helper.rb ├── load_testcase ├── new_problem ├── rename_problem ├── std-script ├── .gitignore ├── box.cc ├── box64-new.c ├── box64.cc ├── check ├── compile ├── grade ├── judge ├── run └── test_dsl.rb ├── templates ├── all_tests.cfg.erb ├── answer-1.txt ├── check.float ├── check.integer ├── check.text ├── check_empty ├── check_wrapper └── test_request_all_tests.cfg.erb └── test ├── .gitignore ├── data ├── add_fail_test_case_1.c ├── add_nonzero_exit_status.c ├── add_too_much_memory_dynamic.c ├── add_too_much_memory_static.c ├── ev │ ├── test_memory │ │ ├── script │ │ │ └── check │ │ └── test_cases │ │ │ ├── 1 │ │ │ ├── answer-1.txt │ │ │ └── input-1.txt │ │ │ ├── 2 │ │ │ ├── answer-2.txt │ │ │ └── input-2.txt │ │ │ └── all_tests.cfg │ ├── test_normal │ │ ├── script │ │ │ └── check │ │ └── test_cases │ │ │ ├── 1 │ │ │ ├── answer-1.txt │ │ │ └── input-1.txt │ │ │ ├── 2 │ │ │ ├── answer-2.txt │ │ │ └── input-2.txt │ │ │ ├── 3 │ │ │ ├── answer-3.txt │ │ │ ├── input-3.txt │ │ │ └── test.cfg │ │ │ ├── 4 │ │ │ ├── answer-4.txt │ │ │ └── input-4.txt │ │ │ ├── 5 │ │ │ ├── answer-5.txt │ │ │ └── input-5.txt │ │ │ ├── 6 │ │ │ ├── answer-6.txt │ │ │ └── input-6.txt │ │ │ ├── 7 │ │ │ ├── answer-7.txt │ │ │ └── input-7.txt │ │ │ ├── 8 │ │ │ ├── answer-8.txt │ │ │ ├── input-8.txt │ │ │ └── test.cfg │ │ │ ├── 9 │ │ │ ├── answer-9.txt │ │ │ └── input-9.txt │ │ │ ├── 10 │ │ │ ├── answer-10.txt │ │ │ └── input-10.txt │ │ │ └── all_tests.cfg │ ├── test_timeout │ │ ├── script │ │ │ └── check │ │ └── test_cases │ │ │ ├── 1 │ │ │ ├── answer-1.txt │ │ │ └── input-1.txt │ │ │ ├── 2 │ │ │ ├── answer-2.txt │ │ │ └── input-2.txt │ │ │ └── all_tests.cfg │ └── test_yesno │ │ ├── script │ │ └── check │ │ └── test_cases │ │ ├── 1 │ │ ├── answer-1.txt │ │ └── input-1.txt │ │ └── all_tests.cfg ├── raw │ └── sum │ │ ├── 1.in │ │ ├── 1.sol │ │ ├── 10a.in │ │ ├── 10a.sol │ │ ├── 10b.in │ │ ├── 10b.sol │ │ ├── 10c.in │ │ ├── 10c.sol │ │ ├── 2a.in │ │ ├── 2a.sol │ │ ├── 2b.in │ │ ├── 2b.sol │ │ ├── 3.in │ │ ├── 3.sol │ │ ├── 4.in │ │ ├── 4.sol │ │ ├── 5.in │ │ ├── 5.sol │ │ ├── 6.in │ │ ├── 6.sol │ │ ├── 7.in │ │ ├── 7.sol │ │ ├── 8.in │ │ ├── 8.sol │ │ ├── 9.in │ │ ├── 9.sol │ │ └── sum.c ├── test1_compile_error.c ├── test1_correct.c ├── test1_correct_cc.cc ├── test2_05sec.c ├── test2_timeout.c ├── test_request │ ├── input │ │ └── in1.txt │ └── problems │ │ └── test_normal │ │ ├── script │ │ └── check │ │ └── test_cases │ │ ├── 1 │ │ └── answer-1.txt │ │ └── all_tests.cfg ├── test_run_stat.c ├── yesno_access_problem_home.c └── yesno_open_file.c ├── engine_spec.rb ├── engine_spec_helper.rb ├── engine_test.rb ├── import_helper_test.rb ├── rakefile ├── runner_spec.rb ├── spec_helper.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | coverage 3 | 4 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2011 Pramook Khungurn, 2007-2012 Jittat Fakcharoenphol 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /check_similar: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | def config 4 | Grader::Configuration.get_instance 5 | end 6 | 7 | def display_manual 8 | puts < false, 33 | } 34 | 35 | 36 | if ARGV.length == 2 37 | options[:sub1] = ARGV[0].to_i 38 | options[:sub2] = ARGV[1].to_i 39 | elsif ARGV.length == 1 40 | options[:problem] = ARGV[0] 41 | end 42 | 43 | 44 | return options 45 | end 46 | 47 | def compare(sub1,sub2,full = sub1.problem.full_score) 48 | dis = @jarow.getDistance(sub1.source, sub2.source) 49 | puts [sub1.user.login,"##{sub1.id}",(sub1.points * 100.0 / full).to_i, 50 | sub2.user.login,"##{sub2.id}",(sub2.points * 100.0 / full).to_i, 51 | "#{dis * 100}%"].join(',') 52 | end 53 | 54 | ######################################### 55 | # main program 56 | ######################################### 57 | 58 | options = process_options_and_stop_file 59 | 60 | # load grader environment 61 | GRADER_ENV = 'grading' 62 | require File.join(File.dirname(__FILE__),'config/environment') 63 | 64 | # boot rails, to be able to use the active record 65 | RAILS_ENV = config.rails_env 66 | require RAILS_ROOT + '/config/environment' 67 | 68 | # load comparator 69 | require 'fuzzystringmatch' 70 | @jarow = FuzzyStringMatch::JaroWinkler.create( :native ) 71 | 72 | if options[:problem] 73 | p = Problem.where(name: options[:problem]).first 74 | unless p 75 | puts "cannot find problem #{options[:problem]}" 76 | exit(0) 77 | end 78 | subs = Submission.where(problem: p) 79 | full_score = p.full_score.to_i 80 | subs.each.with_index do |s1,i| 81 | puts "processing #{i+1} out of #{subs.length}" 82 | subs.each do |s2| 83 | if s1.user != s2.user 84 | compare(s1,s2,full_score) 85 | end 86 | end 87 | end 88 | else 89 | sub1 = Submission.find(options[:sub1]) 90 | sub2 = Submission.find(options[:sub2]) 91 | compare(sub1,sub2) 92 | end 93 | 94 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | env*.rb 2 | 3 | -------------------------------------------------------------------------------- /config/env_exam.rb.SAMPLE: -------------------------------------------------------------------------------- 1 | # 2 | # See documentation in lib/configuration.rb 3 | # 4 | Grader::Initializer.run do |config| 5 | 6 | config.problems_dir = GRADER_ROOT + "/../ev-exam" 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 21 | -------------------------------------------------------------------------------- /config/env_grading.rb.SAMPLE: -------------------------------------------------------------------------------- 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 20 | -------------------------------------------------------------------------------- /config/env_test.rb.SAMPLE: -------------------------------------------------------------------------------- 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 30 | -------------------------------------------------------------------------------- /config/environment.rb.SAMPLE: -------------------------------------------------------------------------------- 1 | # Rails app directory (where you store web interface dir) 2 | RAILS_ROOT = "RAILS-ROOT" 3 | 4 | # This is where scripts dir is. 5 | GRADER_ROOT = "GRADER-DIR/scripts" 6 | 7 | # This load all required codes 8 | require File.join(File.dirname(__FILE__),'../lib/boot') 9 | 10 | # load the required environment file 11 | require File.dirname(__FILE__) + "/env_#{GRADER_ENV}.rb" 12 | -------------------------------------------------------------------------------- /grader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 | File.delete(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 <= 1) and (ARGV[0]=='stop') 87 | if ARGV.length==1 88 | puts "you should specify pid-list or 'all'" 89 | display_manual 90 | elsif (ARGV.length==2) and (ARGV[1]=='all') 91 | stop_grader(:all) 92 | puts "A global stop file ('stop.all') created." 93 | puts "You should remove it manually later." 94 | else 95 | (1..ARGV.length-1).each do |i| 96 | stop_grader(ARGV[i]) 97 | end 98 | puts "stop file(s) created" 99 | end 100 | exit(0) 101 | end 102 | 103 | # Check stop file. 104 | if check_stopfile 105 | puts "Stop file exists. Terminated." 106 | clear_stopfile 107 | exit(0) 108 | end 109 | 110 | #default options 111 | options = { 112 | :mode => 'queue', 113 | :environment => 'exam', 114 | :dry_run => false, 115 | } 116 | 117 | # Process mode and environment option 118 | if ARGV.length >= 1 119 | options[:environment] = ARGV.shift 120 | if ARGV.length >=1 121 | options[:mode] = ARGV.shift 122 | end 123 | else 124 | puts 'no argument specified, using default mode and environment.' 125 | end 126 | 127 | options[:dry_run] = (ARGV.delete('--dry') != nil) 128 | if options[:dry_run] and (not ['prob','contest','autonew'].include? options[:mode]) 129 | puts "Dry run currently works only for 'prob' or 'contest' modes." 130 | exit(0) 131 | end 132 | 133 | options[:report] = (ARGV.delete('--report') != nil) 134 | if options[:report] and (not ['prob','contest','autonew'].include? options[:mode]) 135 | puts "Report currently works only for 'prob' or 'contest' modes." 136 | exit(0) 137 | end 138 | 139 | options[:all_sub] = (ARGV.delete('--all-sub') != nil) 140 | options[:only_err] = (ARGV.delete('--only-error') != nil) 141 | 142 | options[:err_log] = (ARGV.delete('--err-log') != nil) 143 | 144 | return options 145 | end 146 | 147 | class ResultCollector 148 | def initialize 149 | @results = {} 150 | @problems = {} 151 | @users = {} 152 | end 153 | 154 | def after_save_hook(submission, grading_result) 155 | end 156 | 157 | def save(submission, grading_result) 158 | user = submission.user 159 | problem = submission.problem 160 | if not @problems.has_key? problem.id 161 | @problems[problem.id] = problem 162 | end 163 | if not @users.has_key? user.id 164 | @users[user.id] = user 165 | end 166 | @results[[user.id, problem.id]] = grading_result 167 | 168 | after_save_hook(submission, grading_result) 169 | end 170 | 171 | def print_report_by_user 172 | puts "---------------------" 173 | puts " REPORT" 174 | puts "---------------------" 175 | 176 | print "login,email" 177 | @problems.each_value do |problem| 178 | print ",#{problem.name}" 179 | end 180 | print "\n" 181 | 182 | @users.each_value do |user| 183 | print "#{user.login},#{user.email}" 184 | @problems.each_value do |problem| 185 | if @results.has_key? [user.id, problem.id] 186 | print ",#{@results[[user.id,problem.id]][:points]}" 187 | else 188 | print "," 189 | end 190 | end 191 | print "\n" 192 | end 193 | end 194 | end 195 | 196 | def grader_general_loop(engine, grader_proc, options) 197 | runner = Grader::Runner.new(engine, grader_proc) 198 | while true 199 | 200 | if check_stopfile # created by calling grader stop 201 | clear_stopfile 202 | log "stopped (with stop file)" 203 | break 204 | end 205 | 206 | task = yield(runner) 207 | 208 | if task==nil 209 | sleep(1) 210 | end 211 | end 212 | end 213 | 214 | def grader_queue_loop(grader_proc, options) 215 | log "Grader: queue" 216 | engine = Grader::Engine.new 217 | grader_general_loop(engine, grader_proc, options) do |runner| 218 | runner.grade_oldest_task 219 | end 220 | end 221 | 222 | def grader_test_request_loop(grader_proc, options) 223 | log "Grader: test_request" 224 | engine = Grader::Engine.new(:room_maker => Grader::TestRequestRoomMaker.new, 225 | :reporter => Grader::TestRequestReporter.new) 226 | grader_general_loop(engine, grader_proc, options) do |runner| 227 | runner.grade_oldest_test_request 228 | end 229 | end 230 | 231 | def grader_autonew_loop(grader_proc, options) 232 | log "Grader: autonew" 233 | 234 | if options[:report] 235 | result_collector = ResultCollector.new 236 | else 237 | result_collector = nil 238 | end 239 | 240 | if options[:dry_run] 241 | puts "Running in dry mode" 242 | end 243 | 244 | prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run], 245 | :result_collector => result_collector) 246 | 247 | engine = Grader::Engine.new(:reporter => prob_reporter) 248 | runner = Grader::Runner.new(engine, grader_proc) 249 | 250 | grader_proc.report_active if grader_proc!=nil 251 | 252 | latest_submitted_at = nil 253 | graded_submission_ids = {} 254 | 255 | while true 256 | 257 | if check_stopfile # created by calling grader stop 258 | clear_stopfile 259 | log "stopped (with stop file)" 260 | break 261 | end 262 | 263 | if latest_submitted_at==nil 264 | submissions = Submission.all 265 | else 266 | submissions = Submission.all(:conditions => ["submitted_at >= :latest", 267 | {:latest => latest_submitted_at}]) 268 | end 269 | 270 | graded_any = false 271 | 272 | if submissions.length != 0 273 | submissions.each do |submission| 274 | if (submission.problem == nil) or (!submission.problem.available) 275 | next 276 | end 277 | if ! graded_submission_ids[submission.id] 278 | runner.grade_submission(submission) 279 | graded_submission_ids[submission.id] = true 280 | if (!latest_submitted_at or 281 | latest_submitted_at < submission.submitted_at) 282 | latest_submitted_at = submission.submitted_at 283 | end 284 | puts "graded: #{submission.id}" 285 | puts "latest: #{latest_submitted_at}" 286 | graded_any = true 287 | end 288 | end 289 | end 290 | 291 | if ! graded_any 292 | sleep(1) 293 | end 294 | end 295 | end 296 | 297 | def grader_grade_problems(grader_proc, options) 298 | if options[:report] 299 | result_collector = ResultCollector.new 300 | else 301 | result_collector = nil 302 | end 303 | 304 | if options[:dry_run] 305 | puts "Running in dry mode" 306 | end 307 | 308 | prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run], 309 | :result_collector => result_collector) 310 | engine = Grader::Engine.new(:reporter => prob_reporter) 311 | runner = Grader::Runner.new(engine, grader_proc) 312 | 313 | grader_proc.report_active if grader_proc!=nil 314 | 315 | ARGV.each do |prob_name| 316 | prob = Problem.find_by_name(prob_name) 317 | if prob==nil 318 | puts "cannot find problem: #{prob_name}" 319 | else 320 | runner.grade_problem(prob,options) 321 | end 322 | end 323 | 324 | if options[:report] 325 | result_collector.print_report_by_user 326 | end 327 | end 328 | 329 | def grader_grade_contests(grader_proc, options) 330 | # always use dry run when grading during contest 331 | dry_run = options[:dry_run] = true 332 | 333 | contest_name = ARGV.shift 334 | 335 | contest = Contest.find_by_name(contest_name) 336 | if contest==nil 337 | puts "cannot find contest: #{contest_name}" 338 | exit(0) 339 | end 340 | 341 | if options[:report] 342 | result_collector = ResultCollector.new 343 | else 344 | result_collector = nil 345 | end 346 | 347 | if options[:dry_run] 348 | puts "Running in dry mode" 349 | end 350 | 351 | prob_reporter = Grader::SubmissionReporter.new(:dry_run => dry_run, 352 | :result_collector => result_collector) 353 | engine = Grader::Engine.new(:reporter => prob_reporter) 354 | runner = Grader::Runner.new(engine, grader_proc) 355 | 356 | grader_proc.report_active if grader_proc!=nil 357 | 358 | contest.problems.each do |problem| 359 | puts "Grading: #{problem.name}" 360 | runner.grade_problem(problem, 361 | :user_conditions => lambda do |u| 362 | u.contest_finished? and 363 | u.contest_ids.include?(contest.id) 364 | end) 365 | end 366 | 367 | if options[:report] 368 | result_collector.print_report_by_user 369 | end 370 | end 371 | 372 | def grader_grade_submissions(grader_proc, options) 373 | engine = Grader::Engine.new 374 | runner = Grader::Runner.new(engine, grader_proc) 375 | 376 | grader_proc.report_active if grader_proc!=nil 377 | 378 | ARGV.each do |sub_id| 379 | puts "Grading #{sub_id}" 380 | begin 381 | submission = Submission.find(sub_id.to_i) 382 | rescue ActiveRecord::RecordNotFound 383 | puts "Submission #{sub_id} not found" 384 | submission = nil 385 | end 386 | 387 | if submission!=nil 388 | runner.grade_submission(submission) 389 | end 390 | end 391 | end 392 | 393 | ######################################### 394 | # main program 395 | ######################################### 396 | 397 | options = process_options_and_stop_file 398 | GRADER_ENV = options[:environment] 399 | grader_mode = options[:mode] 400 | dry_run = options[:dry_run] 401 | 402 | puts "environment: #{GRADER_ENV}" 403 | puts "grader mode: #{grader_mode}" 404 | require File.join(File.dirname(__FILE__),'config/environment') 405 | 406 | # add grader_mode to config 407 | # this is needed because method log needs it. TODO: clean this up 408 | class << config 409 | attr_accessor :grader_mode 410 | end 411 | config.grader_mode = grader_mode 412 | 413 | # reading rails environment 414 | log 'Reading rails environment' 415 | 416 | RAILS_ENV = config.rails_env 417 | require RAILS_ROOT + '/config/environment' 418 | 419 | # register grader process 420 | if config.report_grader 421 | grader_proc = GraderProcess.register(config.grader_hostname, 422 | Process.pid, 423 | grader_mode) 424 | else 425 | grader_proc = nil 426 | end 427 | 428 | #set loggin environment 429 | ENV['GRADER_LOGGING'] = log_file_name 430 | if options[:err_log] 431 | err_file_name = log_file_name + '.err' 432 | $stderr.reopen(err_file_name,"a") 433 | log "STDERR log to file [#{err_file_name}]" 434 | warn "start logging for grader PID #{Process.pid} on #{Time.now.in_time_zone}" 435 | end 436 | 437 | 438 | # register exit handler to report inactive, and terminated 439 | at_exit do 440 | if grader_proc!=nil 441 | grader_proc.report_inactive 442 | grader_proc.terminate 443 | end 444 | end 445 | 446 | # 447 | # MAIN LOOP 448 | # 449 | 450 | case grader_mode 451 | when "queue" 452 | grader_queue_loop(grader_proc, options) 453 | 454 | when "test_request" 455 | grader_test_request_loop(grader_proc, options) 456 | 457 | when "prob" 458 | grader_grade_problems(grader_proc, options) 459 | 460 | when "contest" 461 | grader_grade_contests(grader_proc, options) 462 | 463 | when "sub" 464 | grader_grade_submissions(grader_proc, options) 465 | 466 | when "autonew" 467 | grader_autonew_loop(grader_proc, options) 468 | 469 | else 470 | display_manual 471 | exit(0) 472 | end 473 | 474 | -------------------------------------------------------------------------------- /grader-process-check.SAMPLE: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | install_dir=/home/john/cafe_grader 3 | ruby_executable=/home/john/.rvm/wrappers/ruby-2.3.0/ruby 4 | 5 | #check number of running grader 6 | count=`ps aux | grep $install_dir | grep "grader grading queue" | wc -l` 7 | 8 | #if there is no grader running, start a new one 9 | if [ $count -lt 1 ]; then 10 | cd $install_dir/judge 11 | $ruby_executable $install_dir/judge/scripts/grader grading queue > $install_dir/judge/grading.log & 12 | fi 13 | -------------------------------------------------------------------------------- /grader_id: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | 5 | def talk(str) 6 | if TALKATIVE 7 | puts str 8 | end 9 | end 10 | 11 | def save_source(submission,dir,fname) 12 | f = File.open("#{dir}/#{fname}","w") 13 | f.write(submission.source) 14 | f.close 15 | end 16 | 17 | def call_judge(problem_home,language,problem_out_dir,fname) 18 | ENV['PROBLEM_HOME'] = problem_home 19 | Dir.chdir problem_out_dir 20 | cmd = "#{problem_home}/script/judge #{language} #{fname}" 21 | # puts "CMD: #{cmd}" 22 | system(cmd) 23 | end 24 | 25 | def read_result(test_result_dir) 26 | cmp_msg_fname = "#{test_result_dir}/compiler_message" 27 | cmp_msg = File.open(cmp_msg_fname).read 28 | 29 | result_fname = "#{test_result_dir}/result" 30 | comment_fname = "#{test_result_dir}/comment" 31 | if FileTest.exist?(result_fname) 32 | result = File.open(result_fname).readline.to_i 33 | comment = File.open(comment_fname).readline.chomp 34 | return {:points => result, 35 | :comment => comment, 36 | :cmp_msg => cmp_msg} 37 | else 38 | return {:points => 0, 39 | :comment => 'compile error', 40 | :cmp_msg => cmp_msg} 41 | end 42 | end 43 | 44 | def save_result(submission,result) 45 | submission.graded_at = Time.now 46 | submission.points = result[:points] 47 | submission.grader_comment = report_comment(result[:comment]) 48 | submission.compiler_message = result[:cmp_msg] 49 | submission.save 50 | end 51 | 52 | def grade(submission_id) 53 | sub = Submission.find(submission_id) 54 | user = sub.user 55 | problem = sub.problem 56 | 57 | language = sub.language.name 58 | lang_ext = sub.language.ext 59 | # FIX THIS 60 | talk 'some hack on language' 61 | if language == 'cpp' 62 | language = 'c++' 63 | end 64 | 65 | user_dir = "#{USER_RESULT_DIR}/#{user.login}" 66 | Dir.mkdir(user_dir) if !FileTest.exist?(user_dir) 67 | 68 | problem_out_dir = "#{user_dir}/#{problem.name}" 69 | Dir.mkdir(problem_out_dir) if !FileTest.exist?(problem_out_dir) 70 | 71 | problem_home = "#{PROBLEMS_DIR}/#{problem.name}" 72 | source_name = "#{problem.name}.#{lang_ext}" 73 | 74 | save_source(sub,problem_out_dir,source_name) 75 | call_judge(problem_home,language,problem_out_dir,source_name) 76 | save_result(sub,read_result("#{problem_out_dir}/test-result")) 77 | end 78 | 79 | # reading environment and options 80 | GRADER_ENV = 'exam' 81 | puts "environment: #{GRADER_ENV}" 82 | require File.dirname(__FILE__) + "/environment.rb" 83 | 84 | #main program 85 | talk 'Reading rails environment' 86 | 87 | RAILS_ENV = 'development' 88 | require RAILS_APP_DIR + '/config/environment' 89 | 90 | current_dir = FileUtils.pwd 91 | grade(ARGV[0].to_i) 92 | -------------------------------------------------------------------------------- /import_problem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'erb' 4 | require 'fileutils' 5 | require File.join(File.dirname(__FILE__),'lib/import_helper') 6 | 7 | JUDGE_ENVIRONMENTS = [:grading, :exam] 8 | ENV_INFO = { 9 | :grading => { 10 | :ev_dir => 'ev', 11 | :raw_prefix => '', 12 | }, 13 | :exam => { 14 | :ev_dir => 'ev-exam', 15 | :raw_prefix => 'ex.', 16 | } 17 | } 18 | 19 | def input_filename(dir,i) 20 | "#{dir}/input-#{i}.txt" 21 | end 22 | 23 | def answer_filename(dir,i) 24 | "#{dir}/answer-#{i}.txt" 25 | end 26 | 27 | def build_testrun_info_from_dir(num_testruns, importing_test_dir, raw_prefix='') 28 | filenames = Dir["#{importing_test_dir}/#{raw_prefix}*.in"].collect do |filename| 29 | File.basename((/(.*)\.in/.match(filename))[1]) 30 | end 31 | build_testrun_info(num_testruns,filenames,raw_prefix) 32 | end 33 | 34 | def copy_testcase(importing_test_dir,fname,dir,i) 35 | #copy the file from importing dir and also remove carriage return 36 | a = File.read("#{importing_test_dir}/#{fname}.in").gsub(/\r\n?/,"\n") 37 | File.write("#{input_filename(dir,i)}",a) 38 | b = File.read("#{importing_test_dir}/#{fname}.sol").gsub(/\r\n?/,"\n") 39 | File.write("#{answer_filename(dir,i)}",b) 40 | end 41 | 42 | def process_options(options) 43 | i = 3 44 | while ii+1 47 | i += 1 48 | end 49 | if ARGV[i]=='-m' 50 | options[:mem_limit] = ARGV[i+1].to_i if ARGV.length>i+1 51 | i += 1 52 | end 53 | i += 1 54 | end 55 | end 56 | 57 | def print_usage 58 | puts "using: import_problem_new name dir check [options] 59 | 60 | where: name = problem_name (put '-' (dash) to use dir name) 61 | dir = importing testcase directory 62 | check = check script, which can be 63 | 'integer', 'text' (for standard script), 64 | path_to_your_script, or 65 | 'wrapper:(path_to_your_wrapped_script)' 66 | options: -t time-limit (in seconds) 67 | -m memory-limit (in megabytes) 68 | 69 | The script looks at test data files in the dir of the forms: *.in and 70 | *.sol and import them to the evaluation dir for their environment, 71 | based on their prefixes. 72 | 73 | Currently supporting environments are:" 74 | 75 | JUDGE_ENVIRONMENTS.each do |env| 76 | prefix = ENV_INFO[env][:raw_prefix] 77 | prefix = 'no prefix' if prefix=='' 78 | puts " * #{env}" 79 | puts " import to: #{ENV_INFO[env][:ev_dir]}" 80 | puts " prefix with: #{prefix} (e.g., #{ENV_INFO[env][:raw_prefix]}1.in, #{ENV_INFO[env][:raw_prefix]}5a.sol)" 81 | end 82 | 83 | puts" 84 | For each environment, the script 85 | * creates a directory for a problem in ev dir of that environment, 86 | * copies testdata in the old format and create standard testcase config file 87 | * copies a check script for grading 88 | * creates a test_request template in the ev dir + '/test_request' 89 | 90 | For wrapped checked script see comment in templates/check_wrapper for 91 | information." 92 | end 93 | 94 | def count_testruns(testcase_dir, raw_prefix) 95 | n = 0 96 | begin 97 | # check for test case n+1 98 | if ((Dir["#{testcase_dir}/#{raw_prefix}#{n+1}.in"].length==0) and 99 | (Dir["#{testcase_dir}/#{raw_prefix}#{n+1}[a-z].in"].length==0)) 100 | return n 101 | end 102 | n += 1 103 | end while true 104 | end 105 | 106 | def create_dir_if_not_exists(dir, options = {} ) 107 | if ! FileTest.exists? dir 108 | FileUtils.mkdir(dir) 109 | end 110 | 111 | FileUtils.rm_rf(Dir.glob("#{dir}/*")) if options[:clear] 112 | end 113 | 114 | def import_problem(ev_dir, problem, testcase_dir, num_testruns, raw_prefix, check_script, options) 115 | testrun_info = build_testrun_info_from_dir(num_testruns, testcase_dir, raw_prefix) 116 | 117 | if !(FileTest.exists? ev_dir) 118 | puts "Testdata dir (#{ev_dir}) not found." 119 | return 120 | end 121 | 122 | problem_dir = "#{ev_dir}/#{problem}" 123 | 124 | # start working 125 | puts "creating directories" 126 | 127 | create_dir_if_not_exists("#{problem_dir}") 128 | create_dir_if_not_exists("#{problem_dir}/script") 129 | create_dir_if_not_exists("#{problem_dir}/test_cases",clear: true) 130 | # clear test cases directory 131 | 132 | puts "copying testcases" 133 | 134 | tr_num = 0 135 | 136 | num_testcases = 0 137 | 138 | testrun_info.each do |testrun| 139 | tr_num += 1 140 | puts "testrun: #{tr_num}" 141 | 142 | testrun.each do |testcase_info| 143 | testcase_num, testcase_fname = testcase_info 144 | 145 | puts "copy #{testcase_fname} to #{testcase_num}" 146 | 147 | create_dir_if_not_exists("#{problem_dir}/test_cases/#{testcase_num}") 148 | copy_testcase("#{testcase_dir}",testcase_fname,"#{problem_dir}/test_cases/#{testcase_num}",testcase_num) 149 | 150 | num_testcases += 1 151 | end 152 | end 153 | 154 | #also include any .txt files 155 | Dir.glob("#{testcase_dir}/*.txt") do |file| 156 | puts "copy data file #{file}" 157 | FileUtils.cp(file,"#{problem_dir}") 158 | end 159 | 160 | # generating all_tests.cfg 161 | puts "generating testcase config file" 162 | 163 | template = File.open(SCRIPT_DIR + "/templates/all_tests.cfg.erb").read 164 | all_test_cfg = ERB.new(template) 165 | 166 | cfg_file = File.open("#{problem_dir}/test_cases/all_tests.cfg","w") 167 | cfg_file.puts all_test_cfg.result binding 168 | cfg_file.close 169 | 170 | # copy check script 171 | if res = /^wrapper:(.*)$/.match(check_script) 172 | # wrapper script 173 | check_script_fname = res[1] 174 | script_name = File.basename(check_script_fname) 175 | check_wrapper_template = File.open(SCRIPT_DIR + "/templates/check_wrapper").read 176 | check_wrapper = ERB.new(check_wrapper_template) 177 | 178 | check_file = File.open("#{problem_dir}/script/check","w") 179 | check_file.puts check_wrapper.result binding 180 | check_file.close 181 | 182 | File.chmod(0755,"#{problem_dir}/script/check") 183 | 184 | FileUtils.cp("#{check_script_fname}", "#{problem_dir}/script/#{script_name}") 185 | else 186 | if File.exists?(SCRIPT_DIR + "/templates/check.#{check_script}") 187 | check_script_fname = SCRIPT_DIR + "/templates/check.#{check_script}" 188 | else 189 | check_script_fname = check_script 190 | end 191 | FileUtils.cp("#{check_script_fname}", "#{problem_dir}/script/check", :preserve => true) 192 | end 193 | 194 | # generating test_request directory 195 | puts "generating test_request template" 196 | FileUtils.mkdir_p("#{ev_dir}/test_request/#{problem}/script") 197 | FileUtils.mkdir_p("#{ev_dir}/test_request/#{problem}/test_cases/1") 198 | 199 | template = File.open(SCRIPT_DIR + "/templates/test_request_all_tests.cfg.erb").read 200 | test_request_all_test_cfg = ERB.new(template) 201 | 202 | cfg_file = File.open("#{ev_dir}/test_request/#{problem}/test_cases/all_tests.cfg","w") 203 | cfg_file.puts test_request_all_test_cfg.result 204 | cfg_file.close 205 | 206 | FileUtils.cp("#{SCRIPT_DIR}/templates/check_empty", 207 | "#{ev_dir}/test_request/#{problem}/script/check") 208 | FileUtils.cp("#{SCRIPT_DIR}/templates/answer-1.txt", 209 | "#{ev_dir}/test_request/#{problem}/test_cases/1") 210 | 211 | puts "done" 212 | end 213 | 214 | 215 | SCRIPT_DIR = File.dirname(__FILE__) 216 | 217 | # print usage 218 | if (ARGV.length < 3) or (ARGV[2][0,1]=="-") 219 | print_usage 220 | exit(127) 221 | end 222 | 223 | # processing arguments 224 | problem = ARGV[0] 225 | testcase_dir = ARGV[1] 226 | problem = File.basename(testcase_dir) if problem=="-" 227 | check_script = ARGV[2] 228 | options = {:time_limit => 1, :mem_limit => 16} 229 | process_options(options) 230 | 231 | JUDGE_ENVIRONMENTS.each do |env| 232 | ev_dir = ENV_INFO[env][:ev_dir] 233 | raw_prefix = ENV_INFO[env][:raw_prefix] 234 | 235 | num_testruns = count_testruns(testcase_dir,raw_prefix) 236 | 237 | puts "" 238 | puts "*** Environment: #{env} (#{num_testruns} test runs) ***" 239 | puts "" 240 | 241 | import_problem(ev_dir, 242 | problem, 243 | testcase_dir, 244 | num_testruns, 245 | raw_prefix, 246 | check_script, 247 | options) 248 | end 249 | -------------------------------------------------------------------------------- /installer/install-opensuse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "This script will install and configure Cafe grader." 4 | 5 | RUBY_VERSION=2.1.2 6 | echo "This will install Ruby $RUBY_VERSION under RVM" 7 | 8 | echo "Installing required apts" 9 | 10 | sudo zypper install \ 11 | g++ gcc libmysqlclient18 build-essential \ 12 | git-core openssl libreadline6 libreadline6-devel \ 13 | zlib1g zlib1g-devel libssl37 libyaml-devel sqlite3-devel \ 14 | sqlite3 libxml2-devel libxslt-devel autoconf libc6-devel \ 15 | ncurses-devel automake libtool bison subversion \ 16 | pkg-config curl nodejs unzip pyflakes java-1_8_0-openjdk \ 17 | libmysqld-devel mercurial python-setuptools python-devel 18 | 19 | 20 | echo "Installing Ruby $RUBY_VERSION in RVM" 21 | 22 | rvm install $RUBY_VERSION 23 | rvm use $RUBY_VERSION 24 | 25 | echo "Fetching Cafe Grader from Git repositories" 26 | 27 | echo "Fetching web interface" 28 | 29 | mkdir cafe_grader 30 | cd cafe_grader 31 | #git clone -q git://github.com/jittat/cafe-grader-web.git web 32 | hg clone git+ssh://git@github.com/nattee/cafe-grader-web.git web 33 | 34 | echo "Configuring rails app" 35 | 36 | cp web/config/application.rb.SAMPLE web/config/application.rb 37 | cp web/config/initializers/cafe_grader_config.rb.SAMPLE web/config/initializers/cafe_grader_config.rb 38 | 39 | #replace UTC in application.rb with the system timezone 40 | timezone='UTC' 41 | if [ -f '/etc/timezone' ]; then 42 | timezone=\"`cat /etc/timezone`\" 43 | else 44 | if [ -f '/etc/sysconfig/clock' ]; then 45 | timezone=`grep -e '^TIMEZONE' /etc/sysconfig/clock | grep -o -e '\".*\"'` 46 | fi 47 | fi 48 | replace="s!'UTC'!$timezone!g" 49 | sed -i $replace web/config/application.rb 50 | 51 | echo "At this point we will need MySQL user and database." 52 | echo "Have you created MySQL user and database for Cafe grader? (Y/N) " 53 | read ch 54 | 55 | if [ "$ch" = "n" -o "$ch" = "N" ] 56 | then 57 | echo "Please open another terminal and create the user and database for Cafe grader." 58 | echo "Don't forget to grant access to that database for the user." 59 | echo "Please have username, password, and database name ready before continue." 60 | echo 61 | echo "The following are instructions:" 62 | echo "1. Run mysql:" 63 | echo 64 | echo " mysql -u root -p" 65 | echo 66 | echo " if you have just installed mysql, the root password is the one that you have just entered" 67 | echo "2. Create a new database, a new user, and grant access to grader database:" 68 | echo 69 | echo " create user 'USERNAME'@'localhost' identified by 'PASSWORD';" 70 | echo " create database \`DATABASENEME\`;" 71 | echo " grant all on \`DATABASENAME\`.* to 'USERNAME'@'localhost';" 72 | echo 73 | echo " Replace USERNAME, PASSWORD, and DATABASENAME accordingly." 74 | echo 75 | echo "Hit enter when ready..." 76 | read dummy 77 | fi 78 | 79 | CAFE_PATH=`pwd` 80 | 81 | cd web 82 | 83 | echo "Please provide grader database:" 84 | read database 85 | 86 | echo "Please provide grader username:" 87 | read username 88 | 89 | echo "Please provide $username password:" 90 | read password 91 | 92 | echo "development:" > config/database.yml 93 | echo " adapter: mysql2" >> config/database.yml 94 | echo " encoding: utf8" >> config/database.yml 95 | echo " reconnect: false" >> config/database.yml 96 | echo " database: $database" >> config/database.yml 97 | echo " pool: 5" >> config/database.yml 98 | echo " username: $username" >> config/database.yml 99 | echo " password: $password" >> config/database.yml 100 | echo " host: localhost" >> config/database.yml 101 | echo " socket: /run/mysql/mysql.sock" >> config/database.yml 102 | echo "" >> config/database.yml 103 | echo "production:" >> config/database.yml 104 | echo " adapter: mysql2" >> config/database.yml 105 | echo " encoding: utf8" >> config/database.yml 106 | echo " reconnect: false" >> config/database.yml 107 | echo " database: $database" >> config/database.yml 108 | echo " pool: 5" >> config/database.yml 109 | echo " username: $username" >> config/database.yml 110 | echo " password: $password" >> config/database.yml 111 | echo " host: localhost" >> config/database.yml 112 | echo " socket: /run/mysql/mysql.sock" >> config/database.yml 113 | 114 | echo "Object.instance_eval{remove_const :GRADER_ROOT_DIR}" >> config/initializers/cafe_grader_config.rb 115 | echo "Object.instance_eval{remove_const :GRADING_RESULT_DIR}" >> config/initializers/cafe_grader_config.rb 116 | echo "GRADER_ROOT_DIR = '$CAFE_PATH/judge'" >> config/initializers/cafe_grader_config.rb 117 | echo "GRADING_RESULT_DIR = '$CAFE_PATH/judge/result'" >> config/initializers/cafe_grader_config.rb 118 | 119 | echo "Installing required gems" 120 | gem install bundler 121 | bundle install 122 | 123 | echo "Running rake tasks to initialize database" 124 | 125 | rake db:migrate 126 | rake db:seed 127 | 128 | echo "Running rake tasks to precompile the assets" 129 | 130 | rake assets:precompile 131 | 132 | echo "Intalling web interface complete..." 133 | echo 134 | echo "Fetching grader" 135 | 136 | cd .. 137 | 138 | mkdir judge 139 | cd judge 140 | #git clone -q git://github.com/jittat/cafe-grader-judge-scripts.git scripts 141 | hg clone git+ssh://git@github.com/nattee/cafe-grader-judge-scripts.git scripts 142 | mkdir raw 143 | mkdir ev-exam 144 | mkdir ev 145 | mkdir result 146 | mkdir log 147 | 148 | echo "Configuring grader" 149 | 150 | cp scripts/config/env_exam.rb.SAMPLE scripts/config/env_exam.rb 151 | cp scripts/config/env_grading.rb.SAMPLE scripts/config/env_grading.rb 152 | 153 | # create new environment.rb file 154 | echo "RAILS_ROOT = '$CAFE_PATH/web'" > scripts/config/environment.rb 155 | echo "GRADER_ROOT = '$CAFE_PATH/judge/scripts'" >> scripts/config/environment.rb 156 | echo "require File.join(File.dirname(__FILE__),'../lib/boot')" >> scripts/config/environment.rb 157 | echo "require File.dirname(__FILE__) + \"/env_#{GRADER_ENV}.rb\"" >> scripts/config/environment.rb 158 | 159 | # compiling box 160 | MACHINE_TYPE=`uname -m` 161 | if [ ${MACHINE_TYPE} == 'x86_64' ]; then 162 | gcc -std=c99 -o scripts/std-script/box scripts/std-script/box64-new.c 163 | else 164 | g++ -o scripts/std-script/box scripts/std-script/box.cc 165 | fi 166 | 167 | 168 | cd .. 169 | 170 | echo "Now you are ready to run cafe grader...." 171 | echo 172 | echo "Try:" 173 | echo 174 | echo " cd web" 175 | echo " rails s" 176 | echo 177 | echo "and access web at http://localhost:3000/" 178 | echo "The root username is 'root', its password is 'ioionrails'." 179 | 180 | -------------------------------------------------------------------------------- /installer/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #installation script for cafe-grader, for ubuntu 16.04 4 | 5 | echo "This script will install and configure Cafe grader." 6 | 7 | RUBY_VERSION=2.6.3 8 | RUBY_GEMSET=grader 9 | 10 | echo "Installing Ruby $RUBY_VERSION in RVM" 11 | 12 | rvm install $RUBY_VERSION 13 | rvm use $RUBY_VERSION@$RUBY_GEMSET 14 | 15 | echo "Fetching Cafe Grader from Git repositories" 16 | 17 | echo "Fetching web interface" 18 | 19 | mkdir cafe_grader 20 | cd cafe_grader 21 | git clone -q git://github.com/cafe-grader-team/cafe-grader-web.git web 22 | 23 | echo "Configuring rails app" 24 | 25 | cp web/config/application.rb.SAMPLE web/config/application.rb 26 | cp web/config/initializers/cafe_grader_config.rb.SAMPLE web/config/initializers/cafe_grader_config.rb 27 | 28 | #replace UTC in application.rb with the system timezone 29 | timezone='UTC' 30 | if [ -f '/etc/timezone' ]; then 31 | timezone=\"`cat /etc/timezone`\" 32 | else 33 | if [ -f '/etc/sysconfig/clock' ]; then 34 | timezone=`grep -e '^TIMEZONE' /etc/sysconfig/clock | grep -o -e '\".*\"'` 35 | fi 36 | fi 37 | replace="s!'UTC'!$timezone!g" 38 | sed -i $replace web/config/application.rb 39 | 40 | echo "At this point we will need MySQL user and database." 41 | echo "Have you created MySQL user and database for Cafe grader? (Y/N) " 42 | read ch 43 | 44 | if [ "$ch" = "n" -o "$ch" = "N" ] 45 | then 46 | echo "Please open another terminal and create the user and database for Cafe grader." 47 | echo "Don't forget to grant access to that database for the user." 48 | echo "Please have username, password, and database name ready before continue." 49 | echo 50 | echo "The following are instructions:" 51 | echo "1. Run mysql:" 52 | echo 53 | echo " mysql -u root -p" 54 | echo 55 | echo " if you have just installed mysql, the root password is the one that you have just entered" 56 | echo "2. Create a new database, a new user, and grant access to grader database:" 57 | echo 58 | echo " create user 'USERNAME'@'localhost' identified by 'PASSWORD';" 59 | echo " create database \`DATABASENEME\`;" 60 | echo " grant all on \`DATABASENAME\`.* to 'USERNAME'@'localhost';" 61 | echo 62 | echo " Replace USERNAME, PASSWORD, and DATABASENAME accordingly." 63 | echo 64 | echo "Hit enter when ready..." 65 | read dummy 66 | fi 67 | 68 | echo "Please provide grader database:" 69 | read database 70 | 71 | echo "Please provide grader username:" 72 | read username 73 | 74 | echo "Please provide $username password:" 75 | read password 76 | 77 | CAFE_PATH=`pwd` 78 | cd web 79 | 80 | echo "development:" > config/database.yml 81 | echo " adapter: mysql2" >> config/database.yml 82 | echo " encoding: utf8" >> config/database.yml 83 | echo " reconnect: false" >> config/database.yml 84 | echo " database: $database" >> config/database.yml 85 | echo " pool: 5" >> config/database.yml 86 | echo " username: $username" >> config/database.yml 87 | echo " password: $password" >> config/database.yml 88 | echo " host: localhost" >> config/database.yml 89 | echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml 90 | echo "" >> config/database.yml 91 | echo "production:" >> config/database.yml 92 | echo " adapter: mysql2" >> config/database.yml 93 | echo " encoding: utf8" >> config/database.yml 94 | echo " reconnect: false" >> config/database.yml 95 | echo " database: $database" >> config/database.yml 96 | echo " pool: 5" >> config/database.yml 97 | echo " username: $username" >> config/database.yml 98 | echo " password: $password" >> config/database.yml 99 | echo " host: localhost" >> config/database.yml 100 | echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml 101 | 102 | echo "Object.instance_eval{remove_const :GRADER_ROOT_DIR}" >> config/initializers/cafe_grader_config.rb 103 | echo "Object.instance_eval{remove_const :GRADING_RESULT_DIR}" >> config/initializers/cafe_grader_config.rb 104 | echo "GRADER_ROOT_DIR = '$CAFE_PATH/judge'" >> config/initializers/cafe_grader_config.rb 105 | echo "GRADING_RESULT_DIR = '$CAFE_PATH/judge/result'" >> config/initializers/cafe_grader_config.rb 106 | 107 | echo "Installing required gems" 108 | #gem install bundler 109 | #bundle install 110 | bundle 111 | 112 | echo "Running rake tasks to initialize database" 113 | 114 | rake db:migrate 115 | rake db:seed 116 | 117 | echo "Running rake tasks to precompile the assets" 118 | 119 | rake assets:precompile 120 | 121 | echo "setup the secret file" 122 | SECRET_A=`rake secret` 123 | SECRET_B=`rake secret` 124 | SECRET_C=`rake secret` 125 | echo "development:" > config/secrets.yml 126 | echo " secret_key_base: '$SECRET_A'" >> config/secrets.yml 127 | echo "test:" >> config/secrets.yml 128 | echo " secret_key_base: '$SECRET_B'" >> config/secrets.yml 129 | echo "production:" >> config/secrets.yml 130 | echo " secret_key_base: '$SECRET_C'" >> config/secrets.yml 131 | 132 | echo "Intalling web interface complete..." 133 | echo 134 | echo "Fetching grader" 135 | 136 | cd .. 137 | 138 | mkdir judge 139 | cd judge 140 | git clone -q git://github.com/cafe-grader-team/cafe-grader-judge-scripts.git scripts 141 | mkdir raw 142 | mkdir ev-exam 143 | mkdir ev 144 | mkdir result 145 | mkdir log 146 | 147 | echo "Configuring grader" 148 | 149 | cp scripts/config/env_exam.rb.SAMPLE scripts/config/env_exam.rb 150 | cp scripts/config/env_grading.rb.SAMPLE scripts/config/env_grading.rb 151 | 152 | # create new environment.rb file 153 | echo "RAILS_ROOT = '$CAFE_PATH/web'" > scripts/config/environment.rb 154 | echo "GRADER_ROOT = '$CAFE_PATH/judge/scripts'" >> scripts/config/environment.rb 155 | echo "require File.join(File.dirname(__FILE__),'../lib/boot')" >> scripts/config/environment.rb 156 | echo "require File.dirname(__FILE__) + \"/env_#{GRADER_ENV}.rb\"" >> scripts/config/environment.rb 157 | 158 | # compiling box 159 | MACHINE_TYPE=`uname -m` 160 | if [ ${MACHINE_TYPE} == 'x86_64' ]; then 161 | gcc -std=c99 -o scripts/std-script/box scripts/std-script/box64-new.c 162 | else 163 | g++ -o scripts/std-script/box scripts/std-script/box.cc 164 | fi 165 | 166 | 167 | cd .. 168 | 169 | echo "Now you are ready to run cafe grader...." 170 | echo 171 | echo "Try:" 172 | echo 173 | echo " cd web" 174 | echo " rails s" 175 | echo 176 | echo "and access web at http://localhost:3000/" 177 | echo "The root username is 'root', its password is 'ioionrails'." 178 | 179 | -------------------------------------------------------------------------------- /isolate/.gitignore: -------------------------------------------------------------------------------- 1 | docbook-xsl.css 2 | isolate 3 | isolate.1 4 | isolate.1.html 5 | *.o 6 | -------------------------------------------------------------------------------- /isolate/.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: gcc 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - asciidoc 9 | - libcap-dev 10 | - libxml2-utils 11 | - xsltproc 12 | - docbook-xml 13 | - docbook-xsl 14 | 15 | script: 16 | - make DESTDIR=/tmp/isolate 17 | - make DESTDIR=/tmp/isolate install 18 | -------------------------------------------------------------------------------- /isolate/LICENSE: -------------------------------------------------------------------------------- 1 | Isolate is free software: you can redistribute it and/or modify 2 | it under the terms of the GNU General Public License as published by 3 | the Free Software Foundation, either version 2 of the License, or 4 | (at your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | 11 | If you have less than 10 copies of the GPL on your system :-), 12 | you can find it at http://www.gnu.org/licenses/. 13 | -------------------------------------------------------------------------------- /isolate/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Isolate 2 | # (c) 2015--2018 Martin Mares 3 | # (c) 2017 Bernard Blackham 4 | 5 | all: isolate isolate.1 isolate.1.html isolate-check-environment 6 | 7 | CC=gcc 8 | CFLAGS=-std=gnu99 -Wall -Wextra -Wno-parentheses -Wno-unused-result -Wno-missing-field-initializers -Wstrict-prototypes -Wmissing-prototypes -D_GNU_SOURCE 9 | LIBS=-lcap 10 | 11 | VERSION=1.5 12 | YEAR=2018 13 | BUILD_DATE:=$(shell date '+%Y-%m-%d') 14 | BUILD_COMMIT:=$(shell if git rev-parse >/dev/null 2>/dev/null ; then git describe --always --tags ; else echo '' ; fi) 15 | 16 | PREFIX = $(DESTDIR)/usr/local 17 | VARPREFIX = $(DESTDIR)/var/local 18 | CONFIGDIR = $(PREFIX)/etc 19 | CONFIG = $(CONFIGDIR)/isolate 20 | BINDIR = $(PREFIX)/bin 21 | DATAROOTDIR = $(PREFIX)/share 22 | DATADIR = $(DATAROOTDIR) 23 | MANDIR = $(DATADIR)/man 24 | MAN1DIR = $(MANDIR)/man1 25 | BOXDIR = $(VARPREFIX)/lib/isolate 26 | 27 | isolate: isolate.o util.o rules.o cg.o config.o 28 | $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) 29 | 30 | %.o: %.c isolate.h config.h 31 | $(CC) $(CFLAGS) -c -o $@ $< 32 | 33 | isolate.o: CFLAGS += -DVERSION='"$(VERSION)"' -DYEAR='"$(YEAR)"' -DBUILD_DATE='"$(BUILD_DATE)"' -DBUILD_COMMIT='"$(BUILD_COMMIT)"' 34 | config.o: CFLAGS += -DCONFIG_FILE='"$(CONFIG)"' 35 | 36 | isolate.1: isolate.1.txt 37 | a2x -f manpage $< 38 | 39 | # The dependency on isolate.1 is there to serialize both calls of asciidoc, 40 | # which does not name temporary files safely. 41 | isolate.1.html: isolate.1.txt isolate.1 42 | a2x -f xhtml -D . $< 43 | 44 | clean: 45 | rm -f *.o 46 | rm -f isolate isolate.1 isolate.1.html 47 | rm -f docbook-xsl.css 48 | 49 | install: isolate isolate-check-environment 50 | install -d $(BINDIR) $(BOXDIR) $(CONFIGDIR) 51 | install isolate-check-environment $(BINDIR) 52 | install -m 4755 isolate $(BINDIR) 53 | install -m 644 default.cf $(CONFIG) 54 | 55 | install-doc: isolate.1 56 | install -d $(MAN1DIR) 57 | install -m 644 $< $(MAN1DIR)/$< 58 | 59 | release: isolate.1.html 60 | git tag v$(VERSION) 61 | git push --tags 62 | git archive --format=tar --prefix=isolate-$(VERSION)/ HEAD | gzip >isolate-$(VERSION).tar.gz 63 | rsync isolate-$(VERSION).tar.gz atrey:ftp/isolate/ 64 | rsync isolate.1.html jw:/var/www/moe/ 65 | ssh jw 'cd web && bin/release-prog isolate $(VERSION)' 66 | 67 | .PHONY: all clean install install-doc release 68 | -------------------------------------------------------------------------------- /isolate/README.md: -------------------------------------------------------------------------------- 1 | isolate 2 | ======= 3 | 4 | Isolate is a sandbox built to safely run untrusted executables, 5 | offering them a limited-access environment and preventing them from 6 | affecting the host system. It takes advantage of features specific to 7 | the Linux kernel, like namespaces and control groups. 8 | 9 | Isolate was developed by Martin Mareš () and Bernard Blackham 10 | (), who still maintain it. Several other people 11 | contributed patches for features and bug fixes (see Git history for a list). 12 | Thanks! 13 | 14 | Originally, Isolate was a part of the [Moe Contest Environment](http://www.ucw.cz/moe/), 15 | but it evolved to a separate project used by different 16 | contest systems, most prominently [CMS](https://github.com/cms-dev/cms). 17 | It now lives at [GitHub](https://github.com/ioi/isolate), 18 | where you can submit bug reports and feature requests. 19 | 20 | If you are interested in more details, please read Martin's 21 | and Bernard's [paper](http://mj.ucw.cz/papers/isolate.pdf) presented 22 | at the IOI Conference. Also, Isolate's [manual page](http://www.ucw.cz/moe/isolate.1.html) 23 | is available online. 24 | 25 | To compile Isolate, you need the headers for the libcap library 26 | (usually available in a libcap-dev package). 27 | 28 | You may need `a2x` (found in [AsciiDoc](http://www.methods.co.nz/asciidoc/a2x.1.html)) for building manual. 29 | But if you only want the isolate binary, you can just run `make isolate` 30 | -------------------------------------------------------------------------------- /isolate/TODO: -------------------------------------------------------------------------------- 1 | Examine the use of taskstats for measuring memory 2 | -------------------------------------------------------------------------------- /isolate/cg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Process Isolator -- Control Groups 3 | * 4 | * (c) 2012-2016 Martin Mares 5 | * (c) 2012-2014 Bernard Blackham 6 | */ 7 | 8 | #include "isolate.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | struct cg_controller_desc { 20 | const char *name; 21 | int optional; 22 | }; 23 | 24 | typedef enum { 25 | CG_MEMORY = 0, 26 | CG_CPUACCT, 27 | CG_CPUSET, 28 | CG_NUM_CONTROLLERS, 29 | CG_PARENT = 256, 30 | } cg_controller; 31 | 32 | static const struct cg_controller_desc cg_controllers[CG_NUM_CONTROLLERS+1] = { 33 | [CG_MEMORY] = { "memory", 0 }, 34 | [CG_CPUACCT] = { "cpuacct", 0 }, 35 | [CG_CPUSET] = { "cpuset", 1 }, 36 | [CG_NUM_CONTROLLERS] = { NULL, 0 }, 37 | }; 38 | 39 | #define FOREACH_CG_CONTROLLER(_controller) \ 40 | for (cg_controller (_controller) = 0; \ 41 | (_controller) < CG_NUM_CONTROLLERS; (_controller)++) 42 | 43 | static const char * 44 | cg_controller_name(cg_controller c) 45 | { 46 | assert(c < CG_NUM_CONTROLLERS); 47 | return cg_controllers[c].name; 48 | } 49 | 50 | static int 51 | cg_controller_optional(cg_controller c) 52 | { 53 | assert(c < CG_NUM_CONTROLLERS); 54 | return cg_controllers[c].optional; 55 | } 56 | 57 | static char cg_name[256]; 58 | static char cg_parent_name[256]; 59 | 60 | #define CG_BUFSIZE 1024 61 | 62 | static void 63 | cg_makepath(char *buf, size_t len, cg_controller c, const char *attr) 64 | { 65 | snprintf(buf, len, "%s/%s/%s/%s", 66 | cf_cg_root, 67 | cg_controller_name(c & ~CG_PARENT), 68 | (c & CG_PARENT) ? cg_parent_name : cg_name, 69 | attr); 70 | } 71 | 72 | static int 73 | cg_read(cg_controller controller, const char *attr, char *buf) 74 | { 75 | int result = 0; 76 | int maybe = 0; 77 | if (attr[0] == '?') 78 | { 79 | attr++; 80 | maybe = 1; 81 | } 82 | 83 | char path[256]; 84 | cg_makepath(path, sizeof(path), controller, attr); 85 | 86 | int fd = open(path, O_RDONLY); 87 | if (fd < 0) 88 | { 89 | if (maybe) 90 | goto fail; 91 | die("Cannot read %s: %m", path); 92 | } 93 | 94 | int n = read(fd, buf, CG_BUFSIZE); 95 | if (n < 0) 96 | { 97 | if (maybe) 98 | goto fail_close; 99 | die("Cannot read %s: %m", path); 100 | } 101 | if (n >= CG_BUFSIZE - 1) 102 | die("Attribute %s too long", path); 103 | if (n > 0 && buf[n-1] == '\n') 104 | n--; 105 | buf[n] = 0; 106 | 107 | if (verbose > 1) 108 | msg("CG: Read %s = <%s>\n", attr, buf); 109 | 110 | result = 1; 111 | fail_close: 112 | close(fd); 113 | fail: 114 | return result; 115 | } 116 | 117 | static void __attribute__((format(printf,3,4))) 118 | cg_write(cg_controller controller, const char *attr, const char *fmt, ...) 119 | { 120 | int maybe = 0; 121 | if (attr[0] == '?') 122 | { 123 | attr++; 124 | maybe = 1; 125 | } 126 | 127 | va_list args; 128 | va_start(args, fmt); 129 | 130 | char buf[CG_BUFSIZE]; 131 | int n = vsnprintf(buf, sizeof(buf), fmt, args); 132 | if (n >= CG_BUFSIZE) 133 | die("cg_write: Value for attribute %s is too long", attr); 134 | 135 | if (verbose > 1) 136 | msg("CG: Write %s = %s", attr, buf); 137 | 138 | char path[256]; 139 | cg_makepath(path, sizeof(path), controller, attr); 140 | 141 | int fd = open(path, O_WRONLY | O_TRUNC); 142 | if (fd < 0) 143 | { 144 | if (maybe) 145 | goto fail; 146 | else 147 | die("Cannot write %s: %m", path); 148 | } 149 | 150 | int written = write(fd, buf, n); 151 | if (written < 0) 152 | { 153 | if (maybe) 154 | goto fail_close; 155 | else 156 | die("Cannot set %s to %s: %m", path, buf); 157 | } 158 | if (written != n) 159 | die("Short write to %s (%d out of %d bytes)", path, written, n); 160 | 161 | fail_close: 162 | close(fd); 163 | fail: 164 | va_end(args); 165 | } 166 | 167 | void 168 | cg_init(void) 169 | { 170 | if (!cg_enable) 171 | return; 172 | 173 | if (!dir_exists(cf_cg_root)) 174 | die("Control group filesystem at %s not mounted", cf_cg_root); 175 | 176 | if (cf_cg_parent) 177 | { 178 | snprintf(cg_name, sizeof(cg_name), "%s/box-%d", cf_cg_parent, box_id); 179 | snprintf(cg_parent_name, sizeof(cg_parent_name), "%s", cf_cg_parent); 180 | } 181 | else 182 | { 183 | snprintf(cg_name, sizeof(cg_name), "box-%d", box_id); 184 | strcpy(cg_parent_name, "."); 185 | } 186 | msg("Using control group %s under parent %s\n", cg_name, cg_parent_name); 187 | } 188 | 189 | void 190 | cg_prepare(void) 191 | { 192 | if (!cg_enable) 193 | return; 194 | 195 | struct stat st; 196 | char buf[CG_BUFSIZE]; 197 | char path[256]; 198 | 199 | FOREACH_CG_CONTROLLER(controller) 200 | { 201 | cg_makepath(path, sizeof(path), controller, ""); 202 | if (stat(path, &st) >= 0 || errno != ENOENT) 203 | { 204 | msg("Control group %s already exists, trying to empty it.\n", path); 205 | if (rmdir(path) < 0) 206 | die("Failed to reset control group %s: %m", path); 207 | } 208 | 209 | if (mkdir(path, 0777) < 0 && !cg_controller_optional(controller)) 210 | die("Failed to create control group %s: %m", path); 211 | } 212 | 213 | // If the cpuset module is enabled, set up allowed cpus and memory nodes. 214 | // If per-box configuration exists, use it; otherwise, inherit the settings 215 | // from the parent cgroup. 216 | struct cf_per_box *cf = cf_current_box(); 217 | if (cg_read(CG_PARENT | CG_CPUSET, "?cpuset.cpus", buf)) 218 | cg_write(CG_CPUSET, "cpuset.cpus", "%s", cf->cpus ? cf->cpus : buf); 219 | if (cg_read(CG_PARENT | CG_CPUSET, "?cpuset.mems", buf)) 220 | cg_write(CG_CPUSET, "cpuset.mems", "%s", cf->mems ? cf->mems : buf); 221 | } 222 | 223 | void 224 | cg_enter(void) 225 | { 226 | if (!cg_enable) 227 | return; 228 | 229 | msg("Entering control group %s\n", cg_name); 230 | 231 | FOREACH_CG_CONTROLLER(controller) 232 | { 233 | if (cg_controller_optional(controller)) 234 | cg_write(controller, "?tasks", "%d\n", (int) getpid()); 235 | else 236 | cg_write(controller, "tasks", "%d\n", (int) getpid()); 237 | } 238 | 239 | if (cg_memory_limit) 240 | { 241 | cg_write(CG_MEMORY, "memory.limit_in_bytes", "%lld\n", (long long) cg_memory_limit << 10); 242 | cg_write(CG_MEMORY, "?memory.memsw.limit_in_bytes", "%lld\n", (long long) cg_memory_limit << 10); 243 | cg_write(CG_MEMORY, "memory.max_usage_in_bytes", "0\n"); 244 | cg_write(CG_MEMORY, "?memory.memsw.max_usage_in_bytes", "0\n"); 245 | } 246 | 247 | if (cg_timing) 248 | cg_write(CG_CPUACCT, "cpuacct.usage", "0\n"); 249 | } 250 | 251 | int 252 | cg_get_run_time_ms(void) 253 | { 254 | if (!cg_enable) 255 | return 0; 256 | 257 | char buf[CG_BUFSIZE]; 258 | cg_read(CG_CPUACCT, "cpuacct.usage", buf); 259 | unsigned long long ns = atoll(buf); 260 | return ns / 1000000; 261 | } 262 | 263 | void 264 | cg_stats(void) 265 | { 266 | if (!cg_enable) 267 | return; 268 | 269 | char buf[CG_BUFSIZE]; 270 | 271 | // Memory usage statistics 272 | unsigned long long mem=0, memsw=0; 273 | if (cg_read(CG_MEMORY, "?memory.max_usage_in_bytes", buf)) 274 | mem = atoll(buf); 275 | if (cg_read(CG_MEMORY, "?memory.memsw.max_usage_in_bytes", buf)) 276 | { 277 | memsw = atoll(buf); 278 | if (memsw > mem) 279 | mem = memsw; 280 | } 281 | if (mem) 282 | meta_printf("cg-mem:%lld\n", mem >> 10); 283 | 284 | // OOM kill detection 285 | if (cg_read(CG_MEMORY, "?memory.oom_control", buf)) 286 | { 287 | int oom_killed = 0; 288 | char *s = buf; 289 | while (s) 290 | { 291 | if (sscanf(s, "oom_kill %d", &oom_killed) == 1 && oom_killed) 292 | { 293 | meta_printf("cg-oom-killed:1\n"); 294 | break; 295 | } 296 | s = strchr(s, '\n'); 297 | if (s) 298 | s++; 299 | } 300 | } 301 | } 302 | 303 | void 304 | cg_remove(void) 305 | { 306 | char buf[CG_BUFSIZE]; 307 | 308 | if (!cg_enable) 309 | return; 310 | 311 | FOREACH_CG_CONTROLLER(controller) 312 | { 313 | // The cgroup can be non-existent at this moment (e.g., --cleanup before the first --init) 314 | if (!cg_read(controller, "?tasks", buf)) 315 | continue; 316 | 317 | if (buf[0]) 318 | die("Some tasks left in controller %s of cgroup %s, failed to remove it", 319 | cg_controller_name(controller), cg_name); 320 | 321 | char path[256]; 322 | cg_makepath(path, sizeof(path), controller, ""); 323 | 324 | if (rmdir(path) < 0) 325 | die("Cannot remove control group %s: %m", path); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /isolate/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Process Isolator -- Configuration File 3 | * 4 | * (c) 2016 Martin Mares 5 | */ 6 | 7 | #include "isolate.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define MAX_LINE_LEN 1024 15 | 16 | char *cf_box_root; 17 | char *cf_cg_root; 18 | char *cf_cg_parent; 19 | int cf_first_uid; 20 | int cf_first_gid; 21 | int cf_num_boxes; 22 | 23 | static int line_number; 24 | static struct cf_per_box *per_box_configs; 25 | 26 | static void NONRET 27 | cf_err(char *msg) 28 | { 29 | die("Error in config file, line %d: %s", line_number, msg); 30 | } 31 | 32 | static char * 33 | cf_string(char *val) 34 | { 35 | return xstrdup(val); 36 | } 37 | 38 | static int 39 | cf_int(char *val) 40 | { 41 | char *end; 42 | errno = 0; 43 | long int x = strtol(val, &end, 10); 44 | if (errno || end == val || end && *end) 45 | cf_err("Invalid number"); 46 | if ((long int)(int) x != x) 47 | cf_err("Number out of range"); 48 | return x; 49 | } 50 | 51 | static void 52 | cf_entry_toplevel(char *key, char *val) 53 | { 54 | if (!strcmp(key, "box_root")) 55 | cf_box_root = cf_string(val); 56 | else if (!strcmp(key, "cg_root")) 57 | cf_cg_root = cf_string(val); 58 | else if (!strcmp(key, "cg_parent")) 59 | cf_cg_parent = cf_string(val); 60 | else if (!strcmp(key, "first_uid")) 61 | cf_first_uid = cf_int(val); 62 | else if (!strcmp(key, "first_gid")) 63 | cf_first_gid = cf_int(val); 64 | else if (!strcmp(key, "num_boxes")) 65 | cf_num_boxes = cf_int(val); 66 | else 67 | cf_err("Unknown configuration item"); 68 | } 69 | 70 | static void 71 | cf_entry_compound(char *key, char *subkey, char *val) 72 | { 73 | if (strncmp(key, "box", 3)) 74 | cf_err("Unknown configuration section"); 75 | int box_id = cf_int(key + 3); 76 | struct cf_per_box *c = cf_per_box(box_id); 77 | 78 | if (!strcmp(subkey, "cpus")) 79 | c->cpus = cf_string(val); 80 | else if (!strcmp(subkey, "mems")) 81 | c->mems = cf_string(val); 82 | else 83 | cf_err("Unknown per-box configuration item"); 84 | } 85 | 86 | static void 87 | cf_entry(char *key, char *val) 88 | { 89 | char *dot = strchr(key, '.'); 90 | if (!dot) 91 | cf_entry_toplevel(key, val); 92 | else 93 | { 94 | *dot++ = 0; 95 | cf_entry_compound(key, dot, val); 96 | } 97 | } 98 | 99 | static void 100 | cf_check(void) 101 | { 102 | if (!cf_box_root || 103 | !cf_cg_root || 104 | !cf_first_uid || 105 | !cf_first_gid || 106 | !cf_num_boxes) 107 | cf_err("Configuration is not complete"); 108 | } 109 | 110 | void 111 | cf_parse(void) 112 | { 113 | FILE *f = fopen(CONFIG_FILE, "r"); 114 | if (!f) 115 | die("Cannot open %s: %m", CONFIG_FILE); 116 | 117 | char line[MAX_LINE_LEN]; 118 | while (fgets(line, sizeof(line), f)) 119 | { 120 | line_number++; 121 | char *nl = strchr(line, '\n'); 122 | if (!nl) 123 | cf_err("Line not terminated or too long"); 124 | *nl = 0; 125 | 126 | if (!line[0] || line[0] == '#') 127 | continue; 128 | 129 | char *s = line; 130 | while (*s && *s != ' ' && *s != '\t' && *s != '=') 131 | s++; 132 | while (*s == ' ' || *s == '\t') 133 | *s++ = 0; 134 | if (*s != '=') 135 | cf_err("Syntax error, expecting key=value"); 136 | *s++ = 0; 137 | while (*s == ' ' || *s == '\t') 138 | *s++ = 0; 139 | 140 | cf_entry(line, s); 141 | } 142 | 143 | fclose(f); 144 | cf_check(); 145 | } 146 | 147 | struct cf_per_box * 148 | cf_per_box(int box_id) 149 | { 150 | struct cf_per_box *c; 151 | 152 | for (c = per_box_configs; c; c = c->next) 153 | if (c->box_id == box_id) 154 | return c; 155 | 156 | c = xmalloc(sizeof(*c)); 157 | memset(c, 0, sizeof(*c)); 158 | c->next = per_box_configs; 159 | per_box_configs = c; 160 | c->box_id = box_id; 161 | return c; 162 | } 163 | 164 | struct cf_per_box * 165 | cf_current_box(void) 166 | { 167 | return cf_per_box(box_id); 168 | } 169 | -------------------------------------------------------------------------------- /isolate/default.cf: -------------------------------------------------------------------------------- 1 | # This is a configuration file for Isolate 2 | 3 | # All sandboxes are created under this directory. 4 | # To avoid symlink attacks, this directory and all its ancestors 5 | # must be writeable only to root. 6 | box_root = /var/local/lib/isolate 7 | 8 | # Root of the control group hierarchy 9 | cg_root = /sys/fs/cgroup 10 | 11 | # If the following variable is defined, the per-box cgroups 12 | # are created as sub-groups of the named cgroup 13 | #cg_parent = boxes 14 | 15 | # Block of UIDs and GIDs reserved for sandboxes 16 | first_uid = 60000 17 | first_gid = 60000 18 | num_boxes = 1000 19 | 20 | # Per-box settings of the set of allowed CPUs and NUMA nodes 21 | # (see linux/Documentation/cgroups/cpusets.txt for precise syntax) 22 | 23 | #box0.cpus = 4-7 24 | #box0.mems = 1 25 | -------------------------------------------------------------------------------- /isolate/isolate-check-environment: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Identifies potential sources issues when using isolate. 4 | # 5 | # (c) 2017 Bernard Blackham 6 | # 7 | 8 | usage() { 9 | cat <&2 10 | Usage: $0 [-q|--quiet] [-e|--execute] 11 | 12 | Use this script to identify sources of run-time variability and other issues on 13 | Linux machines which may affect isolate. If --execute is not specified, the 14 | recommended actions are written to stdout as an executable shell script, 15 | otherwise, using --execute will attempt to make changes to make the system 16 | behave more deterministically. The changes performed by --execute persist only 17 | until a reboot. To persist across reboots, the standard output from this script 18 | should be added to /etc/rc.local or some other script that is run on each boot. 19 | Alternately, you could add the following line to /etc/rc.local to automatically 20 | apply these changes on boot, but use this with caution as not all issues can 21 | be resolved in this way. 22 | 23 | isolate-check-environment --quiet --execute 24 | 25 | The exit status of this script will be 0 if all checks pass, or 1 if some 26 | checks have failed. 27 | 28 | Note that there are more strategies to reduce run-time variability further. 29 | See the man page of isolate for details under REPRODUCIBILITY. 30 | EOT 31 | exit 2 32 | } 33 | 34 | # Parse options. 35 | args=$(getopt -o "ehq" --long "execute,help,quiet" -- "$@") || usage 36 | eval set -- "$args" 37 | quiet= 38 | execute= 39 | while : ; do 40 | case "$1" in 41 | -q|--quiet) quiet=1 ; shift ;; 42 | -e|--execute) execute=1 ; shift ;; 43 | -h|--help) usage ;; 44 | --) shift ; break ;; 45 | *) usage ;; 46 | esac 47 | done 48 | [ -n "$*" ] && usage 49 | 50 | # Some helper boilerplate machinery. 51 | exit_status=0 52 | red=$(tput setaf 1) 53 | green=$(tput setaf 2) 54 | yellow=$(tput setaf 3) 55 | normal=$(tput sgr0) 56 | 57 | # Return true (0) if we are being quiet. 58 | quiet() { 59 | [ -n "$quiet" ] 60 | } 61 | 62 | # Print all arguments to stderr as warning. 63 | warn() { 64 | quiet || echo WARNING: "$*" >&2 65 | } 66 | 67 | # Print first argument to stderr as warning, and second argument to stdout as 68 | # the recommended remedial action, or execute if --execute is given. 69 | action() { 70 | quiet || warn "$1" 71 | if [ -n "$execute" ] ; then 72 | quiet || echo "+ $2" 73 | sh -c "$2" 74 | else 75 | quiet || echo $2 76 | fi 77 | } 78 | 79 | print_start_check() { 80 | quiet && return 81 | print_check_status=1 82 | echo -n "Checking for $@ ... " >&2 83 | } 84 | 85 | print_fail() { 86 | exit_status=1 87 | quiet && return 88 | [ -n "$print_check_status" ] && echo "${red}FAIL${normal}" >&2 89 | print_check_status= 90 | } 91 | 92 | print_dubious() { 93 | exit_status=1 94 | quiet && return 95 | [ -n "$print_check_status" ] && echo "${yellow}CAUTION${normal}" >&2 96 | print_check_status= 97 | } 98 | 99 | print_skipped() { 100 | quiet && return 101 | [ -n "$print_check_status" ] && echo "SKIPPED (not detected)" >&2 102 | print_check_status= 103 | } 104 | 105 | print_finish() { 106 | quiet && return 107 | [ -n "$print_check_status" ] && echo "${green}PASS${normal}" >&2 108 | print_check_status= 109 | } 110 | 111 | # Check that cgroups are enabled. 112 | cgroup_check() { 113 | local cgroup=$1 114 | print_start_check "cgroup support for $cgroup" 115 | if ! test -f "/sys/fs/cgroup/$cgroup/tasks" ; then 116 | print_dubious 117 | warn "the $cgroup is not present. isolate --cg cannot be used." 118 | fi 119 | print_finish 120 | } 121 | cgroup_check memory 122 | cgroup_check cpuacct 123 | cgroup_check cpuset 124 | 125 | # Check that swap is either disabled or accounted for. 126 | swap_check() { 127 | print_start_check "swap" 128 | # If swap is disabled, there is nothing to worry about. 129 | local swaps 130 | swaps=$(swapon --noheadings) 131 | if [ -n "$swaps" ] ; then 132 | # Swap is enabled. We had better have the memsw support in the memory 133 | # cgroup. 134 | if ! test -f "/sys/fs/cgroup/memory/memory.memsw.usage_in_bytes" ; then 135 | print_fail 136 | action \ 137 | "swap is enabled, but swap accounting is not. isolate will not be able to enforce memory limits." \ 138 | "swapoff -a" 139 | else 140 | print_dubious 141 | warn "swap is enabled, and although accounted for, may still give run-time variability under memory pressure." 142 | fi 143 | fi 144 | print_finish 145 | } 146 | swap_check 147 | 148 | # Check that CPU frequency scaling is disabled. 149 | cpufreq_check() { 150 | print_start_check "CPU frequency scaling" 151 | local anycpus policy 152 | anycpus= 153 | # Ensure cpufreq governor is set to performance on all CPUs 154 | for cpufreq_file in $(find /sys/devices/system/cpu/cpufreq/ -name scaling_governor) ; do 155 | policy=$(cat $cpufreq_file) 156 | if [ "$policy" != "performance" ] ; then 157 | print_fail 158 | action \ 159 | "cpufreq governor set to '$policy', but 'performance' would be better" \ 160 | "echo performance > $cpufreq_file" 161 | fi 162 | anycpus=1 163 | done 164 | [ -z "$anycpus" ] && print_skipped 165 | print_finish 166 | } 167 | cpufreq_check 168 | 169 | # Check that address space layout randomisation is disabled. 170 | aslr_check() { 171 | print_start_check "kernel address space randomisation" 172 | local val 173 | if val=$(cat /proc/sys/kernel/randomize_va_space 2>/dev/null) ; then 174 | if [ "$val" -ne 0 ] ; then 175 | print_fail 176 | action \ 177 | "address space randomisation is enabled." \ 178 | "echo 0 > /proc/sys/kernel/randomize_va_space" 179 | fi 180 | else 181 | print_skipped 182 | fi 183 | print_finish 184 | } 185 | aslr_check 186 | 187 | # Check that transparent huge-pages are disabled, as this leads to 188 | # non-determinism depending on whether the kernel can allocate 2 MiB pages or 189 | # not. 190 | thp_check() { 191 | print_start_check "transparent hugepage support" 192 | local val 193 | if val=$(cat /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null) ; then 194 | case $val in 195 | *'[never]'*) ;; 196 | *) print_fail 197 | action \ 198 | "transparent hugepages are enabled." \ 199 | "echo never > /sys/kernel/mm/transparent_hugepage/enabled" ;; 200 | esac 201 | fi 202 | if val=$(cat /sys/kernel/mm/transparent_hugepage/defrag 2>/dev/null) ; then 203 | case $val in 204 | *'[never]'*) ;; 205 | *) print_fail 206 | action \ 207 | "transparent hugepage defrag is enabled." \ 208 | "echo never > /sys/kernel/mm/transparent_hugepage/defrag" ;; 209 | esac 210 | fi 211 | if val=$(cat /sys/kernel/mm/transparent_hugepage/khugepaged/defrag 2>/dev/null) ; then 212 | if [ "$val" -ne 0 ] ; then 213 | print_fail 214 | action \ 215 | "khugepaged defrag is enabled." \ 216 | "echo 0 > /sys/kernel/mm/transparent_hugepage/khugepaged/defrag" 217 | fi 218 | fi 219 | print_finish 220 | } 221 | thp_check 222 | 223 | 224 | exit $exit_status 225 | -------------------------------------------------------------------------------- /isolate/isolate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Process Isolator 3 | * 4 | * (c) 2012-2017 Martin Mares 5 | * (c) 2012-2014 Bernard Blackham 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define NONRET __attribute__((noreturn)) 13 | #define UNUSED __attribute__((unused)) 14 | #define ARRAY_SIZE(a) (int)(sizeof(a)/sizeof(a[0])) 15 | 16 | /* isolate.c */ 17 | 18 | void die(char *msg, ...) NONRET; 19 | void NONRET __attribute__((format(printf,1,2))) err(char *msg, ...); 20 | void __attribute__((format(printf,1,2))) msg(char *msg, ...); 21 | 22 | extern int pass_environ; 23 | extern int verbose; 24 | extern int block_quota; 25 | extern int inode_quota; 26 | extern int cg_enable; 27 | extern int cg_memory_limit; 28 | extern int cg_timing; 29 | 30 | extern int box_id; 31 | extern uid_t box_uid, orig_uid; 32 | extern gid_t box_gid, orig_gid; 33 | 34 | /* util.c */ 35 | 36 | void *xmalloc(size_t size); 37 | char *xstrdup(char *str); 38 | int dir_exists(char *path); 39 | void rmtree(char *path); 40 | void make_dir(char *path); 41 | void chowntree(char *path, uid_t uid, gid_t gid); 42 | void close_all_fds(void); 43 | 44 | void meta_open(const char *name); 45 | void meta_close(void); 46 | void __attribute__((format(printf,1,2))) meta_printf(const char *fmt, ...); 47 | 48 | /* rules.c */ 49 | 50 | int set_env_action(char *a0); 51 | char **setup_environment(void); 52 | 53 | void init_dir_rules(void); 54 | int set_dir_action(char *arg); 55 | void apply_dir_rules(int with_defaults); 56 | 57 | void set_quota(void); 58 | 59 | /* cg.c */ 60 | 61 | void cg_init(void); 62 | void cg_prepare(void); 63 | void cg_enter(void); 64 | int cg_get_run_time_ms(void); 65 | void cg_stats(void); 66 | void cg_remove(void); 67 | 68 | /* config.c */ 69 | 70 | extern char *cf_box_root; 71 | extern char *cf_cg_root; 72 | extern char *cf_cg_parent; 73 | extern int cf_first_uid; 74 | extern int cf_first_gid; 75 | extern int cf_num_boxes; 76 | 77 | struct cf_per_box { 78 | struct cf_per_box *next; 79 | int box_id; 80 | char *cpus; 81 | char *mems; 82 | }; 83 | 84 | void cf_parse(void); 85 | struct cf_per_box *cf_per_box(int box_id); 86 | struct cf_per_box *cf_current_box(void); 87 | -------------------------------------------------------------------------------- /isolate/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Process Isolator -- Utility Functions 3 | * 4 | * (c) 2012-2017 Martin Mares 5 | * (c) 2012-2014 Bernard Blackham 6 | */ 7 | 8 | #include "isolate.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | void * 21 | xmalloc(size_t size) 22 | { 23 | void *p = malloc(size); 24 | if (!p) 25 | die("Out of memory"); 26 | return p; 27 | } 28 | 29 | char * 30 | xstrdup(char *str) 31 | { 32 | char *p = strdup(str); 33 | if (!p) 34 | die("Out of memory"); 35 | return p; 36 | } 37 | 38 | int 39 | dir_exists(char *path) 40 | { 41 | struct stat st; 42 | return (stat(path, &st) >= 0 && S_ISDIR(st.st_mode)); 43 | } 44 | 45 | void 46 | make_dir(char *path) 47 | { 48 | char *sep = (path[0] == '/' ? path+1 : path); 49 | 50 | for (;;) 51 | { 52 | sep = strchr(sep, '/'); 53 | if (sep) 54 | *sep = 0; 55 | 56 | if (mkdir(path, 0777) < 0 && errno != EEXIST) 57 | die("Cannot create directory %s: %m", path); 58 | 59 | if (!sep) 60 | break; 61 | *sep++ = '/'; 62 | } 63 | 64 | // mkdir() above may have returned EEXIST even if the path was not 65 | // a directory. Ensure that it is. 66 | struct stat st; 67 | if (stat(path, &st) < 0) 68 | die("Cannot stat %s: %m", path); 69 | if (!S_ISDIR(st.st_mode)) 70 | die("Cannot create %s: already exists, but not a directory", path); 71 | } 72 | 73 | 74 | static int 75 | rmtree_helper(const char *fpath, const struct stat *sb, int typeflag UNUSED, struct FTW *ftwbuf UNUSED) 76 | { 77 | if (S_ISDIR(sb->st_mode)) 78 | { 79 | if (rmdir(fpath) < 0) 80 | die("Cannot rmdir %s: %m", fpath); 81 | } 82 | else 83 | { 84 | if (unlink(fpath) < 0) 85 | die("Cannot unlink %s: %m", fpath); 86 | } 87 | return 0; 88 | } 89 | 90 | void 91 | rmtree(char *path) 92 | { 93 | nftw(path, rmtree_helper, 32, FTW_MOUNT | FTW_PHYS | FTW_DEPTH); 94 | } 95 | 96 | static uid_t chown_uid; 97 | static gid_t chown_gid; 98 | 99 | static int 100 | chowntree_helper(const char *fpath, const struct stat *sb UNUSED, int typeflag UNUSED, struct FTW *ftwbuf UNUSED) 101 | { 102 | if (lchown(fpath, chown_uid, chown_gid) < 0) 103 | die("Cannot chown %s: %m", fpath); 104 | else 105 | return 0; 106 | } 107 | 108 | void 109 | chowntree(char *path, uid_t uid, gid_t gid) 110 | { 111 | chown_uid = uid; 112 | chown_gid = gid; 113 | nftw(path, chowntree_helper, 32, FTW_MOUNT | FTW_PHYS); 114 | } 115 | 116 | static int fd_to_keep = -1; 117 | 118 | void 119 | close_all_fds(void) 120 | { 121 | /* Close all file descriptors except 0, 1, 2 */ 122 | 123 | DIR *dir = opendir("/proc/self/fd"); 124 | if (!dir) 125 | die("Cannot open /proc/self/fd: %m"); 126 | int dir_fd = dirfd(dir); 127 | 128 | struct dirent *e; 129 | while (e = readdir(dir)) 130 | { 131 | char *end; 132 | long int fd = strtol(e->d_name, &end, 10); 133 | if (*end) 134 | continue; 135 | if (fd >= 0 && fd <= 2 || fd == dir_fd || fd == fd_to_keep) 136 | continue; 137 | close(fd); 138 | } 139 | 140 | closedir(dir); 141 | } 142 | 143 | /*** Meta-files ***/ 144 | 145 | static FILE *metafile; 146 | 147 | void 148 | meta_open(const char *name) 149 | { 150 | if (!strcmp(name, "-")) 151 | { 152 | metafile = stdout; 153 | return; 154 | } 155 | if (setfsuid(getuid()) < 0) 156 | die("Failed to switch FS UID: %m"); 157 | metafile = fopen(name, "w"); 158 | if (setfsuid(geteuid()) < 0) 159 | die("Failed to switch FS UID back: %m"); 160 | if (!metafile) 161 | die("Failed to open metafile '%s'",name); 162 | fd_to_keep = fileno(metafile); 163 | } 164 | 165 | void 166 | meta_close(void) 167 | { 168 | if (metafile && metafile != stdout) 169 | fclose(metafile); 170 | } 171 | 172 | void 173 | meta_printf(const char *fmt, ...) 174 | { 175 | if (!metafile) 176 | return; 177 | 178 | va_list args; 179 | va_start(args, fmt); 180 | vfprintf(metafile, fmt, args); 181 | va_end(args); 182 | } 183 | -------------------------------------------------------------------------------- /lib/boot.rb: -------------------------------------------------------------------------------- 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 | 11 | -------------------------------------------------------------------------------- /lib/configuration.rb: -------------------------------------------------------------------------------- 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 85 | -------------------------------------------------------------------------------- /lib/dir_init.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | # DirInit::Manager handles directory initialization and clean-up when 4 | # there are many concurrent processes that wants to modify the 5 | # directory in the same way. 6 | # 7 | # An example usage is when each process wants to copy some temporary 8 | # files to the directory and delete these files after finishing its 9 | # job. Problems may occur when the first process delete the files 10 | # while the second process is still using the files. 11 | # 12 | # This library maintain a reference counter on the processes using the 13 | # directory. It locks the dir to manage critical section when 14 | # updating the reference counter. 15 | 16 | module DirInit 17 | 18 | class Manager 19 | 20 | def initialize(dir_name, usage_filename='.usage_counter') 21 | @dir_name = dir_name 22 | @usage_filename = usage_filename 23 | end 24 | 25 | def lock_filename 26 | return @dir_name + '/lockfile' 27 | end 28 | 29 | # Check if someone has initialized the dir. If not, call block. 30 | 31 | def setup # :yields: block 32 | dir = File.new(lock_filename,"w+") 33 | dir.flock(File::LOCK_EX) 34 | begin 35 | counter_filename = get_counter_filename 36 | if File.exist? counter_filename 37 | # someone is here 38 | f = File.new(counter_filename,"r+") 39 | counter = f.read.to_i 40 | f.seek(0) 41 | f.write("#{counter+1}\n") 42 | f.close 43 | else 44 | # i'm the first, create the counter file 45 | counter = 0 46 | f = File.new(counter_filename,"w") 47 | f.write("1\n") 48 | f.close 49 | end 50 | 51 | # if no one is here 52 | if counter == 0 53 | if block_given? 54 | yield 55 | end 56 | end 57 | 58 | rescue 59 | raise 60 | 61 | ensure 62 | # make sure it unlock the directory 63 | dir.flock(File::LOCK_UN) 64 | dir.close 65 | end 66 | end 67 | 68 | # Check if I am the last one using the dir. If true, call block. 69 | 70 | def teardown 71 | dir = File.new(lock_filename) 72 | dir.flock(File::LOCK_EX) 73 | begin 74 | counter_filename = get_counter_filename 75 | if File.exist? counter_filename 76 | # someone is here 77 | f = File.new(counter_filename,"r+") 78 | counter = f.read.to_i 79 | f.seek(0) 80 | f.write("#{counter-1}\n") 81 | f.close 82 | 83 | if counter == 1 84 | # i'm the last one 85 | 86 | File.delete(counter_filename) 87 | if block_given? 88 | yield 89 | end 90 | end 91 | else 92 | # This is BAD 93 | raise "Error: reference count missing" 94 | end 95 | 96 | rescue 97 | raise 98 | 99 | ensure 100 | # make sure it unlock the directory 101 | dir.flock(File::LOCK_UN) 102 | dir.close 103 | end 104 | end 105 | 106 | protected 107 | 108 | def get_counter_filename 109 | return File.join(@dir_name,@usage_filename) 110 | end 111 | 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/engine.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require File.join(File.dirname(__FILE__),'dir_init') 3 | 4 | module Grader 5 | 6 | # 7 | # A grader engine grades a submission, against anything: a test 8 | # data, or a user submitted test data. It uses two helpers objects: 9 | # room_maker and reporter. 10 | # 11 | class Engine 12 | 13 | attr_writer :room_maker 14 | attr_writer :reporter 15 | 16 | def initialize(options={}) 17 | # default options 18 | if not options.include? :room_maker 19 | options[:room_maker] = Grader::SubmissionRoomMaker.new 20 | end 21 | if not options.include? :reporter 22 | options[:reporter] = Grader::SubmissionReporter.new 23 | end 24 | 25 | @config = Grader::Configuration.get_instance 26 | 27 | @room_maker = options[:room_maker] 28 | @reporter = options[:reporter] 29 | end 30 | 31 | # takes a submission, asks room_maker to produce grading directories, 32 | # calls grader scripts, and asks reporter to save the result 33 | def grade(submission) 34 | current_dir = FileUtils.pwd 35 | 36 | user = submission.user 37 | problem = submission.problem 38 | 39 | begin 40 | # TODO: will have to create real exception for this 41 | if user==nil or problem == nil 42 | @reporter.report_error(submission,"Grading error: problem with submission") 43 | raise "engine: user or problem is nil" 44 | end 45 | 46 | # TODO: this is another hack so that output only task can be judged 47 | if submission.language!=nil 48 | language = submission.language.name 49 | lang_ext = submission.language.ext 50 | else 51 | language = 'c' 52 | lang_ext = 'c' 53 | end 54 | 55 | # This is needed because older version of std-scripts/compile 56 | # only look for c++. 57 | if language == 'cpp' 58 | language = 'c++' 59 | end 60 | 61 | # COMMENT: should it be only source.ext? 62 | if problem!=nil 63 | source_name = "#{problem.name}.#{lang_ext}" 64 | else 65 | source_name = "source.#{lang_ext}" 66 | end 67 | 68 | grading_dir = @room_maker.produce_grading_room(submission) 69 | @room_maker.save_source(submission,source_name) 70 | problem_home = @room_maker.find_problem_home(submission) 71 | 72 | # puts "GRADING DIR: #{grading_dir}" 73 | # puts "PROBLEM DIR: #{problem_home}" 74 | 75 | if !FileTest.exist?(problem_home) 76 | puts "PROBLEM DIR: #{problem_home}" 77 | raise "engine: No test data." 78 | end 79 | 80 | talk "ENGINE: grading dir at #{grading_dir} is created" 81 | talk "ENGINE: located problem home at #{problem_home} is created" 82 | 83 | # copy the source script, using lock 84 | dinit = DirInit::Manager.new(problem_home) 85 | 86 | # lock the directory and copy the scripts 87 | dinit.setup do 88 | copy_log = copy_script(problem_home) 89 | save_copy_log(problem_home,copy_log) 90 | talk "ENGINE: following std script is copied: #{copy_log.join ' '}" 91 | end 92 | 93 | 94 | call_judge(problem_home,language,grading_dir,source_name) 95 | 96 | @reporter.report(submission,"#{grading_dir}/test-result") 97 | 98 | # unlock the directory 99 | dinit.teardown do 100 | copy_log = load_copy_log(problem_home) 101 | clear_copy_log(problem_home) 102 | clear_script(copy_log,problem_home) 103 | end 104 | 105 | rescue RuntimeError => msg 106 | @reporter.report_error(submission, msg) 107 | puts "ERROR: #{msg}" 108 | 109 | ensure 110 | @room_maker.clean_up(submission) 111 | Dir.chdir(current_dir) # this is really important 112 | end 113 | end 114 | 115 | protected 116 | 117 | def talk(str) 118 | if @config.talkative 119 | puts str 120 | end 121 | end 122 | 123 | #change directory to problem_home 124 | #call the "judge" script 125 | def call_judge(problem_home,language,grading_dir,fname) 126 | ENV['PROBLEM_HOME'] = problem_home 127 | ENV['RUBYOPT'] = '' 128 | 129 | Dir.chdir grading_dir 130 | script_name = "#{problem_home}/script/judge" 131 | cmd = "#{script_name} #{language} #{fname}" 132 | talk "ENGINE: Calling Judge at #{cmd}" 133 | warn "ERROR: file does not exists #{script_name}" unless File.exists? script_name 134 | system(cmd) 135 | end 136 | 137 | def get_std_script_dir 138 | GRADER_ROOT + '/std-script' 139 | end 140 | 141 | #copy any script presented in std-script directory that is not in the problem_home 142 | #this allow a problem setter to provide their own version for each script 143 | #in case that they want to hack something 144 | def copy_script(problem_home) 145 | script_dir = "#{problem_home}/script" 146 | std_script_dir = get_std_script_dir 147 | 148 | raise "engine: std-script directory not found" if !FileTest.exist?(std_script_dir) 149 | 150 | scripts = Dir[std_script_dir + '/*'] 151 | 152 | copied = [] 153 | 154 | scripts.each do |s| 155 | fname = File.basename(s) 156 | next if FileTest.directory?(s) 157 | if !FileTest.exist?("#{script_dir}/#{fname}") 158 | copied << fname 159 | FileUtils.cp(s, "#{script_dir}", :preserve => true) 160 | end 161 | end 162 | 163 | return copied 164 | end 165 | 166 | def copy_log_filename(problem_home) 167 | return File.join(problem_home, '.scripts_copied') 168 | end 169 | 170 | def save_copy_log(problem_home, log) 171 | f = File.new(copy_log_filename(problem_home),"w") 172 | log.each do |fname| 173 | f.write("#{fname}\n") 174 | end 175 | f.close 176 | end 177 | 178 | def load_copy_log(problem_home) 179 | f = File.new(copy_log_filename(problem_home),"r") 180 | log = [] 181 | f.readlines.each do |line| 182 | log << line.strip 183 | end 184 | f.close 185 | log 186 | end 187 | 188 | def clear_copy_log(problem_home) 189 | File.delete(copy_log_filename(problem_home)) 190 | end 191 | 192 | def clear_script(log,problem_home) 193 | log.each do |s| 194 | FileUtils.rm("#{problem_home}/script/#{s}") 195 | end 196 | end 197 | 198 | def mkdir_if_does_not_exist(dirname) 199 | Dir.mkdir(dirname) if !FileTest.exist?(dirname) 200 | end 201 | 202 | end 203 | 204 | end 205 | -------------------------------------------------------------------------------- /lib/import_helper.rb: -------------------------------------------------------------------------------- 1 | 2 | def filter_filename_for_testrun(testrun, filename_list, raw_prefix='') 3 | l = [] 4 | regex = Regexp.new("^(#{Regexp.escape(raw_prefix)}#{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, raw_prefix='') 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,raw_prefix) 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 | 29 | -------------------------------------------------------------------------------- /lib/initializer.rb: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /lib/runner.rb: -------------------------------------------------------------------------------- 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 | # grade a specified problem for the latest submission of each user 28 | # optionally, on all submission when options[:all_sub] is set 29 | # optionally, only submission that has error (use when the problem itself has some problem) 30 | def grade_problem(problem, options={}) 31 | user_index = 0 32 | user_count = User.count 33 | User.find_each do |u| 34 | puts "user: #{u.login} (#{user_index}/#{user_count})" 35 | user_index += 1 36 | if options[:user_conditions]!=nil 37 | con_proc = options[:user_conditions] 38 | next if not con_proc.call(u) 39 | end 40 | if options[:all_sub] 41 | Submission.where(user_id: u.id,problem_id: problem.id).find_each do |sub| 42 | next if options[:only_err] and sub.grader_comment != 'error during grading' 43 | @engine.grade(sub) 44 | end 45 | else 46 | last_sub = Submission.find_last_by_user_and_problem(u.id,problem.id) 47 | if last_sub!=nil 48 | @engine.grade(last_sub) unless options[:only_err] and last_sub.grader_comment != 'error during grading' 49 | end 50 | end 51 | end 52 | end 53 | 54 | def grade_submission(submission) 55 | puts "RUNNER: grade submission: #{submission.id} by #{submission.try(:user).try(:full_name)}" 56 | @engine.grade(submission) 57 | end 58 | 59 | def grade_oldest_test_request 60 | test_request = TestRequest.get_inqueue_and_change_status(Task::STATUS_GRADING) 61 | if test_request!=nil 62 | @grader_process.report_active(test_request) if @grader_process!=nil 63 | 64 | @engine.grade(test_request) 65 | test_request.status_complete! 66 | @grader_process.report_inactive(test_request) if @grader_process!=nil 67 | end 68 | return test_request 69 | end 70 | 71 | end 72 | 73 | end 74 | 75 | -------------------------------------------------------------------------------- /lib/submission_helper.rb: -------------------------------------------------------------------------------- 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(options={}) 36 | options = {:dry_run => false, :result_collector => nil}.merge(options) 37 | @config = Grader::Configuration.get_instance 38 | @dry_run = options[:dry_run] 39 | @result_collector = options[:result_collector] 40 | end 41 | 42 | def report(sub,test_result_dir) 43 | result = read_result(test_result_dir) 44 | if @result_collector 45 | @result_collector.save(sub, 46 | result) 47 | end 48 | save_result(sub,result) 49 | end 50 | 51 | def report_error(sub,msg) 52 | save_result(sub,{:points => 0, 53 | :comment => "Grading error: #{msg}" }) 54 | end 55 | 56 | protected 57 | def read_result(test_result_dir) 58 | cmp_msg_fname = "#{test_result_dir}/compiler_message" 59 | if FileTest.exist?(cmp_msg_fname) 60 | cmp_file = File.open(cmp_msg_fname) 61 | cmp_msg = cmp_file.read 62 | cmp_file.close 63 | else 64 | cmp_msg = "" 65 | end 66 | 67 | result_fname = "#{test_result_dir}/result" 68 | comment_fname = "#{test_result_dir}/comment" 69 | runstat_fname = "#{test_result_dir}/run_stat" 70 | if FileTest.exist?(result_fname) 71 | comment = "" 72 | begin 73 | result_file = File.open(result_fname) 74 | result = result_file.readline.to_i 75 | result_file.close 76 | rescue 77 | result = 0 78 | comment = "error reading result file." 79 | end 80 | 81 | begin 82 | comment_file = File.open(comment_fname) 83 | comment += comment_file.readline.chomp 84 | comment_file.close 85 | rescue 86 | comment += "" 87 | end 88 | 89 | begin 90 | runstat_file = File.open(runstat_fname) 91 | max_runtime = runstat_file.readline.to_f 92 | peak_memory = runstat_file.readline.to_i 93 | rescue 94 | max_runtime = -1 95 | peak_memory = -1 96 | end 97 | 98 | 99 | return {points: result, 100 | comment: comment, 101 | cmp_msg: cmp_msg, 102 | max_runtime: max_runtime, 103 | peak_memory: peak_memory 104 | } 105 | else 106 | if FileTest.exist?("#{test_result_dir}/a.out") 107 | return {:points => 0, 108 | :comment => 'error during grading', 109 | :cmp_msg => cmp_msg} 110 | else 111 | return {:points => 0, 112 | :comment => 'compilation error', 113 | :cmp_msg => cmp_msg} 114 | end 115 | end 116 | end 117 | 118 | def save_result(submission,result) 119 | problem = submission.problem 120 | submission.graded_at = Time.now.gmtime 121 | points = result[:points] 122 | submission.points = points 123 | comment = @config.report_comment(result[:comment]) 124 | 125 | submission.peak_memory = result[:peak_memory] 126 | submission.max_runtime = result[:max_runtime] 127 | submission.effective_code_length =submission.source.length 128 | 129 | # 130 | # TODO: FIX THIS MESSAGE 131 | # 132 | if problem == nil 133 | submission.grader_comment = 'PASSED: ' + comment + '(problem is nil)' 134 | elsif points == problem.full_score 135 | #submission.grader_comment = 'PASSED: ' + comment 136 | submission.grader_comment = comment 137 | elsif result[:comment].chomp =~ /^[\[\]P]+$/ 138 | submission.grader_comment = 'PASSED: ' + comment + '(inconsistent score)' 139 | else 140 | #submission.grader_comment = 'FAILED: ' + comment 141 | submission.grader_comment = comment 142 | end 143 | 144 | #very lazy trim the string 145 | submission.compiler_message = result[:cmp_msg][0..60000] or '' 146 | 147 | if not @dry_run 148 | submission.save 149 | end 150 | end 151 | 152 | end 153 | 154 | end 155 | -------------------------------------------------------------------------------- /lib/test_request_helper.rb: -------------------------------------------------------------------------------- 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 | def self.link_or_copy(src, des) 9 | begin 10 | FileUtils.ln_s(src, des) 11 | rescue NotImplementedError 12 | FileUtils.cp(src,des) 13 | end 14 | end 15 | 16 | def self.call_and_log(error_message) 17 | begin 18 | yield 19 | rescue 20 | msg = "ERROR: #{error_message}" 21 | raise msg 22 | end 23 | end 24 | 25 | # 26 | # A TestRequestRoomMaker is a helper object for Engine 27 | # - finds grading room: in user_result_dir/(user)/test_request/ ... 28 | # - prepare problem configuration for grading --- basically it copy 29 | # all config files, and copy user's input into the testcase 30 | # directory. First, it finds the template from problem template 31 | # directory; if it can't find a template, it'll use the template 32 | # from default template. 33 | class TestRequestRoomMaker 34 | def initialize 35 | @config = Grader::Configuration.get_instance 36 | end 37 | 38 | def produce_grading_room(test_request) 39 | grading_room = grading_room_dir(test_request) 40 | FileUtils.mkdir_p(grading_room) 41 | 42 | # 43 | # Also copy additional submitted file to this directory as well. 44 | # The program would see this file only if it is copied 45 | # to the sandbox directory later. The run script should do it. 46 | # 47 | if FileTest.exists?("#{test_request.input_file_name}.files") 48 | FileUtils.cp_r("#{test_request.input_file_name}.files/.", 49 | "#{grading_room}") 50 | end 51 | 52 | grading_room 53 | end 54 | 55 | def find_problem_home(test_request) 56 | problem_name = test_request.problem_name 57 | 58 | template_dir = "#{@config.test_request_problem_templates_dir}/" + problem_name 59 | 60 | raise "Test Request: error template not found" if !File.exists?(template_dir) 61 | 62 | problem_home = problem_home_dir(test_request) 63 | FileUtils.mkdir_p(problem_home) 64 | 65 | copy_problem_template(template_dir,problem_home) 66 | link_input_file(test_request,problem_home) 67 | 68 | problem_home 69 | end 70 | 71 | def save_source(test_request,source_name) 72 | dir = self.produce_grading_room(test_request) 73 | submission = test_request.submission 74 | f = File.open("#{dir}/#{source_name}","w") 75 | f.write(submission.source) 76 | f.close 77 | end 78 | 79 | def clean_up(test_request) 80 | problem_home = problem_home_dir(test_request) 81 | remove_data_files(problem_home) 82 | end 83 | 84 | protected 85 | def grading_room_dir(test_request) 86 | problem_name = test_request.problem_name 87 | user = test_request.user 88 | grading_room = "#{@config.user_result_dir}" + 89 | "/#{user.login}/test_request" + 90 | "/#{problem_name}/#{test_request.id}" 91 | grading_room 92 | end 93 | 94 | def problem_home_dir(test_request) 95 | problem_name = test_request.problem_name 96 | user = test_request.user 97 | "#{@config.user_result_dir}" + 98 | "/#{user.login}/test_request/#{problem_name}" 99 | end 100 | 101 | def copy_problem_template(template_dir,problem_home) 102 | Grader::call_and_log("Test Request: cannot copy problem template") { 103 | FileUtils.cp_r("#{template_dir}/.","#{problem_home}") 104 | } 105 | end 106 | 107 | def link_input_file(test_request, problem_home) 108 | input_fname = "#{test_request.input_file_name}" 109 | if !File.exists?(input_fname) 110 | raise "Test Request: input file not found." 111 | end 112 | 113 | input_fname_problem_home = "#{problem_home}/test_cases/1/input-1.txt" 114 | if File.exists?(input_fname_problem_home) 115 | FileUtils.rm([input_fname_problem_home], :force => true) 116 | end 117 | 118 | Grader::link_or_copy("#{input_fname}", "#{input_fname_problem_home}") 119 | end 120 | 121 | def remove_data_files(problem_home) 122 | if File.exists?("#{problem_home}/test_cases/1/input-1.txt") 123 | Grader::call_and_log("Test Request: cannot remove data files") { 124 | FileUtils.rm Dir.glob("#{problem_home}/test_cases/1/*") 125 | } 126 | end 127 | end 128 | 129 | end 130 | 131 | class TestRequestReporter 132 | def initialize 133 | @config = Grader::Configuration.get_instance 134 | end 135 | 136 | def report(test_request,test_result_dir) 137 | save_result(test_request,read_result(test_result_dir)) 138 | end 139 | 140 | def report_error(test_request, msg) 141 | save_result(test_request, {:running_stat => { 142 | :msg => "#{msg}", 143 | :running_time => nil, 144 | :exit_status => "Some error occured. Program did not run", 145 | :memory_usage => nil 146 | }}) 147 | end 148 | 149 | protected 150 | def read_result(test_result_dir) 151 | # TODO: 152 | cmp_msg_fname = "#{test_result_dir}/compiler_message" 153 | cmp_file = File.open(cmp_msg_fname) 154 | cmp_msg = cmp_file.read 155 | cmp_file.close 156 | 157 | result_file_name = "#{test_result_dir}/1/result" 158 | 159 | if File.exists?(result_file_name) 160 | output_file_name = "#{test_result_dir}/1/output.txt" 161 | results = [] 162 | File.open("#{test_result_dir}/1/result") do |f| 163 | results = f.readlines 164 | end 165 | stat = extract_running_stat(results) 166 | 167 | return { 168 | :output_file_name => output_file_name, 169 | :running_stat => stat, 170 | :comment => "", 171 | :cmp_msg => cmp_msg} 172 | else 173 | return { 174 | :running_stat => nil, 175 | :comment => "Compilation error", 176 | :cmp_msg => cmp_msg} 177 | end 178 | end 179 | 180 | def extract_running_stat(results) 181 | running_stat_line = results[-1] 182 | 183 | # extract exit status line 184 | run_stat = "" 185 | if !(/[Cc]orrect/.match(results[0])) 186 | run_stat = results[0].chomp 187 | else 188 | run_stat = 'Program exited normally' 189 | end 190 | 191 | # extract running time 192 | if res = /r(.*)u(.*)s/.match(running_stat_line) 193 | seconds = (res[1].to_f + res[2].to_f) 194 | time_stat = "Time used: #{seconds} sec." 195 | else 196 | seconds = nil 197 | time_stat = "Time used: n/a sec." 198 | end 199 | 200 | # extract memory usage 201 | if res = /s(.*)kbytes/.match(running_stat_line) 202 | memory_used = res[1].to_i 203 | else 204 | memory_used = -1 205 | end 206 | 207 | return { 208 | :msg => "#{run_stat}\n#{time_stat}", 209 | :running_time => seconds, 210 | :exit_status => run_stat, 211 | :memory_usage => memory_used 212 | } 213 | end 214 | 215 | def save_result(test_request,result) 216 | if result[:output_file_name]!=nil 217 | test_request.output_file_name = link_output_file(test_request, 218 | result[:output_file_name]) 219 | end 220 | test_request.graded_at = Time.now 221 | test_request.compiler_message = (result[:cmp_msg] or '') 222 | test_request.grader_comment = (result[:comment] or '') 223 | if result[:running_stat]!=nil 224 | test_request.running_stat = (result[:running_stat][:msg] or '') 225 | test_request.running_time = (result[:running_stat][:running_time] or nil) 226 | test_request.exit_status = result[:running_stat][:exit_status] 227 | test_request.memory_usage = result[:running_stat][:memory_usage] 228 | else 229 | test_request.running_stat = '' 230 | end 231 | test_request.save 232 | end 233 | 234 | protected 235 | def link_output_file(test_request, fname) 236 | target_file_name = random_output_file_name(test_request.user, 237 | test_request.problem) 238 | FileUtils.mkdir_p(File.dirname(target_file_name)) 239 | Grader::link_or_copy("#{fname}", "#{target_file_name}") 240 | return target_file_name 241 | end 242 | 243 | def random_output_file_name(user,problem) 244 | problem_name = TestRequest.name_of(problem) 245 | begin 246 | tmpname = "#{@config.test_request_output_base_dir}" + 247 | "/#{user.login}/#{problem_name}/#{rand(10000)}" 248 | end while File.exists?(tmpname) 249 | tmpname 250 | end 251 | 252 | end 253 | 254 | end 255 | -------------------------------------------------------------------------------- /load_testcase: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | def config 4 | Grader::Configuration.get_instance 5 | end 6 | 7 | def display_manual 8 | puts < false, 31 | } 32 | 33 | options[:dry_run] = (ARGV.delete('--dry') != nil) 34 | options[:all] = (ARGV.delete('--all') != nil) 35 | 36 | return options 37 | end 38 | 39 | def process_problem(prob,dry_run = false) 40 | prob.testcases.destroy_all 41 | testcases_root = File.expand_path(GRADER_ROOT+"/../ev/#{prob.name}/test_cases/") 42 | num = 1 43 | puts "Processing problem #{prob.name}" 44 | loop do 45 | file_root = testcases_root + "/#{num}/" 46 | puts " checking file #{file_root}" 47 | break unless File.exists? file_root 48 | input = File.read(file_root + "/input-#{num}.txt") 49 | answer = File.read(file_root + "/answer-#{num}.txt") 50 | #we also remove carraige return 51 | input.gsub!(/\r\n?/,"\n") 52 | answer.gsub!(/\r\n?/,"\n") 53 | puts " got test case ##{num} of size #{input.size} and #{answer.size}" 54 | 55 | #THIS IS JUST A PLACE HOLDER 56 | group = num #this is wrong!!! fix it!! 57 | score = 10 58 | #BEWARE 59 | 60 | prob.testcases.create(input: input,sol: answer, num: num, score:score,group: group) unless dry_run 61 | num += 1 62 | end 63 | end 64 | 65 | ######################################### 66 | # main program 67 | ######################################### 68 | 69 | options = process_options_and_stop_file 70 | 71 | # load grader environment 72 | GRADER_ENV = 'grading' 73 | require File.join(File.dirname(__FILE__),'config/environment') 74 | 75 | # boot rails, to be able to use the active record 76 | RAILS_ENV = config.rails_env 77 | require RAILS_ROOT + '/config/environment' 78 | 79 | if options[:all] 80 | Problem.all.each { |prob| process_problem(prob,options[:dry_run]) } 81 | else 82 | ARGV.each do |name| 83 | prob = Problem.find_by(name: name) 84 | process_problem(prob,options[:dry_run]) if prob 85 | puts "Cannot find the problem #{name}" unless prob 86 | end 87 | end 88 | 89 | -------------------------------------------------------------------------------- /new_problem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 ii+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 < 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" 74 | -------------------------------------------------------------------------------- /rename_problem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENVIRONMENT_DIRS = ['ev', 'ev-exam'] 4 | 5 | def config 6 | Grader::Configuration.get_instance 7 | end 8 | 9 | def rename_problem(old_problem_name, new_problem_name) 10 | 11 | unless valid_problem_name(new_problem_name) 12 | puts "Bad new problem name: #{new_problem_name}" 13 | return 14 | end 15 | 16 | problem = Problem.find_by_name(old_problem_name) 17 | if problem==nil 18 | puts "Problem #{old_problem_name} does not exist." 19 | return 20 | end 21 | 22 | puts "Problem: #{old_problem_name} -> #{new_problem_name}" 23 | 24 | ENVIRONMENT_DIRS.each do |dir| 25 | problem_dir = File.join(GRADER_ROOT,'..',dir,old_problem_name) 26 | new_problem_dir = File.join(GRADER_ROOT,'..',dir,new_problem_name) 27 | 28 | if FileTest.exists? problem_dir 29 | puts "Moving #{problem_dir} to #{new_problem_dir}." 30 | File.rename(problem_dir, new_problem_dir) 31 | 32 | tr_problem_dir = File.join(GRADER_ROOT,'..',dir, 33 | 'test_request',old_problem_name) 34 | new_tr_problem_dir = File.join(GRADER_ROOT,'..',dir, 35 | 'test_request',new_problem_name) 36 | File.rename(tr_problem_dir, new_tr_problem_dir) 37 | end 38 | end 39 | 40 | problem.name = new_problem_name 41 | problem.save 42 | end 43 | 44 | def usage 45 | puts < []" 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 85 | -------------------------------------------------------------------------------- /std-script/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | 5 | ############################## 6 | # 7 | # Standard Compile Script 8 | # 9 | # Supported compilers: 10 | # gcc, g++, and fpc. 11 | # 12 | ############################## 13 | 14 | def talk(str='') 15 | if ENV['TALKATIVE']!=nil 16 | puts str 17 | end 18 | if ENV['GRADER_LOGGING']!=nil 19 | log_fname = ENV['GRADER_LOGGING'] 20 | fp = File.open(log_fname,"a") 21 | fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}") 22 | fp.close 23 | end 24 | end 25 | 26 | C_COMPILER = "/usr/bin/gcc" 27 | CPLUSPLUS_COMPILER = "/usr/bin/g++" 28 | PASCAL_COMPILER = "/usr/bin/fpc" 29 | JAVA_COMPILER = "/usr/bin/javac" 30 | RUBY_INTERPRETER = "/usr/bin/ruby" 31 | PYTHON_INTERPRETER = "/usr/bin/python3" 32 | PYTHON_CHECKER = "/usr/bin/pyflakes" 33 | PHP_INTERPRETER = "/usr/bin/php" 34 | HASKELL_COMPILER = "/usr/bin/ghc" 35 | 36 | C_OPTIONS = "-O2 -s -static -std=c99 -DCONTEST -lm -Wall" 37 | CPLUSPLUS_OPTIONS = "-O2 -s -std=c++11 -static -DCONTEST -lm -Wall" 38 | PASCAL_OPTIONS = "-O1 -XS -dCONTEST" 39 | JAVA_OPTIONS = "" 40 | PYTHON_OPTIONS = "" 41 | PHP_OPTIONS = "-l" 42 | HASKELL_OPTIONS = "" 43 | 44 | # Check for the correct number of arguments. Otherwise, print usage. 45 | if ARGV.length == 0 or ARGV.length > 4 46 | puts "Usage: compile [] [] []" 47 | puts 48 | puts " is defaulted to \"source\"." 49 | puts " is defaulted to \"a.out\"." 50 | puts " is defaulted to \"compiler_message\"." 51 | puts 52 | exit(127) 53 | end 54 | 55 | PARAMS = { 56 | :source_file => [1,'source'], 57 | :output_file => [2,'a.out'], 58 | :message_file => [3,'compiler_message'] 59 | } 60 | 61 | params = {} 62 | params[:prog_lang] = ARGV[0] 63 | PARAMS.each_key do |param_name| 64 | index, default = PARAMS[param_name] 65 | if ARGV.length > index 66 | params[param_name] = ARGV[index] 67 | else 68 | params[param_name] = default 69 | end 70 | talk "COMPILE: param: #{param_name}: #{params[param_name]}" 71 | end 72 | talk "COMPILE: working dir = " + Dir.pwd 73 | 74 | # Remove any remaining output files or message files. 75 | if FileTest.exists? params[:output_file] 76 | FileUtils.rm(params[:output_file]) 77 | end 78 | if FileTest.exists? params[:message_file] 79 | FileUtils.rm(params[:message_file]) 80 | end 81 | 82 | # Check if the source file exists before attempt compiling. 83 | if !FileTest.exists? params[:source_file] 84 | talk("COMPILE: ERROR: The source file does not exist!") 85 | open(params[:message_file],"w") do |f| 86 | f.puts "ERROR: The source file did not exist." 87 | end 88 | exit(127) 89 | end 90 | 91 | if params[:prog_lang]=='cpp' 92 | params[:prog_lang] = 'c++' 93 | end 94 | 95 | 96 | # Compile. 97 | case params[:prog_lang] 98 | 99 | when "c" 100 | command = "#{C_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{C_OPTIONS}" 101 | talk "COMPILE: compiling command [#{command}]" 102 | system(command, err: params[:message_file]) 103 | 104 | when "c++" 105 | command = "#{CPLUSPLUS_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{CPLUSPLUS_OPTIONS}" 106 | talk "COMPILE: compiling command [#{command}]" 107 | system(command, err: params[:message_file]) 108 | 109 | when "pas" 110 | command = "#{PASCAL_COMPILER} #{params[:source_file]} -ooutpas #{PASCAL_OPTIONS}" 111 | talk "COMPILE: compiling command [#{command}]" 112 | system(command,out: params[:message_file]) 113 | FileUtils.mv("output", params[:output_file]) 114 | 115 | when "java" 116 | #rename the file to the public class name 117 | 118 | #get the class name 119 | classname = 'DUMMY' 120 | source = Array.new 121 | File.foreach(params[:source_file],'r:UTF-8') do |line| 122 | line.encode!('UTF-8','UTF-8',invalid: :replace, replace: '') 123 | md = /\s*public\s*class\s*(\w*)/.match(line) 124 | classname=md[1] if md 125 | source << line unless line =~ /\s*package\s*\w+\s*\;/ 126 | end 127 | File.open("#{classname}.java","w") do |file| 128 | source.each do |s| 129 | file.puts s 130 | end 131 | end 132 | #system("cp #{params[:source_file]} #{classname}.java") 133 | command = "#{JAVA_COMPILER} -encoding utf8 #{classname}.java" 134 | talk "COMPILE: compiling command [#{command}]" 135 | system(command, err: params[:message_file]) 136 | if File.exists?(classname + ".class") 137 | File.open(params[:output_file],"w") {|file| file.write("#{classname}")} 138 | end 139 | if classname == 'DUMMY' 140 | File.open(params[:message_file],"w") {|file| file.write("Cannot find any public class in the source code\n")} 141 | end 142 | 143 | when "ruby" 144 | command = "#{RUBY_INTERPRETER} -c #{params[:source_file]}" 145 | talk "COMPILE: compiling command [#{command}]" 146 | if system(command, err: params[:message_file]) 147 | File.open(params[:output_file],"w") do |out_file| 148 | out_file.puts "#!#{RUBY_INTERPRETER}" 149 | File.open(params[:source_file],"r").each do |line| 150 | out_file.print line 151 | end 152 | end 153 | File.chmod(0755, params[:output_file]) 154 | end 155 | 156 | when "python" 157 | #command = "#{PYTHON_CHECKER} #{params[:source_file]}" 158 | #if system(command, out: params[:message_file]) 159 | #compile to python bytecode 160 | command = "#{PYTHON_INTERPRETER} -c \"import py_compile; py_compile.compile('#{params[:source_file]}','#{params[:source_file]}c');\"" 161 | talk "COMPILE: compiling command [#{command}]" 162 | system(command, err: params[:message_file]) 163 | if FileTest.exists?("#{params[:source_file]}c") 164 | File.open(params[:output_file],"w") do |out_file| 165 | out_file.puts "#!#{PYTHON_INTERPRETER} #{params[:source_file]}c" 166 | end 167 | File.chmod(0755, params[:output_file]) 168 | FileUtils.cp("#{params[:source_file]}c",params[:output_file]) 169 | end 170 | #end 171 | 172 | when "php" 173 | command = "#{PHP_INTERPRETER} #{PHP_OPTIONS} #{params[:source_file]}" 174 | if system(command, err: params[:message_file]) 175 | File.open(params[:output_file],"w") do |out_file| 176 | out_file.puts "#!#{PHP_INTERPRETER}" 177 | File.open(params[:source_file],"r").each do |line| 178 | out_file.print line 179 | end 180 | end 181 | File.chmod(0755, params[:output_file]) 182 | end 183 | 184 | when "haskell" 185 | command = "#{HASKELL_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{HASKELL_OPTIONS}" 186 | talk "COMPILE: compiling command [#{command}]" 187 | system(command, err: params[:message_file]) 188 | 189 | else 190 | talk("COMPILE: ERROR: Invalid language specified!") 191 | open(params[:message_file],"w") do |f| 192 | f.puts "ERROR: Invalid language specified!" 193 | end 194 | exit(127) 195 | end 196 | 197 | # Report success or failure. 198 | if FileTest.exists? params[:output_file] 199 | talk "COMPILE: Compilation was successful!" 200 | else 201 | talk "COMPILE: ERROR: Something was wrong during the compilation!" 202 | end 203 | -------------------------------------------------------------------------------- /std-script/grade: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 | def extract_time(t) 35 | #puts "TIME: #{t}" 36 | if (result=/^(.*)r(.*)u(.*)s(.*)kbytes/.match(t)) 37 | {:real => result[1], :user => result[2], :sys => result[3], :mem => result[4]} 38 | else 39 | #{:real => 0, :user => 0, :sys => 0} 40 | #puts "ERROR READING RUNNING TIME: #{t}" 41 | raise "Error reading running time: #{t}" 42 | end 43 | end 44 | 45 | problem_home = ENV['PROBLEM_HOME'] 46 | require "#{problem_home}/script/test_dsl.rb" 47 | load "#{problem_home}/test_cases/all_tests.cfg" 48 | problem = Problem.get_instance 49 | 50 | if problem.well_formed? == false 51 | log "The problem specification is not well formed." 52 | exit(127) 53 | end 54 | 55 | all_score = 0 56 | all_comment = '' 57 | peak_memory = -1 58 | max_runtime = -1 59 | (1..(problem.runs.length-1)).each do |k| 60 | log "grade run #{k}" 61 | run = problem.runs[k] 62 | run_score = nil 63 | run_comment = '' 64 | run_comment_short = '' 65 | run.tests.each do |test_num| 66 | result_file_name = "#{test_num}/result" 67 | if not File.exists?(result_file_name) 68 | run_comment += "result file for test #{test_num} not found\n" 69 | run_comment_short += RUN_ERROR_MARK 70 | log "Cannot find the file #{test_num}/result!" 71 | else 72 | result_file = File.new(result_file_name, "r") 73 | result_file_lines = result_file.readlines 74 | if result_file_lines.length>=3 75 | current_run_score = result_file_lines[1].to_i 76 | run_comment += result_file_lines[0] 77 | run_comment_short += char_comment(result_file_lines[0].chomp) 78 | 79 | #update max runtime & memory 80 | run_stat = extract_time result_file_lines[2] 81 | peak_memory = [peak_memory,run_stat[:mem].to_i].max 82 | max_runtime = [max_runtime,run_stat[:user].to_f + run_stat[:sys].to_f].max 83 | else 84 | current_run_score = 0 85 | run_comment += "result file for test #{test_num} error\n" 86 | run_comment_short += RUN_ERROR_MARK 87 | log "Error in #{test_num}/result!" 88 | end 89 | 90 | # the score of this run should be the minimum of the score for 91 | # each test case 92 | if (run_score==nil) or (run_score>current_run_score) 93 | run_score = current_run_score 94 | end 95 | result_file.close 96 | end 97 | end 98 | 99 | run_result_file = File.new("result-#{k}", "w") 100 | run_result_file.write run_score 101 | run_result_file.write "\n" 102 | run_result_file.close 103 | 104 | run_comment_file = File.new("comment-#{k}", "w") 105 | run_comment_file.write "#{run_comment}\n" 106 | run_comment_file.close 107 | 108 | all_score = all_score + run_score 109 | 110 | # append comment for test run with many test cases 111 | if run.tests.length > 1 112 | run_comment_short = '[' + run_comment_short + ']' 113 | end 114 | all_comment += run_comment_short 115 | end 116 | 117 | result_file = File.new("result", "w") 118 | result_file.write all_score 119 | result_file.write "\n" 120 | result_file.close 121 | 122 | comment_file = File.new("comment", "w") 123 | comment_file.write "#{all_comment}\n" 124 | comment_file.close 125 | 126 | 127 | File.open("run_stat","w") do |file| 128 | file.puts max_runtime 129 | file.puts peak_memory 130 | end 131 | 132 | puts "#{all_score} #{all_comment}" 133 | log "score = #{all_score}\ncomment = #{all_comment}" 134 | log "max_runtime = #{max_runtime}\npeak_memory = #{peak_memory}" 135 | -------------------------------------------------------------------------------- /std-script/judge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | 5 | def log(str='') 6 | if ENV['TALKATIVE']!=nil 7 | puts str 8 | end 9 | if ENV['GRADER_LOGGING']!=nil 10 | log_fname = ENV['GRADER_LOGGING'] 11 | fp = File.open(log_fname,"a") 12 | fp.puts("judge: #{Time.new.strftime("%H:%M")} #{str}") 13 | fp.close 14 | end 15 | end 16 | 17 | problem_home = ENV['PROBLEM_HOME'] 18 | 19 | def execute(command, error_message="") 20 | if not system(command) 21 | msg = "ERROR: #{error_message}" 22 | log msg 23 | raise(msg) 24 | end 25 | end 26 | 27 | def call_and_log(error_message) 28 | begin 29 | yield 30 | rescue 31 | msg = "JUDGE: ERROR: #{error_message}" 32 | log msg 33 | raise msg 34 | end 35 | end 36 | 37 | def clear_and_create_empty_dir(dir) 38 | FileUtils.rm_rf(dir, :secure => true) 39 | call_and_log("Cannot make directory #{dir}.") { FileUtils.mkdir(dir) } 40 | end 41 | 42 | # ARGV[0] --- language 43 | # ARGV[1] --- program source file 44 | # ARGV[2] --- test result directory 45 | # ARGV[3] --- sandbox directory 46 | 47 | if ARGV.length < 2 || ARGV.length > 4 48 | puts "Usage: judge [] []" 49 | puts " is defaulted to ./sandbox" 50 | puts " is defaulted to ./test-result" 51 | puts "WARNING: The judge script will forcefully create the (implicitly and explicitly) specified directories and remove anything inside it." 52 | exit(127) 53 | end 54 | 55 | language = ARGV[0] 56 | if language != "c" && language != "c++" && language != "pas" && language != "java" && language != "ruby" && language != "python" && language != "php" && language != "haskell" 57 | log "JUDGE: You specified a language that is not supported: #{language}." 58 | exit(127) 59 | end 60 | 61 | source_file = ARGV[1] 62 | ENV['SOURCE_NAME'] = source_file 63 | if File.exist?(source_file) == false 64 | log "JUDGE: The source file does not exist." 65 | exit(127) 66 | end 67 | 68 | log "JUDGE: Making test result and sandbox directories..." 69 | 70 | current_dir = FileUtils.pwd 71 | current_dir.strip! 72 | 73 | if ARGV.length >= 3 74 | test_result_dir = ARGV[2] 75 | else 76 | test_result_dir = "#{current_dir}/test-result" 77 | end 78 | 79 | log "JUDGE: Test result directory: #{test_result_dir}" 80 | clear_and_create_empty_dir(test_result_dir) 81 | 82 | if ARGV.length >= 4 83 | sandbox_dir = ARGV[3] 84 | else 85 | sandbox_dir = "#{current_dir}/sandbox" 86 | end 87 | log "JUDGE: Sandbox directory: #{sandbox_dir}" 88 | clear_and_create_empty_dir(sandbox_dir) 89 | 90 | # ------------------------------ 91 | # Compile 92 | # ------------------------------ 93 | log "JUDGE: Compiling..." 94 | log 95 | call_and_log("Cannot copy the source file to #{sandbox_dir}") { 96 | FileUtils.cp(source_file, sandbox_dir) 97 | } 98 | begin 99 | Dir.chdir sandbox_dir 100 | rescue 101 | log "JUDGE: ERROR: Cannot change directory to #{sandbox_dir}." 102 | exit(127) 103 | end 104 | execute("#{problem_home}/script/compile #{language} #{source_file}", "Compilation error!") 105 | compile_message = open("compiler_message").read 106 | compile_message.strip! 107 | call_and_log("Cannot move the compiler message to #{test_result_dir}.") { 108 | FileUtils.mv("compiler_message", test_result_dir) 109 | } 110 | if !FileTest.exist?("a.out") 111 | log "JUDGE: EROOR: Cannot compile the source code. See message in #{test_result_dir}/compile_message" 112 | exit(127) 113 | else 114 | call_and_log("Cannot move the compiled program to #{test_result_dir}") { 115 | FileUtils.mv("a.out",test_result_dir) 116 | if language == "java" then Dir["*.class"].each { |file| FileUtils.mv(file,test_result_dir)} end 117 | if language == "python" then Dir["*.pyc"].each { |file| FileUtils.mv(file,test_result_dir)} end 118 | } 119 | FileUtils.rm_rf("#{sandbox_dir}/.") 120 | end 121 | 122 | 123 | #----------------------------------------------- 124 | # run 125 | #----------------------------------------------- 126 | require "#{problem_home}/script/test_dsl.rb" 127 | load "#{problem_home}/test_cases/all_tests.cfg" 128 | problem = Problem.get_instance 129 | 130 | if problem.well_formed? == false 131 | log "The problem specification is not well formed." 132 | exit(127) 133 | end 134 | 135 | # Doing the testing. 136 | log 137 | log "JUDGE: Running each test case..." 138 | (1..(problem.num_tests)).each do |test_num| 139 | 140 | $stdout.print "[#{test_num}]" 141 | $stdout.flush 142 | 143 | call_and_log("Cannot copy the compiled program into #{sandbox_dir}") { 144 | FileUtils.cp("#{test_result_dir}/a.out", sandbox_dir, :preserve => true) 145 | if language == "java" then Dir["#{test_result_dir}/*.class"].each { |file| FileUtils.cp(file,sandbox_dir)} end 146 | if language == "python" then Dir["#{test_result_dir}/*.pyc"].each { |file| FileUtils.cp(file,sandbox_dir)} end 147 | } 148 | 149 | #additionally copy any extra .txt file 150 | data_files = Dir[problem_home + '/*.txt'] 151 | data_files.each do |file| 152 | FileUtils.cp(file,sandbox_dir) 153 | end 154 | 155 | begin 156 | execute("#{problem_home}/script/run #{language} #{test_num} ", "Error occured during execution of the run script") 157 | rescue 158 | # do nothing 159 | end 160 | 161 | 162 | #copy the output of run script to each test-result folder 163 | call_and_log("Cannot create directory #{test_result_dir}/#{test_num}") { 164 | FileUtils.mkdir "#{test_result_dir}/#{test_num}" 165 | } 166 | call_and_log("Cannot copy the result file into #{test_result_dir}/#{test_num}") { 167 | FileUtils.mv "#{sandbox_dir}/result", "#{test_result_dir}/#{test_num}" 168 | } 169 | call_and_log("Cannot copy the comment file into #{test_result_dir}/#{test_num}") { 170 | FileUtils.mv "#{sandbox_dir}/comment", "#{test_result_dir}/#{test_num}" 171 | } 172 | call_and_log("Cannot copy the output file into #{test_result_dir}/#{test_num}") { 173 | FileUtils.mv "#{sandbox_dir}/output.txt", "#{test_result_dir}/#{test_num}" 174 | } 175 | call_and_log("Cannot clear #{sandbox_dir}") { 176 | FileUtils.rm_rf(Dir.glob("#{sandbox_dir}/*"), :secure => true) 177 | } 178 | end 179 | 180 | $stdout.print "[done]\n" 181 | 182 | # Grade 183 | log 184 | log "JUDGE: Grading..." 185 | begin 186 | Dir.chdir test_result_dir 187 | rescue 188 | log "ERROR: Cannot change directory to #{test_result_dir}." 189 | exit(127) 190 | end 191 | execute("#{problem_home}/script/grade", "An error occured during grading!") 192 | 193 | log 194 | log "All done!" 195 | -------------------------------------------------------------------------------- /std-script/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ## 4 | # This program should be run in the sandbox dir containing the compiled file 5 | # (or source file for script language). It will call the sandbox program with 6 | # the given input and process the output of the sandbox 7 | # 8 | # If sandbox exit normally, this program will call the "check" script to do 9 | # scoring. Otherwise, it would record the error accordingly 10 | # 11 | # This program produces several file 12 | # * result - the result from check script 13 | # * comment - comment from sandbox 14 | # * output - output of the program 15 | # 16 | 17 | require 'fileutils' 18 | 19 | def log(str='') 20 | if ENV['TALKATIVE']!=nil 21 | puts str 22 | end 23 | if ENV['GRADER_LOGGING']!=nil 24 | log_fname = ENV['GRADER_LOGGING'] 25 | fp = File.open(log_fname,"a") 26 | fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}") 27 | fp.close 28 | end 29 | end 30 | 31 | def extract_time(t) 32 | # puts "TIME: #{t}" 33 | if (result=/^(.*)r(.*)u(.*)s/.match(t)) 34 | {:real => result[1], :user => result[2], :sys => result[3]} 35 | else 36 | #{:real => 0, :user => 0, :sys => 0} 37 | #puts "ERROR READING RUNNING TIME: #{t}" 38 | raise "Error reading running time: #{t}" 39 | end 40 | end 41 | 42 | def compile_box(source,bin) 43 | system("g++ #{source} -o #{bin}") 44 | end 45 | 46 | #------------------------------------------ 47 | # MAIN 48 | #------------------------------------------ 49 | 50 | #parse parameter 51 | if ARGV.length < 2 || ARGV.length > 3 52 | puts "Usage: run []" 53 | exit(127) 54 | end 55 | 56 | language = ARGV[0] 57 | test_num = ARGV[1].to_i 58 | if ARGV.length > 2 59 | program_name = ARGV[2] 60 | else 61 | program_name = "a.out" 62 | end 63 | 64 | problem_home = ENV['PROBLEM_HOME'] 65 | source_name = ENV['SOURCE_NAME'] 66 | require "#{problem_home}/script/test_dsl.rb" 67 | load "#{problem_home}/test_cases/all_tests.cfg" 68 | problem = Problem.get_instance 69 | 70 | sandbox_dir = Dir.getwd 71 | 72 | if problem.well_formed? == false 73 | log "RUN: ERROR: The problem specification is not well formed." 74 | exit(127) 75 | end 76 | 77 | # Check if the test number is okay. 78 | if test_num <= 0 || test_num > problem.num_tests 79 | log "RUN: ERROR: You have specified a wrong test number." 80 | exit(127) 81 | end 82 | 83 | ##################################### 84 | # Set the relavant file names here. # 85 | ##################################### 86 | 87 | input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt" 88 | 89 | ##################################### 90 | 91 | time_limit = problem.get_time_limit test_num 92 | mem_limit = problem.get_mem_limit(test_num) * 1024 93 | 94 | # Copy the input file. 95 | #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .` 96 | 97 | # check if box is there, if not, compile it! 98 | if !File.exists?("#{problem_home}/script/box") 99 | log "WARNING: Compiling box: to increase efficiency, it should be compile manually" 100 | compile_box("#{problem_home}/script/box.cc", 101 | "#{problem_home}/script/box") 102 | end 103 | 104 | # Hide PROBLEM_HOME 105 | ENV['PROBLEM_HOME'] = nil 106 | ENV['SOURCE_NAME'] = nil 107 | 108 | # Run the program. 109 | #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}" 110 | # 111 | 112 | JAVA_OPTION = "-s set_robust_list -s futex -s clone -s getppid -s clone -s wait4 -p /usr/bin/ -p ./" 113 | RUBY_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /lib64/ -p /dev/urandom -p #{sandbox_dir}/#{program_name} -p #{sandbox_dir}/ -s set_robust_list -s sched_getaffinity -s clock_gettime -s sigaltstack -s pipe2 -s clone -s futex -s openat -s pipe -s getrandom" 114 | PYTHON_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /usr/bin/ -p /lib64/ -p /dev/urandom -p /usr/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p #{sandbox_dir}/#{source_name} -p /proc/sys/crypto/fips_enabled -p /proc/self/status -p /proc/mounts -p /var/lib/dpkg/status -s statfs -s set_robust_list -s openat -s sysinfo -s recvmsg -s connect -s socket -s sendto -s futex -s sigaltstack -s getrandom -s prlimit64 -E PYTHONNOUSERSITE=yes" 115 | PHP_OPTION = "-p /usr/lib64/ -p/lib64/ -p /usr/bin/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p /usr/share/ -s setfsuid -s setfsgid -s openat -s set_robust_list -s futex -s clone -s socket -s connect" 116 | HASKELL_OPTION = "-s set_robust_list -s clock_gettime -s sysinfo -s timer_create -s timer_settime -s futex -s timer_delete" 117 | 118 | case language 119 | when "java" 120 | # for java, extract the classname 121 | # wne have to add additional systemcall and we don't check the mem limit (dunno how to fix...) 122 | classname = 'DUMMY' 123 | File.open(program_name,"r").each do |line| 124 | classname = line 125 | end 126 | #for java, we cannot really check the memory limit... 127 | run_command = "#{problem_home}/script/box -a 3 -f -T -t #{time_limit} #{JAVA_OPTION} -i #{input_file_name} -o output.txt /usr/bin/java -A -Xmx#{mem_limit}k -A #{classname} " 128 | when "ruby" 129 | run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{RUBY_OPTION} -i #{input_file_name} -o output.txt /usr/bin/ruby #{program_name} " 130 | when "python" 131 | run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{[512 * 1024,mem_limit].max} #{PYTHON_OPTION} -i #{input_file_name} -o output.txt /usr/bin/python3 #{program_name} " 132 | when "haskell" 133 | run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{[512 * 1024,mem_limit].max} #{HASKELL_OPTION} -i #{input_file_name} -o output.txt #{program_name} " 134 | when "php" 135 | run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{[512 * 1024,mem_limit].max} #{PHP_OPTION} -i #{input_file_name} -o output.txt /usr/bin/php -A -d -A memory_limit=#{mem_limit}k -A #{program_name} " 136 | else # for c++, pascal, we do the normal checking 137 | 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} " 138 | end 139 | 140 | 141 | log "RUN: Running test #{test_num}..." 142 | log "RUN: Run command = [#{run_command}]" 143 | log 144 | system(run_command,err: 'run_result') 145 | 146 | # Restore PROBLEM_HOME 147 | ENV['PROBLEM_HOME'] = problem_home 148 | 149 | # Create the result file. 150 | result_file = File.new("result", "w") 151 | comment_file = File.new("comment", "w") 152 | 153 | # Check if the program actually produced any output. 154 | run_result_file = File.new("run_result", "r") 155 | run_result = run_result_file.readlines 156 | run_result_file.close 157 | 158 | run_stat = run_result[run_result.length-1] 159 | running_time = extract_time(run_stat) 160 | 161 | report = lambda{ |status, points, comment| 162 | result_file.write status.strip 163 | result_file.write "\n" 164 | result_file.write points.to_s.strip 165 | result_file.write "\n" 166 | result_file.write run_stat.strip 167 | result_file.write "\n" 168 | result_file.close 169 | FileUtils.rm "run_result" 170 | # `rm output.txt` --- keep the output 171 | 172 | comment_file.write comment 173 | 174 | # added for debuggin --- jittat 175 | comment_file.write "--run-result--\n" 176 | run_result.each do |l| 177 | comment_file.write l 178 | end 179 | 180 | comment_file.close 181 | 182 | log "Done!" 183 | exit(0) 184 | } 185 | 186 | 187 | if run_result[0][0,2] != "OK" 188 | log "There was a runtime error." 189 | report.call(run_result[0], 0, "No comment.\n") 190 | end 191 | 192 | if running_time[:user].to_f > time_limit 193 | log "Time limit exceeded." 194 | report.call("Time limit exceeded", 0, "No comment.\n") 195 | end 196 | 197 | # Run 'check' to evaluate the output. 198 | #puts "There was no runtime error. Proceed to checking the output." 199 | check_command = "#{problem_home}/script/check #{language} #{test_num}" 200 | log "Checking the output..." 201 | log check_command 202 | if not system(check_command) 203 | log "Problem with check script" 204 | report.call("Incorrect",0,"Check script error.\n") 205 | exit(127) 206 | end 207 | 208 | check_file = File.new("check_result", "r") 209 | check_file_lines = check_file.readlines 210 | 211 | report.call(check_file_lines[0], check_file_lines[1], "No comment.\n") 212 | -------------------------------------------------------------------------------- /std-script/test_dsl.rb: -------------------------------------------------------------------------------- 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 181 | -------------------------------------------------------------------------------- /templates/all_tests.cfg.erb: -------------------------------------------------------------------------------- 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 21 | -------------------------------------------------------------------------------- /templates/answer-1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe-grader-team/cafe-grader-judge-scripts/af9755cc8372e00ac2144b1590963c1e92c38eb3/templates/answer-1.txt -------------------------------------------------------------------------------- /templates/check.float: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 FLOAT with EPSILON error ########## 49 | 50 | 51 | def is_float?(fl) 52 | !!Float(fl) rescue false 53 | end 54 | 55 | EPSILON = 0.000001 56 | 57 | out_items = output_file_content.split 58 | ans_items = answer_file_content.split 59 | 60 | if out_items.length != ans_items.length 61 | report_wrong.call 62 | else 63 | out_items.length.times do |i| 64 | if is_float?(out_items[i]) && is_float?(ans_items[i]) 65 | out_value = out_items[i].to_f 66 | ans_value = ans_items[i].to_f 67 | if (out_value - ans_value).abs > EPSILON * [out_value.abs,ans_value.abs].max 68 | report_wrong.call 69 | end 70 | else 71 | report_wrong.call if out_items[i] != ans_items[i] 72 | end 73 | end 74 | report_correct.call 75 | end 76 | -------------------------------------------------------------------------------- /templates/check.integer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 | 63 | -------------------------------------------------------------------------------- /templates/check.text: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 65 | -------------------------------------------------------------------------------- /templates/check_empty: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 48 | -------------------------------------------------------------------------------- /templates/check_wrapper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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) 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 []" 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) 49 | -------------------------------------------------------------------------------- /templates/test_request_all_tests.cfg.erb: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | sandbox 2 | coverage 3 | 4 | -------------------------------------------------------------------------------- /test/data/add_fail_test_case_1.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int a,b; 6 | int r; 7 | r = scanf("%d %d",&a,&b); 8 | if((a==1) && (b==1)) 9 | printf("100\n"); 10 | else 11 | printf("%d\n",a+b); 12 | 13 | a+=r; 14 | return 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /test/data/add_nonzero_exit_status.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int a,b; 6 | int r; 7 | r = scanf("%d %d",&a,&b); 8 | printf("%d\n",a+b); 9 | a+=r; 10 | return 10; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /test/data/add_too_much_memory_dynamic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | int a,b; 7 | int r; 8 | char *huge_array; 9 | 10 | r = scanf("%d %d",&a,&b); 11 | 12 | huge_array = (char *)malloc(5000000); 13 | if(huge_array==NULL) 14 | printf("NO!"); 15 | else 16 | printf("%d\n",a+b); 17 | 18 | a+=r; 19 | return 0; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /test/data/add_too_much_memory_static.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int big_array[2000000]; 4 | 5 | int main() 6 | { 7 | int a,b; 8 | int r; 9 | r = scanf("%d %d",&a,&b); 10 | printf("%d\n",a+b); 11 | a += r; 12 | return 0; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/data/ev/test_memory/script/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 60 | -------------------------------------------------------------------------------- /test/data/ev/test_memory/test_cases/1/answer-1.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_memory/test_cases/1/input-1.txt: -------------------------------------------------------------------------------- 1 | 1 1 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_memory/test_cases/2/answer-2.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_memory/test_cases/2/input-2.txt: -------------------------------------------------------------------------------- 1 | 1 1 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_memory/test_cases/all_tests.cfg: -------------------------------------------------------------------------------- 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 21 | -------------------------------------------------------------------------------- /test/data/ev/test_normal/script/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 60 | -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/1/answer-1.txt: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/1/input-1.txt: -------------------------------------------------------------------------------- 1 | 1 1 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/10/answer-10.txt: -------------------------------------------------------------------------------- 1 | -256 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/10/input-10.txt: -------------------------------------------------------------------------------- 1 | -128 -128 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/2/answer-2.txt: -------------------------------------------------------------------------------- 1 | 32 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/2/input-2.txt: -------------------------------------------------------------------------------- 1 | 20 12 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/3/answer-3.txt: -------------------------------------------------------------------------------- 1 | 4 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/3/input-3.txt: -------------------------------------------------------------------------------- 1 | 1 3 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/3/test.cfg: -------------------------------------------------------------------------------- 1 | mem_limit 32 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/4/answer-4.txt: -------------------------------------------------------------------------------- 1 | 64 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/4/input-4.txt: -------------------------------------------------------------------------------- 1 | 32 32 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/5/answer-5.txt: -------------------------------------------------------------------------------- 1 | -2 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/5/input-5.txt: -------------------------------------------------------------------------------- 1 | -1 -1 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/6/answer-6.txt: -------------------------------------------------------------------------------- 1 | -32 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/6/input-6.txt: -------------------------------------------------------------------------------- 1 | -16 -16 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/7/answer-7.txt: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/7/input-7.txt: -------------------------------------------------------------------------------- 1 | 0 0 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/8/answer-8.txt: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/8/input-8.txt: -------------------------------------------------------------------------------- 1 | 0 -1 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/8/test.cfg: -------------------------------------------------------------------------------- 1 | mem_limit 64 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/9/answer-9.txt: -------------------------------------------------------------------------------- 1 | 256 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/9/input-9.txt: -------------------------------------------------------------------------------- 1 | 128 128 -------------------------------------------------------------------------------- /test/data/ev/test_normal/test_cases/all_tests.cfg: -------------------------------------------------------------------------------- 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 41 | -------------------------------------------------------------------------------- /test/data/ev/test_timeout/script/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 60 | -------------------------------------------------------------------------------- /test/data/ev/test_timeout/test_cases/1/answer-1.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_timeout/test_cases/1/input-1.txt: -------------------------------------------------------------------------------- 1 | 1 1 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_timeout/test_cases/2/answer-2.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_timeout/test_cases/2/input-2.txt: -------------------------------------------------------------------------------- 1 | 1 1 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_timeout/test_cases/all_tests.cfg: -------------------------------------------------------------------------------- 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 25 | -------------------------------------------------------------------------------- /test/data/ev/test_yesno/script/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 65 | -------------------------------------------------------------------------------- /test/data/ev/test_yesno/test_cases/1/answer-1.txt: -------------------------------------------------------------------------------- 1 | yes 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_yesno/test_cases/1/input-1.txt: -------------------------------------------------------------------------------- 1 | hello (this won't be read) 2 | 3 | -------------------------------------------------------------------------------- /test/data/ev/test_yesno/test_cases/all_tests.cfg: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /test/data/raw/sum/1.in: -------------------------------------------------------------------------------- 1 | 10 2 | 1 3 | 2 4 | 3 5 | 4 6 | 5 7 | 6 8 | 7 9 | 8 10 | 9 11 | 10 12 | 13 | -------------------------------------------------------------------------------- /test/data/raw/sum/1.sol: -------------------------------------------------------------------------------- 1 | 55 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/10a.in: -------------------------------------------------------------------------------- 1 | 10 2 | 4 3 | 50 4 | 24 5 | 81 6 | 91 7 | 85 8 | 64 9 | 60 10 | 42 11 | 58 12 | -------------------------------------------------------------------------------- /test/data/raw/sum/10a.sol: -------------------------------------------------------------------------------- 1 | 559 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/10b.in: -------------------------------------------------------------------------------- 1 | 10 2 | 80 3 | 20 4 | 39 5 | 78 6 | 46 7 | 20 8 | 22 9 | 60 10 | 81 11 | 34 12 | -------------------------------------------------------------------------------- /test/data/raw/sum/10b.sol: -------------------------------------------------------------------------------- 1 | 480 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/10c.in: -------------------------------------------------------------------------------- 1 | 10 2 | 48 3 | 65 4 | 52 5 | 4 6 | 20 7 | 9 8 | 42 9 | 71 10 | 73 11 | 15 12 | -------------------------------------------------------------------------------- /test/data/raw/sum/10c.sol: -------------------------------------------------------------------------------- 1 | 399 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/2a.in: -------------------------------------------------------------------------------- 1 | 10 2 | 12 3 | 3 4 | 70 5 | 46 6 | 29 7 | 47 8 | 32 9 | 55 10 | 78 11 | 26 12 | -------------------------------------------------------------------------------- /test/data/raw/sum/2a.sol: -------------------------------------------------------------------------------- 1 | 398 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/2b.in: -------------------------------------------------------------------------------- 1 | 100 2 | 9 3 | 15 4 | 42 5 | 15 6 | 51 7 | 16 8 | 30 9 | 56 10 | 30 11 | 3 12 | 76 13 | 33 14 | 80 15 | 18 16 | 43 17 | 50 18 | 90 19 | 19 20 | 85 21 | 57 22 | 66 23 | 73 24 | 15 25 | 4 26 | 79 27 | 77 28 | 50 29 | 2 30 | 35 31 | 49 32 | 21 33 | 98 34 | 25 35 | 17 36 | 89 37 | 7 38 | 22 39 | 11 40 | 12 41 | 58 42 | 99 43 | 47 44 | 34 45 | 68 46 | 58 47 | 2 48 | 61 49 | 7 50 | 93 51 | 86 52 | 88 53 | 33 54 | 27 55 | 26 56 | 21 57 | 27 58 | 41 59 | 4 60 | 85 61 | 28 62 | 44 63 | 5 64 | 94 65 | 21 66 | 80 67 | 50 68 | 2 69 | 8 70 | 80 71 | 96 72 | 89 73 | 28 74 | 72 75 | 35 76 | 34 77 | 44 78 | 28 79 | 34 80 | 26 81 | 25 82 | 35 83 | 17 84 | 24 85 | 99 86 | 67 87 | 81 88 | 12 89 | 49 90 | 62 91 | 94 92 | 35 93 | 62 94 | 36 95 | 34 96 | 39 97 | 40 98 | 43 99 | 75 100 | 96 101 | 53 102 | -------------------------------------------------------------------------------- /test/data/raw/sum/2b.sol: -------------------------------------------------------------------------------- 1 | 4511 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/3.in: -------------------------------------------------------------------------------- 1 | 10 2 | 50 3 | 62 4 | 88 5 | 99 6 | 67 7 | 39 8 | 13 9 | 69 10 | 66 11 | 45 12 | -------------------------------------------------------------------------------- /test/data/raw/sum/3.sol: -------------------------------------------------------------------------------- 1 | 598 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/4.in: -------------------------------------------------------------------------------- 1 | 20 2 | 22 3 | 13 4 | 28 5 | 15 6 | 40 7 | 10 8 | 27 9 | 97 10 | 36 11 | 99 12 | 7 13 | 48 14 | 35 15 | 68 16 | 20 17 | 88 18 | 59 19 | 56 20 | 5 21 | 27 22 | -------------------------------------------------------------------------------- /test/data/raw/sum/4.sol: -------------------------------------------------------------------------------- 1 | 800 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/5.in: -------------------------------------------------------------------------------- 1 | 20 2 | 27 3 | 81 4 | 39 5 | 29 6 | 5 7 | 53 8 | 52 9 | 89 10 | 26 11 | 90 12 | 2 13 | 49 14 | 3 15 | 90 16 | 73 17 | 39 18 | 12 19 | 92 20 | 38 21 | 18 22 | -------------------------------------------------------------------------------- /test/data/raw/sum/5.sol: -------------------------------------------------------------------------------- 1 | 907 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/6.in: -------------------------------------------------------------------------------- 1 | 120 2 | 47 3 | 77 4 | 81 5 | 100 6 | 95 7 | 34 8 | 100 9 | 93 10 | 76 11 | 62 12 | 40 13 | 20 14 | 70 15 | 20 16 | 22 17 | 31 18 | 93 19 | 57 20 | 96 21 | 26 22 | 37 23 | 26 24 | 98 25 | 57 26 | 52 27 | 91 28 | 84 29 | 38 30 | 68 31 | 95 32 | 4 33 | 17 34 | 14 35 | 26 36 | 10 37 | 64 38 | 35 39 | 74 40 | 36 41 | 22 42 | 47 43 | 2 44 | 64 45 | 76 46 | 62 47 | 99 48 | 74 49 | 9 50 | 62 51 | 92 52 | 41 53 | 95 54 | 76 55 | 3 56 | 42 57 | 69 58 | 58 59 | 18 60 | 41 61 | 44 62 | 83 63 | 41 64 | 39 65 | 70 66 | 23 67 | 36 68 | 14 69 | 32 70 | 60 71 | 75 72 | 80 73 | 17 74 | 3 75 | 83 76 | 79 77 | 80 78 | 52 79 | 86 80 | 90 81 | 55 82 | 54 83 | 90 84 | 67 85 | 83 86 | 60 87 | 53 88 | 90 89 | 25 90 | 97 91 | 7 92 | 64 93 | 66 94 | 61 95 | 57 96 | 86 97 | 36 98 | 46 99 | 31 100 | 40 101 | 54 102 | 11 103 | 13 104 | 94 105 | 83 106 | 3 107 | 42 108 | 72 109 | 74 110 | 70 111 | 2 112 | 2 113 | 68 114 | 96 115 | 40 116 | 40 117 | 95 118 | 64 119 | 83 120 | 34 121 | 98 122 | -------------------------------------------------------------------------------- /test/data/raw/sum/6.sol: -------------------------------------------------------------------------------- 1 | 6611 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/7.in: -------------------------------------------------------------------------------- 1 | 120 2 | 80 3 | 59 4 | 34 5 | 76 6 | 54 7 | 45 8 | 93 9 | 44 10 | 62 11 | 88 12 | 75 13 | 49 14 | 84 15 | 29 16 | 2 17 | 59 18 | 81 19 | 35 20 | 76 21 | 72 22 | 26 23 | 75 24 | 94 25 | 36 26 | 40 27 | 81 28 | 35 29 | 9 30 | 49 31 | 14 32 | 76 33 | 13 34 | 69 35 | 29 36 | 72 37 | 38 38 | 99 39 | 61 40 | 24 41 | 33 42 | 91 43 | 28 44 | 100 45 | 55 46 | 23 47 | 41 48 | 64 49 | 25 50 | 42 51 | 3 52 | 30 53 | 34 54 | 22 55 | 9 56 | 92 57 | 23 58 | 10 59 | 15 60 | 14 61 | 9 62 | 38 63 | 2 64 | 91 65 | 56 66 | 89 67 | 24 68 | 47 69 | 56 70 | 37 71 | 18 72 | 36 73 | 88 74 | 48 75 | 88 76 | 28 77 | 28 78 | 84 79 | 59 80 | 75 81 | 93 82 | 90 83 | 11 84 | 72 85 | 53 86 | 97 87 | 13 88 | 26 89 | 49 90 | 35 91 | 12 92 | 1 93 | 93 94 | 3 95 | 61 96 | 67 97 | 48 98 | 59 99 | 27 100 | 69 101 | 50 102 | 45 103 | 30 104 | 3 105 | 99 106 | 82 107 | 50 108 | 95 109 | 59 110 | 72 111 | 8 112 | 55 113 | 53 114 | 30 115 | 53 116 | 14 117 | 98 118 | 59 119 | 78 120 | 93 121 | 100 122 | -------------------------------------------------------------------------------- /test/data/raw/sum/7.sol: -------------------------------------------------------------------------------- 1 | 6097 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/8.in: -------------------------------------------------------------------------------- 1 | 30 2 | 41 3 | 74 4 | 54 5 | 22 6 | 90 7 | 58 8 | 39 9 | 16 10 | 8 11 | 57 12 | 96 13 | 73 14 | 20 15 | 7 16 | 12 17 | 34 18 | 42 19 | 8 20 | 29 21 | 18 22 | 1 23 | 42 24 | 94 25 | 39 26 | 46 27 | 69 28 | 85 29 | 50 30 | 85 31 | 33 32 | -------------------------------------------------------------------------------- /test/data/raw/sum/8.sol: -------------------------------------------------------------------------------- 1 | 1342 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/9.in: -------------------------------------------------------------------------------- 1 | 30 2 | 29 3 | 98 4 | 11 5 | 51 6 | 45 7 | 6 8 | 50 9 | 32 10 | 34 11 | 42 12 | 83 13 | 8 14 | 23 15 | 6 16 | 90 17 | 92 18 | 29 19 | 73 20 | 60 21 | 99 22 | 48 23 | 29 24 | 73 25 | 52 26 | 39 27 | 63 28 | 99 29 | 70 30 | 73 31 | 36 32 | -------------------------------------------------------------------------------- /test/data/raw/sum/9.sol: -------------------------------------------------------------------------------- 1 | 1543 2 | -------------------------------------------------------------------------------- /test/data/raw/sum/sum.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | main() 4 | { 5 | int n, x, total; 6 | int i; 7 | scanf("%d",&n); 8 | total = 0; 9 | for(i=0; i 5 | 6 | int main() 7 | { 8 | int r; 9 | int a, 10 | r = scanf("%d %d",&a,&b); 11 | printf("%d\n",a+b); 12 | return 0; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/data/test1_correct.c: -------------------------------------------------------------------------------- 1 | /* 2 | LANG: C 3 | */ 4 | #include 5 | 6 | int main() 7 | { 8 | int a,b,r; 9 | r = scanf("%d %d",&a,&b); 10 | printf("%d\n",a+b); 11 | a += r; 12 | return 0; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/data/test1_correct_cc.cc: -------------------------------------------------------------------------------- 1 | /* 2 | LANG: C++ 3 | */ 4 | #include 5 | 6 | int main() 7 | { 8 | int a,b,r; 9 | r = scanf("%d %d",&a,&b); 10 | printf("%d\n",a+b); 11 | a += r; 12 | return 0; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /test/data/test2_05sec.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // run it for 1.5 s 9 | 10 | int main() 11 | { 12 | int a,b; 13 | 14 | int c=0; 15 | int r; 16 | 17 | r = scanf("%d %d",&a,&b); 18 | printf("%d\n",a+b); 19 | 20 | struct rusage ru; 21 | 22 | while(1) { 23 | c++; 24 | b+=c; 25 | while(c<100000) { 26 | c++; 27 | b+=c; 28 | } 29 | getrusage(RUSAGE_SELF,&ru); 30 | double rtime = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec; 31 | rtime += (double)ru.ru_utime.tv_usec / 1000000.0; 32 | rtime += (double)ru.ru_stime.tv_usec / 1000000.0; 33 | if(rtime > 0.5) 34 | break; 35 | } 36 | printf("%d\n",b); 37 | a += r; 38 | exit(0); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /test/data/test2_timeout.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int a,b; 6 | int r; 7 | 8 | r = scanf("%d %d",&a,&b); 9 | while(1) 10 | ; 11 | a += r; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /test/data/test_request/input/in1.txt: -------------------------------------------------------------------------------- 1 | 10 2 | 20 3 | -------------------------------------------------------------------------------- /test/data/test_request/problems/test_normal/script/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 []" 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 48 | -------------------------------------------------------------------------------- /test/data/test_request/problems/test_normal/test_cases/1/answer-1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe-grader-team/cafe-grader-judge-scripts/af9755cc8372e00ac2144b1590963c1e92c38eb3/test/data/test_request/problems/test_normal/test_cases/1/answer-1.txt -------------------------------------------------------------------------------- /test/data/test_request/problems/test_normal/test_cases/all_tests.cfg: -------------------------------------------------------------------------------- 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 12 | -------------------------------------------------------------------------------- /test/data/test_run_stat.c: -------------------------------------------------------------------------------- 1 | /* 2 | LANG: C 3 | */ 4 | #include 5 | #include 6 | 7 | int main() 8 | { 9 | int a,b,d; 10 | int i,j; 11 | char *c = malloc(100000); 12 | int r; 13 | 14 | r = scanf("%d %d",&a,&b); 15 | d = a+b; 16 | // printf("%d\n",a+b); 17 | for(j=0; j<1; j++) 18 | for(i=0; i<100000000; i++) { 19 | b+=a; 20 | a++; 21 | } 22 | if((c!=NULL) || (b<100)) 23 | b++; 24 | if(b==100) 25 | printf("hello"); 26 | else 27 | printf("%d\n",d); 28 | 29 | a += r; 30 | return 0; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /test/data/yesno_access_problem_home.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 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 | } 13 | -------------------------------------------------------------------------------- /test/data/yesno_open_file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 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 | } 14 | -------------------------------------------------------------------------------- /test/engine_spec.rb: -------------------------------------------------------------------------------- 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 grade normal submission in C++" do 32 | cpp_lang = stub(Language, :name => 'cpp', :ext => 'cpp') 33 | 34 | grader_should(:grade => "test1_correct_cc.cc", 35 | :language => cpp_lang, 36 | :on => @problem_test_normal, 37 | :and_report => { 38 | :score => 135, 39 | :comment => /^(\[|P|\])+/}) 40 | end 41 | 42 | it "should produce error message when submission cannot compile" do 43 | grader_should(:grade => "test1_compile_error.c", 44 | :on => @problem_test_normal, 45 | :and_report => { 46 | :score => 0, 47 | :comment => 'compilation error', 48 | :compiler_message => /[Ee]rror/}) 49 | end 50 | 51 | it "should produce timeout error when submission runs forever" do 52 | @problem_test_timeout = stub(Problem, 53 | :id => 1, :name => 'test_timeout', 54 | :full_score => 10) 55 | grader_should(:grade => "test2_timeout.c", 56 | :on => @problem_test_timeout, 57 | :and_report => { 58 | :score => 0, 59 | :comment => 'TT'}) 60 | end 61 | 62 | it "should produce timeout error correctly with fractional running time and fractional time limits" do 63 | @problem_test_timeout = stub(Problem, 64 | :id => 1, :name => 'test_timeout', 65 | :full_score => 20) 66 | grader_should(:grade => "test2_05sec.c", 67 | :on => @problem_test_timeout, 68 | :and_report => { 69 | :score => 10, 70 | :comment => 'TP'}) 71 | end 72 | 73 | it "should produce runtime error when submission uses too much static 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_static.c", 78 | :on => @problem_test_memory, 79 | :and_report => { 80 | :score => 10, 81 | :comment => /[^P]P/}) 82 | end 83 | 84 | it "should not allow submission to allocate too much dynamic memory" do 85 | @problem_test_memory = stub(Problem, 86 | :id => 1, :name => 'test_memory', 87 | :full_score => 20) 88 | grader_should(:grade => "add_too_much_memory_dynamic.c", 89 | :on => @problem_test_memory, 90 | :and_report => { 91 | :score => 10, 92 | :comment => /[^P]P/}) 93 | end 94 | 95 | it "should score test runs correctly when submission fails in some test case" do 96 | grader_should(:grade => "add_fail_test_case_1.c", 97 | :on => @problem_test_normal, 98 | :and_report => { 99 | :score => 105, 100 | :comment => /^.*(-|x).*$/}) 101 | end 102 | 103 | it "should fail submission with non-zero exit status" do 104 | grader_should(:grade => "add_nonzero_exit_status.c", 105 | :on => @problem_test_normal, 106 | :and_report => { 107 | :score => 0, 108 | :comment => /^(\[|x|\])+$/}) 109 | end 110 | 111 | it "should not allow malicious submission to see PROBLEM_HOME" do 112 | problem_test_yesno = stub(Problem, 113 | :id => 1, :name => 'test_yesno', 114 | :full_score => 10) 115 | grader_should(:grade => "yesno_access_problem_home.c", 116 | :on => problem_test_yesno, 117 | :and_report => { 118 | :score => 0, 119 | :comment => /(-|x)/}) 120 | end 121 | 122 | it "should not allow malicious submission to open files" do 123 | problem_test_yesno = stub(Problem, 124 | :id => 1, :name => 'test_yesno', 125 | :full_score => 10) 126 | grader_should(:grade => "yesno_open_file.c", 127 | :on => problem_test_yesno, 128 | :and_report => { 129 | :score => 0, 130 | :comment => /(-|x)/}) 131 | end 132 | 133 | def grader_should(args) 134 | @user1 = stub(User, 135 | :id => 1, :login => 'user1') 136 | 137 | submission = 138 | create_submission_from_file(1, @user1, args[:on], args[:grade], args[:language]) 139 | submission.should_receive(:graded_at=) 140 | 141 | expected_score = args[:and_report][:score] 142 | expected_comment = args[:and_report][:comment] 143 | if args[:and_report][:compiler_message]!=nil 144 | expected_compiler_message = args[:and_report][:compiler_message] 145 | else 146 | expected_compiler_message = '' 147 | end 148 | 149 | submission.should_receive(:points=).with(expected_score) 150 | submission.should_receive(:grader_comment=).with(expected_comment) 151 | submission.should_receive(:compiler_message=).with(expected_compiler_message) 152 | submission.should_receive(:save) 153 | 154 | @engine.grade(submission) 155 | end 156 | 157 | protected 158 | 159 | def create_normal_submission_mock_from_file(source_fname) 160 | create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname) 161 | end 162 | 163 | end 164 | 165 | describe "A grader engine, when grading test requests" do 166 | 167 | include GraderEngineHelperMethods 168 | 169 | before(:each) do 170 | @config = Grader::Configuration.get_instance 171 | @engine = Grader::Engine.new(:room_maker => Grader::TestRequestRoomMaker.new, 172 | :reporter => Grader::TestRequestReporter.new) 173 | init_sandbox 174 | end 175 | 176 | it "should report error if there is no problem template" do 177 | problem = stub(Problem, 178 | :id => 1, :name => 'nothing') 179 | grader_should(:grade => 'test1_correct.c', 180 | :on => problem, 181 | :with => 'in1.txt', 182 | :and_report => { 183 | :graded_at= => nil, 184 | :compiler_message= => '', 185 | :grader_comment= => '', 186 | :running_stat= => /template not found/, 187 | :running_time= => nil, 188 | :exit_status= => nil, 189 | :memory_usage= => nil, 190 | :save => nil}) 191 | end 192 | 193 | it "should run test request and produce output file" do 194 | problem = stub(Problem, 195 | :id => 1, :name => 'test_normal') 196 | grader_should(:grade => 'test1_correct.c', 197 | :on => problem, 198 | :with => 'in1.txt', 199 | :and_report => { 200 | :graded_at= => nil, 201 | :compiler_message= => '', 202 | :grader_comment= => '', 203 | :running_stat= => /0.0\d* sec./, 204 | :output_file_name= => lambda { |fname| 205 | File.exists?(fname).should be true 206 | }, 207 | :running_time= => nil, 208 | :exit_status= => nil, 209 | :memory_usage= => nil, 210 | :save => nil}) 211 | end 212 | 213 | it "should clean up problem directory after running test request" do 214 | problem = stub(Problem, 215 | :id => 1, :name => 'test_normal') 216 | grader_should(:grade => 'test1_correct.c', 217 | :on => problem, 218 | :with => 'in1.txt', 219 | :and_report => { 220 | :graded_at= => nil, 221 | :compiler_message= => '', 222 | :grader_comment= => '', 223 | :running_stat= => nil, 224 | :output_file_name= => nil, 225 | :running_time= => nil, 226 | :exit_status= => nil, 227 | :memory_usage= => nil, 228 | :save => nil}) 229 | File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be false 230 | end 231 | 232 | it "should compile test request with error and report compilation error" do 233 | problem = stub(Problem, 234 | :id => 1, :name => 'test_normal') 235 | grader_should(:grade => 'test1_compile_error.c', 236 | :on => problem, 237 | :with => 'in1.txt', 238 | :and_report => { 239 | :graded_at= => nil, 240 | :compiler_message= => /.+/, 241 | :grader_comment= => /[Cc]ompil.*error/, 242 | :running_stat= => '', 243 | :save => nil}) 244 | end 245 | 246 | it "should report exit status" do 247 | problem = stub(Problem, 248 | :id => 1, :name => 'test_normal') 249 | grader_should(:grade => 'add_nonzero_exit_status.c', 250 | :on => problem, 251 | :with => 'in1.txt', 252 | :and_report => { 253 | :graded_at= => nil, 254 | :compiler_message= => '', 255 | :grader_comment= => '', 256 | :running_stat= => /[Ee]xit.*status.*10.*0\.0\d* sec/m, 257 | :output_file_name= => lambda { |fname| 258 | File.exists?(fname).should be true 259 | }, 260 | :running_time= => nil, 261 | :exit_status= => /10/, 262 | :memory_usage= => nil, 263 | :save => nil}) 264 | end 265 | 266 | it "should produce running statistics for normal submission" do 267 | problem = stub(Problem, 268 | :id => 1, :name => 'test_normal') 269 | grader_should(:grade => 'test_run_stat.c', 270 | :on => problem, 271 | :with => 'in1.txt', 272 | :and_report => { 273 | :graded_at= => nil, 274 | :compiler_message= => '', 275 | :grader_comment= => '', 276 | :running_stat= => nil, 277 | :output_file_name= => lambda { |fname| 278 | File.exists?(fname).should be true 279 | }, 280 | :running_time= => lambda { |t| 281 | (t>=0.14) and (t<=0.16) 282 | }, 283 | :exit_status= => nil, 284 | :memory_usage= => lambda { |s| 285 | (s>=500) and (s<=1000) 286 | }, 287 | :save => nil}) 288 | end 289 | 290 | protected 291 | def grader_should(args) 292 | @user1 = stub(User, 293 | :id => 1, :login => 'user1') 294 | 295 | problem = args[:on] 296 | input_file = @config.test_request_input_base_dir + "/" + args[:with] 297 | 298 | submission = 299 | create_submission_from_file(1, @user1, args[:on], args[:grade]) 300 | 301 | test_request = stub(TestRequest, 302 | :id => 1, 303 | :user => @user1, 304 | :problem => problem, 305 | :submission => submission, 306 | :input_file_name => input_file, 307 | :language => submission.language, 308 | :problem_name => problem.name) 309 | 310 | expectations = args[:and_report] 311 | 312 | expectations.each do |key,val| 313 | if val==nil 314 | test_request.should_receive(key) 315 | elsif val.class == Proc 316 | test_request.should_receive(key) { |fname| 317 | val.call(fname) 318 | } 319 | else 320 | test_request.should_receive(key).with(val) 321 | end 322 | end 323 | 324 | @engine.grade(test_request) 325 | end 326 | 327 | end 328 | 329 | -------------------------------------------------------------------------------- /test/engine_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module GraderEngineHelperMethods 4 | 5 | def clear_sandbox 6 | config = Grader::Configuration.get_instance 7 | FileUtils.rm_rf(Dir.glob("#{config.test_sandbox_dir}/*"), 8 | :secure => true) 9 | end 10 | 11 | def init_sandbox 12 | config = Grader::Configuration.get_instance 13 | clear_sandbox 14 | FileUtils.mkdir_p config.user_result_dir 15 | FileUtils.cp_r("#{config.test_data_dir}/ev", "#{config.test_sandbox_dir}",:preserve => true) 16 | end 17 | 18 | def create_submission_from_file(id, user, problem, 19 | source_fname, language=nil) 20 | 21 | language = stub(Language, :name => 'c', :ext => 'c') if language==nil 22 | 23 | config = Grader::Configuration.get_instance 24 | source = File.open(config.test_data_dir + "/" + source_fname).read 25 | stub(Submission, 26 | :id => id, :user => user, :problem => problem, 27 | :source => source, :language => language) 28 | end 29 | 30 | end 31 | 32 | -------------------------------------------------------------------------------- /test/engine_test.rb: -------------------------------------------------------------------------------- 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 153 | -------------------------------------------------------------------------------- /test/import_helper_test.rb: -------------------------------------------------------------------------------- 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 41 | -------------------------------------------------------------------------------- /test/rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'spec/rake/spectask' 3 | 4 | desc "Run all examples" 5 | Spec::Rake::SpecTask.new('spec') do |t| 6 | t.spec_files = FileList['*spec.rb'] 7 | end 8 | 9 | desc "Run all examples with RCov" 10 | Spec::Rake::SpecTask.new('spec_with_rcov') do |t| 11 | t.spec_files = FileList['*spec.rb'] 12 | t.rcov = true 13 | t.rcov_opts = ['--exclude', '.*_spec\.rb'] 14 | end 15 | -------------------------------------------------------------------------------- /test/runner_spec.rb: -------------------------------------------------------------------------------- 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("[PP][PPPPP][PPP]") 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 | 66 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 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 26 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 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 26 | --------------------------------------------------------------------------------