@ -703,7 +703,8 @@ $(function() {
|
|||||||
panel.find('.row .col-sm-9').eq(1).find('.number').eq(0).text((result.score * result.weight).toFixed(2));
|
panel.find('.row .col-sm-9').eq(1).find('.number').eq(0).text((result.score * result.weight).toFixed(2));
|
||||||
panel.find('.row .col-sm-9').eq(1).find('.number').eq(1).text(result.weight);
|
panel.find('.row .col-sm-9').eq(1).find('.number').eq(1).text(result.weight);
|
||||||
panel.find('.row .col-sm-9').eq(2).text(result.message);
|
panel.find('.row .col-sm-9').eq(2).text(result.message);
|
||||||
panel.find('.row .col-sm-9').eq(3).find('a').attr('href', '#output-' + index);
|
if (result.error_messages) panel.find('.row .col-sm-9').eq(3).text(result.error_messages.join(', '));
|
||||||
|
panel.find('.row .col-sm-9').eq(4).find('a').attr('href', '#output-' + index);
|
||||||
};
|
};
|
||||||
|
|
||||||
var chunkBuffer = [{streamedResponse: true}];
|
var chunkBuffer = [{streamedResponse: true}];
|
||||||
|
@ -26,3 +26,26 @@
|
|||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.unit-test-result {
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.positive-result {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #8efa00;
|
||||||
|
-webkit-box-shadow: 0px 0px 11px 1px rgba(44,222,0,1);
|
||||||
|
-moz-box-shadow: 0px 0px 11px 1px rgba(44,222,0,1);
|
||||||
|
box-shadow: 0px 0px 11px 1px rgba(44,222,0,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.negative-result {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ff2600;
|
||||||
|
-webkit-box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
|
||||||
|
-moz-box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
|
||||||
|
box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
|
||||||
|
}
|
||||||
|
@ -6,7 +6,11 @@ module SubmissionScoring
|
|||||||
future = Concurrent::Future.execute do
|
future = Concurrent::Future.execute do
|
||||||
assessor = Assessor.new(execution_environment: submission.execution_environment)
|
assessor = Assessor.new(execution_environment: submission.execution_environment)
|
||||||
output = execute_test_file(file, submission)
|
output = execute_test_file(file, submission)
|
||||||
output.merge!(assessor.assess(output))
|
assessment = assessor.assess(output)
|
||||||
|
passed = ((assessment[:passed] == assessment[:count]) and (assessment[:score] > 0))
|
||||||
|
testrun_output = passed ? nil : output[:stderr]
|
||||||
|
Testrun.new(submission: submission, file: file, passed: passed, output: testrun_output).save
|
||||||
|
output.merge!(assessment)
|
||||||
output.merge!(filename: file.name_with_extension, message: feedback_message(file, output[:score]), weight: file.weight)
|
output.merge!(filename: file.name_with_extension, message: feedback_message(file, output[:score]), weight: file.weight)
|
||||||
end
|
end
|
||||||
future.value
|
future.value
|
||||||
@ -27,9 +31,9 @@ module SubmissionScoring
|
|||||||
def score_submission(submission)
|
def score_submission(submission)
|
||||||
outputs = collect_test_results(submission)
|
outputs = collect_test_results(submission)
|
||||||
score = 0.0
|
score = 0.0
|
||||||
if not (outputs.nil? || outputs.empty?)
|
unless outputs.nil? || outputs.empty?
|
||||||
outputs.each do |output|
|
outputs.each do |output|
|
||||||
if not output.nil?
|
unless output.nil?
|
||||||
score += output[:score] * output[:weight]
|
score += output[:score] * output[:weight]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,6 +21,7 @@ module CodeOcean
|
|||||||
belongs_to :file_type
|
belongs_to :file_type
|
||||||
|
|
||||||
has_many :files
|
has_many :files
|
||||||
|
has_many :testruns
|
||||||
alias_method :descendants, :files
|
alias_method :descendants, :files
|
||||||
|
|
||||||
mount_uploader :native_file, FileUploader
|
mount_uploader :native_file, FileUploader
|
||||||
|
@ -7,6 +7,8 @@ class Submission < ActiveRecord::Base
|
|||||||
|
|
||||||
belongs_to :exercise
|
belongs_to :exercise
|
||||||
|
|
||||||
|
has_many :testruns
|
||||||
|
|
||||||
delegate :execution_environment, to: :exercise
|
delegate :execution_environment, to: :exercise
|
||||||
|
|
||||||
scope :final, -> { where(cause: 'submit') }
|
scope :final, -> { where(cause: 'submit') }
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
class Testrun < ActiveRecord::Base
|
class Testrun < ActiveRecord::Base
|
||||||
belongs_to :file
|
belongs_to :file, class_name: 'CodeOcean::File'
|
||||||
belongs_to :submission
|
belongs_to :submission
|
||||||
end
|
end
|
||||||
|
@ -38,7 +38,7 @@ h1 = "#{@exercise} (external user #{@external_user})"
|
|||||||
table.table
|
table.table
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
- ['.time', '.cause', '.score', '.time_difference'].each do |title|
|
- ['.time', '.cause', '.score', '.tests', '.time_difference'].each do |title|
|
||||||
th.header = t(title)
|
th.header = t(title)
|
||||||
tbody
|
tbody
|
||||||
- deltas = submissions.map.with_index {|item, index| delta = item.created_at - submissions[index - 1].created_at if index > 0; if delta == nil or delta > 30*60 then 0 else delta end}
|
- deltas = submissions.map.with_index {|item, index| delta = item.created_at - submissions[index - 1].created_at if index > 0; if delta == nil or delta > 30*60 then 0 else delta end}
|
||||||
@ -47,6 +47,12 @@ h1 = "#{@exercise} (external user #{@external_user})"
|
|||||||
td.clickable = submission.created_at.strftime("%F %T")
|
td.clickable = submission.created_at.strftime("%F %T")
|
||||||
td = submission.cause
|
td = submission.cause
|
||||||
td = submission.score
|
td = submission.score
|
||||||
|
td
|
||||||
|
-submission.testruns.each do |run|
|
||||||
|
- if run.passed
|
||||||
|
.unit-test-result.positive-result
|
||||||
|
- else
|
||||||
|
.unit-test-result.negative-result
|
||||||
td = Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0
|
td = Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0
|
||||||
p = t('.addendum')
|
p = t('.addendum')
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
= row(label: '.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
= row(label: '.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
||||||
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
||||||
= row(label: '.feedback')
|
= row(label: '.feedback')
|
||||||
|
= row(label: '.error_messages')
|
||||||
= row(label: '.output', value: link_to(t('shared.show'), '#'))
|
= row(label: '.output', value: link_to(t('shared.show'), '#'))
|
||||||
#score data-maximum-score=@exercise.maximum_score data-score=@submission.try(:score)
|
#score data-maximum-score=@exercise.maximum_score data-score=@submission.try(:score)
|
||||||
h4
|
h4
|
||||||
|
@ -222,6 +222,7 @@ de:
|
|||||||
text: 'Ihr Browser unterstützt nicht alle Funktionalitäten, die %{application_name} benötigt. Bitte nutzen Sie einen modernen Browser, um %{application_name} zu besuchen.'
|
text: 'Ihr Browser unterstützt nicht alle Funktionalitäten, die %{application_name} benötigt. Bitte nutzen Sie einen modernen Browser, um %{application_name} zu besuchen.'
|
||||||
title: Ihr Browser wird nicht unterstützt!
|
title: Ihr Browser wird nicht unterstützt!
|
||||||
default_feedback: Sehr gut. Alle Tests waren erfolgreich.
|
default_feedback: Sehr gut. Alle Tests waren erfolgreich.
|
||||||
|
error_messages: Fehlermeldungen
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
file: 'Test-Datei <span class="number">%{number}</span> (<span class="filename">%{filename}</span>)'
|
file: 'Test-Datei <span class="number">%{number}</span> (<span class="filename">%{filename}</span>)'
|
||||||
hint: Tipp
|
hint: Tipp
|
||||||
@ -269,6 +270,7 @@ de:
|
|||||||
time: Zeit
|
time: Zeit
|
||||||
cause: Grund
|
cause: Grund
|
||||||
score: Punktzahl
|
score: Punktzahl
|
||||||
|
tests: Unit Tests
|
||||||
time_difference: 'Arbeitszeit bis hier*'
|
time_difference: 'Arbeitszeit bis hier*'
|
||||||
addendum: '* Differenzen von mehr als 30 Minuten werden ignoriert.'
|
addendum: '* Differenzen von mehr als 30 Minuten werden ignoriert.'
|
||||||
external_users:
|
external_users:
|
||||||
|
@ -222,6 +222,7 @@ en:
|
|||||||
text: 'Your browser does not support features required for using %{application_name}. Please access %{application_name} using a modern browser.'
|
text: 'Your browser does not support features required for using %{application_name}. Please access %{application_name} using a modern browser.'
|
||||||
title: Your browser is not supported!
|
title: Your browser is not supported!
|
||||||
default_feedback: Well done. All tests have been passed.
|
default_feedback: Well done. All tests have been passed.
|
||||||
|
error_messages: Error Messages
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
file: 'Test File <span class="number">%{number}</span> (<span class="filename">%{filename}</span>)'
|
file: 'Test File <span class="number">%{number}</span> (<span class="filename">%{filename}</span>)'
|
||||||
hint: Hint
|
hint: Hint
|
||||||
@ -269,6 +270,7 @@ en:
|
|||||||
time: Time
|
time: Time
|
||||||
cause: Cause
|
cause: Cause
|
||||||
score: Score
|
score: Score
|
||||||
|
tests: Unit Test Results
|
||||||
time_difference: 'Working Time until here*'
|
time_difference: 'Working Time until here*'
|
||||||
addendum: '* Deltas longer than 30 minutes are ignored.'
|
addendum: '* Deltas longer than 30 minutes are ignored.'
|
||||||
external_users:
|
external_users:
|
||||||
|
@ -2,6 +2,7 @@ class JunitAdapter < TestingFrameworkAdapter
|
|||||||
COUNT_REGEXP = /Tests run: (\d+)/
|
COUNT_REGEXP = /Tests run: (\d+)/
|
||||||
FAILURES_REGEXP = /Failures: (\d+)/
|
FAILURES_REGEXP = /Failures: (\d+)/
|
||||||
SUCCESS_REGEXP = /OK \((\d+) test[s]?\)/
|
SUCCESS_REGEXP = /OK \((\d+) test[s]?\)/
|
||||||
|
ASSERTION_ERROR_REGEXP = /java\.lang\.AssertionError:\s(.*)/
|
||||||
|
|
||||||
def self.framework_name
|
def self.framework_name
|
||||||
'JUnit'
|
'JUnit'
|
||||||
@ -13,7 +14,8 @@ class JunitAdapter < TestingFrameworkAdapter
|
|||||||
else
|
else
|
||||||
count = COUNT_REGEXP.match(output[:stdout]).try(:captures).try(:first).try(:to_i) || 0
|
count = COUNT_REGEXP.match(output[:stdout]).try(:captures).try(:first).try(:to_i) || 0
|
||||||
failed = FAILURES_REGEXP.match(output[:stdout]).try(:captures).try(:first).try(:to_i) || 0
|
failed = FAILURES_REGEXP.match(output[:stdout]).try(:captures).try(:first).try(:to_i) || 0
|
||||||
{count: count, failed: failed}
|
error_matches = ASSERTION_ERROR_REGEXP.match(output[:stdout]).try(:captures) || []
|
||||||
|
{count: count, failed: failed, error_messages: error_matches}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
class PyUnitAdapter < TestingFrameworkAdapter
|
class PyUnitAdapter < TestingFrameworkAdapter
|
||||||
COUNT_REGEXP = /Ran (\d+) test/
|
COUNT_REGEXP = /Ran (\d+) test/
|
||||||
FAILURES_REGEXP = /FAILED \(failures=(\d+)\)/
|
FAILURES_REGEXP = /FAILED \(failures=(\d+)\)/
|
||||||
|
ASSERTION_ERROR_REGEXP = /AssertionError:\s(.*)/
|
||||||
|
|
||||||
def self.framework_name
|
def self.framework_name
|
||||||
'PyUnit'
|
'PyUnit'
|
||||||
@ -10,6 +11,7 @@ class PyUnitAdapter < TestingFrameworkAdapter
|
|||||||
count = COUNT_REGEXP.match(output[:stderr]).captures.first.to_i
|
count = COUNT_REGEXP.match(output[:stderr]).captures.first.to_i
|
||||||
matches = FAILURES_REGEXP.match(output[:stderr])
|
matches = FAILURES_REGEXP.match(output[:stderr])
|
||||||
failed = matches ? matches.captures.try(:first).to_i : 0
|
failed = matches ? matches.captures.try(:first).to_i : 0
|
||||||
{count: count, failed: failed}
|
error_matches = ASSERTION_ERROR_REGEXP.match(output[:stderr]).captures
|
||||||
|
{count: count, failed: failed, error_messages: error_matches}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user