Merge pull request #142 from openHPI/fix-structured-errors-2

Structured Errors revisited
This commit is contained in:
rteusner
2018-01-31 15:22:03 +01:00
committed by GitHub
7 changed files with 38 additions and 28 deletions

View File

@ -9,7 +9,7 @@ module SubmissionScoring
assessment = assessor.assess(output) assessment = assessor.assess(output)
passed = ((assessment[:passed] == assessment[:count]) and (assessment[:score] > 0)) passed = ((assessment[:passed] == assessment[:count]) and (assessment[:score] > 0))
testrun_output = passed ? nil : 'message: ' + output[:message].to_s + "\n stdout: " + output[:stdout].to_s + "\n stderr: " + output[:stderr].to_s testrun_output = passed ? nil : 'message: ' + output[:message].to_s + "\n stdout: " + output[:stdout].to_s + "\n stderr: " + output[:stderr].to_s
if !testrun_output.blank? unless testrun_output.blank?
submission.exercise.execution_environment.error_templates.each do |template| submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze pattern = Regexp.new(template.signature).freeze
if pattern.match(testrun_output) if pattern.match(testrun_output)

View File

@ -202,21 +202,11 @@ class SubmissionsController < ApplicationController
tubesock.close tubesock.close
end end
def extract_errors
if !@message_buffer.blank?
@submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze
if pattern.match(@message_buffer)
StructuredError.create_from_template(template, @message_buffer)
end
end
end
end
def handle_message(message, tubesock, container) def handle_message(message, tubesock, container)
@run_output ||= "" @raw_output ||= ''
@run_output ||= ''
# Handle special commands first # Handle special commands first
if (/^#exit/.match(message)) if /^#exit/.match(message)
# Just call exit_container on the docker_client. # Just call exit_container on the docker_client.
# Do not call kill_socket for the websocket to the client here. # Do not call kill_socket for the websocket to the client here.
# @docker_client.exit_container closes the socket to the container, # @docker_client.exit_container closes the socket to the container,
@ -228,17 +218,17 @@ class SubmissionsController < ApplicationController
# Filter out information about run_command, test_command, user or working directory # Filter out information about run_command, test_command, user or working directory
run_command = @submission.execution_environment.run_command % command_substitutions(params[:filename]) run_command = @submission.execution_environment.run_command % command_substitutions(params[:filename])
test_command = @submission.execution_environment.test_command % command_substitutions(params[:filename]) test_command = @submission.execution_environment.test_command % command_substitutions(params[:filename])
if !(/root|workspace|#{run_command}|#{test_command}/.match(message)) unless /root|workspace|#{run_command}|#{test_command}/.match(message)
parse_message(message, 'stdout', tubesock) parse_message(message, 'stdout', tubesock)
end end
end end
end end
def parse_message(message, output_stream, socket, recursive = true) def parse_message(message, output_stream, socket, recursive = true)
parsed = ''; parsed = ''
begin begin
parsed = JSON.parse(message) parsed = JSON.parse(message)
if(parsed.class == Hash && parsed.key?('cmd')) if parsed.class == Hash and parsed.key?('cmd')
socket.send_data message socket.send_data message
Rails.logger.info('parse_message sent: ' + message) Rails.logger.info('parse_message sent: ' + message)
else else
@ -248,24 +238,24 @@ class SubmissionsController < ApplicationController
end end
rescue JSON::ParserError => e rescue JSON::ParserError => e
# Check wether the message contains multiple lines, if true try to parse each line # Check wether the message contains multiple lines, if true try to parse each line
if ((recursive == true) && (message.include? "\n")) if recursive and message.include? "\n"
for part in message.split("\n") for part in message.split("\n")
self.parse_message(part,output_stream,socket,false) self.parse_message(part,output_stream,socket,false)
end end
elsif(message.include? "<img") elsif message.include? '<img'
#Rails.logger.info('img foung') #Rails.logger.info('img foung')
@buffering = true @buffering = true
@buffer = "" @buffer = ''
@buffer += message @buffer += message
#Rails.logger.info('Starting to buffer') #Rails.logger.info('Starting to buffer')
elsif(@buffering && (message.include? "/>")) elsif @buffering and message.include? '/>'
@buffer += message @buffer += message
parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>@buffer} parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>@buffer}
socket.send_data JSON.dump(parsed) socket.send_data JSON.dump(parsed)
#socket.send_data @buffer #socket.send_data @buffer
@buffering = false @buffering = false
#Rails.logger.info('Sent complete buffer') #Rails.logger.info('Sent complete buffer')
elsif(@buffering) elsif @buffering
@buffer += message @buffer += message
#Rails.logger.info('Appending to buffer') #Rails.logger.info('Appending to buffer')
else else
@ -275,18 +265,30 @@ class SubmissionsController < ApplicationController
Rails.logger.info('parse_message sent: ' + JSON.dump(parsed)) Rails.logger.info('parse_message sent: ' + JSON.dump(parsed))
end end
ensure ensure
@raw_output += parsed['data'] if parsed.class == Hash and parsed.key? 'data'
# save the data that was send to the run_output if there is enough space left. this will be persisted as a testrun with cause "run" # save the data that was send to the run_output if there is enough space left. this will be persisted as a testrun with cause "run"
@run_output += JSON.dump(parsed) if @run_output.size <= max_run_output_buffer_size @run_output += JSON.dump(parsed) if @run_output.size <= max_run_output_buffer_size
end end
end end
def save_run_output def save_run_output
if !@run_output.blank? unless @run_output.blank?
@run_output = @run_output[(0..max_run_output_buffer_size-1)] # trim the string to max_message_buffer_size chars @run_output = @run_output[(0..max_run_output_buffer_size-1)] # trim the string to max_message_buffer_size chars
Testrun.create(file: @file, cause: 'run', submission: @submission, output: @run_output) Testrun.create(file: @file, cause: 'run', submission: @submission, output: @run_output)
end end
end end
def extract_errors
unless @raw_output.blank?
@submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze
if pattern.match(@raw_output)
StructuredError.create_from_template(template, @raw_output, @submission)
end
end
end
end
def score def score
hijack do |tubesock| hijack do |tubesock|
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?

View File

@ -1,9 +1,10 @@
class StructuredError < ActiveRecord::Base class StructuredError < ActiveRecord::Base
belongs_to :error_template belongs_to :error_template
belongs_to :submission
belongs_to :file, class_name: 'CodeOcean::File' belongs_to :file, class_name: 'CodeOcean::File'
def self.create_from_template(template, message_buffer) def self.create_from_template(template, message_buffer, submission)
instance = self.create(error_template: template) instance = self.create(error_template: template, submission: submission)
template.error_template_attributes.each do |attribute| template.error_template_attributes.each do |attribute|
StructuredErrorAttribute.create_from_template(attribute, instance, message_buffer) StructuredErrorAttribute.create_from_template(attribute, instance, message_buffer)
end end

View File

@ -3,15 +3,13 @@ class StructuredErrorAttribute < ActiveRecord::Base
belongs_to :error_template_attribute belongs_to :error_template_attribute
def self.create_from_template(attribute, structured_error, message_buffer) def self.create_from_template(attribute, structured_error, message_buffer)
match = false
value = nil value = nil
result = message_buffer.match(attribute.regex) result = message_buffer.match(attribute.regex)
if result != nil if result != nil
match = true
if result.captures.size > 0 if result.captures.size > 0
value = result.captures[0] value = result.captures[0]
end end
end end
self.create(structured_error: structured_error, error_template_attribute: attribute, value: value, match: match) self.create(structured_error: structured_error, error_template_attribute: attribute, value: value, match: result != nil)
end end
end end

View File

@ -8,6 +8,7 @@ class Submission < ActiveRecord::Base
belongs_to :exercise belongs_to :exercise
has_many :testruns has_many :testruns
has_many :structured_errors
has_many :comments, through: :files has_many :comments, through: :files
delegate :execution_environment, to: :exercise delegate :execution_environment, to: :exercise

View File

@ -0,0 +1,5 @@
class AddSubmissionToStructuredErrors < ActiveRecord::Migration
def change
add_reference :structured_errors, :submission, index: true
end
end

View File

@ -306,8 +306,11 @@ ActiveRecord::Schema.define(version: 20180130172021) do
t.integer "file_id" t.integer "file_id"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "submission_id"
end end
add_index "structured_errors", ["submission_id"], name: "index_structured_errors_on_submission_id", using: :btree
create_table "submissions", force: :cascade do |t| create_table "submissions", force: :cascade do |t|
t.integer "exercise_id" t.integer "exercise_id"
t.float "score" t.float "score"