Add exit_code and status to Testruns, create model for TestrunMessages

* This is the first step to migrate the `output` column from Testruns to a dedicated table TestrunMessages
This commit is contained in:
Sebastian Serth
2022-04-24 18:09:37 +02:00
parent 5f16792ee9
commit e9efb5bc2b
7 changed files with 146 additions and 25 deletions

View File

@ -146,18 +146,26 @@ class SubmissionsController < ApplicationController
end
runner_socket.on :exit do |exit_code|
@exit_code = exit_code
exit_statement =
if @output.empty? && exit_code.zero?
@status = :ok
t('exercises.implement.no_output_exit_successful', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)
elsif @output.empty?
@status = :failed
t('exercises.implement.no_output_exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)
elsif exit_code.zero?
@status = :ok
"\n#{t('exercises.implement.exit_successful', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
else
@status = :failed
"\n#{t('exercises.implement.exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
end
client_socket.send_data JSON.dump({cmd: :write, stream: :stdout, data: "#{exit_statement}\n"})
client_socket.send_data JSON.dump({cmd: :out_of_memory}) if exit_code == 137
if exit_code == 137
client_socket.send_data JSON.dump({cmd: :out_of_memory})
@status = :out_of_memory
end
close_client_connection(client_socket)
end
@ -169,30 +177,38 @@ class SubmissionsController < ApplicationController
close_client_connection(client_socket)
Rails.logger.debug { "Running a submission timed out: #{e.message}" }
@output = "timeout: #{@output}"
@status = :timeout
extract_durations(e)
rescue Runner::Error => e
client_socket.send_data JSON.dump({cmd: :status, status: :container_depleted})
close_client_connection(client_socket)
Rails.logger.debug { "Runner error while running a submission: #{e.message}" }
@status = :container_depleted
extract_durations(e)
ensure
save_run_output
save_testrun_output 'run'
end
def score
hijack do |tubesock|
tubesock.onopen do |_event|
kill_client_socket(tubesock) if @embed_options[:disable_score]
switch_locale do
kill_client_socket(tubesock) if @embed_options[:disable_score]
tubesock.send_data(JSON.dump(@submission.calculate_score))
# To enable hints when scoring a submission, uncomment the next line:
# send_hints(tubesock, StructuredError.where(submission: @submission))
kill_client_socket(tubesock)
tubesock.send_data(JSON.dump(@submission.calculate_score))
# To enable hints when scoring a submission, uncomment the next line:
# send_hints(tubesock, StructuredError.where(submission: @submission))
kill_client_socket(tubesock)
rescue Runner::Error => e
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
kill_client_socket(tubesock)
Rails.logger.debug { "Runner error while scoring submission #{@submission.id}: #{e.message}" }
@passed = false
@status = :container_depleted
extract_durations(e)
save_testrun_output 'assess'
end
end
rescue Runner::Error => e
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
kill_client_socket(tubesock)
Rails.logger.debug { "Runner error while scoring submission #{@submission.id}: #{e.message}" }
end
end
@ -203,15 +219,21 @@ class SubmissionsController < ApplicationController
def test
hijack do |tubesock|
tubesock.onopen do |_event|
kill_client_socket(tubesock) if @embed_options[:disable_run]
switch_locale do
kill_client_socket(tubesock) if @embed_options[:disable_run]
tubesock.send_data(JSON.dump(@submission.test(@file)))
kill_client_socket(tubesock)
tubesock.send_data(JSON.dump(@submission.test(@file)))
kill_client_socket(tubesock)
rescue Runner::Error => e
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
kill_client_socket(tubesock)
Rails.logger.debug { "Runner error while testing submission #{@submission.id}: #{e.message}" }
@passed = false
@status = :container_depleted
extract_durations(e)
save_testrun_output 'assess'
end
end
rescue Runner::Error => e
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
kill_client_socket(tubesock)
Rails.logger.debug { "Runner error while testing submission #{@submission.id}: #{e.message}" }
end
end
@ -293,12 +315,15 @@ class SubmissionsController < ApplicationController
end
# save the output of this "run" as a "testrun" (scoring runs are saved in submission.rb)
def save_run_output
testrun = Testrun.create(
def save_testrun_output(cause)
testrun = Testrun.create!(
file: @file,
cause: 'run',
passed: @passed,
cause: cause,
submission: @submission,
output: @output,
exit_code: @exit_code, # might be nil, e.g., when the run did not finish
status: @status,
output: @output.presence, # TODO: Remove duplicated saving of the output after creating TestrunMessages
container_execution_time: @container_execution_time,
waiting_for_container_time: @waiting_for_container_time
)

View File

@ -116,7 +116,7 @@ class Runner < ApplicationRecord
output.merge!(status: :failed, container_execution_time: e.execution_duration)
rescue Runner::Error => e
Rails.logger.debug { "Running command `#{command}` failed: #{e.message}" }
output.merge!(status: :failed, container_execution_time: e.execution_duration)
output.merge!(status: :container_depleted, container_execution_time: e.execution_duration)
ensure
# We forward the exception if requested
raise e if raise_exception && defined?(e) && e.present?

View File

@ -252,7 +252,9 @@ class Submission < ApplicationRecord
cause: 'assess', # Required to differ run and assess for RfC show
file: file, # Test file that was executed
passed: passed,
output: testrun_output,
exit_code: output[:exit_code],
status: output[:status],
output: testrun_output.presence,
container_execution_time: output[:container_execution_time],
waiting_for_container_time: output[:waiting_for_container_time]
)

View File

@ -4,4 +4,16 @@ class Testrun < ApplicationRecord
belongs_to :file, class_name: 'CodeOcean::File', optional: true
belongs_to :submission
belongs_to :testrun_execution_environment, optional: true, dependent: :destroy
has_many :testrun_messages, dependent: :destroy
enum status: {
ok: 0,
failed: 1,
container_depleted: 2,
timeout: 3,
out_of_memory: 4,
}, _default: :ok, _prefix: true
validates :exit_code, numericality: {only_integer: true, min: 0, max: 255}, allow_nil: true
validates :status, presence: true
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
class TestrunMessage < ApplicationRecord
belongs_to :testrun
enum cmd: {
input: 0,
write: 1,
clear: 2,
turtle: 3,
turtlebatch: 4,
render: 5,
exit: 6,
timeout: 7,
out_of_memory: 8,
status: 9,
hint: 10,
client_kill: 11,
exception: 12,
result: 13,
}, _default: :write, _prefix: true
enum stream: {
stdin: 0,
stdout: 1,
stderr: 2,
}, _prefix: true
validates :cmd, presence: true
validates :timestamp, presence: true
validate :either_data_or_log
def either_data_or_log
if [data, log].count(&:present?) > 1
errors.add(log, "can't be present if data is also present")
end
end
private :either_data_or_log
end