diff --git a/app/controllers/evaluators_controller.rb b/app/controllers/evaluators_controller.rb
index 1c61e1fa..5a636055 100644
--- a/app/controllers/evaluators_controller.rb
+++ b/app/controllers/evaluators_controller.rb
@@ -2,7 +2,7 @@ class EvaluatorsController < ApplicationController
def permitted_params
@_permitted_params ||= begin
- permitted_attributes = [:name, :description, :source, :language_id]
+ permitted_attributes = [:name, :description, :source, :language_id, :interactive_processes]
permitted_attributes << :owner_id if policy(@evaluator || Evaluator).transfer?
params.require(:evaluator).permit(*permitted_attributes)
end
diff --git a/app/models/evaluator.rb b/app/models/evaluator.rb
index 1011a1e2..f4629f69 100644
--- a/app/models/evaluator.rb
+++ b/app/models/evaluator.rb
@@ -6,5 +6,6 @@ class Evaluator < ActiveRecord::Base
belongs_to :language
validates :name, :presence => true
+ validates :interactive_processes, :presence => true, :numericality => { :greater_than_or_equal_to => 0, :less_than_or_equal_to => 2 }
end
diff --git a/app/services/isolate.rb b/app/services/isolate.rb
index 07478662..27c5e8ba 100644
--- a/app/services/isolate.rb
+++ b/app/services/isolate.rb
@@ -12,8 +12,6 @@ class Isolate
#
# Alternatively, the isolate box is passed as an argument to a block with an arity, which is not instance_exec-ed
def self.box options = {}, &block
- options.reverse_merge!(:cg => has_cgroups?).assert_valid_keys(:cg)
- raise CGroupsUnavailableError if options[:cg] && !has_cgroups?
isolate = self.new(options)
yield isolate if block_given?
true
@@ -50,6 +48,13 @@ def exec command, options = {}
end
end
+ # Spawn a single command in isolate context
+ def spawn_command command, options = {}
+ sandbox_command(command, options) do |command, options|
+ spawn(*command, options)
+ end
+ end
+
# popen a single command in isolate context
#
# Example:
@@ -177,6 +182,8 @@ def read_pipe_limited(pipe, count)
protected
def initialize(options = {})
+ options.reverse_merge!(:cg => self.class.has_cgroups?).assert_valid_keys(:cg)
+ raise CGroupsUnavailableError if options[:cg] && !self.class.has_cgroups?
@has_cgroup = !!options[:cg]
@box_id = Kernel.send(:`, "isolock --lock -- #{"--cg" if has_cgroup?}").to_i
raise LockError unless $?.success?
diff --git a/app/views/evaluators/_form.html.erb b/app/views/evaluators/_form.html.erb
index 33f4d741..d6f4fb28 100644
--- a/app/views/evaluators/_form.html.erb
+++ b/app/views/evaluators/_form.html.erb
@@ -27,6 +27,10 @@
<%= f.label :language_id %>
<%= f.select :language_id, grouped_options_for_select(Language.grouped_submission_options, @evaluator.language_id), :include_blank => true %>
+
<%= f.label :owner_id %>
<% if policy(@evaluator).transfer? %>
diff --git a/app/views/evaluators/show.html.erb b/app/views/evaluators/show.html.erb
index d91657de..3835fda5 100644
--- a/app/views/evaluators/show.html.erb
+++ b/app/views/evaluators/show.html.erb
@@ -10,6 +10,8 @@
Language:
<%= @evaluator.language&.name %>
+ Interactive processes:
+ <%= @evaluator.interactive_processes %>
Source:
<%= predisplay @evaluator.source, language: @evaluator.language&.lexer %>
diff --git a/app/workers/judge_submission_worker.rb b/app/workers/judge_submission_worker.rb
index 8d862905..a3d11317 100644
--- a/app/workers/judge_submission_worker.rb
+++ b/app/workers/judge_submission_worker.rb
@@ -134,7 +134,7 @@ def judge
end
private
- attr_accessor :problem, :box, :tmpdir
+ attr_accessor :problem, :box, :interactive_boxes, :tmpdir
def time_limit
problem.time_limit || 0.001
@@ -154,18 +154,22 @@ def extra_time
end
def wall_time
- time_limit*3+extra_time+5
+ time_limit*2+extra_time+1
end
def setup_judging
self.problem = submission.problem
+ self.box = Isolate.new
+ self.interactive_boxes = Array.new(problem.evaluator&.interactive_processes || 0) { Isolate.new }
Dir.mktmpdir do |tmpdir|
self.tmpdir = tmpdir
- Isolate.box do |box|
- self.box = box
- yield
- end or raise Isolate::LockError, "Error locking box"
+ yield
end
+ ensure
+ box.send(:destroy) if !box.nil?
+ interactive_boxes.each do |box|
+ box.send(:destroy) if !box.nil?
+ end if !interactive_boxes.nil?
end
def compile!(source, language, output)
@@ -177,15 +181,19 @@ def compile!(source, language, output)
end
def judge_test_case(test_case, run_command, eval_command, resource_limits)
- FileUtils.copy(File.expand_path(exe_filename, tmpdir), box.expand_path(exe_filename))
- result = run_test_case(test_case, run_command, resource_limits)
- result['evaluator'] = evaluate_output(test_case, result['output'], result['output_size'], eval_command)
+ if problem.evaluator&.interactive_processes&.positive?
+ result = run_interactive(test_case, run_command, eval_command, resource_limits)
+ else
+ result = run_test_case(test_case, run_command, resource_limits)
+ result['evaluator'] = evaluate_output(test_case, result['output'], result['output_size'], eval_command)
+ end
result['log'] = truncate_output(result['log']) # log only a small portion
result['output'] = truncate_output(result['output'].slice(0,100)) # store only a small portion
result
end
def run_test_case(test_case, run_command, resource_limits = {})
+ FileUtils.copy(File.expand_path(exe_filename, tmpdir), box.expand_path(exe_filename))
stream_limit = OutputBaseLimit + test_case.output.bytesize*2
run_opts = resource_limits.reverse_merge(:output_limit => stream_limit, :clean_utf8 => true)
if submission.input.nil?
@@ -254,6 +262,144 @@ def evaluate_output(test_case, output, output_size, eval_command)
box.clean!
end
+ def run_interactive(test_case, run_command, eval_command, resource_limits)
+ stream_limit = OutputBaseLimit + test_case.output.bytesize*2
+ num_processes = problem.evaluator.interactive_processes
+
+ metafiles = []
+ boxfiles = []
+ logfiles = []
+ manager_pipes = []
+ user_pids = []
+
+ interactive_boxes.each_with_index do |box, index|
+ FileUtils.copy(File.expand_path(exe_filename, tmpdir), box.expand_path(exe_filename))
+ metafiles << Tempfile.new('metafile')
+ boxfiles << Tempfile.new('boxfile')
+ logfiles << box.tmpfile
+ manager_to_user = IO.pipe
+ user_to_manager = IO.pipe
+ manager_pipes << [user_to_manager[0], manager_to_user[1]]
+
+ run_opts = resource_limits.merge(:meta => metafiles.last.path, :err => boxfiles.last, :in => manager_to_user[0], :out => user_to_manager[1], :stderr => logfiles.last)
+ box_run_command = run_command
+ if num_processes > 1
+ box_run_command += " " + index.to_s
+ end
+ user_pids << box.spawn_command(box_run_command, run_opts)
+
+ manager_to_user[0].close
+ user_to_manager[1].close
+ end
+
+ expected = conditioned_output(test_case.output)
+ FileUtils.copy(File.expand_path(EvalFileName, tmpdir), box.expand_path(EvalFileName))
+ manager_resource_limits = { :mem => 524288, :time => num_processes * time_limit + 15, :wall_time => num_processes * wall_time + 30 }
+
+ eval_output = nil
+ eval_result = {}
+ str_to_pipe(expected) do |output_stream|
+ run_opts = manager_resource_limits.merge(:processes => true, 4 => output_stream, :stdin_data => test_case.input, :output_limit => OutputBaseLimit + test_case.output.bytesize*4, :clean_utf8 => true, :inherit_fds => true)
+ manager_pipes.each_with_index do |pipes, index|
+ run_opts.merge!(index * 2 + 5 => pipes[0], index * 2 + 6 => pipes[1])
+ end
+ (stdout,), (eval_result['log'],eval_result['log_size']), (eval_result['box'],), eval_result['meta'], status = box.capture5("#{eval_command}", run_opts)
+ eval_result['log'] = truncate_output(eval_result['log'])
+ eval_output = stdout.strip.split(nil,2)
+ eval_result['stat'] = status.exitstatus
+ end
+
+ manager_pipes.each do |pipes|
+ pipes[0].close
+ pipes[1].close
+ end
+
+ if eval_output.empty?
+ eval_result.delete('evaluation') # error
+ else
+ eval_result['evaluation'] = eval_output[0].to_d
+ eval_result['message'] = truncate_output(eval_output[1] || "")
+ end
+
+ r = { 'evaluator' => eval_result }
+
+ r['stat'] = 0
+ user_pids.each do |pid|
+ exit_status = Process.detach(pid).value.exitstatus
+ r['stat'] = r['stat'].nonzero? || exit_status
+ end
+
+ metafiles.each do |metafile|
+ metafile.open
+ meta = Isolate.parse_meta(metafile.read)
+
+ if r['meta'].nil?
+ r['meta'] = meta
+ else
+ # Merge execution stats
+ r['meta']['time'] += meta['time']
+ r['meta']['time-wall'] = [r['meta']['time-wall'], meta['time-wall']].max
+ r['meta']['cg-mem'] += meta['cg-mem'] if meta.has_key?('cg-mem')
+ r['meta']['max-rss'] += meta['max-rss']
+
+ # Use first non-OK status and related values
+ if r['meta']['status'] == 'OK' && meta['status'] != 'OK'
+ r['meta']['status'] = meta['status']
+ r['meta']['killed'] = meta['killed'] if meta.has_key?('killed')
+ r['meta']['exitcode'] = meta['exitcode'] if meta.has_key?('exitcode')
+ r['meta']['exitsig'] = meta['exitsig'] if meta.has_key?('exitsig')
+ r['meta']['message'] = meta['message'] if meta.has_key?('message')
+ end
+ end
+ metafile.close
+ end
+
+ # Check for MLE/TLE based on merged execution stats
+ if r['meta']['status'] == 'OK'
+ if (r['meta']['cg-mem'] || r['meta']['max-rss']).to_f > memory_limit*1024
+ r['meta']['status'] = 'SG'
+ r['meta']['exitsig'] = 9
+ r['meta']['message'] = "Memory Limit Exceeded"
+ r['meta']['cg-mem'] = [r['meta']['cg-mem'],memory_limit*1024].min if r['meta'].has_key?('cg-mem')
+ r['meta']['max-rss'] = [r['meta']['max-rss'],memory_limit*1024].min
+ r['stat'] = 1
+ elsif r['meta']['time'] > time_limit.to_f
+ r['meta']['status'] = 'TO'
+ r['meta']['message'] = "Time Limit Exceeded"
+ r['stat'] = 1
+ end
+ end
+
+ r['box'] = ''
+ boxfiles.each do |boxfile|
+ boxfile.open
+ r['box'] << boxfile.read
+ boxfile.close
+ end
+
+ r['log'] = ''
+ r['log_size'] = 0
+ interactive_boxes.zip(logfiles).each do |box, logfile|
+ stderr = File.open(box.expand_path(logfile)) { |f| box.read_pipe_limited(f, stream_limit) }
+ (log, log_size) = box.clean_utf8(stderr)
+ r['log'] << log
+ r['log_size'] += log_size
+ end
+ r['log'] = truncate_output(r['log'])
+
+ r['output'] = '' # no user output
+ r['output_size'] = 0
+
+ r['time'] = [r['meta']['time'],time_limit.to_f].min
+
+ return r
+ ensure
+ metafiles.each(&:close!)
+ boxfiles.each(&:close!)
+ interactive_boxes.each(&:clean!)
+ box.clean!
+ end
+
def grade_test_set test_set, evaluated_test_cases
result = {'cases' => []}
pending, error, sig = false, false, false
diff --git a/db/migrate/20230225064438_add_interactive_processes_to_evaluators.rb b/db/migrate/20230225064438_add_interactive_processes_to_evaluators.rb
new file mode 100644
index 00000000..fdaa86fc
--- /dev/null
+++ b/db/migrate/20230225064438_add_interactive_processes_to_evaluators.rb
@@ -0,0 +1,5 @@
+class AddInteractiveProcessesToEvaluators < ActiveRecord::Migration
+ def change
+ add_column :evaluators, :interactive_processes, :integer, null: false, default: 0
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0c980263..cb0af18d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20230225054132) do
+ActiveRecord::Schema.define(version: 20230225064438) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -87,13 +87,14 @@
end
create_table "evaluators", force: :cascade do |t|
- t.string "name", null: false
- t.text "description", default: "", null: false
- t.text "source", default: "", null: false
- t.integer "owner_id", null: false
+ t.string "name", null: false
+ t.text "description", default: "", null: false
+ t.text "source", default: "", null: false
+ t.integer "owner_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "language_id"
+ t.integer "interactive_processes", default: 0, null: false
end
create_table "file_attachments", force: :cascade do |t|
diff --git a/spec/factories/problems.rb b/spec/factories/problems.rb
index d50edc1e..13a10226 100644
--- a/spec/factories/problems.rb
+++ b/spec/factories/problems.rb
@@ -4,34 +4,173 @@
factory :problem do
sequence(:name) {|n| "Problem #{n}" }
statement { "Do nothing" }
- sequence(:input) {|n| "#{n}.in" }
- sequence(:output) {|n| "#{n}.out"}
+ input { nil }
+ output { nil }
memory_limit { 1 }
time_limit { 1 }
owner_id { 0 }
- factory :adding_problem do
+
+ test_sets { test_cases.map{FactoryBot.create(:test_set)} }
+
+ after(:create) do |problem|
+ problem.test_cases.zip(problem.test_sets).each do |test_case, test_set|
+ FactoryBot.create(:test_case_relation, :test_case => test_case, :test_set => test_set)
+ end
+ end
+
+ factory :adding_problem_stdio do
sequence(:name) {|n| "Adding problem #{n}" }
statement { "Read two integers from input and output the sum." }
- input { "add.in" }
- output { "add.out" }
memory_limit { 30 }
time_limit { 1 }
test_cases { [FactoryBot.create(:test_case, :input => "5 9", :output => "14"),
FactoryBot.create(:test_case, :input => "100 -50", :output => "50"),
FactoryBot.create(:test_case, :input => "1235 942", :output => "2177"),
FactoryBot.create(:test_case, :input => "-4000 123", :output => "-3877")] }
- test_sets { (0...4).map{FactoryBot.create(:test_set)} }
- after(:create) do |problem|
- (0...4).each do |i|
- FactoryBot.create(:test_case_relation, :test_case => problem.test_cases[i], :test_set => problem.test_sets[i])
- end
+ factory :adding_problem do
+ input { "add.in" }
+ output { "add.out" }
end
+ end
- factory :adding_problem_stdio do
- input { nil }
- output { nil }
- end
+ factory :binary_search_problem do
+ name { "Binary search problem" }
+ statement { "Find the target number within Q guesses. After each guess you are told whether the target is lower, higher, or correct." }
+ memory_limit { 16 }
+ time_limit { 0.1 }
+ test_cases { [FactoryBot.create(:test_case, :input => "100 100 98"),
+ FactoryBot.create(:test_case, :input => "100000 17 37")] }
+
+ evaluator { FactoryBot.create(:evaluator, :language => LanguageGroup.find_by_identifier("c++").current_language,
+ :interactive_processes => 1, :source => <<~'sourcecode' ) }
+ #include
+ #include
+ #include
+
+ void grade(int score, const char* message = NULL) {
+ fprintf(stdout, "%d\n", score);
+ if (message)
+ fprintf(stderr, "%s\n", message);
+ exit(0);
+ }
+
+ int main() {
+ {
+ // Keep alive on broken pipes
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, NULL);
+ }
+
+ FILE* user_in = fdopen(5, "r");
+ FILE* user_out = fdopen(6, "w");
+
+ int N, Q, K;
+ scanf("%d %d %d", &N, &Q, &K);
+ fclose(stdin);
+ fprintf(user_out, "%d %d\n", N, Q);
+ fflush(user_out);
+
+ int guess;
+ for (int i = 0; i < Q; ++i) {
+ if (fscanf(user_in, "%d", &guess) != 1) {
+ grade(0, "Could not read guess");
+ }
+ if (guess == K) {
+ fprintf(user_out, "0\n");
+ fflush(user_out);
+ break;
+ } else if (guess < K) {
+ fprintf(user_out, "1\n");
+ fflush(user_out);
+ } else {
+ fprintf(user_out, "-1\n");
+ fflush(user_out);
+ }
+ if (i == Q - 1) {
+ grade(0, "Too many guesses");
+ }
+ }
+
+ if (fscanf(user_in, "%d", &guess) != EOF)
+ grade(0, "Wrong output format, trailing garbage");
+
+ grade(1);
+ }
+ sourcecode
+ end
+
+ factory :integer_encoding_problem do
+ name { "Integer encoding problem" }
+ statement { "Send the input number between two processes using only alphabetic characters." }
+ memory_limit { 16 }
+ time_limit { 0.5 }
+ test_cases { [FactoryBot.create(:test_case, :input => "0"),
+ FactoryBot.create(:test_case, :input => "42"),
+ FactoryBot.create(:test_case, :input => "9999")] }
+
+ evaluator { FactoryBot.create(:evaluator, :interactive_processes => 2, :source => <<~'sourcecode' ) }
+ #!/usr/bin/env python3
+ import os
+ import sys
+ import functools
+ import traceback
+ import time
+
+ print = functools.partial(print, flush=True) # Always flush
+
+ user1_in = os.fdopen(5, 'r')
+ user1_out = os.fdopen(6, 'w')
+ user2_in = os.fdopen(7, 'r')
+ user2_out = os.fdopen(8, 'w')
+
+ def grade(score, admin_message=None, user_message=None):
+ if not user2_out.closed:
+ try:
+ print(-1, file=user2_out)
+ except:
+ pass
+ print(score)
+ if user_message is not None:
+ print(user_message)
+ if admin_message is not None:
+ print(admin_message, file=sys.stderr)
+ sys.exit(0)
+
+ N = int(input())
+
+ try:
+ print(1, file=user1_out)
+ print(N, file=user1_out)
+ user1_out.close()
+ encoded_string = user1_in.read(100000).strip()
+ except (BrokenPipeError, ValueError):
+ grade(0, traceback.format_exc())
+
+ if user1_in.read(100000).strip():
+ grade(0, "Wrong output format, trailing garbage")
+ user1_in.close()
+
+ if not encoded_string.isalpha():
+ grade(0, "Invalid encoded string: " + encoded_string)
+
+ try:
+ print(2, file=user2_out)
+ print(encoded_string, file=user2_out)
+ user2_out.close()
+ decoded_integer = int(user2_in.readline(100000))
+ except (BrokenPipeError, ValueError):
+ grade(0, traceback.format_exc())
+
+ if user2_in.read(100000).strip():
+ grade(0, "Wrong output format, trailing garbage")
+ user2_in.close()
+
+ if decoded_integer != N:
+ grade(0, "Wrong answer")
+ grade(1)
+ sourcecode
end
end
end
diff --git a/spec/factories/submissions.rb b/spec/factories/submissions.rb
index f658bbc6..33c9c869 100644
--- a/spec/factories/submissions.rb
+++ b/spec/factories/submissions.rb
@@ -70,6 +70,128 @@
fprintf(out, "%u\\n",(int)c);
return 0;
}
+sourcecode
+ end
+ factory :binary_search_submission do
+ language { LanguageGroup.find_by_identifier("c++").current_language }
+ source { <
+ using namespace std;
+ int main() {
+ int lo=0, hi, attempts, result;
+ cin >> hi >> attempts;
+ while (hi - lo > 1) {
+ int mid = (lo + hi) / 2;
+ cout << mid << endl;
+ cin >> result;
+ if ( result == 0 )
+ return 0;
+ else if ( result < 0 )
+ hi = mid;
+ else
+ lo = mid + 1;
+ }
+ cout << lo << endl;
+ }
+sourcecode
+ end
+ factory :binary_search_submission_incorrect do
+ language { LanguageGroup.find_by_identifier("c++").current_language }
+ source { <
+ using namespace std;
+ int main() {
+ int hi, attempts, result;
+ cin >> hi >> attempts;
+ for (int i = 0; i < hi; i++) {
+ cout << i << endl;
+ cin >> result;
+ if (result == 0)
+ break;
+ }
+ }
+sourcecode
+ end
+ factory :binary_search_submission_wall_tle do
+ language { LanguageGroup.find_by_identifier("c++").current_language }
+ source { <
+ using namespace std;
+ int main() {
+ int hi, attempts, result;
+ cin >> hi >> attempts;
+ for (int i = 0; i < hi; i++) {
+ //cout << i << endl;
+ cin >> result;
+ }
+ }
+sourcecode
+ end
+ factory :integer_encoding_submission do
+ language { LanguageGroup.find_by_identifier("c++").current_language }
+ source { <
+ #include
+ #include
+ using namespace std;
+ int main() {
+ int mode, N = 0;
+ std::string encoded_string;
+ cin >> mode;
+ if (mode == 1) {
+ cin >> N;
+ while (N) {
+ encoded_string += 'a' + (N & 1);
+ N >>= 1;
+ }
+ encoded_string += 'a';
+ reverse(encoded_string.begin(), encoded_string.end());
+ cout << encoded_string << endl;
+ }
+ if (mode == 2) {
+ cin >> encoded_string;
+ for (char c : encoded_string) {
+ N <<= 1;
+ N += c > 'a';
+ }
+ cout << N << endl;
+ }
+ }
+sourcecode
+ end
+ factory :integer_encoding_submission_mle do
+ language { LanguageGroup.find_by_identifier("c++").current_language }
+ source { <
+ #include
+ #include
+ using namespace std;
+ int main() {
+ std::array arr; // x2 = 20 MiB; should MLE
+ arr.fill(-1);
+
+ int mode, N = 0;
+ std::string encoded_string;
+ cin >> mode;
+ if (mode == 1) {
+ cin >> N;
+ while (N) {
+ encoded_string += 'a' + (N & 1);
+ N >>= 1;
+ }
+ encoded_string += 'a';
+ reverse(encoded_string.begin(), encoded_string.end());
+ cout << encoded_string << endl;
+ }
+ if (mode == 2) {
+ cin >> encoded_string;
+ for (char c : encoded_string) {
+ N <<= 1;
+ N += c > 'a';
+ }
+ cout << N << endl;
+ }
+ }
sourcecode
end
end
diff --git a/spec/models/submission_spec.rb b/spec/models/submission_spec.rb
index dd66ff1d..ab8c0bff 100644
--- a/spec/models/submission_spec.rb
+++ b/spec/models/submission_spec.rb
@@ -40,4 +40,60 @@
expect(@unsigned_submission.evaluation).to eq(0.75)
end
end
+
+ context 'on "binary search" problem' do
+ before(:all) do
+ @user = FactoryBot.create(:user)
+ @problem = FactoryBot.create(:binary_search_problem)
+ end
+ after(:all) do
+ [@user, @problem].reverse_each { |object| object.destroy }
+ end
+ it 'judges submission' do
+ submission = FactoryBot.create(:binary_search_submission, :problem => @problem, :user => @user)
+ expect(submission.score).to be_nil
+ submission.judge
+ submission.reload
+ expect(submission.evaluation).to eq(1)
+ end
+ it 'judges incorrect submission' do
+ submission = FactoryBot.create(:binary_search_submission_incorrect, :problem => @problem, :user => @user)
+ expect(submission.evaluation).to be_nil
+ submission.judge
+ submission.reload
+ expect(submission.evaluation).to eq(0.5)
+ end
+ it 'judges wall timeout submission' do
+ submission = FactoryBot.create(:binary_search_submission_wall_tle, :problem => @problem, :user => @user)
+ expect(submission.evaluation).to be_nil
+ submission.judge
+ submission.reload
+ expect(submission.evaluation).to eq(0)
+ end
+ end
+
+ context 'on "integer encoding" problem' do
+ before(:all) do
+ @user = FactoryBot.create(:user)
+ @problem = FactoryBot.create(:integer_encoding_problem)
+ end
+ after(:all) do
+ [@user, @problem].reverse_each { |object| object.destroy }
+ end
+ it 'judges submission' do
+ submission = FactoryBot.create(:integer_encoding_submission, :problem => @problem, :user => @user)
+ expect(submission.score).to be_nil
+ submission.judge
+ submission.reload
+ expect(submission.evaluation).to eq(1)
+ end
+ it 'judges memory limit exceeded submission' do
+ submission = FactoryBot.create(:integer_encoding_submission_mle, :problem => @problem, :user => @user)
+ expect(submission.score).to be_nil
+ submission.judge
+ submission.reload
+ expect(submission.judge_data.test_cases.first[1].status).to eq(:memory)
+ expect(submission.evaluation).to eq(0)
+ end
+ end
end