Description:
add selectable checker (for now, only 'text' and 'float') in the problem import
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r402:7f3e18dca393 - - 4 files changed: 25 inserted, 3 deleted

@@ -1,114 +1,121
1 class Problem < ActiveRecord::Base
1 class Problem < ActiveRecord::Base
2
2
3 belongs_to :description
3 belongs_to :description
4 has_and_belongs_to_many :contests, :uniq => true
4 has_and_belongs_to_many :contests, :uniq => true
5 has_many :test_pairs, :dependent => :delete_all
5 has_many :test_pairs, :dependent => :delete_all
6
6
7 validates_presence_of :name
7 validates_presence_of :name
8 validates_format_of :name, :with => /^\w+$/
8 validates_format_of :name, :with => /^\w+$/
9 validates_presence_of :full_name
9 validates_presence_of :full_name
10
10
11 scope :available, :conditions => {:available => true}
11 scope :available, :conditions => {:available => true}
12
12
13 DEFAULT_TIME_LIMIT = 1
13 DEFAULT_TIME_LIMIT = 1
14 DEFAULT_MEMORY_LIMIT = 32
14 DEFAULT_MEMORY_LIMIT = 32
15
15
16 def self.find_available_problems
16 def self.find_available_problems
17 Problem.available.all(:order => "date_added DESC")
17 Problem.available.all(:order => "date_added DESC")
18 end
18 end
19
19
20 def self.create_from_import_form_params(params, old_problem=nil)
20 def self.create_from_import_form_params(params, old_problem=nil)
21 org_problem = old_problem || Problem.new
21 org_problem = old_problem || Problem.new
22 import_params, problem = Problem.extract_params_and_check(params,
22 import_params, problem = Problem.extract_params_and_check(params,
23 org_problem)
23 org_problem)
24
24
25 if !problem.errors.empty?
25 if !problem.errors.empty?
26 return problem, 'Error importing'
26 return problem, 'Error importing'
27 end
27 end
28
28
29 problem.full_score = 100
29 problem.full_score = 100
30 problem.date_added = Time.new
30 problem.date_added = Time.new
31 problem.test_allowed = true
31 problem.test_allowed = true
32 problem.output_only = false
32 problem.output_only = false
33 problem.available = false
33 problem.available = false
34
34
35 if not problem.save
35 if not problem.save
36 return problem, 'Error importing'
36 return problem, 'Error importing'
37 end
37 end
38
38
39 import_to_db = params.has_key? :import_to_db
39 import_to_db = params.has_key? :import_to_db
40
40
41 importer = TestdataImporter.new(problem)
41 importer = TestdataImporter.new(problem)
42
42
43 if not importer.import_from_file(import_params[:file],
43 if not importer.import_from_file(import_params[:file],
44 import_params[:time_limit],
44 import_params[:time_limit],
45 import_params[:memory_limit],
45 import_params[:memory_limit],
46 + import_params[:checker_name],
46 import_to_db)
47 import_to_db)
47 problem.errors.add_to_base('Import error.')
48 problem.errors.add_to_base('Import error.')
48 end
49 end
49
50
50 return problem, importer.log_msg
51 return problem, importer.log_msg
51 end
52 end
52
53
53 def self.download_file_basedir
54 def self.download_file_basedir
54 return "#{Rails.root}/data/tasks"
55 return "#{Rails.root}/data/tasks"
55 end
56 end
56
57
57 protected
58 protected
58
59
59 def self.to_i_or_default(st, default)
60 def self.to_i_or_default(st, default)
60 if st!=''
61 if st!=''
61 result = st.to_i
62 result = st.to_i
62 end
63 end
63 result ||= default
64 result ||= default
64 end
65 end
65
66
66 def self.to_f_or_default(st, default)
67 def self.to_f_or_default(st, default)
67 if st!=''
68 if st!=''
68 result = st.to_f
69 result = st.to_f
69 end
70 end
70 result ||= default
71 result ||= default
71 end
72 end
72
73
73 def self.extract_params_and_check(params, problem)
74 def self.extract_params_and_check(params, problem)
74 time_limit = Problem.to_f_or_default(params[:time_limit],
75 time_limit = Problem.to_f_or_default(params[:time_limit],
75 DEFAULT_TIME_LIMIT)
76 DEFAULT_TIME_LIMIT)
76 memory_limit = Problem.to_i_or_default(params[:memory_limit],
77 memory_limit = Problem.to_i_or_default(params[:memory_limit],
77 DEFAULT_MEMORY_LIMIT)
78 DEFAULT_MEMORY_LIMIT)
78
79
79 if time_limit<=0 or time_limit >60
80 if time_limit<=0 or time_limit >60
80 problem.errors.add_to_base('Time limit out of range.')
81 problem.errors.add_to_base('Time limit out of range.')
81 end
82 end
82
83
83 if memory_limit==0 and params[:memory_limit]!='0'
84 if memory_limit==0 and params[:memory_limit]!='0'
84 problem.errors.add_to_base('Memory limit format errors.')
85 problem.errors.add_to_base('Memory limit format errors.')
85 elsif memory_limit<=0 or memory_limit >512
86 elsif memory_limit<=0 or memory_limit >512
86 problem.errors.add_to_base('Memory limit out of range.')
87 problem.errors.add_to_base('Memory limit out of range.')
87 end
88 end
88
89
89 if params[:file]==nil or params[:file]==''
90 if params[:file]==nil or params[:file]==''
90 problem.errors.add_to_base('No testdata file.')
91 problem.errors.add_to_base('No testdata file.')
91 end
92 end
92
93
94 + checker_name = 'text'
95 + if ['text','float'].include? params[:checker]
96 + checker_name = params[:checker]
97 + end
98 +
93 file = params[:file]
99 file = params[:file]
94
100
95 if !problem.errors.empty?
101 if !problem.errors.empty?
96 return nil, problem
102 return nil, problem
97 end
103 end
98
104
99 problem.name = params[:name]
105 problem.name = params[:name]
100 if params[:full_name]!=''
106 if params[:full_name]!=''
101 problem.full_name = params[:full_name]
107 problem.full_name = params[:full_name]
102 else
108 else
103 problem.full_name = params[:name]
109 problem.full_name = params[:name]
104 end
110 end
105
111
106 return [{
112 return [{
107 :time_limit => time_limit,
113 :time_limit => time_limit,
108 :memory_limit => memory_limit,
114 :memory_limit => memory_limit,
109 - :file => file
115 + :file => file,
116 + :checker_name => checker_name
110 },
117 },
111 problem]
118 problem]
112 end
119 end
113
120
114 end
121 end
@@ -1,58 +1,71
1 - content_for :head do
1 - content_for :head do
2 = stylesheet_link_tag 'problems'
2 = stylesheet_link_tag 'problems'
3
3
4 %h1 Import problems
4 %h1 Import problems
5
5
6 %p= link_to '[Back to problem list]', :action => 'list'
6 %p= link_to '[Back to problem list]', :action => 'list'
7
7
8 - if @problem and @problem.errors
8 - if @problem and @problem.errors
9 =error_messages_for 'problem'
9 =error_messages_for 'problem'
10
10
11 = form_tag({:action => 'do_import'}, :multipart => true) do
11 = form_tag({:action => 'do_import'}, :multipart => true) do
12 .submitbox
12 .submitbox
13 %table
13 %table
14 %tr
14 %tr
15 %td Name:
15 %td Name:
16 %td= text_field_tag 'name'
16 %td= text_field_tag 'name'
17 %tr
17 %tr
18 %td Full name:
18 %td Full name:
19 %td
19 %td
20 = text_field_tag 'full_name'
20 = text_field_tag 'full_name'
21 %span{:class => 'help'} Leave blank to use the same value as the name above.
21 %span{:class => 'help'} Leave blank to use the same value as the name above.
22 %tr
22 %tr
23 %td Testdata file:
23 %td Testdata file:
24 %td= file_field_tag 'file'
24 %td= file_field_tag 'file'
25 %tr
25 %tr
26 %td
26 %td
27 %td
27 %td
28 %span{:class => 'help'}
28 %span{:class => 'help'}
29 In .zip, .tgz, tar.gz, .tar format.
29 In .zip, .tgz, tar.gz, .tar format.
30 It should includes inputs (e.g., 1.in, 2a.in, 2b.in)
30 It should includes inputs (e.g., 1.in, 2a.in, 2b.in)
31 and solutions (e.g., 1.sol, 2a.sol, 2b.sol).
31 and solutions (e.g., 1.sol, 2a.sol, 2b.sol).
32 %br/
32 %br/
33 You may put task description in *.html for raw html
33 You may put task description in *.html for raw html
34 and *.md or *.markdown for markdown.
34 and *.md or *.markdown for markdown.
35 + %br/
36 + You may also put a pdf file for the task description
37 + %tr
38 + %td Checker:
39 + %td= select_tag 'checker', options_for_select([['Text checker','text'],['Float checker','float']], 'text')
40 + %tr
41 + %td
42 + %td
43 + %span{:class => 'help'}
44 + "Text" checker checks if the text (including numbers) is the same, ignoring any whitespace
45 + %br/
46 + "Float" checker checks if all numbers is within EPSILON error using formula |a-b| < EPSILON * max(|a|,|b|)
47 +
35 - if @allow_test_pair_import
48 - if @allow_test_pair_import
36 %tr
49 %tr
37 %td
50 %td
38 %td
51 %td
39 = check_box_tag 'import_to_db'
52 = check_box_tag 'import_to_db'
40 Import test data to database (for a test-pair task)
53 Import test data to database (for a test-pair task)
41 %tr
54 %tr
42 %td Time limit:
55 %td Time limit:
43 %td
56 %td
44 = text_field_tag 'time_limit'
57 = text_field_tag 'time_limit'
45 %span{:class => 'help'} In seconds. Leave blank to use 1 sec.
58 %span{:class => 'help'} In seconds. Leave blank to use 1 sec.
46 %tr
59 %tr
47 %td Memory limit:
60 %td Memory limit:
48 %td
61 %td
49 = text_field_tag 'memory_limit'
62 = text_field_tag 'memory_limit'
50 %span{:class => 'help'} In MB. Leave blank to use 32MB.
63 %span{:class => 'help'} In MB. Leave blank to use 32MB.
51 %tr
64 %tr
52 %td
65 %td
53 %td= submit_tag 'Import problem'
66 %td= submit_tag 'Import problem'
54
67
55 - if @log
68 - if @log
56 %h3 Import log
69 %h3 Import log
57 %pre.import-log
70 %pre.import-log
58 = @log
71 = @log
@@ -1,58 +1,58
1 module GraderScript
1 module GraderScript
2
2
3 def self.grader_control_enabled?
3 def self.grader_control_enabled?
4 if defined? GRADER_ROOT_DIR
4 if defined? GRADER_ROOT_DIR
5 GRADER_ROOT_DIR != ''
5 GRADER_ROOT_DIR != ''
6 else
6 else
7 false
7 false
8 end
8 end
9 end
9 end
10
10
11 def self.raw_dir
11 def self.raw_dir
12 File.join GRADER_ROOT_DIR, "raw"
12 File.join GRADER_ROOT_DIR, "raw"
13 end
13 end
14
14
15 def self.call_grader(params)
15 def self.call_grader(params)
16 if GraderScript.grader_control_enabled?
16 if GraderScript.grader_control_enabled?
17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 system(cmd)
18 system(cmd)
19 end
19 end
20 end
20 end
21
21
22 def self.stop_grader(pid)
22 def self.stop_grader(pid)
23 GraderScript.call_grader "stop #{pid}"
23 GraderScript.call_grader "stop #{pid}"
24 end
24 end
25
25
26 def self.stop_graders(pids)
26 def self.stop_graders(pids)
27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 GraderScript.call_grader "stop #{pid_str}"
28 GraderScript.call_grader "stop #{pid_str}"
29 end
29 end
30
30
31 def self.start_grader(env)
31 def self.start_grader(env)
32 GraderScript.call_grader "#{env} queue &"
32 GraderScript.call_grader "#{env} queue &"
33 GraderScript.call_grader "#{env} test_request &"
33 GraderScript.call_grader "#{env} test_request &"
34 end
34 end
35
35
36 def self.call_import_problem(problem_name,
36 def self.call_import_problem(problem_name,
37 problem_dir,
37 problem_dir,
38 time_limit=1,
38 time_limit=1,
39 memory_limit=32,
39 memory_limit=32,
40 checker_name='text')
40 checker_name='text')
41 if GraderScript.grader_control_enabled?
41 if GraderScript.grader_control_enabled?
42 cur_dir = `pwd`.chomp
42 cur_dir = `pwd`.chomp
43 Dir.chdir(GRADER_ROOT_DIR)
43 Dir.chdir(GRADER_ROOT_DIR)
44
44
45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 " -t #{time_limit} -m #{memory_limit}"
47 " -t #{time_limit} -m #{memory_limit}"
48
48
49 output = `#{cmd}`
49 output = `#{cmd}`
50
50
51 Dir.chdir(cur_dir)
51 Dir.chdir(cur_dir)
52
52
53 - return output
53 + return "import CMD: #{cmd}\n" + output
54 end
54 end
55 return ''
55 return ''
56 end
56 end
57
57
58 end
58 end
@@ -1,183 +1,185
1 require 'tmpdir'
1 require 'tmpdir'
2
2
3 class TestdataImporter
3 class TestdataImporter
4
4
5 attr :log_msg
5 attr :log_msg
6
6
7 def initialize(problem)
7 def initialize(problem)
8 @problem = problem
8 @problem = problem
9 end
9 end
10
10
11 def import_from_file(tempfile,
11 def import_from_file(tempfile,
12 time_limit,
12 time_limit,
13 memory_limit,
13 memory_limit,
14 + checker_name='text',
14 import_to_db=false)
15 import_to_db=false)
15
16
16 dirname = extract(tempfile)
17 dirname = extract(tempfile)
17 return false if not dirname
18 return false if not dirname
18 if not import_to_db
19 if not import_to_db
19 @log_msg = GraderScript.call_import_problem(@problem.name,
20 @log_msg = GraderScript.call_import_problem(@problem.name,
20 dirname,
21 dirname,
21 time_limit,
22 time_limit,
22 - memory_limit)
23 + memory_limit,
24 + checker_name)
23 else
25 else
24 # Import test data to test pairs.
26 # Import test data to test pairs.
25
27
26 @problem.test_pairs.clear
28 @problem.test_pairs.clear
27 if import_test_pairs(dirname)
29 if import_test_pairs(dirname)
28 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
30 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
29 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
31 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
30 else
32 else
31 @log_msg = "Importing test pair failed. (0 test pairs imported)"
33 @log_msg = "Importing test pair failed. (0 test pairs imported)"
32 end
34 end
33 end
35 end
34
36
35 @log_msg << import_problem_description(dirname)
37 @log_msg << import_problem_description(dirname)
36 @log_msg << import_problem_pdf(dirname)
38 @log_msg << import_problem_pdf(dirname)
37 @log_msg << import_full_score(dirname)
39 @log_msg << import_full_score(dirname)
38
40
39 return true
41 return true
40 end
42 end
41
43
42 protected
44 protected
43
45
44 def self.long_ext(filename)
46 def self.long_ext(filename)
45 i = filename.index('.')
47 i = filename.index('.')
46 len = filename.length
48 len = filename.length
47 return filename.slice(i..len)
49 return filename.slice(i..len)
48 end
50 end
49
51
50 def extract(tempfile)
52 def extract(tempfile)
51 testdata_filename = save_testdata_file(tempfile)
53 testdata_filename = save_testdata_file(tempfile)
52 ext = TestdataImporter.long_ext(tempfile.original_filename)
54 ext = TestdataImporter.long_ext(tempfile.original_filename)
53
55
54 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
56 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
55 if File.exists? extract_dir
57 if File.exists? extract_dir
56 backup_count = 0
58 backup_count = 0
57 begin
59 begin
58 backup_count += 1
60 backup_count += 1
59 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
61 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
60 end while File.exists? backup_dirname
62 end while File.exists? backup_dirname
61 File.rename(extract_dir, backup_dirname)
63 File.rename(extract_dir, backup_dirname)
62 end
64 end
63 Dir.mkdir extract_dir
65 Dir.mkdir extract_dir
64
66
65 if ext=='.tar.gz' or ext=='.tgz'
67 if ext=='.tar.gz' or ext=='.tgz'
66 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
68 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
67 elsif ext=='.tar'
69 elsif ext=='.tar'
68 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
70 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
69 elsif ext=='.zip'
71 elsif ext=='.zip'
70 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
72 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
71 else
73 else
72 return nil
74 return nil
73 end
75 end
74
76
75 system(cmd)
77 system(cmd)
76
78
77 files = Dir["#{extract_dir}/**/*1*.in"]
79 files = Dir["#{extract_dir}/**/*1*.in"]
78 return nil if files.length==0
80 return nil if files.length==0
79
81
80 File.delete(testdata_filename)
82 File.delete(testdata_filename)
81
83
82 return File.dirname(files[0])
84 return File.dirname(files[0])
83 end
85 end
84
86
85 def save_testdata_file(tempfile)
87 def save_testdata_file(tempfile)
86 ext = TestdataImporter.long_ext(tempfile.original_filename)
88 ext = TestdataImporter.long_ext(tempfile.original_filename)
87 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
89 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
88
90
89 return nil if tempfile==""
91 return nil if tempfile==""
90
92
91 if tempfile.instance_of?(Tempfile)
93 if tempfile.instance_of?(Tempfile)
92 tempfile.close
94 tempfile.close
93 FileUtils.move(tempfile.path,testdata_filename)
95 FileUtils.move(tempfile.path,testdata_filename)
94 else
96 else
95 File.open(testdata_filename, "wb") do |f|
97 File.open(testdata_filename, "wb") do |f|
96 f.write(tempfile.read)
98 f.write(tempfile.read)
97 end
99 end
98 end
100 end
99
101
100 return testdata_filename
102 return testdata_filename
101 end
103 end
102
104
103 def import_test_pairs(dirname)
105 def import_test_pairs(dirname)
104 test_num = 1
106 test_num = 1
105 while FileTest.exists? "#{dirname}/#{test_num}.in"
107 while FileTest.exists? "#{dirname}/#{test_num}.in"
106 in_filename = "#{dirname}/#{test_num}.in"
108 in_filename = "#{dirname}/#{test_num}.in"
107 sol_filename = "#{dirname}/#{test_num}.sol"
109 sol_filename = "#{dirname}/#{test_num}.sol"
108
110
109 break if not FileTest.exists? sol_filename
111 break if not FileTest.exists? sol_filename
110
112
111 test_pair = TestPair.new(:input => open(in_filename).read,
113 test_pair = TestPair.new(:input => open(in_filename).read,
112 :solution => open(sol_filename).read,
114 :solution => open(sol_filename).read,
113 :problem => @problem)
115 :problem => @problem)
114 break if not test_pair.save
116 break if not test_pair.save
115
117
116 test_num += 1
118 test_num += 1
117 end
119 end
118 return test_num > 1
120 return test_num > 1
119 end
121 end
120
122
121 def import_problem_description(dirname)
123 def import_problem_description(dirname)
122 html_files = Dir["#{dirname}/*.html"]
124 html_files = Dir["#{dirname}/*.html"]
123 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
125 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
124 if (html_files.length != 0) or (markdown_files.length != 0)
126 if (html_files.length != 0) or (markdown_files.length != 0)
125 description = @problem.description || Description.new
127 description = @problem.description || Description.new
126
128
127 if html_files.length != 0
129 if html_files.length != 0
128 filename = html_files[0]
130 filename = html_files[0]
129 description.markdowned = false
131 description.markdowned = false
130 else
132 else
131 filename = markdown_files[0]
133 filename = markdown_files[0]
132 description.markdowned = true
134 description.markdowned = true
133 end
135 end
134
136
135 description.body = open(filename).read
137 description.body = open(filename).read
136 description.save
138 description.save
137 @problem.description = description
139 @problem.description = description
138 @problem.save
140 @problem.save
139 return "\nProblem description imported from #{filename}."
141 return "\nProblem description imported from #{filename}."
140 else
142 else
141 return ''
143 return ''
142 end
144 end
143 end
145 end
144
146
145 def import_problem_pdf(dirname)
147 def import_problem_pdf(dirname)
146 pdf_files = Dir["#{dirname}/*.pdf"]
148 pdf_files = Dir["#{dirname}/*.pdf"]
147 puts "CHECKING... #{dirname}"
149 puts "CHECKING... #{dirname}"
148 if pdf_files.length != 0
150 if pdf_files.length != 0
149 puts "HAS PDF FILE"
151 puts "HAS PDF FILE"
150 filename = pdf_files[0]
152 filename = pdf_files[0]
151
153
152 @problem.save if not @problem.id
154 @problem.save if not @problem.id
153 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
155 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
154 if not FileTest.exists? out_dirname
156 if not FileTest.exists? out_dirname
155 Dir.mkdir out_dirname
157 Dir.mkdir out_dirname
156 end
158 end
157
159
158 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
160 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
159
161
160 if FileTest.exists? out_filename
162 if FileTest.exists? out_filename
161 File.delete out_filename
163 File.delete out_filename
162 end
164 end
163
165
164 File.rename(filename, out_filename)
166 File.rename(filename, out_filename)
165 @problem.description_filename = "#{@problem.name}.pdf"
167 @problem.description_filename = "#{@problem.name}.pdf"
166 @problem.save
168 @problem.save
167 return "\nProblem pdf imported from #{filename}."
169 return "\nProblem pdf imported from #{filename}."
168 else
170 else
169 return ""
171 return ""
170 end
172 end
171 end
173 end
172
174
173 #just set the full score to the total number of test case
175 #just set the full score to the total number of test case
174 #it is not perfect but works on most normal use case
176 #it is not perfect but works on most normal use case
175 def import_full_score(dirname)
177 def import_full_score(dirname)
176 in_file = Dir["#{dirname}/*.in"]
178 in_file = Dir["#{dirname}/*.in"]
177 full_score =in_file.length * 10
179 full_score =in_file.length * 10
178 @problem.full_score = full_score
180 @problem.full_score = full_score
179 @problem.save
181 @problem.save
180 return "\nFull score is set to #{full_score}."
182 return "\nFull score is set to #{full_score}."
181 end
183 end
182
184
183 end
185 end
You need to be logged in to leave comments. Login now