Merge remote-tracking branch 'origin/master' into statistics
This commit is contained in:
@@ -50,6 +50,7 @@ GEM
|
|||||||
bootstrap-will_paginate (0.0.10)
|
bootstrap-will_paginate (0.0.10)
|
||||||
will_paginate
|
will_paginate
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
|
byebug (6.0.2)
|
||||||
capistrano (3.3.5)
|
capistrano (3.3.5)
|
||||||
capistrano-stats (~> 1.1.0)
|
capistrano-stats (~> 1.1.0)
|
||||||
i18n
|
i18n
|
||||||
@@ -108,6 +109,7 @@ GEM
|
|||||||
json
|
json
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
eventmachine (1.0.8)
|
eventmachine (1.0.8)
|
||||||
|
eventmachine (1.0.8-java)
|
||||||
excon (0.45.2)
|
excon (0.45.2)
|
||||||
execjs (2.5.2)
|
execjs (2.5.2)
|
||||||
factory_girl (4.5.0)
|
factory_girl (4.5.0)
|
||||||
@@ -321,6 +323,8 @@ GEM
|
|||||||
websocket (1.2.1)
|
websocket (1.2.1)
|
||||||
websocket-driver (0.6.2)
|
websocket-driver (0.6.2)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
|
websocket-driver (0.6.2-java)
|
||||||
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.2)
|
websocket-extensions (0.1.2)
|
||||||
will_paginate (3.0.7)
|
will_paginate (3.0.7)
|
||||||
xpath (2.0.0)
|
xpath (2.0.0)
|
||||||
@@ -337,6 +341,7 @@ DEPENDENCIES
|
|||||||
better_errors
|
better_errors
|
||||||
binding_of_caller
|
binding_of_caller
|
||||||
bootstrap-will_paginate
|
bootstrap-will_paginate
|
||||||
|
byebug
|
||||||
capistrano (~> 3.3.0)
|
capistrano (~> 3.3.0)
|
||||||
capistrano-rails
|
capistrano-rails
|
||||||
capistrano-rvm
|
capistrano-rvm
|
||||||
@@ -387,3 +392,6 @@ DEPENDENCIES
|
|||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
web-console (~> 2.0)
|
web-console (~> 2.0)
|
||||||
will_paginate (~> 3.0)
|
will_paginate (~> 3.0)
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.10.6
|
||||||
|
@@ -28,15 +28,16 @@ Execute: vagrant box add ubuntu/trusty64
|
|||||||
Execute: vagrant up
|
Execute: vagrant up
|
||||||
Install docker environments
|
Install docker environments
|
||||||
export DOCKER_HOST=tcp://192.168.23.75:2375
|
export DOCKER_HOST=tcp://192.168.23.75:2375
|
||||||
|
docker pull openhpi/docker_java
|
||||||
|
docker pull openhpi/docker_ruby
|
||||||
|
docker pull openhpi/docker_python
|
||||||
|
|
||||||
|
(The following images need to be moved to openhpi/docker_[coffee|sqlite|etc.] if they are required at some point.
|
||||||
docker pull jprberlin/ubuntu-coffee
|
docker pull jprberlin/ubuntu-coffee
|
||||||
docker pull jprberlin/ubuntu-java
|
|
||||||
docker pull jprberlin/ubuntu-sqlite
|
docker pull jprberlin/ubuntu-sqlite
|
||||||
docker pull jprberlin/ubuntu-sinatra
|
docker pull jprberlin/ubuntu-sinatra
|
||||||
docker pull jprberlin/ubuntu-ruby
|
|
||||||
docker pull jprberlin/ubuntu-python
|
|
||||||
docker pull jprberlin/ubuntu-node
|
|
||||||
docker pull jprberlin/ubuntu-html
|
docker pull jprberlin/ubuntu-html
|
||||||
docker pull jprberlin/ubuntu-jruby
|
docker pull jprberlin/ubuntu-jruby)
|
||||||
|
|
||||||
cd repoPath
|
cd repoPath
|
||||||
bundle install
|
bundle install
|
||||||
|
@@ -30,7 +30,7 @@ $(function() {
|
|||||||
numMessages = 0,
|
numMessages = 0,
|
||||||
turtlecanvas = $('#turtlecanvas'),
|
turtlecanvas = $('#turtlecanvas'),
|
||||||
prompt = $('#prompt'),
|
prompt = $('#prompt'),
|
||||||
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'status'],
|
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'timeout', 'status'],
|
||||||
streams = ['stdin', 'stdout', 'stderr'];
|
streams = ['stdin', 'stdout', 'stderr'];
|
||||||
|
|
||||||
var ENTER_KEY_CODE = 13;
|
var ENTER_KEY_CODE = 13;
|
||||||
@@ -686,8 +686,8 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var isBrowserSupported = function() {
|
var isBrowserSupported = function() {
|
||||||
// todo event streams are no longer required with websockets
|
// eventsource tests for server send events (used for scoring), websockets is used for run
|
||||||
return window.EventSource !== undefined;
|
return Modernizr.eventsource && Modernizr.websockets;
|
||||||
};
|
};
|
||||||
|
|
||||||
var populatePanel = function(panel, result, index) {
|
var populatePanel = function(panel, result, index) {
|
||||||
@@ -1004,7 +1004,7 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var showTimeoutMessage = function() {
|
var showTimeoutMessage = function() {
|
||||||
$.flash.danger({
|
$.flash.info({
|
||||||
icon: ['fa', 'fa-clock-o'],
|
icon: ['fa', 'fa-clock-o'],
|
||||||
text: $('#editor').data('message-timeout')
|
text: $('#editor').data('message-timeout')
|
||||||
});
|
});
|
||||||
@@ -1059,14 +1059,6 @@ $(function() {
|
|||||||
running = false;
|
running = false;
|
||||||
toggleButtonStates();
|
toggleButtonStates();
|
||||||
hidePrompt();
|
hidePrompt();
|
||||||
flashKillMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
var flashKillMessage = function() {
|
|
||||||
$.flash.info({
|
|
||||||
icon: ['fa', 'fa-clock-o'],
|
|
||||||
text: "Your program was stopped." // todo get data attribute
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo set this from websocket command, required to e.g. stop container
|
// todo set this from websocket command, required to e.g. stop container
|
||||||
@@ -1125,7 +1117,7 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var initWebsocketConnection = function(url) {
|
var initWebsocketConnection = function(url) {
|
||||||
websocket = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + url);
|
websocket = new WebSocket('wss://' + window.location.hostname + ':' + window.location.port + url);
|
||||||
websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection
|
websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection
|
||||||
websocket.onclose = function(evt) { /* expected at some point */ };
|
websocket.onclose = function(evt) { /* expected at some point */ };
|
||||||
websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); };
|
websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); };
|
||||||
@@ -1160,7 +1152,7 @@ $(function() {
|
|||||||
}
|
}
|
||||||
switch(msg.cmd) {
|
switch(msg.cmd) {
|
||||||
case 'input':
|
case 'input':
|
||||||
showPrompt();
|
showPrompt(msg);
|
||||||
break;
|
break;
|
||||||
case 'write':
|
case 'write':
|
||||||
printWebsocketOutput(msg);
|
printWebsocketOutput(msg);
|
||||||
@@ -1176,6 +1168,10 @@ $(function() {
|
|||||||
case 'exit':
|
case 'exit':
|
||||||
killWebsocketAndContainer();
|
killWebsocketAndContainer();
|
||||||
break;
|
break;
|
||||||
|
case 'timeout':
|
||||||
|
// just show the timeout message here. Another exit command is sent by the rails backend when the socket to the docker container closes.
|
||||||
|
showTimeoutMessage();
|
||||||
|
break;
|
||||||
case 'status':
|
case 'status':
|
||||||
showStatus(msg)
|
showStatus(msg)
|
||||||
break;
|
break;
|
||||||
@@ -1249,11 +1245,13 @@ $(function() {
|
|||||||
executeWebsocketCommand(msg);
|
executeWebsocketCommand(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
var showPrompt = function() {
|
var showPrompt = function(msg) {
|
||||||
if (prompt.isPresent() && prompt.hasClass('hidden')) {
|
var label = $('#prompt .input-group-addon');
|
||||||
|
label.text(msg.data || label.data('prompt'));
|
||||||
|
if (prompt.isPresent() && prompt.hasClass('hidden')) {
|
||||||
prompt.removeClass('hidden');
|
prompt.removeClass('hidden');
|
||||||
}
|
}
|
||||||
prompt.focus();
|
$('#prompt input').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hidePrompt = function() {
|
var hidePrompt = function() {
|
||||||
|
3
app/assets/javascripts/modernizr-custom.js
Normal file
3
app/assets/javascripts/modernizr-custom.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/*! modernizr 3.1.0 (Custom Build) | MIT *
|
||||||
|
* http://modernizr.com/download/?-eventsource-websockets !*/
|
||||||
|
!function(e,n,s){function o(e,n){return typeof e===n}function a(e){var n=l.className,s=Modernizr._config.classPrefix||"";if(f&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+s+"no-js(\\s|$)");n=n.replace(o,"$1"+s+"js$2")}Modernizr._config.enableClasses&&(n+=" "+s+e.join(" "+s),f?l.className.baseVal=n:l.className=n)}function t(){var e,n,s,a,t,l,f;for(var r in c){if(e=[],n=c[r],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(s=0;s<n.options.aliases.length;s++)e.push(n.options.aliases[s].toLowerCase());for(a=o(n.fn,"function")?n.fn():n.fn,t=0;t<e.length;t++)l=e[t],f=l.split("."),1===f.length?Modernizr[f[0]]=a:(!Modernizr[f[0]]||Modernizr[f[0]]instanceof Boolean||(Modernizr[f[0]]=new Boolean(Modernizr[f[0]])),Modernizr[f[0]][f[1]]=a),i.push((a?"":"no-")+f.join("-"))}}var i=[],l=n.documentElement,f="svg"===l.nodeName.toLowerCase(),c=[],r={_version:"3.1.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var s=this;setTimeout(function(){n(s[e])},0)},addTest:function(e,n,s){c.push({name:e,fn:n,options:s})},addAsyncTest:function(e){c.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=r,Modernizr=new Modernizr,Modernizr.addTest("websockets","WebSocket"in e&&2===e.WebSocket.CLOSING),Modernizr.addTest("eventsource","EventSource"in e),t(),a(i),delete r.addTest,delete r.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();e.Modernizr=Modernizr}(window,document);
|
@@ -90,6 +90,12 @@ class SubmissionsController < ApplicationController
|
|||||||
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?
|
||||||
|
|
||||||
|
|
||||||
|
# socket is the socket into the container, tubesock is the socket to the client
|
||||||
|
|
||||||
|
# give the docker_client the tubesock object, so that it can send messages (timeout)
|
||||||
|
@docker_client.tubesock = tubesock
|
||||||
|
|
||||||
result = @docker_client.execute_run_command(@submission, params[:filename])
|
result = @docker_client.execute_run_command(@submission, params[:filename])
|
||||||
tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]})
|
tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]})
|
||||||
|
|
||||||
|
@@ -3,6 +3,6 @@ class Comment < ActiveRecord::Base
|
|||||||
include Creation
|
include Creation
|
||||||
attr_accessor :username
|
attr_accessor :username
|
||||||
|
|
||||||
belongs_to :file, class: CodeOcean::File
|
belongs_to :file, class_name: 'CodeOcean::File'
|
||||||
belongs_to :user, polymorphic: true
|
belongs_to :user, polymorphic: true
|
||||||
end
|
end
|
||||||
|
@@ -2,7 +2,7 @@ module Context
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
has_many :files, as: :context, class: CodeOcean::File
|
has_many :files, as: :context, class_name: 'CodeOcean::File'
|
||||||
accepts_nested_attributes_for :files
|
accepts_nested_attributes_for :files
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
class RequestForComment < ActiveRecord::Base
|
class RequestForComment < ActiveRecord::Base
|
||||||
belongs_to :exercise
|
belongs_to :exercise
|
||||||
belongs_to :file, class: CodeOcean::File
|
belongs_to :file, class_name: 'CodeOcean::File'
|
||||||
belongs_to :user, polymorphic: true
|
belongs_to :user, polymorphic: true
|
||||||
|
|
||||||
before_create :set_requested_timestamp
|
before_create :set_requested_timestamp
|
||||||
|
@@ -39,7 +39,7 @@
|
|||||||
#output-col1
|
#output-col1
|
||||||
// todo set to full width if turtle isnt used
|
// todo set to full width if turtle isnt used
|
||||||
#prompt.input-group.hidden
|
#prompt.input-group.hidden
|
||||||
span.input-group-addon = t('exercises.editor.input')
|
span.input-group-addon data-prompt=t('exercises.editor.input') = t('exercises.editor.input')
|
||||||
input#prompt-input.form-control type='text'
|
input#prompt-input.form-control type='text'
|
||||||
span.input-group-btn
|
span.input-group-btn
|
||||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||||
|
@@ -22,6 +22,7 @@ production:
|
|||||||
interval: 30
|
interval: 30
|
||||||
timeout: 60
|
timeout: 60
|
||||||
workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %>
|
workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %>
|
||||||
|
ws_host: ws://localhost:4243
|
||||||
|
|
||||||
test:
|
test:
|
||||||
<<: *default
|
<<: *default
|
||||||
|
@@ -191,7 +191,7 @@ de:
|
|||||||
stop: Stoppen
|
stop: Stoppen
|
||||||
submit: Code zur Bewertung abgeben
|
submit: Code zur Bewertung abgeben
|
||||||
test: Testen
|
test: Testen
|
||||||
timeout: 'Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.'
|
timeout: 'Ausführung gestoppt. Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.'
|
||||||
tooltips:
|
tooltips:
|
||||||
save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig.
|
save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig.
|
||||||
editor_file_tree:
|
editor_file_tree:
|
||||||
|
@@ -191,7 +191,7 @@ en:
|
|||||||
stop: Stop
|
stop: Stop
|
||||||
submit: Submit Code For Assessment
|
submit: Submit Code For Assessment
|
||||||
test: Test
|
test: Test
|
||||||
timeout: 'Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.'
|
timeout: 'Execution stopped. Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.'
|
||||||
tooltips:
|
tooltips:
|
||||||
save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary.
|
save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary.
|
||||||
editor_file_tree:
|
editor_file_tree:
|
||||||
|
@@ -12,6 +12,7 @@ class DockerClient
|
|||||||
|
|
||||||
attr_reader :container
|
attr_reader :container
|
||||||
attr_reader :socket
|
attr_reader :socket
|
||||||
|
attr_accessor :tubesock
|
||||||
|
|
||||||
def self.check_availability!
|
def self.check_availability!
|
||||||
Timeout.timeout(config[:connection_timeout]) { Docker.version }
|
Timeout.timeout(config[:connection_timeout]) { Docker.version }
|
||||||
@@ -61,7 +62,7 @@ class DockerClient
|
|||||||
def create_socket(container, stderr=false)
|
def create_socket(container, stderr=false)
|
||||||
# todo factor out query params
|
# todo factor out query params
|
||||||
# todo separate stderr
|
# todo separate stderr
|
||||||
query_params = 'logs=1&stream=1&' + (stderr ? 'stderr=1' : 'stdout=1&stdin=1')
|
query_params = 'logs=0&stream=1&' + (stderr ? 'stderr=1' : 'stdout=1&stdin=1')
|
||||||
|
|
||||||
# Headers are required by Docker
|
# Headers are required by Docker
|
||||||
headers = {'Origin' => 'http://localhost'}
|
headers = {'Origin' => 'http://localhost'}
|
||||||
@@ -138,8 +139,8 @@ class DockerClient
|
|||||||
def execute_command(command, before_execution_block, output_consuming_block)
|
def execute_command(command, before_execution_block, output_consuming_block)
|
||||||
#tries ||= 0
|
#tries ||= 0
|
||||||
@container = DockerContainerPool.get_container(@execution_environment)
|
@container = DockerContainerPool.get_container(@execution_environment)
|
||||||
@container.status = :executing
|
|
||||||
if @container
|
if @container
|
||||||
|
@container.status = :executing
|
||||||
before_execution_block.try(:call)
|
before_execution_block.try(:call)
|
||||||
send_command(command, @container, &output_consuming_block)
|
send_command(command, @container, &output_consuming_block)
|
||||||
else
|
else
|
||||||
@@ -153,8 +154,8 @@ class DockerClient
|
|||||||
|
|
||||||
def execute_websocket_command(command, before_execution_block, output_consuming_block)
|
def execute_websocket_command(command, before_execution_block, output_consuming_block)
|
||||||
@container = DockerContainerPool.get_container(@execution_environment)
|
@container = DockerContainerPool.get_container(@execution_environment)
|
||||||
@container.status = :executing
|
|
||||||
if @container
|
if @container
|
||||||
|
@container.status = :executing
|
||||||
before_execution_block.try(:call)
|
before_execution_block.try(:call)
|
||||||
# todo catch exception if socket could not be created
|
# todo catch exception if socket could not be created
|
||||||
@socket ||= create_socket(@container)
|
@socket ||= create_socket(@container)
|
||||||
@@ -171,17 +172,25 @@ class DockerClient
|
|||||||
We need to start a second thread to kill the websocket connection,
|
We need to start a second thread to kill the websocket connection,
|
||||||
as it is impossible to determine whether further input is requested.
|
as it is impossible to determine whether further input is requested.
|
||||||
"""
|
"""
|
||||||
Thread.new do
|
@thread = Thread.new do
|
||||||
timeout = @execution_environment.permitted_execution_time.to_i # seconds
|
timeout = @execution_environment.permitted_execution_time.to_i # seconds
|
||||||
sleep(timeout)
|
sleep(timeout)
|
||||||
if container.status != :returned
|
if container.status != :returned
|
||||||
Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.")
|
Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.")
|
||||||
|
# send timeout to the tubesock socket
|
||||||
|
if(@tubesock)
|
||||||
|
@tubesock.send_data JSON.dump({'cmd' => 'timeout'})
|
||||||
|
end
|
||||||
kill_container(container)
|
kill_container(container)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def exit_container(container)
|
def exit_container(container)
|
||||||
|
# exit the timeout thread if it is still alive
|
||||||
|
if(@thread && @thread.alive?)
|
||||||
|
@thread.exit
|
||||||
|
end
|
||||||
# if we use pooling and recylce the containers, put it back. otherwise, destroy it.
|
# 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)
|
(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container)
|
||||||
end
|
end
|
||||||
|
13
provision.sh
13
provision.sh
@@ -29,15 +29,10 @@ DOCKER_OPTS="-H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock"
|
|||||||
EOF
|
EOF
|
||||||
service docker restart
|
service docker restart
|
||||||
fi
|
fi
|
||||||
# XXX docker pull jprberlin/ubuntu-coffee
|
|
||||||
docker pull jprberlin/ubuntu-java
|
docker pull openhpi/docker_java
|
||||||
# XXX docker pull jprberlin/ubuntu-sqlite
|
docker pull openhpi/docker_ruby
|
||||||
# XXX docker pull jprberlin/ubuntu-sinatra
|
docker pull openhpi/docker_python
|
||||||
# XXX docker pull jprberlin/ubuntu-ruby
|
|
||||||
# XXX docker pull jprberlin/ubuntu-python
|
|
||||||
docker pull jprberlin/ubuntu-node
|
|
||||||
# XXX docker pull jprberlin/ubuntu-html
|
|
||||||
# XXX docker pull jprberlin/ubuntu-jruby
|
|
||||||
|
|
||||||
|
|
||||||
# rvm
|
# rvm
|
||||||
|
@@ -1,234 +1 @@
|
|||||||
import webpython, contextlib, io, json, sys, turtle, ast
|
# moved to dockerfiles/ubuntu-python
|
||||||
from webpython import shell
|
|
||||||
|
|
||||||
turtle_operations = []
|
|
||||||
bindings = {}
|
|
||||||
|
|
||||||
class RecordingPen:
|
|
||||||
_pen = None
|
|
||||||
_screen = None
|
|
||||||
def __init__(self):
|
|
||||||
self.operations = turtle_operations
|
|
||||||
self._pos = (0,0)
|
|
||||||
turtle_operations.append(('__init__',()))
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
turtle_operations.clear()
|
|
||||||
|
|
||||||
def onclick(self, fun, btn=1, add=None):
|
|
||||||
self.operations.append(('onclick', (fun,)))
|
|
||||||
def eventfun(event):
|
|
||||||
fun(event.x, event.y)
|
|
||||||
bindings['<Button-1>'] = eventfun
|
|
||||||
|
|
||||||
def goto(self, x, y):
|
|
||||||
self._pos = (x,y)
|
|
||||||
self.operations.append(('goto', (x,y)))
|
|
||||||
|
|
||||||
def pos(self):
|
|
||||||
self.operations.append(('pos', ()))
|
|
||||||
return self._pos
|
|
||||||
|
|
||||||
def __getattr__(self, method):
|
|
||||||
def func(*args):
|
|
||||||
self.operations.append((method, args))
|
|
||||||
return func
|
|
||||||
|
|
||||||
class FakeCanvas(turtle.WebCanvas):
|
|
||||||
def flushbatch(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_width(self):
|
|
||||||
return 400
|
|
||||||
|
|
||||||
def get_height(self):
|
|
||||||
return 400
|
|
||||||
|
|
||||||
def delete(self, item):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def css(self, key, value):
|
|
||||||
pass
|
|
||||||
|
|
||||||
fake_events = []
|
|
||||||
def mainloop():
|
|
||||||
while fake_events:
|
|
||||||
e = turtle.Event(fake_events.pop(0))
|
|
||||||
if e.type in bindings:
|
|
||||||
bindings[e.type](e)
|
|
||||||
|
|
||||||
turtle.Turtle = RecordingPen
|
|
||||||
turtle.WebCanvas = FakeCanvas
|
|
||||||
pen = turtle._getpen()
|
|
||||||
turtle.mainloop = mainloop
|
|
||||||
|
|
||||||
def filter_operations(name):
|
|
||||||
return [o for o in turtle_operations if o[0] == name]
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def capture():
|
|
||||||
global captured_out
|
|
||||||
import sys
|
|
||||||
oldout,olderr = sys.stdout, sys.stderr
|
|
||||||
try:
|
|
||||||
out=[io.StringIO(), io.StringIO()]
|
|
||||||
captured_out = out
|
|
||||||
sys.stdout,sys.stderr = out
|
|
||||||
yield out
|
|
||||||
finally:
|
|
||||||
sys.stdout,sys.stderr = oldout, olderr
|
|
||||||
out[0] = out[0].getvalue()
|
|
||||||
out[1] = out[1].getvalue()
|
|
||||||
|
|
||||||
def get_source():
|
|
||||||
message = json.loads(sys.argv[1])
|
|
||||||
return message['data']
|
|
||||||
|
|
||||||
def get_ast():
|
|
||||||
s = get_source()
|
|
||||||
return ast.parse(s, "programm.py", "exec")
|
|
||||||
|
|
||||||
def has_bare_except():
|
|
||||||
for node in ast.walk(get_ast()):
|
|
||||||
if isinstance(node, ast.ExceptHandler):
|
|
||||||
if node.type is None:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def runcaptured(prefix='', tracing=None, variables=None, source=''):
|
|
||||||
#message = json.loads(sys.argv[1])
|
|
||||||
#source = prefix + message['data']
|
|
||||||
with open("programm.py", "w", encoding='utf-8') as f:
|
|
||||||
f.write(source)
|
|
||||||
c = compile(source, "programm.py", 'exec')
|
|
||||||
with capture() as out, trace(tracing):
|
|
||||||
if variables is None:
|
|
||||||
variables = {}
|
|
||||||
exec(c, variables)
|
|
||||||
return source, out[0], out[1], variables
|
|
||||||
|
|
||||||
def runfunc(func, *args, tracing=None):
|
|
||||||
with capture() as out, trace(tracing):
|
|
||||||
res = func(*args)
|
|
||||||
return out[0], out[1], res
|
|
||||||
|
|
||||||
def passed():
|
|
||||||
msg_in = json.loads(sys.argv[1])
|
|
||||||
msg_out = {'cmd':'passed'}
|
|
||||||
msg_out['lis_outcome_service_url'] = msg_in['lis_outcome_service_url']
|
|
||||||
msg_out['lis_result_sourcedid'] = msg_in['lis_result_sourcedid']
|
|
||||||
webpython.shell.sendpickle(msg_out)
|
|
||||||
|
|
||||||
def failed(msg):
|
|
||||||
msg_in = json.loads(sys.argv[1])
|
|
||||||
msg_out = {'cmd':'failed', 'data':'Dein Programm ist leider falsch:\n'+msg}
|
|
||||||
msg_out['lis_outcome_service_url'] = msg_in['lis_outcome_service_url']
|
|
||||||
msg_out['lis_result_sourcedid'] = msg_in['lis_result_sourcedid']
|
|
||||||
webpython.shell.sendpickle(msg_out)
|
|
||||||
|
|
||||||
def modified(variables, name, val):
|
|
||||||
if variables.get(name) != val:
|
|
||||||
msg_in = json.loads(sys.argv[1])
|
|
||||||
msg_out = {'cmd':'failed',
|
|
||||||
'data':('Bitte lösche Deine Zuweisung der Variable %s, '+
|
|
||||||
'damit wir Dein Programm überprüfen können.') % name}
|
|
||||||
msg_out['lis_outcome_service_url'] = msg_in['lis_outcome_service_url']
|
|
||||||
msg_out['lis_result_sourcedid'] = msg_in['lis_result_sourcedid']
|
|
||||||
webpython.shell.sendpickle(msg_out)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
undefined = object()
|
|
||||||
def getvar(variables, name):
|
|
||||||
try:
|
|
||||||
return variables['name']
|
|
||||||
except KeyError:
|
|
||||||
name = name.lower()
|
|
||||||
for k,v in variables.items():
|
|
||||||
if k.lower() == name:
|
|
||||||
return v
|
|
||||||
return undefined
|
|
||||||
|
|
||||||
def _match(n1, n2):
|
|
||||||
if n1 == n2:
|
|
||||||
return True
|
|
||||||
if n1 is None or n2 is None:
|
|
||||||
return False
|
|
||||||
return n1.lower() == n2.lower()
|
|
||||||
|
|
||||||
class Call:
|
|
||||||
def __init__(self, name, args):
|
|
||||||
self.name = name
|
|
||||||
self.args = args
|
|
||||||
self.calls = []
|
|
||||||
self.current = None
|
|
||||||
|
|
||||||
def findcall(self, f):
|
|
||||||
if _match(self.name, f):
|
|
||||||
return self
|
|
||||||
for c in self.calls:
|
|
||||||
r = c.findcall(f)
|
|
||||||
if r:
|
|
||||||
return r
|
|
||||||
return None
|
|
||||||
|
|
||||||
def calling(self, caller, callee):
|
|
||||||
if _match(self.name, caller):
|
|
||||||
for c in self.calls:
|
|
||||||
if _match(c.name, callee):
|
|
||||||
return True
|
|
||||||
for c in self.calls:
|
|
||||||
if c.calling(caller, callee):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def countcalls(self, caller, callee):
|
|
||||||
calls = 0
|
|
||||||
if _match(self.name, caller):
|
|
||||||
for c in self.calls:
|
|
||||||
if _match(c.name, callee):
|
|
||||||
calls += 1
|
|
||||||
return calls
|
|
||||||
for c in self.calls:
|
|
||||||
r = c.countcalls(caller, callee)
|
|
||||||
if r > 0:
|
|
||||||
return r
|
|
||||||
return 0
|
|
||||||
|
|
||||||
class Tracing(Call):
|
|
||||||
def __init__(self):
|
|
||||||
Call.__init__(self, None, None)
|
|
||||||
|
|
||||||
def trace(self, frame, event, arg):
|
|
||||||
if event == 'call':
|
|
||||||
c = Call(frame.f_code.co_name, frame.f_locals.copy())
|
|
||||||
cur = self
|
|
||||||
while cur.current:
|
|
||||||
cur = cur.current
|
|
||||||
cur.calls.append(c)
|
|
||||||
cur.current = c
|
|
||||||
return self.trace
|
|
||||||
elif event in ('return', 'exception'):
|
|
||||||
cur = self
|
|
||||||
if not cur.current:
|
|
||||||
# XXX return without call? happens when invocation of top function fails
|
|
||||||
return
|
|
||||||
while cur.current.current:
|
|
||||||
cur = cur.current
|
|
||||||
cur.current = None
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
sys.settrace(self.trace)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
sys.settrace(None)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def trace(t):
|
|
||||||
try:
|
|
||||||
if t:
|
|
||||||
t.start()
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
if t:
|
|
||||||
t.stop()
|
|
||||||
|
4079
webpython/turtle.py
4079
webpython/turtle.py
File diff suppressed because it is too large
Load Diff
@@ -1,178 +1 @@
|
|||||||
#!/usr/bin/python3
|
# moved to dockerfiles/ubuntu-python/webpython.py
|
||||||
# Main interpreter entry for webpython
|
|
||||||
import io, select, sys, os, threading, code
|
|
||||||
import pickle, struct, builtins, json
|
|
||||||
#, ressource
|
|
||||||
from queue import Queue
|
|
||||||
from argparse import ArgumentParser
|
|
||||||
|
|
||||||
# hard limit to 64M
|
|
||||||
#try:
|
|
||||||
# resource.setrlimit(resource.RLIMIT_AS, (1<<26, 1<<26))
|
|
||||||
#except ValueError:
|
|
||||||
# tried to raise it
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# output limit 16MiB
|
|
||||||
output_capacity = 16*1024*1024
|
|
||||||
# adapted from IDLE (PyShell.py)
|
|
||||||
class PseudoFile(io.TextIOBase):
|
|
||||||
|
|
||||||
def __init__(self, shell, name):
|
|
||||||
self.shell = shell
|
|
||||||
self._name = name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def encoding(self):
|
|
||||||
return "UTF-8"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return '<%s>' % self._name
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
class PseudoInputFile(PseudoFile):
|
|
||||||
|
|
||||||
def __init__(self, shell, name):
|
|
||||||
PseudoFile.__init__(self, shell, name)
|
|
||||||
self._line_buffer = ''
|
|
||||||
|
|
||||||
def readable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def read(self, size=-1):
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("read from closed file")
|
|
||||||
if size is None:
|
|
||||||
size = -1
|
|
||||||
elif not isinstance(size, int):
|
|
||||||
raise TypeError('must be int, not ' + type(size).__name__)
|
|
||||||
result = self._line_buffer
|
|
||||||
self._line_buffer = ''
|
|
||||||
if size < 0:
|
|
||||||
while True:
|
|
||||||
line = self.shell.readline()
|
|
||||||
if not line: break
|
|
||||||
result += line
|
|
||||||
else:
|
|
||||||
while len(result) < size:
|
|
||||||
line = self.shell.readline()
|
|
||||||
if not line: break
|
|
||||||
result += line
|
|
||||||
self._line_buffer = result[size:]
|
|
||||||
result = result[:size]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def readline(self, size=-1):
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("read from closed file")
|
|
||||||
if size is None:
|
|
||||||
size = -1
|
|
||||||
elif not isinstance(size, int):
|
|
||||||
raise TypeError('must be int, not ' + type(size).__name__)
|
|
||||||
line = self._line_buffer or self.shell.readline()
|
|
||||||
if size < 0:
|
|
||||||
size = len(line)
|
|
||||||
self._line_buffer = line[size:]
|
|
||||||
return line[:size]
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.shell.close()
|
|
||||||
|
|
||||||
class PseudoOutputFile(PseudoFile):
|
|
||||||
|
|
||||||
def writable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def write(self, s):
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("write to closed file")
|
|
||||||
if not isinstance(s, str):
|
|
||||||
raise TypeError('must be str, not ' + type(s).__name__)
|
|
||||||
return self.shell.write(s, self._name)
|
|
||||||
|
|
||||||
# RPC proxy
|
|
||||||
orig_stdin = sys.stdin
|
|
||||||
orig_stdout = sys.stdout
|
|
||||||
orig_stderr = sys.stderr
|
|
||||||
class Shell:
|
|
||||||
def __init__(self):
|
|
||||||
self.stdin = io.FileIO(0)
|
|
||||||
self.buf = b''
|
|
||||||
self.canvas = []
|
|
||||||
self.messages = []
|
|
||||||
self.capacity = output_capacity
|
|
||||||
|
|
||||||
# PseudoFile interaction
|
|
||||||
#def readline(self):
|
|
||||||
# self.sendpickle({'cmd':'readline',
|
|
||||||
# 'stream':'stdin',
|
|
||||||
# })
|
|
||||||
# return self.inputq.get()
|
|
||||||
|
|
||||||
def write(self, data, name):
|
|
||||||
self.sendpickle({'cmd':'write',
|
|
||||||
'stream':name,
|
|
||||||
'data':data
|
|
||||||
})
|
|
||||||
|
|
||||||
def input(self, prompt=''):
|
|
||||||
self.sendpickle({'cmd':'input',
|
|
||||||
'stream':'stdin',
|
|
||||||
'data':prompt})
|
|
||||||
result = self.receivemsg()
|
|
||||||
return result['data']
|
|
||||||
|
|
||||||
# internal
|
|
||||||
def sendpickle(self, data):
|
|
||||||
data = json.dumps(data) + "\n\r"
|
|
||||||
self.capacity -= len(data)
|
|
||||||
if self.capacity < 0:
|
|
||||||
data = json.dumps({'cmd':'stop',
|
|
||||||
'timedout':True}, 2)
|
|
||||||
orig_stdout.write(data)
|
|
||||||
raise SystemExit
|
|
||||||
orig_stdout.write(data)
|
|
||||||
|
|
||||||
def receivepickle(self):
|
|
||||||
msg = json.loads(orig_stdin.readline())
|
|
||||||
if msg['cmd'] == 'canvasevent':
|
|
||||||
self.canvas.append(msg)
|
|
||||||
else:
|
|
||||||
self.messages.append(msg)
|
|
||||||
|
|
||||||
def receivemsg(self):
|
|
||||||
while not self.messages:
|
|
||||||
self.receivepickle()
|
|
||||||
return self.messages.pop()
|
|
||||||
|
|
||||||
def receivecanvas(self):
|
|
||||||
while not self.canvas:
|
|
||||||
self.receivepickle()
|
|
||||||
return self.canvas.pop(0)
|
|
||||||
|
|
||||||
# Hide 0/1 from sys
|
|
||||||
shell = Shell()
|
|
||||||
sys.__stdin__ = sys.stdin = PseudoInputFile(shell, 'stdin')
|
|
||||||
sys.__stdout__ = sys.stdout = PseudoOutputFile(shell, 'stdout')
|
|
||||||
#sys.__stderr__ = sys.stderr = PseudoOutputFile(shell, 'stderr')
|
|
||||||
builtins.input = shell.input
|
|
||||||
|
|
||||||
#iothread = threading.Thread(target=shell.run)
|
|
||||||
#iothread.start()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = ArgumentParser(description='A python interpreter that generates json commands based on the standard I/O streams.')
|
|
||||||
parser.add_argument('-f', '--filename', type=str, required=True, default='exercise.py', help='Python file to be interpreted.')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
filepath = os.path.join("/", "workspace", args.filename)
|
|
||||||
with open(filepath, "r", encoding='utf-8') as f:
|
|
||||||
script = f.read()
|
|
||||||
c = compile(script, args.filename, 'exec')
|
|
||||||
exec(c, {})
|
|
||||||
|
|
||||||
# work-around for docker not terminating properly
|
|
||||||
shell.sendpickle({'cmd':'exit'})
|
|
||||||
|
Reference in New Issue
Block a user