Description:
merge from algo-bm
Commit status:
[Not Reviewed]
References:
merge java
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r173:aa6e7ff3942c - - 9 files changed: 124 inserted, 34 deleted

@@ -25,53 +25,64
25 25
26 26 def log_file_name
27 27 if !File.exists?(config.log_dir)
28 28 raise "Log directory does not exist: #{config.log_dir}"
29 29 end
30 30 config.log_dir +
31 31 "/#{GRADER_ENV}_#{config.grader_mode}.#{Process.pid}"
32 32 end
33 33
34 34 def log(str)
35 35 if config.talkative
36 36 puts str
37 37 end
38 38 if config.logging
39 39 fp = File.open(log_file_name,"a")
40 40 fp.puts("GRADER: #{Time.new.strftime("%H:%M")} #{str}")
41 41 fp.close
42 42 end
43 43 end
44 44
45 45 def display_manual
46 46 puts <<USAGE
47 47 Grader.
48 48 using: (1) grader
49 - (2) grader environment [mode]
49 + (2) grader environment [mode] [options]
50 50 (3) grader stop [all|pids-list]
51 51 (4) grader --help
52 52 (1) call grader with environment = 'exam', mode = 'queue'
53 53 (2) possible modes are: 'queue', 'test_request', 'prob', 'sub', 'contest', and 'autonew'
54 + queue: repeatedly check the task queue and grade any available tasks
55 +
56 + prob: re-grade every user latest submission of the specific problem.
57 + the problem name must be specified by the next argument.
58 +
59 + additional options:
60 +
61 + --all-sub re-grade every submissions instead of just the latest submission of each user.
62 + sub: re-grader the specified submission.
63 + The submission ID to be re-graded must be specified by the next argument.
64 +
54 65 (3) create stop-file to stop running grader in queue mode
55 66 (4) You are here.
56 67 USAGE
57 68 end
58 69
59 70 def process_options_and_stop_file
60 71 # The list of options are:
61 72 # - stop [all|process ids]
62 73 # -
63 74
64 75 # Process 'help' option
65 76 if (ARGV.length==1) and (/help/.match(ARGV[0]))
66 77 display_manual
67 78 exit(0)
68 79 end
69 80
70 81 # Process 'stop' option.
71 82 if (ARGV.length >= 1) and (ARGV[0]=='stop')
72 83 if ARGV.length==1
73 84 puts "you should specify pid-list or 'all'"
74 85 display_manual
75 86 elsif (ARGV.length==2) and (ARGV[1]=='all')
76 87 stop_grader(:all)
77 88 puts "A global stop file ('stop.all') created."
@@ -84,62 +95,66
84 95 end
85 96 exit(0)
86 97 end
87 98
88 99 # Check stop file.
89 100 if check_stopfile
90 101 puts "Stop file exists. Terminated."
91 102 clear_stopfile
92 103 exit(0)
93 104 end
94 105
95 106 #default options
96 107 options = {
97 108 :mode => 'queue',
98 109 :environment => 'exam',
99 110 :dry_run => false,
100 111 }
101 112
102 113 # Process mode and environment option
103 114 if ARGV.length >= 1
104 115 options[:environment] = ARGV.shift
105 116 if ARGV.length >=1
106 117 options[:mode] = ARGV.shift
107 118 end
119 + else
120 + puts 'no argument specified, using default mode and environment.'
108 121 end
109 122
110 123 options[:dry_run] = (ARGV.delete('--dry') != nil)
111 124 if options[:dry_run] and (not ['prob','contest','autonew'].include? options[:mode])
112 125 puts "Dry run currently works only for 'prob' or 'contest' modes."
113 126 exit(0)
114 127 end
115 128
116 129 options[:report] = (ARGV.delete('--report') != nil)
117 130 if options[:report] and (not ['prob','contest','autonew'].include? options[:mode])
118 131 puts "Report currently works only for 'prob' or 'contest' modes."
119 132 exit(0)
120 133 end
121 134
135 + options[:all_sub] = (ARGV.delete('--all-sub') != nil)
136 +
122 137 return options
123 138 end
124 139
125 140 class ResultCollector
126 141 def initialize
127 142 @results = {}
128 143 @problems = {}
129 144 @users = {}
130 145 end
131 146
132 147 def after_save_hook(submission, grading_result)
133 148 end
134 149
135 150 def save(submission, grading_result)
136 151 user = submission.user
137 152 problem = submission.problem
138 153 if not @problems.has_key? problem.id
139 154 @problems[problem.id] = problem
140 155 end
141 156 if not @users.has_key? user.id
142 157 @users[user.id] = user
143 158 end
144 159 @results[[user.id, problem.id]] = grading_result
145 160
@@ -274,49 +289,49
274 289
275 290 def grader_grade_problems(grader_proc, options)
276 291 if options[:report]
277 292 result_collector = ResultCollector.new
278 293 else
279 294 result_collector = nil
280 295 end
281 296
282 297 if options[:dry_run]
283 298 puts "Running in dry mode"
284 299 end
285 300
286 301 prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run],
287 302 :result_collector => result_collector)
288 303 engine = Grader::Engine.new(:reporter => prob_reporter)
289 304 runner = Grader::Runner.new(engine, grader_proc)
290 305
291 306 grader_proc.report_active if grader_proc!=nil
292 307
293 308 ARGV.each do |prob_name|
294 309 prob = Problem.find_by_name(prob_name)
295 310 if prob==nil
296 311 puts "cannot find problem: #{prob_name}"
297 312 else
298 - runner.grade_problem(prob)
313 + runner.grade_problem(prob,options)
299 314 end
300 315 end
301 316
302 317 if options[:report]
303 318 result_collector.print_report_by_user
304 319 end
305 320 end
306 321
307 322 def grader_grade_contests(grader_proc, options)
308 323 # always use dry run when grading during contest
309 324 dry_run = options[:dry_run] = true
310 325
311 326 contest_name = ARGV.shift
312 327
313 328 contest = Contest.find_by_name(contest_name)
314 329 if contest==nil
315 330 puts "cannot find contest: #{contest_name}"
316 331 exit(0)
317 332 end
318 333
319 334 if options[:report]
320 335 result_collector = ResultCollector.new
321 336 else
322 337 result_collector = nil
@@ -357,48 +372,49
357 372 puts "Grading #{sub_id}"
358 373 begin
359 374 submission = Submission.find(sub_id.to_i)
360 375 rescue ActiveRecord::RecordNotFound
361 376 puts "Submission #{sub_id} not found"
362 377 submission = nil
363 378 end
364 379
365 380 if submission!=nil
366 381 runner.grade_submission(submission)
367 382 end
368 383 end
369 384 end
370 385
371 386 #########################################
372 387 # main program
373 388 #########################################
374 389
375 390 options = process_options_and_stop_file
376 391 GRADER_ENV = options[:environment]
377 392 grader_mode = options[:mode]
378 393 dry_run = options[:dry_run]
379 394
380 395 puts "environment: #{GRADER_ENV}"
396 + puts "grader mode: #{grader_mode}"
381 397 require File.join(File.dirname(__FILE__),'config/environment')
382 398
383 399 # add grader_mode to config
384 400 # this is needed because method log needs it. TODO: clean this up
385 401 class << config
386 402 attr_accessor :grader_mode
387 403 end
388 404 config.grader_mode = grader_mode
389 405
390 406 # reading rails environment
391 407 log 'Reading rails environment'
392 408
393 409 RAILS_ENV = config.rails_env
394 410 require RAILS_ROOT + '/config/environment'
395 411
396 412 # register grader process
397 413 if config.report_grader
398 414 grader_proc = GraderProcess.register(config.grader_hostname,
399 415 Process.pid,
400 416 grader_mode)
401 417 else
402 418 grader_proc = nil
403 419 end
404 420
@@ -4,58 +4,63
4 4
5 5 module Grader
6 6
7 7 class Runner
8 8
9 9 def initialize(engine, grader_process=nil)
10 10 @engine = engine
11 11 @grader_process = grader_process
12 12 end
13 13
14 14 def grade_oldest_task
15 15 task = Task.get_inqueue_and_change_status(Task::STATUS_GRADING)
16 16 if task!=nil
17 17 @grader_process.report_active(task) if @grader_process!=nil
18 18
19 19 submission = Submission.find(task.submission_id)
20 20 @engine.grade(submission)
21 21 task.status_complete!
22 22 @grader_process.report_inactive(task) if @grader_process!=nil
23 23 end
24 24 return task
25 25 end
26 26
27 27 def grade_problem(problem, options={})
28 - users = User.find(:all)
29 - users.each do |u|
28 + User.find_each do |u|
30 29 puts "user: #{u.login}"
31 30 if options[:user_conditions]!=nil
32 31 con_proc = options[:user_conditions]
33 32 next if not con_proc.call(u)
34 33 end
35 - last_sub = Submission.find_last_by_user_and_problem(u.id,problem.id)
36 - if last_sub!=nil
37 - @engine.grade(last_sub)
34 + if options[:all_sub]
35 + Submission.where(user_id: u.id,problem_id: problem.id).find_each do |sub|
36 + @engine.grade(sub)
37 + end
38 + else
39 + last_sub = Submission.find_last_by_user_and_problem(u.id,problem.id)
40 + if last_sub!=nil
41 + @engine.grade(last_sub)
42 + end
38 43 end
39 44 end
40 45 end
41 46
42 47 def grade_submission(submission)
43 48 puts "Submission: #{submission.id} by #{submission.user.full_name}"
44 49 @engine.grade(submission)
45 50 end
46 51
47 52 def grade_oldest_test_request
48 53 test_request = TestRequest.get_inqueue_and_change_status(Task::STATUS_GRADING)
49 54 if test_request!=nil
50 55 @grader_process.report_active(test_request) if @grader_process!=nil
51 56
52 57 @engine.grade(test_request)
53 58 test_request.status_complete!
54 59 @grader_process.report_inactive(test_request) if @grader_process!=nil
55 60 end
56 61 return test_request
57 62 end
58 63
59 64 end
60 65
61 66 end
@@ -44,91 +44,109
44 44 if @result_collector
45 45 @result_collector.save(sub,
46 46 result)
47 47 end
48 48 save_result(sub,result)
49 49 end
50 50
51 51 def report_error(sub,msg)
52 52 save_result(sub,{:points => 0,
53 53 :comment => "Grading error: #{msg}" })
54 54 end
55 55
56 56 protected
57 57 def read_result(test_result_dir)
58 58 cmp_msg_fname = "#{test_result_dir}/compiler_message"
59 59 if FileTest.exist?(cmp_msg_fname)
60 60 cmp_file = File.open(cmp_msg_fname)
61 61 cmp_msg = cmp_file.read
62 62 cmp_file.close
63 63 else
64 64 cmp_msg = ""
65 65 end
66 66
67 67 result_fname = "#{test_result_dir}/result"
68 - comment_fname = "#{test_result_dir}/comment"
68 + comment_fname = "#{test_result_dir}/comment"
69 + runstat_fname = "#{test_result_dir}/run_stat"
69 70 if FileTest.exist?(result_fname)
70 71 comment = ""
71 72 begin
72 73 result_file = File.open(result_fname)
73 74 result = result_file.readline.to_i
74 75 result_file.close
75 76 rescue
76 77 result = 0
77 78 comment = "error reading result file."
78 79 end
79 80
80 81 begin
81 82 comment_file = File.open(comment_fname)
82 83 comment += comment_file.readline.chomp
83 84 comment_file.close
84 85 rescue
85 86 comment += ""
86 87 end
87 88
88 - return {:points => result,
89 - :comment => comment,
90 - :cmp_msg => cmp_msg}
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 + }
91 105 else
92 106 if FileTest.exist?("#{test_result_dir}/a.out")
93 107 return {:points => 0,
94 108 :comment => 'error during grading',
95 109 :cmp_msg => cmp_msg}
96 110 else
97 111 return {:points => 0,
98 112 :comment => 'compilation error',
99 113 :cmp_msg => cmp_msg}
100 114 end
101 115 end
102 116 end
103 117
104 118 def save_result(submission,result)
105 119 problem = submission.problem
106 120 submission.graded_at = Time.now.gmtime
107 121 points = result[:points]
108 122 submission.points = points
109 123 comment = @config.report_comment(result[:comment])
110 124
125 + submission.peak_memory = result[:peak_memory]
126 + submission.max_runtime = result[:max_runtime]
127 + submission.effective_code_length =submission.source.length
128 +
111 129 #
112 130 # TODO: FIX THIS MESSAGE
113 131 #
114 132 if problem == nil
115 133 submission.grader_comment = 'PASSED: ' + comment + '(problem is nil)'
116 134 elsif points == problem.full_score
117 135 #submission.grader_comment = 'PASSED: ' + comment
118 136 submission.grader_comment = comment
119 137 elsif result[:comment].chomp =~ /^[\[\]P]+$/
120 138 submission.grader_comment = 'PASSED: ' + comment + '(inconsistent score)'
121 139 else
122 140 #submission.grader_comment = 'FAILED: ' + comment
123 141 submission.grader_comment = comment
124 142 end
125 143 submission.compiler_message = result[:cmp_msg] or ''
126 144
127 145 if not @dry_run
128 146 submission.save
129 147 end
130 148 end
131 149
132 150 end
133 151
134 152 end
@@ -177,49 +177,49
177 177 end
178 178 end
179 179
180 180 def extract_running_stat(results)
181 181 running_stat_line = results[-1]
182 182
183 183 # extract exit status line
184 184 run_stat = ""
185 185 if !(/[Cc]orrect/.match(results[0]))
186 186 run_stat = results[0].chomp
187 187 else
188 188 run_stat = 'Program exited normally'
189 189 end
190 190
191 191 # extract running time
192 192 if res = /r(.*)u(.*)s/.match(running_stat_line)
193 193 seconds = (res[1].to_f + res[2].to_f)
194 194 time_stat = "Time used: #{seconds} sec."
195 195 else
196 196 seconds = nil
197 197 time_stat = "Time used: n/a sec."
198 198 end
199 199
200 200 # extract memory usage
201 - if res = /s(.*)m/.match(running_stat_line)
201 + if res = /s(.*)kbytes/.match(running_stat_line)
202 202 memory_used = res[1].to_i
203 203 else
204 204 memory_used = -1
205 205 end
206 206
207 207 return {
208 208 :msg => "#{run_stat}\n#{time_stat}",
209 209 :running_time => seconds,
210 210 :exit_status => run_stat,
211 211 :memory_usage => memory_used
212 212 }
213 213 end
214 214
215 215 def save_result(test_request,result)
216 216 if result[:output_file_name]!=nil
217 217 test_request.output_file_name = link_output_file(test_request,
218 218 result[:output_file_name])
219 219 end
220 220 test_request.graded_at = Time.now
221 221 test_request.compiler_message = (result[:cmp_msg] or '')
222 222 test_request.grader_comment = (result[:comment] or '')
223 223 if result[:running_stat]!=nil
224 224 test_request.running_stat = (result[:running_stat][:msg] or '')
225 225 test_request.running_time = (result[:running_stat][:running_time] or nil)
@@ -139,98 +139,98
139 139 fclose(metafile);
140 140 }
141 141
142 142 static void __attribute__((format(printf,1,2)))
143 143 meta_printf(const char *fmt, ...)
144 144 {
145 145 if (!metafile)
146 146 return;
147 147
148 148 va_list args;
149 149 va_start(args, fmt);
150 150 vfprintf(metafile, fmt, args);
151 151 va_end(args);
152 152 }
153 153
154 154
155 155 static void print_running_stat(double wall_time,
156 156 double user_time,
157 157 double system_time,
158 158 int mem_usage)
159 159 {
160 160 //total is user
161 161 //wall is wall
162 162 //
163 - fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dm\n",
163 + fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dkbytes\n",
164 164 wall_time, user_time, system_time, mem_usage);
165 165 }
166 166
167 167 static void
168 168 final_stats(struct rusage *rus)
169 169 {
170 170 struct timeval total, now, wall;
171 171 timeradd(&rus->ru_utime, &rus->ru_stime, &total);
172 172 total_ms = total.tv_sec*1000 + total.tv_usec/1000;
173 173 gettimeofday(&now, NULL);
174 174 timersub(&now, &start_time, &wall);
175 175 wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
176 176 sys_ms = rus->ru_stime.tv_sec * 1000 + rus->ru_stime.tv_usec / 1000;
177 177
178 178 meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000);
179 179 meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000);
180 180 meta_printf("mem:%llu\n", (unsigned long long) mem_peak_kb * 1024);
181 181 }
182 182
183 183 /*** Messages and exits ***/
184 184
185 185 static void NONRET
186 186 box_exit(int rc)
187 187 {
188 188 if (box_pid > 0)
189 189 {
190 190 sample_mem_peak();
191 191 if (is_ptraced)
192 192 ptrace(PTRACE_KILL, box_pid);
193 193 kill(-box_pid, SIGKILL);
194 194 kill(box_pid, SIGKILL);
195 195 meta_printf("killed:1\n");
196 196
197 197 struct rusage rus;
198 198 int p, stat;
199 199 do
200 200 p = wait4(box_pid, &stat, 0, &rus);
201 201 while (p < 0 && errno == EINTR);
202 202 if (p < 0)
203 203 fprintf(stderr, "UGH: Lost track of the process (%m)\n");
204 204 else {
205 205 final_stats(&rus);
206 206 }
207 207 }
208 208 print_running_stat(
209 209 (double)wall_ms/1000,
210 210 (double)total_ms/1000,
211 211 (double)sys_ms/1000,
212 - (mem_peak_kb + 1023) / 1024);
212 + mem_peak_kb);
213 213 meta_close();
214 214 exit(rc);
215 215 }
216 216
217 217 static void
218 218 flush_line(void)
219 219 {
220 220 if (partial_line)
221 221 fputc('\n', stderr);
222 222 partial_line = 0;
223 223 }
224 224
225 225 /* Report an error of the sandbox itself */
226 226 static void NONRET __attribute__((format(printf,1,2)))
227 227 die(char *msg, ...)
228 228 {
229 229 va_list args;
230 230 va_start(args, msg);
231 231 flush_line();
232 232 char buf[1024];
233 233 vsnprintf(buf, sizeof(buf), msg, args);
234 234 meta_printf("status:XX\nmessage:%s\n", buf);
235 235 fputs(buf, stderr);
236 236 fputc('\n', stderr);
@@ -1435,53 +1435,48
1435 1435 die("wait4: unknown pid %d exited!", p);
1436 1436 if (WIFEXITED(stat))
1437 1437 {
1438 1438 box_pid = 0;
1439 1439 final_stats(&rus);
1440 1440 if (WEXITSTATUS(stat))
1441 1441 {
1442 1442 if (syscall_count)
1443 1443 {
1444 1444 meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
1445 1445 err("RE: Exited with error status %d", WEXITSTATUS(stat));
1446 1446 }
1447 1447 else
1448 1448 {
1449 1449 // Internal error happened inside the child process and it has been already reported.
1450 1450 box_exit(2);
1451 1451 }
1452 1452 }
1453 1453 if (timeout && total_ms > timeout)
1454 1454 err("TO: Time limit exceeded");
1455 1455 if (wall_timeout && wall_ms > wall_timeout)
1456 1456 err("TO: Time limit exceeded (wall clock)");
1457 1457 flush_line();
1458 1458 fprintf(stderr,"OK\n");
1459 - print_running_stat(
1460 - (double)wall_ms/1000,
1461 - (double)total_ms/1000,
1462 - (double)sys_ms/1000,
1463 - (mem_peak_kb + 1023) / 1024);
1464 1459 box_exit(0);
1465 1460 }
1466 1461 if (WIFSIGNALED(stat))
1467 1462 {
1468 1463 box_pid = 0;
1469 1464 meta_printf("exitsig:%d\n", WTERMSIG(stat));
1470 1465 final_stats(&rus);
1471 1466 err("SG: Caught fatal signal %d%s", WTERMSIG(stat), (syscall_count ? "" : " during startup"));
1472 1467 }
1473 1468 if (WIFSTOPPED(stat))
1474 1469 {
1475 1470 int sig = WSTOPSIG(stat);
1476 1471 if (sig == SIGTRAP)
1477 1472 {
1478 1473 if (verbose > 2)
1479 1474 msg("[ptrace status %08x] ", stat);
1480 1475 static int stop_count;
1481 1476 if (!stop_count++) /* Traceme request */
1482 1477 msg(">> Traceme request caught\n");
1483 1478 else
1484 1479 err("SG: Breakpoint");
1485 1480 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1486 1481 }
1487 1482 else if (sig == (SIGTRAP | 0x80))
@@ -9,54 +9,56
9 9 # Supported compilers:
10 10 # gcc, g++, and fpc.
11 11 #
12 12 ##############################
13 13
14 14 def talk(str='')
15 15 if ENV['TALKATIVE']!=nil
16 16 puts str
17 17 end
18 18 if ENV['GRADER_LOGGING']!=nil
19 19 log_fname = ENV['GRADER_LOGGING']
20 20 fp = File.open(log_fname,"a")
21 21 fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
22 22 fp.close
23 23 end
24 24 end
25 25
26 26 C_COMPILER = "/usr/bin/gcc"
27 27 CPLUSPLUS_COMPILER = "/usr/bin/g++"
28 28 PASCAL_COMPILER = "/usr/bin/fpc"
29 29 JAVA_COMPILER = "/usr/bin/javac"
30 30 RUBY_INTERPRETER = "/usr/bin/ruby"
31 31 PYTHON_INTERPRETER = "/usr/bin/python"
32 32 PYTHON_CHECKER = "/usr/bin/pyflakes"
33 + PHP_INTERPRETER = "/usr/bin/php"
33 34
34 35 C_OPTIONS = "-O2 -s -static -std=c99 -DCONTEST -lm -Wall"
35 - CPLUSPLUS_OPTIONS = "-O2 -s -static -DCONTEST -lm -Wall"
36 + CPLUSPLUS_OPTIONS = "-O2 -s -std=c++11 -static -DCONTEST -lm -Wall"
36 37 PASCAL_OPTIONS = "-O1 -XS -dCONTEST"
37 38 JAVA_OPTIONS = ""
38 39 PYTHON_OPTIONS = ""
40 + PHP_OPTIONS = "-l"
39 41
40 42 # Check for the correct number of arguments. Otherwise, print usage.
41 43 if ARGV.length == 0 or ARGV.length > 4
42 44 puts "Usage: compile <language> [<source-file>] [<output-file>] [<message-file>]"
43 45 puts
44 46 puts "<source-file> is defaulted to \"source\"."
45 47 puts "<output-file> is defaulted to \"a.out\"."
46 48 puts "<message-file> is defaulted to \"compiler_message\"."
47 49 puts
48 50 exit(127)
49 51 end
50 52
51 53 PARAMS = {
52 54 :source_file => [1,'source'],
53 55 :output_file => [2,'a.out'],
54 56 :message_file => [3,'compiler_message']
55 57 }
56 58
57 59 params = {}
58 60 params[:prog_lang] = ARGV[0]
59 61 PARAMS.each_key do |param_name|
60 62 index, default = PARAMS[param_name]
61 63 if ARGV.length > index
62 64 params[param_name] = ARGV[index]
@@ -87,79 +89,99
87 89 params[:prog_lang] = 'c++'
88 90 end
89 91
90 92 # Compile.
91 93 case params[:prog_lang]
92 94
93 95 when "c"
94 96 command = "#{C_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{C_OPTIONS} 2> #{params[:message_file]}"
95 97 system(command)
96 98
97 99 when "c++"
98 100 command = "#{CPLUSPLUS_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{CPLUSPLUS_OPTIONS} 2> #{params[:message_file]}"
99 101 system(command)
100 102
101 103 when "pas"
102 104 command = "#{PASCAL_COMPILER} #{params[:source_file]} -ooutpas #{PASCAL_OPTIONS} > #{params[:message_file]}"
103 105 system(command)
104 106 FileUtils.mv("output", params[:output_file])
105 107
106 108 when "java"
107 109 #rename the file to the public class name
108 110
109 111 #get the class name
110 112 classname = 'DUMMY'
113 + source = Array.new
111 114 File.foreach(params[:source_file]) do |line|
112 115 md = /\s*public\s*class\s*(\w*)/.match(line)
113 116 classname=md[1] if md
117 + source << line unless line =~ /\s*package\s*\w+\s*\;/
114 118 end
115 - system("cp #{params[:source_file]} #{classname}.java")
119 + File.open("#{classname}.java","w") do |file|
120 + source.each do |s|
121 + file.puts s
122 + end
123 + end
124 + #system("cp #{params[:source_file]} #{classname}.java")
116 125 command = "#{JAVA_COMPILER} #{classname}.java 2> #{params[:message_file]}"
117 126 system(command)
118 127 if File.exists?(classname + ".class")
119 128 File.open(params[:output_file],"w") {|file| file.write("#{classname}")}
120 129 end
121 130 if classname == 'DUMMY'
122 131 File.open(params[:message_file],"w") {|file| file.write("Cannot find any public class in the source code\n")}
123 132 end
124 133
125 134 when "ruby"
126 135 command = "#{RUBY_INTERPRETER} -c #{params[:source_file]} 2> #{params[:message_file]}"
127 136 if system(command)
128 137 File.open(params[:output_file],"w") do |out_file|
129 138 out_file.puts "#!#{RUBY_INTERPRETER}"
130 139 File.open(params[:source_file],"r").each do |line|
131 140 out_file.print line
132 141 end
133 142 end
134 143 File.chmod(0755, params[:output_file])
135 144 end
136 145
137 146 when "python"
138 147 command = "#{PYTHON_CHECKER} #{params[:source_file]} > #{params[:message_file]}"
139 148 if system(command)
140 149 #compile to python bytecode
141 150 command = "#{PYTHON_INTERPRETER} -m py_compile #{params[:source_file]}"
142 151 puts "compile: #{command}"
143 152 system(command)
144 153 puts "pwd: " + Dir.pwd
145 154 Dir.new('.').each {|file| puts file}
146 155 File.open(params[:output_file],"w") do |out_file|
147 156 out_file.puts "#!#{PYTHON_INTERPRETER} #{params[:source_file]}c"
148 157 end
149 158 File.chmod(0755, params[:output_file])
159 + FileUtils.cp("#{params[:source_file]}c",params[:output_file])
150 160 end
151 -
161 +
162 + when "php"
163 + command = "#{PHP_INTERPRETER} #{PHP_OPTIONS} #{params[:source_file]} 2> #{params[:message_file]}"
164 + if system(command)
165 + File.open(params[:output_file],"w") do |out_file|
166 + out_file.puts "#!#{PHP_INTERPRETER}"
167 + File.open(params[:source_file],"r").each do |line|
168 + out_file.print line
169 + end
170 + end
171 + File.chmod(0755, params[:output_file])
172 + end
173 +
152 174 else
153 175 talk("ERROR: Invalid language specified!")
154 176 open(params[:message_file],"w") do |f|
155 177 f.puts "ERROR: Invalid language specified!"
156 178 end
157 179 exit(127)
158 180 end
159 181
160 182 # Report success or failure.
161 183 if FileTest.exists? params[:output_file]
162 184 talk "Compilation was successful!"
163 185 else
164 186 talk "ERROR: Something was wrong during the compilation!"
165 187 end
@@ -10,99 +10,124
10 10 puts str
11 11 end
12 12 if ENV['GRADER_LOGGING']!=nil
13 13 log_fname = ENV['GRADER_LOGGING']
14 14 fp = File.open(log_fname,"a")
15 15 fp.puts("grade: #{Time.new.strftime("%H:%M")} #{str}")
16 16 fp.close
17 17 end
18 18 end
19 19
20 20 def char_comment(comment)
21 21 if comment =~ /[Ii]ncorrect/
22 22 INCORRECT_MARK
23 23 elsif comment =~ /[Cc]orrect/
24 24 CORRECT_MARK
25 25 elsif comment =~ /[Tt]ime/
26 26 TIMEOUT_MARK
27 27 elsif res = /^[Cc]omment:(.*)$/.match(comment)
28 28 res[1]
29 29 else
30 30 RUN_ERROR_MARK # these are run time errors
31 31 end
32 32 end
33 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 +
34 45 problem_home = ENV['PROBLEM_HOME']
35 46 require "#{problem_home}/script/test_dsl.rb"
36 47 load "#{problem_home}/test_cases/all_tests.cfg"
37 48 problem = Problem.get_instance
38 49
39 50 if problem.well_formed? == false
40 51 log "The problem specification is not well formed."
41 52 exit(127)
42 53 end
43 54
44 55 all_score = 0
45 56 all_comment = ''
57 + peak_memory = -1
58 + max_runtime = -1
46 59 (1..(problem.runs.length-1)).each do |k|
47 60 log "grade run #{k}"
48 61 run = problem.runs[k]
49 62 run_score = nil
50 63 run_comment = ''
51 64 run_comment_short = ''
52 65 run.tests.each do |test_num|
53 66 result_file_name = "#{test_num}/result"
54 67 if not File.exists?(result_file_name)
55 68 run_comment += "result file for test #{test_num} not found\n"
56 69 run_comment_short += RUN_ERROR_MARK
57 70 log "Cannot find the file #{test_num}/result!"
58 71 else
59 72 result_file = File.new(result_file_name, "r")
60 73 result_file_lines = result_file.readlines
61 - if result_file_lines.length>=2
74 + if result_file_lines.length>=3
62 75 current_run_score = result_file_lines[1].to_i
63 76 run_comment += result_file_lines[0]
64 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
65 83 else
66 84 current_run_score = 0
67 85 run_comment += "result file for test #{test_num} error\n"
68 86 run_comment_short += RUN_ERROR_MARK
69 87 log "Error in #{test_num}/result!"
70 88 end
71 89
72 90 # the score of this run should be the minimum of the score for
73 91 # each test case
74 92 if (run_score==nil) or (run_score>current_run_score)
75 93 run_score = current_run_score
76 94 end
77 95 result_file.close
78 96 end
79 97 end
80 98
81 99 run_result_file = File.new("result-#{k}", "w")
82 100 run_result_file.write run_score
83 101 run_result_file.write "\n"
84 102 run_result_file.close
85 103
86 104 run_comment_file = File.new("comment-#{k}", "w")
87 105 run_comment_file.write "#{run_comment}\n"
88 106 run_comment_file.close
89 107
90 108 all_score = all_score + run_score
91 109
92 110 # append comment for test run with many test cases
93 111 if run.tests.length > 1
94 112 run_comment_short = '[' + run_comment_short + ']'
95 113 end
96 114 all_comment += run_comment_short
97 115 end
98 116
99 117 result_file = File.new("result", "w")
100 118 result_file.write all_score
101 119 result_file.write "\n"
102 120 result_file.close
103 121
104 122 comment_file = File.new("comment", "w")
105 123 comment_file.write "#{all_comment}\n"
106 124 comment_file.close
107 125
108 - log "score = #{all_score} comment = #{all_comment}"
126 +
127 + File.open("run_stat","w") do |file|
128 + file.puts max_runtime
129 + file.puts peak_memory
130 + end
131 +
132 + log "score = #{all_score}\ncomment = #{all_comment}"
133 + log "max_runtime = #{max_runtime}\npeak_memory = #{peak_memory}"
@@ -32,54 +32,55
32 32 log msg
33 33 raise msg
34 34 end
35 35 end
36 36
37 37 def clear_and_create_empty_dir(dir)
38 38 FileUtils.rm_rf(dir, :secure => true)
39 39 call_and_log("Cannot make directory #{dir}.") { FileUtils.mkdir(dir) }
40 40 end
41 41
42 42 # ARGV[0] --- language
43 43 # ARGV[1] --- program source file
44 44 # ARGV[2] --- test result directory
45 45 # ARGV[3] --- sandbox directory
46 46
47 47 if ARGV.length < 2 || ARGV.length > 4
48 48 puts "Usage: judge <language> <program-source> [<test-result-directory>] [<sandbox-directory>]"
49 49 puts " <sandbox-directory> is defaulted to ./sandbox"
50 50 puts " <test-result-directory> is defaulted to ./test-result"
51 51 puts "WARNING: The judge script will forcefully create the (implicitly and explicitly) specified directories and remove anything inside it."
52 52 exit(127)
53 53 end
54 54
55 55 language = ARGV[0]
56 - if language != "c" && language != "c++" && language != "pas" && language != "java" && language != "ruby" && language != "python"
56 + if language != "c" && language != "c++" && language != "pas" && language != "java" && language != "ruby" && language != "python" && language != "php"
57 57 log "You specified a language that is not supported: #{language}."
58 58 exit(127)
59 59 end
60 60
61 61 source_file = ARGV[1]
62 + ENV['SOURCE_NAME'] = source_file
62 63 if File.exist?(source_file) == false
63 64 log "The source file does not exist."
64 65 exit(127)
65 66 end
66 67
67 68 log "Making test result and sandbox directories..."
68 69
69 70 current_dir = FileUtils.pwd
70 71 current_dir.strip!
71 72
72 73 if ARGV.length >= 3
73 74 test_result_dir = ARGV[2]
74 75 else
75 76 test_result_dir = "#{current_dir}/test-result"
76 77 end
77 78
78 79 log "Test result directory: #{test_result_dir}"
79 80 clear_and_create_empty_dir(test_result_dir)
80 81
81 82 if ARGV.length >= 4
82 83 sandbox_dir = ARGV[3]
83 84 else
84 85 sandbox_dir = "#{current_dir}/sandbox"
85 86 end
@@ -119,49 +120,49
119 120 require "#{problem_home}/script/test_dsl.rb"
120 121 load "#{problem_home}/test_cases/all_tests.cfg"
121 122 problem = Problem.get_instance
122 123
123 124 if problem.well_formed? == false
124 125 log "The problem specification is not well formed."
125 126 exit(127)
126 127 end
127 128
128 129 # Doing the testing.
129 130 (1..(problem.num_tests)).each do |test_num|
130 131
131 132 $stdout.print "[#{test_num}]"
132 133 $stdout.flush
133 134
134 135 log "Test number: #{test_num}"
135 136
136 137 call_and_log("Cannot copy the compiled program into #{sandbox_dir}") {
137 138 FileUtils.cp("#{test_result_dir}/a.out", sandbox_dir, :preserve => true)
138 139 if language == "java" then Dir["#{test_result_dir}/*.class"].each { |file| FileUtils.cp(file,sandbox_dir)} end
139 140 if language == "python" then Dir["#{test_result_dir}/*.pyc"].each { |file| FileUtils.cp(file,sandbox_dir)} end
140 141 }
141 142
142 143 begin
143 - execute("#{problem_home}/script/run #{language} #{test_num}", "Error occured during execution of the run script")
144 + execute("#{problem_home}/script/run #{language} #{test_num} ", "Error occured during execution of the run script")
144 145 rescue
145 146 # do nothing
146 147 end
147 148
148 149 call_and_log("Cannot create directory #{test_result_dir}/#{test_num}") {
149 150 FileUtils.mkdir "#{test_result_dir}/#{test_num}"
150 151 }
151 152 call_and_log("Cannot copy the result file into #{test_result_dir}/#{test_num}") {
152 153 FileUtils.mv "#{sandbox_dir}/result", "#{test_result_dir}/#{test_num}"
153 154 }
154 155 call_and_log("Cannot copy the comment file into #{test_result_dir}/#{test_num}") {
155 156 FileUtils.mv "#{sandbox_dir}/comment", "#{test_result_dir}/#{test_num}"
156 157 }
157 158 call_and_log("Cannot copy the output file into #{test_result_dir}/#{test_num}") {
158 159 FileUtils.mv "#{sandbox_dir}/output.txt", "#{test_result_dir}/#{test_num}"
159 160 }
160 161 call_and_log("Cannot clear #{sandbox_dir}") {
161 162 FileUtils.rm_rf(Dir.glob("#{sandbox_dir}/*"), :secure => true)
162 163 }
163 164 end
164 165
165 166 $stdout.print "[done]\n"
166 167
167 168 # Grade
@@ -22,159 +22,167
22 22 #{:real => 0, :user => 0, :sys => 0}
23 23 #puts "ERROR READING RUNNING TIME: #{t}"
24 24 raise "Error reading running time: #{t}"
25 25 end
26 26 end
27 27
28 28 def compile_box(source,bin)
29 29 system("g++ #{source} -o #{bin}")
30 30 end
31 31
32 32 if ARGV.length < 2 || ARGV.length > 3
33 33 puts "Usage: run <language> <test-num> [<program-name>]"
34 34 exit(127)
35 35 end
36 36
37 37 language = ARGV[0]
38 38 test_num = ARGV[1].to_i
39 39 if ARGV.length > 2
40 40 program_name = ARGV[2]
41 41 else
42 42 program_name = "a.out"
43 43 end
44 44
45 45 problem_home = ENV['PROBLEM_HOME']
46 + source_name = ENV['SOURCE_NAME']
46 47 require "#{problem_home}/script/test_dsl.rb"
47 48 load "#{problem_home}/test_cases/all_tests.cfg"
48 49 problem = Problem.get_instance
49 50
51 + sandbox_dir = Dir.getwd
52 +
50 53 if problem.well_formed? == false
51 54 log "The problem specification is not well formed."
52 55 exit(127)
53 56 end
54 57
55 58 # Check if the test number is okay.
56 59 if test_num <= 0 || test_num > problem.num_tests
57 60 log "You have specified a wrong test number."
58 61 exit(127)
59 62 end
60 63
61 64 #####################################
62 65 # Set the relavant file names here. #
63 66 #####################################
64 67
65 68 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
66 69
67 70 #####################################
68 71
69 72 time_limit = problem.get_time_limit test_num
70 73 mem_limit = problem.get_mem_limit(test_num) * 1024
71 74
72 75 # Copy the input file.
73 76 #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
74 77
75 78 # check if box is there, if not, compile it!
76 79 if !File.exists?("#{problem_home}/script/box")
77 80 log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
78 81 compile_box("#{problem_home}/script/box.cc",
79 82 "#{problem_home}/script/box")
80 83 end
81 84
82 85 # Hide PROBLEM_HOME
83 86 ENV['PROBLEM_HOME'] = nil
87 + ENV['SOURCE_NAME'] = nil
84 88
85 89 # Run the program.
86 90 #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}"
87 91 #
88 92
89 -
90 -
93 + JAVA_OPTION = "-s set_robust_list -s futex -s clone -s getppid -s clone -s wait4 -p /usr/bin/ -p ./"
94 + 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"
95 + PYTHON_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /usr/bin/ -p /lib64/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p #{sandbox_dir}/#{source_name} -s set_robust_list -s openat -s recvmsg -s connect -s socket -s sendto -s futex -E PYTHONNOUSERSITE=yes"
96 + 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"
91 97
92 98 case language
93 99 when "java"
94 -
95 100 # for java, extract the classname
96 101 # wne have to add additional systemcall and we don't check the mem limit (dunno how to fix...)
97 102 classname = 'DUMMY'
98 103 File.open(program_name,"r").each do |line|
99 104 classname = line
100 105 end
101 - run_command = "#{problem_home}/script/box -T -t #{time_limit} -s getppid -s clone -s wait4 -p /usr/bin/ -p ./ -i #{input_file_name} -o output.txt /usr/bin/java #{classname} 2>run_result"
106 + #for java, we cannot really check the memory limit...
107 + 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 #{classname} 2>run_result"
102 108 when "ruby"
103 - run_command = "#{problem_home}/script/box -T -t #{time_limit} -s getppid -s wait4 -s clone -s set_robust_list -s futex -s sigaltstack -p /dev/urandom -p ./ -p /home/dae/.rvm/rubies/ruby-1.9.2-p320/ -p #{problem_home}/ -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
109 + 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} 2>run_result"
104 110 when "python"
105 - #this code just run without any checking
106 - run_command = "#{problem_home}/script/box -T -t #{time_limit} -p #{problem_home}/ -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
111 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{PYTHON_OPTION} -i #{input_file_name} -o output.txt /usr/bin/python #{program_name} 2>run_result"
112 + when "php"
113 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} #{PHP_OPTION} -i #{input_file_name} -o output.txt /usr/bin/php #{program_name} 2>run_result"
107 114 else # for c++, pascal, we do the normal checking
108 115 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
109 116 end
110 117
111 118
112 119 log "Running test #{test_num}..."
113 120 log run_command
114 - log
121 + log
115 122 system(run_command)
116 123
117 124 # Restore PROBLEM_HOME
118 125 ENV['PROBLEM_HOME'] = problem_home
119 126
120 127 # Create the result file.
121 128 result_file = File.new("result", "w")
122 129 comment_file = File.new("comment", "w")
123 130
124 131 # Check if the program actually produced any output.
125 132 run_result_file = File.new("run_result", "r")
126 133 run_result = run_result_file.readlines
127 134 run_result_file.close
128 135
129 136 run_stat = run_result[run_result.length-1]
130 137 running_time = extract_time(run_stat)
131 138
132 139 report = lambda{ |status, points, comment|
133 140 result_file.write status.strip
134 141 result_file.write "\n"
135 142 result_file.write points.to_s.strip
136 143 result_file.write "\n"
137 144 result_file.write run_stat.strip
138 145 result_file.write "\n"
139 146 result_file.close
140 147 FileUtils.rm "run_result"
141 148 # `rm output.txt` --- keep the output
142 149
143 150 comment_file.write comment
144 151
145 152 # added for debuggin --- jittat
146 153 comment_file.write "--run-result--\n"
147 154 run_result.each do |l|
148 155 comment_file.write l
149 156 end
150 157
151 158 comment_file.close
152 159
153 160 log "Done!"
154 161 exit(0)
155 162 }
156 163
164 +
157 165 if run_result[0][0,2] != "OK"
158 166 log "There was a runtime error."
159 167 report.call(run_result[0], 0, "No comment.\n")
160 168 end
161 169
162 170 if running_time[:user].to_f > time_limit
163 171 log "Time limit exceeded."
164 172 report.call("Time limit exceeded", 0, "No comment.\n")
165 173 end
166 174
167 175 # Run 'check' to evaluate the output.
168 176 #puts "There was no runtime error. Proceed to checking the output."
169 177 check_command = "#{problem_home}/script/check #{language} #{test_num}"
170 178 log "Checking the output..."
171 179 log check_command
172 180 if not system(check_command)
173 181 log "Problem with check script"
174 182 report.call("Incorrect",0,"Check script error.\n")
175 183 exit(127)
176 184 end
177 185
178 186 check_file = File.new("check_result", "r")
179 187 check_file_lines = check_file.readlines
180 188
You need to be logged in to leave comments. Login now