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:
@ -146,18 +146,26 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
runner_socket.on :exit do |exit_code|
|
runner_socket.on :exit do |exit_code|
|
||||||
|
@exit_code = exit_code
|
||||||
exit_statement =
|
exit_statement =
|
||||||
if @output.empty? && exit_code.zero?
|
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)
|
t('exercises.implement.no_output_exit_successful', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)
|
||||||
elsif @output.empty?
|
elsif @output.empty?
|
||||||
|
@status = :failed
|
||||||
t('exercises.implement.no_output_exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)
|
t('exercises.implement.no_output_exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)
|
||||||
elsif exit_code.zero?
|
elsif exit_code.zero?
|
||||||
|
@status = :ok
|
||||||
"\n#{t('exercises.implement.exit_successful', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
|
"\n#{t('exercises.implement.exit_successful', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
|
||||||
else
|
else
|
||||||
|
@status = :failed
|
||||||
"\n#{t('exercises.implement.exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
|
"\n#{t('exercises.implement.exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}"
|
||||||
end
|
end
|
||||||
client_socket.send_data JSON.dump({cmd: :write, stream: :stdout, data: "#{exit_statement}\n"})
|
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)
|
close_client_connection(client_socket)
|
||||||
end
|
end
|
||||||
@ -169,30 +177,38 @@ class SubmissionsController < ApplicationController
|
|||||||
close_client_connection(client_socket)
|
close_client_connection(client_socket)
|
||||||
Rails.logger.debug { "Running a submission timed out: #{e.message}" }
|
Rails.logger.debug { "Running a submission timed out: #{e.message}" }
|
||||||
@output = "timeout: #{@output}"
|
@output = "timeout: #{@output}"
|
||||||
|
@status = :timeout
|
||||||
extract_durations(e)
|
extract_durations(e)
|
||||||
rescue Runner::Error => e
|
rescue Runner::Error => e
|
||||||
client_socket.send_data JSON.dump({cmd: :status, status: :container_depleted})
|
client_socket.send_data JSON.dump({cmd: :status, status: :container_depleted})
|
||||||
close_client_connection(client_socket)
|
close_client_connection(client_socket)
|
||||||
Rails.logger.debug { "Runner error while running a submission: #{e.message}" }
|
Rails.logger.debug { "Runner error while running a submission: #{e.message}" }
|
||||||
|
@status = :container_depleted
|
||||||
extract_durations(e)
|
extract_durations(e)
|
||||||
ensure
|
ensure
|
||||||
save_run_output
|
save_testrun_output 'run'
|
||||||
end
|
end
|
||||||
|
|
||||||
def score
|
def score
|
||||||
hijack do |tubesock|
|
hijack do |tubesock|
|
||||||
tubesock.onopen do |_event|
|
tubesock.onopen do |_event|
|
||||||
|
switch_locale do
|
||||||
kill_client_socket(tubesock) if @embed_options[:disable_score]
|
kill_client_socket(tubesock) if @embed_options[:disable_score]
|
||||||
|
|
||||||
tubesock.send_data(JSON.dump(@submission.calculate_score))
|
tubesock.send_data(JSON.dump(@submission.calculate_score))
|
||||||
# To enable hints when scoring a submission, uncomment the next line:
|
# To enable hints when scoring a submission, uncomment the next line:
|
||||||
# send_hints(tubesock, StructuredError.where(submission: @submission))
|
# send_hints(tubesock, StructuredError.where(submission: @submission))
|
||||||
kill_client_socket(tubesock)
|
kill_client_socket(tubesock)
|
||||||
end
|
|
||||||
rescue Runner::Error => e
|
rescue Runner::Error => e
|
||||||
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
|
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
|
||||||
kill_client_socket(tubesock)
|
kill_client_socket(tubesock)
|
||||||
Rails.logger.debug { "Runner error while scoring submission #{@submission.id}: #{e.message}" }
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -203,15 +219,21 @@ class SubmissionsController < ApplicationController
|
|||||||
def test
|
def test
|
||||||
hijack do |tubesock|
|
hijack do |tubesock|
|
||||||
tubesock.onopen do |_event|
|
tubesock.onopen do |_event|
|
||||||
|
switch_locale do
|
||||||
kill_client_socket(tubesock) if @embed_options[:disable_run]
|
kill_client_socket(tubesock) if @embed_options[:disable_run]
|
||||||
|
|
||||||
tubesock.send_data(JSON.dump(@submission.test(@file)))
|
tubesock.send_data(JSON.dump(@submission.test(@file)))
|
||||||
kill_client_socket(tubesock)
|
kill_client_socket(tubesock)
|
||||||
end
|
|
||||||
rescue Runner::Error => e
|
rescue Runner::Error => e
|
||||||
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
|
tubesock.send_data JSON.dump({cmd: :status, status: :container_depleted})
|
||||||
kill_client_socket(tubesock)
|
kill_client_socket(tubesock)
|
||||||
Rails.logger.debug { "Runner error while testing submission #{@submission.id}: #{e.message}" }
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -293,12 +315,15 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
# save the output of this "run" as a "testrun" (scoring runs are saved in submission.rb)
|
# save the output of this "run" as a "testrun" (scoring runs are saved in submission.rb)
|
||||||
def save_run_output
|
def save_testrun_output(cause)
|
||||||
testrun = Testrun.create(
|
testrun = Testrun.create!(
|
||||||
file: @file,
|
file: @file,
|
||||||
cause: 'run',
|
passed: @passed,
|
||||||
|
cause: cause,
|
||||||
submission: @submission,
|
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,
|
container_execution_time: @container_execution_time,
|
||||||
waiting_for_container_time: @waiting_for_container_time
|
waiting_for_container_time: @waiting_for_container_time
|
||||||
)
|
)
|
||||||
|
@ -116,7 +116,7 @@ class Runner < ApplicationRecord
|
|||||||
output.merge!(status: :failed, container_execution_time: e.execution_duration)
|
output.merge!(status: :failed, container_execution_time: e.execution_duration)
|
||||||
rescue Runner::Error => e
|
rescue Runner::Error => e
|
||||||
Rails.logger.debug { "Running command `#{command}` failed: #{e.message}" }
|
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
|
ensure
|
||||||
# We forward the exception if requested
|
# We forward the exception if requested
|
||||||
raise e if raise_exception && defined?(e) && e.present?
|
raise e if raise_exception && defined?(e) && e.present?
|
||||||
|
@ -252,7 +252,9 @@ class Submission < ApplicationRecord
|
|||||||
cause: 'assess', # Required to differ run and assess for RfC show
|
cause: 'assess', # Required to differ run and assess for RfC show
|
||||||
file: file, # Test file that was executed
|
file: file, # Test file that was executed
|
||||||
passed: passed,
|
passed: passed,
|
||||||
output: testrun_output,
|
exit_code: output[:exit_code],
|
||||||
|
status: output[:status],
|
||||||
|
output: testrun_output.presence,
|
||||||
container_execution_time: output[:container_execution_time],
|
container_execution_time: output[:container_execution_time],
|
||||||
waiting_for_container_time: output[:waiting_for_container_time]
|
waiting_for_container_time: output[:waiting_for_container_time]
|
||||||
)
|
)
|
||||||
|
@ -4,4 +4,16 @@ class Testrun < ApplicationRecord
|
|||||||
belongs_to :file, class_name: 'CodeOcean::File', optional: true
|
belongs_to :file, class_name: 'CodeOcean::File', optional: true
|
||||||
belongs_to :submission
|
belongs_to :submission
|
||||||
belongs_to :testrun_execution_environment, optional: true, dependent: :destroy
|
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
|
end
|
||||||
|
40
app/models/testrun_message.rb
Normal file
40
app/models/testrun_message.rb
Normal 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
|
24
db/migrate/20220415215111_add_details_to_testruns.rb
Normal file
24
db/migrate/20220415215111_add_details_to_testruns.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddDetailsToTestruns < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
change_table :testruns do |t|
|
||||||
|
t.integer :exit_code, limit: 2, null: true, comment: 'No exit code is available in case of a timeout'
|
||||||
|
t.check_constraint 'exit_code >= 0 AND exit_code <= 255', name: 'exit_code_constraint'
|
||||||
|
t.integer :status, limit: 1, null: false, default: 0, comment: 'Used as enum in Rails'
|
||||||
|
end
|
||||||
|
|
||||||
|
enable_extension 'pgcrypto' unless extensions.include?('pgcrypto')
|
||||||
|
|
||||||
|
create_table :testrun_messages, id: :uuid do |t|
|
||||||
|
t.belongs_to :testrun, foreign_key: true, null: false, index: true
|
||||||
|
t.interval :timestamp, null: false, default: '00:00:00'
|
||||||
|
t.integer :cmd, limit: 1, null: false, default: 1, comment: 'Used as enum in Rails'
|
||||||
|
t.integer :stream, limit: 1, null: true, comment: 'Used as enum in Rails'
|
||||||
|
t.text :log, null: true
|
||||||
|
t.jsonb :data, null: true
|
||||||
|
t.check_constraint 'log IS NULL OR data IS NULL', name: 'either_data_or_log'
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
db/schema.rb
20
db/schema.rb
@ -10,10 +10,11 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_04_15_125948) do
|
ActiveRecord::Schema.define(version: 2022_04_15_215111) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
|
enable_extension "pgcrypto"
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
create_table "anomaly_notifications", id: :serial, force: :cascade do |t|
|
create_table "anomaly_notifications", id: :serial, force: :cascade do |t|
|
||||||
@ -470,6 +471,19 @@ ActiveRecord::Schema.define(version: 2022_04_15_125948) do
|
|||||||
t.index ["testrun_id"], name: "index_testrun_execution_environments_on_testrun_id"
|
t.index ["testrun_id"], name: "index_testrun_execution_environments_on_testrun_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "testrun_messages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
t.bigint "testrun_id", null: false
|
||||||
|
t.interval "timestamp", default: "PT0S", null: false
|
||||||
|
t.integer "cmd", limit: 2, default: 0, null: false, comment: "Used as enum in Rails"
|
||||||
|
t.integer "stream", limit: 2, comment: "Used as enum in Rails"
|
||||||
|
t.text "log"
|
||||||
|
t.jsonb "data"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["testrun_id"], name: "index_testrun_messages_on_testrun_id"
|
||||||
|
t.check_constraint "(log IS NULL) OR (data IS NULL)", name: "either_data_or_log"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "testruns", id: :serial, force: :cascade do |t|
|
create_table "testruns", id: :serial, force: :cascade do |t|
|
||||||
t.boolean "passed"
|
t.boolean "passed"
|
||||||
t.text "output"
|
t.text "output"
|
||||||
@ -480,7 +494,10 @@ ActiveRecord::Schema.define(version: 2022_04_15_125948) do
|
|||||||
t.string "cause"
|
t.string "cause"
|
||||||
t.interval "container_execution_time"
|
t.interval "container_execution_time"
|
||||||
t.interval "waiting_for_container_time"
|
t.interval "waiting_for_container_time"
|
||||||
|
t.integer "exit_code", limit: 2, comment: "No exit code is available in case of a timeout"
|
||||||
|
t.integer "status", limit: 2, default: 0, null: false, comment: "Used as enum in Rails"
|
||||||
t.index ["submission_id"], name: "index_testruns_on_submission_id"
|
t.index ["submission_id"], name: "index_testruns_on_submission_id"
|
||||||
|
t.check_constraint "(exit_code >= 0) AND (exit_code <= 255)", name: "exit_code_constraint"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "tips", force: :cascade do |t|
|
create_table "tips", force: :cascade do |t|
|
||||||
@ -547,6 +564,7 @@ ActiveRecord::Schema.define(version: 2022_04_15_125948) do
|
|||||||
add_foreign_key "submissions", "study_groups"
|
add_foreign_key "submissions", "study_groups"
|
||||||
add_foreign_key "testrun_execution_environments", "execution_environments"
|
add_foreign_key "testrun_execution_environments", "execution_environments"
|
||||||
add_foreign_key "testrun_execution_environments", "testruns"
|
add_foreign_key "testrun_execution_environments", "testruns"
|
||||||
|
add_foreign_key "testrun_messages", "testruns"
|
||||||
add_foreign_key "tips", "file_types"
|
add_foreign_key "tips", "file_types"
|
||||||
add_foreign_key "user_exercise_feedbacks", "submissions"
|
add_foreign_key "user_exercise_feedbacks", "submissions"
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user