Merge branch 'master' into rt/comments
Conflicts: app/assets/javascripts/editor.js
This commit is contained in:
@ -274,6 +274,29 @@ $(function() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var lastCopyText;
|
||||||
|
var handleCopyEvent = function(text){
|
||||||
|
lastCopyText = text;
|
||||||
|
};
|
||||||
|
|
||||||
|
var handlePasteEvent = function (pasteObject) {
|
||||||
|
//console.log("Handling paste event. this is ", this );
|
||||||
|
//console.log("Text: " + pasteObject.text);
|
||||||
|
|
||||||
|
var same = (lastCopyText === pasteObject.text)
|
||||||
|
//console.log("Text is the same: " + same);
|
||||||
|
|
||||||
|
// if the text is not copied from within the editor (from any file), send an event to lanalytics
|
||||||
|
//if(!same){
|
||||||
|
// publishCodeOceanEvent("codeocean_editor_paste", {
|
||||||
|
// text: pasteObject.text,
|
||||||
|
// exercise: $('#editor').data('exercise-id'),
|
||||||
|
// file_id: "1"
|
||||||
|
//
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var handleScoringResponse = function(response) {
|
var handleScoringResponse = function(response) {
|
||||||
printScoringResults(response);
|
printScoringResults(response);
|
||||||
var score = _.reduce(response, function(sum, result) {
|
var score = _.reduce(response, function(sum, result) {
|
||||||
@ -362,18 +385,12 @@ $(function() {
|
|||||||
var initializeEditors = function() {
|
var initializeEditors = function() {
|
||||||
$('.editor').each(function(index, element) {
|
$('.editor').each(function(index, element) {
|
||||||
var editor = ace.edit(element);
|
var editor = ace.edit(element);
|
||||||
|
|
||||||
if (qa_api) {
|
if (qa_api) {
|
||||||
editor.getSession().on("change", function (deltaObject) {
|
editor.getSession().on("change", function (deltaObject) {
|
||||||
qa_api.executeCommand('syncEditor', [active_file, deltaObject]);
|
qa_api.executeCommand('syncEditor', [active_file, deltaObject]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// listener for autosave
|
|
||||||
editor.getSession().on("change", function (deltaObject) {
|
|
||||||
resetSaveTimer();
|
|
||||||
});
|
|
||||||
|
|
||||||
var document = editor.getSession().getDocument();
|
var document = editor.getSession().getDocument();
|
||||||
// insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added
|
// insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added
|
||||||
var file_id = $(element).data('file-id');
|
var file_id = $(element).data('file-id');
|
||||||
@ -412,37 +429,28 @@ $(function() {
|
|||||||
var row = e.getDocumentPosition().row;
|
var row = e.getDocumentPosition().row;
|
||||||
e.stop();
|
e.stop();
|
||||||
|
|
||||||
var commentModal = $('#comment-modal');
|
/*
|
||||||
|
* Register event handlers
|
||||||
|
*/
|
||||||
|
|
||||||
if (hasCommentsInRow(editor, row)) {
|
// editor itself
|
||||||
var rowComments = getCommentsForRow(editor, row);
|
editor.on("paste", handlePasteEvent);
|
||||||
var comments = _.pluck(rowComments, 'text').join('\n');
|
editor.on("copy", handleCopyEvent);
|
||||||
commentModal.find('#other-comments').text(comments);
|
editor.on("guttermousedown", handleSidebarClick);
|
||||||
} else {
|
|
||||||
commentModal.find('#other-comments').text('none');
|
|
||||||
}
|
|
||||||
|
|
||||||
commentModal.find('#addCommentButton').off('click');
|
/* // alternative:
|
||||||
commentModal.find('#removeAllButton').off('click');
|
editor.on("guttermousedown", function(e) {
|
||||||
|
handleSidebarClick(e);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
commentModal.find('#addCommentButton').on('click', function(e){
|
//session
|
||||||
var user_id = $(element).data('user-id');
|
session.on('annotationRemoval', handleAnnotationRemoval);
|
||||||
var commenttext = commentModal.find('textarea').val();
|
session.on('annotationChange', handleAnnotationChange);
|
||||||
var file_id = $(element).data('id');
|
|
||||||
|
|
||||||
if (commenttext !== "") {
|
// listener for autosave
|
||||||
createComment(user_id, file_id, row, editor, commenttext);
|
session.on("change", function (deltaObject) {
|
||||||
commentModal.modal('hide');
|
resetSaveTimer();
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
commentModal.find('#removeAllButton').on('click', function(e){
|
|
||||||
var user_id = $(element).data('user-id');
|
|
||||||
deleteComment(user_id,file_id,row,editor);
|
|
||||||
commentModal.modal('hide');
|
|
||||||
})
|
|
||||||
|
|
||||||
commentModal.modal('show');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -451,13 +459,13 @@ $(function() {
|
|||||||
return editor.getSession().getAnnotations().some(function(element) {
|
return editor.getSession().getAnnotations().some(function(element) {
|
||||||
return element.row === row;
|
return element.row === row;
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
var getCommentsForRow = function (editor, row){
|
var getCommentsForRow = function (editor, row){
|
||||||
return editor.getSession().getAnnotations().filter(function(element) {
|
return editor.getSession().getAnnotations().filter(function(element) {
|
||||||
return element.row === row;
|
return element.row === row;
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
var setAnnotations = function (editor, file_id){
|
var setAnnotations = function (editor, file_id){
|
||||||
var session = editor.getSession();
|
var session = editor.getSession();
|
||||||
@ -476,29 +484,27 @@ $(function() {
|
|||||||
setAnnotationsCallback(response, session);
|
setAnnotationsCallback(response, session);
|
||||||
});
|
});
|
||||||
jqrequest.fail(ajaxError);
|
jqrequest.fail(ajaxError);
|
||||||
}
|
};
|
||||||
|
|
||||||
var setAnnotationsCallback = function (response, session) {
|
var setAnnotationsCallback = function (response, session) {
|
||||||
var annotations = response;
|
var annotations = response;
|
||||||
|
|
||||||
|
// add classname and the username in front of each comment
|
||||||
$.each(annotations, function(index, comment){
|
$.each(annotations, function(index, comment){
|
||||||
comment.className = "code-ocean_comment";
|
comment.className = "code-ocean_comment";
|
||||||
comment.text = comment.username + ": " + comment.text;
|
comment.text = comment.username + ": " + comment.text;
|
||||||
// comment.text = comment.user_id + ": " + comment.text;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.setAnnotations(annotations);
|
session.setAnnotations(annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
var deleteComment = function (user_id, file_id, row, editor) {
|
var deleteComment = function (file_id, row, editor) {
|
||||||
var jqxhr = $.ajax({
|
var jqxhr = $.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: "/comments",
|
url: "/comments",
|
||||||
data: {
|
data: {
|
||||||
row: row,
|
row: row,
|
||||||
file_id: file_id,
|
file_id: file_id }
|
||||||
user_id: user_id
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
jqxhr.done(function (response) {
|
jqxhr.done(function (response) {
|
||||||
setAnnotations(editor, file_id);
|
setAnnotations(editor, file_id);
|
||||||
@ -506,7 +512,7 @@ $(function() {
|
|||||||
jqxhr.fail(ajaxError);
|
jqxhr.fail(ajaxError);
|
||||||
}
|
}
|
||||||
|
|
||||||
var createComment = function (user_id, file_id, row, editor, commenttext){
|
var createComment = function (file_id, row, editor, commenttext){
|
||||||
var jqxhr = $.ajax({
|
var jqxhr = $.ajax({
|
||||||
data: {
|
data: {
|
||||||
comment: {
|
comment: {
|
||||||
@ -524,7 +530,7 @@ $(function() {
|
|||||||
setAnnotations(editor, file_id);
|
setAnnotations(editor, file_id);
|
||||||
});
|
});
|
||||||
jqxhr.fail(ajaxError);
|
jqxhr.fail(ajaxError);
|
||||||
}
|
};
|
||||||
|
|
||||||
var handleAnnotationRemoval = function(removedAnnotations) {
|
var handleAnnotationRemoval = function(removedAnnotations) {
|
||||||
removedAnnotations.forEach(function(annotation) {
|
removedAnnotations.forEach(function(annotation) {
|
||||||
@ -536,7 +542,7 @@ $(function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
var handleAnnotationChange = function(changedAnnotations) {
|
var handleAnnotationChange = function(changedAnnotations) {
|
||||||
changedAnnotations.forEach(function(annotation) {
|
changedAnnotations.forEach(function(annotation) {
|
||||||
@ -553,7 +559,53 @@ $(function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Code for clicks on gutter / sidepanel
|
||||||
|
var handleSidebarClick = function(e) {
|
||||||
|
var target = e.domEvent.target;
|
||||||
|
var editor = e.editor;
|
||||||
|
|
||||||
|
if (target.className.indexOf("ace_gutter-cell") == -1) return;
|
||||||
|
if (!editor.isFocused()) return;
|
||||||
|
if (e.clientX > 25 + target.getBoundingClientRect().left) return;
|
||||||
|
|
||||||
|
var row = e.getDocumentPosition().row;
|
||||||
|
e.stop();
|
||||||
|
|
||||||
|
var commentModal = $('#comment-modal');
|
||||||
|
|
||||||
|
if (hasCommentsInRow(editor, row)) {
|
||||||
|
var rowComments = getCommentsForRow(editor, row);
|
||||||
|
var comments = _.pluck(rowComments, 'text').join('\n');
|
||||||
|
commentModal.find('#other-comments').text(comments);
|
||||||
|
} else {
|
||||||
|
commentModal.find('#other-comments').text('none');
|
||||||
|
}
|
||||||
|
|
||||||
|
commentModal.find('#addCommentButton').off('click');
|
||||||
|
commentModal.find('#removeAllButton').off('click');
|
||||||
|
|
||||||
|
commentModal.find('#addCommentButton').on('click', function(e){
|
||||||
|
var commenttext = commentModal.find('textarea').val();
|
||||||
|
// attention: use id of data attribute here, not file-id (file-id is the original file)
|
||||||
|
var file_id = $(editor.container).data('id');
|
||||||
|
|
||||||
|
if (commenttext !== "") {
|
||||||
|
createComment(file_id, row, editor, commenttext);
|
||||||
|
commentModal.modal('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
commentModal.find('#removeAllButton').on('click', function(e){
|
||||||
|
// attention: use id of data attribute here, not file-id (file-id is the original file)
|
||||||
|
var file_id = $(editor.container).data('id');
|
||||||
|
deleteComment(file_id,row, editor);
|
||||||
|
commentModal.modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
commentModal.modal('show');
|
||||||
|
};
|
||||||
|
|
||||||
var initializeEventHandlers = function() {
|
var initializeEventHandlers = function() {
|
||||||
$(document).on('click', '#results a', showOutput);
|
$(document).on('click', '#results a', showOutput);
|
||||||
@ -733,6 +785,31 @@ $(function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Publishing events for other (JS) components to react to codeocean events
|
||||||
|
var publishCodeOceanEvent = function (eventName, contextData) {
|
||||||
|
|
||||||
|
var payload = {
|
||||||
|
user: {
|
||||||
|
resource_uuid: $('#editor').data('user-id')
|
||||||
|
},
|
||||||
|
verb: eventName,
|
||||||
|
resource: {},
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
with_result: {},
|
||||||
|
in_context: contextData
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax("https://open.hpi.de/lanalytics/log", {
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
dataType: 'JSON',
|
||||||
|
data: payload,
|
||||||
|
success: {},
|
||||||
|
error: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
var renderCode = function(event) {
|
var renderCode = function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if ($('#render').is(':visible')) {
|
if ($('#render').is(':visible')) {
|
||||||
@ -1044,12 +1121,13 @@ $(function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save on quit
|
||||||
$(window).on("beforeunload", function() {
|
$(window).on("beforeunload", function() {
|
||||||
if(autosaveTimer){
|
if(autosaveTimer){
|
||||||
autosave();
|
autosave();
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
});
|
||||||
|
|
||||||
if ($('#editor').isPresent()) {
|
if ($('#editor').isPresent()) {
|
||||||
if (isBrowserSupported()) {
|
if (isBrowserSupported()) {
|
||||||
|
@ -26,8 +26,14 @@ module SubmissionScoring
|
|||||||
|
|
||||||
def score_submission(submission)
|
def score_submission(submission)
|
||||||
outputs = collect_test_results(submission)
|
outputs = collect_test_results(submission)
|
||||||
score = outputs.map { |output|
|
score = 0.0
|
||||||
output[:score] * output[:weight] }.reduce(:+)
|
if not (outputs.nil? || outputs.empty?)
|
||||||
|
outputs.each do |output|
|
||||||
|
if not output.nil?
|
||||||
|
score += output[:score] * output[:weight]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
submission.update(score: score)
|
submission.update(score: score)
|
||||||
outputs
|
outputs
|
||||||
end
|
end
|
||||||
|
@ -56,7 +56,7 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Submission.last(100).search(params[:q])
|
@search = Submission.search(params[:q])
|
||||||
@submissions = @search.result.includes(:exercise, :user).paginate(page: params[:page])
|
@submissions = @search.result.includes(:exercise, :user).paginate(page: params[:page])
|
||||||
authorize!
|
authorize!
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,7 @@ require 'pathname'
|
|||||||
class DockerClient
|
class DockerClient
|
||||||
CONTAINER_WORKSPACE_PATH = '/workspace'
|
CONTAINER_WORKSPACE_PATH = '/workspace'
|
||||||
DEFAULT_MEMORY_LIMIT = 256
|
DEFAULT_MEMORY_LIMIT = 256
|
||||||
|
# Ralf: I suggest to replace this with the environment variable. Ask Hauke why this is not the case!
|
||||||
LOCAL_WORKSPACE_ROOT = Rails.root.join('tmp', 'files', Rails.env)
|
LOCAL_WORKSPACE_ROOT = Rails.root.join('tmp', 'files', Rails.env)
|
||||||
MINIMUM_MEMORY_LIMIT = 4
|
MINIMUM_MEMORY_LIMIT = 4
|
||||||
RECYCLE_CONTAINERS = true
|
RECYCLE_CONTAINERS = true
|
||||||
@ -17,6 +18,14 @@ class DockerClient
|
|||||||
raise(Error, "The Docker host at #{Docker.url} is not reachable!")
|
raise(Error, "The Docker host at #{Docker.url} is not reachable!")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.clean_container_workspace(container)
|
||||||
|
local_workspace_path = local_workspace_path(container)
|
||||||
|
if local_workspace_path && Pathname.new(local_workspace_path).exist?
|
||||||
|
Pathname.new(local_workspace_path).children.each{ |p| p.rmtree}
|
||||||
|
#FileUtils.rmdir(Pathname.new(local_workspace_path))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def command_substitutions(filename)
|
def command_substitutions(filename)
|
||||||
{class_name: File.basename(filename, File.extname(filename)).camelize, filename: filename, module_name: File.basename(filename, File.extname(filename)).underscore}
|
{class_name: File.basename(filename, File.extname(filename)).camelize, filename: filename, module_name: File.basename(filename, File.extname(filename)).underscore}
|
||||||
end
|
end
|
||||||
@ -51,6 +60,7 @@ class DockerClient
|
|||||||
tries ||= 0
|
tries ||= 0
|
||||||
container = Docker::Container.create(container_creation_options(execution_environment))
|
container = Docker::Container.create(container_creation_options(execution_environment))
|
||||||
local_workspace_path = generate_local_workspace_path
|
local_workspace_path = generate_local_workspace_path
|
||||||
|
# container.start always creates the passed local_workspace_path on disk. Seems like we have to live with that, therefore we can also just create the empty folder ourselves.
|
||||||
FileUtils.mkdir(local_workspace_path)
|
FileUtils.mkdir(local_workspace_path)
|
||||||
container.start(container_start_options(execution_environment, local_workspace_path))
|
container.start(container_start_options(execution_environment, local_workspace_path))
|
||||||
container.start_time = Time.now
|
container.start_time = Time.now
|
||||||
@ -61,8 +71,8 @@ class DockerClient
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_workspace_files(container, submission)
|
def create_workspace_files(container, submission)
|
||||||
#clear directory (it should be emtpy anyhow)
|
#clear directory (it should be empty anyhow)
|
||||||
Pathname.new(self.class.local_workspace_path(container)).children.each{ |p| p.rmtree}
|
#Pathname.new(self.class.local_workspace_path(container)).children.each{ |p| p.rmtree}
|
||||||
submission.collect_files.each do |file|
|
submission.collect_files.each do |file|
|
||||||
FileUtils.mkdir_p(File.join(self.class.local_workspace_path(container), file.path || ''))
|
FileUtils.mkdir_p(File.join(self.class.local_workspace_path(container), file.path || ''))
|
||||||
if file.file_type.binary?
|
if file.file_type.binary?
|
||||||
@ -85,10 +95,7 @@ class DockerClient
|
|||||||
Rails.logger.info('destroying container ' + container.to_s)
|
Rails.logger.info('destroying container ' + container.to_s)
|
||||||
container.stop.kill
|
container.stop.kill
|
||||||
container.port_bindings.values.each { |port| PortPool.release(port) }
|
container.port_bindings.values.each { |port| PortPool.release(port) }
|
||||||
local_workspace_path = local_workspace_path(container)
|
clean_container_workspace(container)
|
||||||
if local_workspace_path && Pathname.new(local_workspace_path).exist?
|
|
||||||
Pathname.new(local_workspace_path).children.each{ |p| p.rmtree}
|
|
||||||
end
|
|
||||||
container.delete(force: true, v: true)
|
container.delete(force: true, v: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -157,6 +164,7 @@ class DockerClient
|
|||||||
|
|
||||||
def self.mapped_directories(local_workspace_path)
|
def self.mapped_directories(local_workspace_path)
|
||||||
remote_workspace_path = local_workspace_path.sub(LOCAL_WORKSPACE_ROOT.to_s, config[:workspace_root])
|
remote_workspace_path = local_workspace_path.sub(LOCAL_WORKSPACE_ROOT.to_s, config[:workspace_root])
|
||||||
|
# create the string to be returned
|
||||||
["#{remote_workspace_path}:#{CONTAINER_WORKSPACE_PATH}"]
|
["#{remote_workspace_path}:#{CONTAINER_WORKSPACE_PATH}"]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -170,38 +178,39 @@ class DockerClient
|
|||||||
`docker pull #{docker_image}` if docker_image
|
`docker pull #{docker_image}` if docker_image
|
||||||
end
|
end
|
||||||
|
|
||||||
def return_container(container)
|
def self.return_container(container, execution_environment)
|
||||||
local_workspace_path = self.class.local_workspace_path(container)
|
clean_container_workspace(container)
|
||||||
Pathname.new(local_workspace_path).children.each{ |p| p.rmtree}
|
DockerContainerPool.return_container(container, execution_environment)
|
||||||
DockerContainerPool.return_container(container, @execution_environment)
|
|
||||||
end
|
end
|
||||||
private :return_container
|
#private :return_container
|
||||||
|
|
||||||
def send_command(command, container, &block)
|
def send_command(command, container, &block)
|
||||||
|
result = {status: :failed, stdout: '', stderr: ''}
|
||||||
Timeout.timeout(@execution_environment.permitted_execution_time.to_i) do
|
Timeout.timeout(@execution_environment.permitted_execution_time.to_i) do
|
||||||
output = container.exec(['bash', '-c', command])
|
output = container.exec(['bash', '-c', command])
|
||||||
Rails.logger.info "output from container.exec"
|
Rails.logger.info "output from container.exec"
|
||||||
Rails.logger.info output
|
Rails.logger.info output
|
||||||
{status: output[2] == 0 ? :ok : :failed, stdout: output[0].join, stderr: output[1].join}
|
result = {status: output[2] == 0 ? :ok : :failed, stdout: output[0].join, stderr: output[1].join}
|
||||||
end
|
end
|
||||||
|
# if we use pooling and recylce the containers, put it back. otherwise, destroy it.
|
||||||
|
(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container)
|
||||||
|
result
|
||||||
rescue Timeout::Error
|
rescue Timeout::Error
|
||||||
timeout_occured = true
|
|
||||||
Rails.logger.info('got timeout error for container ' + container.to_s)
|
Rails.logger.info('got timeout error for container ' + container.to_s)
|
||||||
#container.restart if RECYCLE_CONTAINERS
|
|
||||||
DockerContainerPool.remove_from_all_containers(container, @execution_environment)
|
# remove container from pool, then destroy it
|
||||||
|
(DockerContainerPool.config[:active]) ? DockerContainerPool.remove_from_all_containers(container, @execution_environment) :
|
||||||
|
|
||||||
# destroy container
|
# destroy container
|
||||||
self.class.destroy_container(container)
|
self.class.destroy_container(container)
|
||||||
|
|
||||||
if(RECYCLE_CONTAINERS)
|
# if we recylce containers, we start a fresh one
|
||||||
# create new container and add it to @all_containers. will be added to @containers on return_container
|
if(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS)
|
||||||
|
# create new container and add it to @all_containers and @containers.
|
||||||
container = self.class.create_container(@execution_environment)
|
container = self.class.create_container(@execution_environment)
|
||||||
DockerContainerPool.add_to_all_containers(container, @execution_environment)
|
DockerContainerPool.add_to_all_containers(container, @execution_environment)
|
||||||
end
|
end
|
||||||
{status: :timeout}
|
{status: :timeout}
|
||||||
ensure
|
|
||||||
Rails.logger.info('send_command ensuring for' + container.to_s)
|
|
||||||
RECYCLE_CONTAINERS ? return_container(container) : self.class.destroy_container(container)
|
|
||||||
end
|
end
|
||||||
private :send_command
|
private :send_command
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ describe 'Editor', js: true do
|
|||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
visit(sign_in_path)
|
visit(sign_in_path)
|
||||||
fill_in('Email', with: user.email)
|
fill_in('email', with: user.email)
|
||||||
fill_in('Password', with: FactoryGirl.attributes_for(:teacher)[:password])
|
fill_in('password', with: FactoryGirl.attributes_for(:teacher)[:password])
|
||||||
click_button(I18n.t('sessions.new.link'))
|
click_button(I18n.t('sessions.new.link'))
|
||||||
visit(implement_exercise_path(exercise))
|
visit(implement_exercise_path(exercise))
|
||||||
end
|
end
|
||||||
@ -83,8 +83,9 @@ describe 'Editor', js: true do
|
|||||||
describe 'Progress Tab' do
|
describe 'Progress Tab' do
|
||||||
before(:each) { click_link(I18n.t('exercises.implement.progress')) }
|
before(:each) { click_link(I18n.t('exercises.implement.progress')) }
|
||||||
|
|
||||||
it 'contains a button for submitting the exercise' do
|
it 'does not contains a button for submitting the exercise' do
|
||||||
expect(page).to have_css('#submit')
|
# the button is only displayed when an correct LTI handshake to a running course happened. This is not the case in the test
|
||||||
|
expect(page).not_to have_css('#submit')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -182,7 +182,7 @@ describe DockerClient, docker: true do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'deletes the container' do
|
it 'deletes the container' do
|
||||||
expect(container).to receive(:delete).with(force: true)
|
expect(container).to receive(:delete).with(force: true, v: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -220,6 +220,7 @@ describe DockerClient, docker: true do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'raises the error' do
|
it 'raises the error' do
|
||||||
|
pending("retries are disabled")
|
||||||
#!TODO Retries is disabled
|
#!TODO Retries is disabled
|
||||||
#expect { execute_arbitrary_command }.to raise_error(error)
|
#expect { execute_arbitrary_command }.to raise_error(error)
|
||||||
end
|
end
|
||||||
@ -345,17 +346,20 @@ describe DockerClient, docker: true do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'provides the command to be executed as input' do
|
it 'provides the command to be executed as input' do
|
||||||
|
pending("we are currently not using any input and for output server send events instead of attach.")
|
||||||
expect(container).to receive(:attach).with(stdin: kind_of(StringIO))
|
expect(container).to receive(:attach).with(stdin: kind_of(StringIO))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calls the block' do
|
it 'calls the block' do
|
||||||
|
pending("block is no longer called, see revision 4cbf9970b13362efd4588392cafe4f7fd7cb31c3 to get information how it was done before.")
|
||||||
expect(block).to receive(:call)
|
expect(block).to receive(:call)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a timeout occurs' do
|
context 'when a timeout occurs' do
|
||||||
before(:each) { expect(container).to receive(:attach).and_raise(Timeout::Error) }
|
before(:each) { expect(container).to receive(:exec).and_raise(Timeout::Error) }
|
||||||
|
|
||||||
it 'destroys the container asynchronously' do
|
it 'destroys the container asynchronously' do
|
||||||
|
pending("Container is destroyed, but not as expected in this test. ToDo update this test.")
|
||||||
expect(Concurrent::Future).to receive(:execute)
|
expect(Concurrent::Future).to receive(:execute)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -366,6 +370,7 @@ describe DockerClient, docker: true do
|
|||||||
|
|
||||||
context 'when the container terminates timely' do
|
context 'when the container terminates timely' do
|
||||||
it 'destroys the container asynchronously' do
|
it 'destroys the container asynchronously' do
|
||||||
|
pending("Container is destroyed, but not as expected in this test. ToDo update this test.")
|
||||||
expect(Concurrent::Future).to receive(:execute)
|
expect(Concurrent::Future).to receive(:execute)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user