coursemology-evaluator 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env +3 -3
- data/.gitignore +23 -22
- data/.hound.yml +8 -8
- data/.idea/Coursemology Evaluator.iml +8 -8
- data/.rspec +2 -2
- data/.rubocop.unhound.yml +109 -109
- data/.rubocop.yml +46 -46
- data/.travis.yml +17 -17
- data/Gemfile +4 -4
- data/Procfile +1 -1
- data/README.md +29 -29
- data/Rakefile +6 -6
- data/bin/evaluator +5 -5
- data/coursemology-evaluator.gemspec +37 -37
- data/lib/coursemology/evaluator.rb +36 -36
- data/lib/coursemology/evaluator/cli.rb +52 -52
- data/lib/coursemology/evaluator/client.rb +81 -75
- data/lib/coursemology/evaluator/docker_container.rb +59 -59
- data/lib/coursemology/evaluator/logging.rb +12 -12
- data/lib/coursemology/evaluator/logging/client_log_subscriber.rb +25 -25
- data/lib/coursemology/evaluator/logging/docker_log_subscriber.rb +18 -18
- data/lib/coursemology/evaluator/models.rb +7 -7
- data/lib/coursemology/evaluator/models/base.rb +50 -50
- data/lib/coursemology/evaluator/models/programming_evaluation.rb +55 -67
- data/lib/coursemology/evaluator/models/programming_evaluation/package.rb +12 -12
- data/lib/coursemology/evaluator/services.rb +6 -6
- data/lib/coursemology/evaluator/services/evaluate_programming_package_service.rb +151 -151
- data/lib/coursemology/evaluator/string_io.rb +14 -14
- data/lib/coursemology/evaluator/utils.rb +42 -42
- data/lib/coursemology/evaluator/version.rb +5 -5
- data/lib/coursemology/polyglot/extensions.rb +3 -3
- data/lib/coursemology/polyglot/extensions/language.rb +24 -24
- metadata +15 -4
- data/Gemfile.lock +0 -114
@@ -1,59 +1,59 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::DockerContainer < Docker::Container
|
3
|
-
class << self
|
4
|
-
def create(image, argv: nil)
|
5
|
-
pull_image(image)
|
6
|
-
|
7
|
-
ActiveSupport::Notifications.instrument('create.docker.evaluator.coursemology',
|
8
|
-
image: image) do |payload|
|
9
|
-
options = { 'Image' => image }
|
10
|
-
options['Cmd'] = argv if argv && !argv.empty?
|
11
|
-
|
12
|
-
payload[:container] = super(options)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def pull_image(image)
|
19
|
-
ActiveSupport::Notifications.instrument('pull.docker.evaluator.coursemology',
|
20
|
-
image: image) do
|
21
|
-
Docker::Image.create('fromImage' => image)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Waits for the container to exit the Running state.
|
27
|
-
#
|
28
|
-
# This will time out for long running operations, so keep retrying until we return.
|
29
|
-
#
|
30
|
-
# @param [Fixnum|nil] time The amount of time to wait.
|
31
|
-
# @return [Fixnum] The exit code of the container.
|
32
|
-
def wait(time = nil)
|
33
|
-
container_state = info
|
34
|
-
while container_state.fetch('State', {}).fetch('Running', true)
|
35
|
-
super
|
36
|
-
refresh!
|
37
|
-
container_state = info
|
38
|
-
end
|
39
|
-
|
40
|
-
container_state['State']['ExitCode']
|
41
|
-
rescue Docker::Error::TimeoutError
|
42
|
-
retry
|
43
|
-
end
|
44
|
-
|
45
|
-
# Gets the exit code of the container.
|
46
|
-
#
|
47
|
-
# @return [Fixnum] The exit code of the container, if +wait+ was called before.
|
48
|
-
# @return [nil] If the container is still running, or +wait+ was not called.
|
49
|
-
def exit_code
|
50
|
-
info.fetch('State', {})['ExitCode']
|
51
|
-
end
|
52
|
-
|
53
|
-
def delete
|
54
|
-
ActiveSupport::Notifications.instrument('destroy.docker.evaluator.coursemology',
|
55
|
-
container: id) do
|
56
|
-
super
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::DockerContainer < Docker::Container
|
3
|
+
class << self
|
4
|
+
def create(image, argv: nil)
|
5
|
+
pull_image(image)
|
6
|
+
|
7
|
+
ActiveSupport::Notifications.instrument('create.docker.evaluator.coursemology',
|
8
|
+
image: image) do |payload|
|
9
|
+
options = { 'Image' => image }
|
10
|
+
options['Cmd'] = argv if argv && !argv.empty?
|
11
|
+
|
12
|
+
payload[:container] = super(options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def pull_image(image)
|
19
|
+
ActiveSupport::Notifications.instrument('pull.docker.evaluator.coursemology',
|
20
|
+
image: image) do
|
21
|
+
Docker::Image.create('fromImage' => image)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Waits for the container to exit the Running state.
|
27
|
+
#
|
28
|
+
# This will time out for long running operations, so keep retrying until we return.
|
29
|
+
#
|
30
|
+
# @param [Fixnum|nil] time The amount of time to wait.
|
31
|
+
# @return [Fixnum] The exit code of the container.
|
32
|
+
def wait(time = nil)
|
33
|
+
container_state = info
|
34
|
+
while container_state.fetch('State', {}).fetch('Running', true)
|
35
|
+
super
|
36
|
+
refresh!
|
37
|
+
container_state = info
|
38
|
+
end
|
39
|
+
|
40
|
+
container_state['State']['ExitCode']
|
41
|
+
rescue Docker::Error::TimeoutError
|
42
|
+
retry
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gets the exit code of the container.
|
46
|
+
#
|
47
|
+
# @return [Fixnum] The exit code of the container, if +wait+ was called before.
|
48
|
+
# @return [nil] If the container is still running, or +wait+ was not called.
|
49
|
+
def exit_code
|
50
|
+
info.fetch('State', {})['ExitCode']
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete
|
54
|
+
ActiveSupport::Notifications.instrument('destroy.docker.evaluator.coursemology',
|
55
|
+
container: id) do
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module Coursemology::Evaluator::Logging
|
3
|
-
extend ActiveSupport::Autoload
|
4
|
-
|
5
|
-
eager_autoload do
|
6
|
-
autoload :ClientLogSubscriber
|
7
|
-
autoload :DockerLogSubscriber
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# Define +Rails+ to trick ActiveSupport into logging to our logger.
|
12
|
-
Rails = Coursemology::Evaluator
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Coursemology::Evaluator::Logging
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
eager_autoload do
|
6
|
+
autoload :ClientLogSubscriber
|
7
|
+
autoload :DockerLogSubscriber
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Define +Rails+ to trick ActiveSupport into logging to our logger.
|
12
|
+
Rails = Coursemology::Evaluator
|
@@ -1,25 +1,25 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::Logging::ClientLogSubscriber < ActiveSupport::LogSubscriber
|
3
|
-
def publish(name, *args)
|
4
|
-
send(name.split('.').first, *args)
|
5
|
-
end
|
6
|
-
|
7
|
-
def allocate(event)
|
8
|
-
info color("Client: Allocate (#{event.duration.round(1)}ms)", MAGENTA)
|
9
|
-
end
|
10
|
-
|
11
|
-
def allocate_fail(e:)
|
12
|
-
error color("Client: Allocate failed: #{e.message}", RED)
|
13
|
-
end
|
14
|
-
|
15
|
-
def evaluate(event)
|
16
|
-
info "#{color("Client: Evaluate (#{event.duration.round(1)}ms)", CYAN)} "\
|
17
|
-
"#{event.payload[:evaluation].language.class.display_name}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def save(event)
|
21
|
-
info color("Client: Save (#{event.duration.round(1)}ms)", GREEN)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
Coursemology::Evaluator::Logging::ClientLogSubscriber.attach_to(:'client.evaluator.coursemology')
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::Logging::ClientLogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def publish(name, *args)
|
4
|
+
send(name.split('.').first, *args)
|
5
|
+
end
|
6
|
+
|
7
|
+
def allocate(event)
|
8
|
+
info color("Client: Allocate (#{event.duration.round(1)}ms)", MAGENTA)
|
9
|
+
end
|
10
|
+
|
11
|
+
def allocate_fail(e:)
|
12
|
+
error color("Client: Allocate failed: #{e.message}", RED)
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(event)
|
16
|
+
info "#{color("Client: Evaluate (#{event.duration.round(1)}ms)", CYAN)} "\
|
17
|
+
"#{event.payload[:evaluation].language.class.display_name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def save(event)
|
21
|
+
info color("Client: Save (#{event.duration.round(1)}ms)", GREEN)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Coursemology::Evaluator::Logging::ClientLogSubscriber.attach_to(:'client.evaluator.coursemology')
|
@@ -1,18 +1,18 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::Logging::DockerLogSubscriber < ActiveSupport::LogSubscriber
|
3
|
-
def pull(event)
|
4
|
-
info "#{color("Docker Pull (#{event.duration.round(1)}ms)", GREEN)} #{event.payload[:image]}"
|
5
|
-
end
|
6
|
-
|
7
|
-
def create(event)
|
8
|
-
info "#{color("Docker Create (#{event.duration.round(1)}ms)", MAGENTA)} "\
|
9
|
-
"#{event.payload[:image]} => #{event.payload[:container].id}"
|
10
|
-
end
|
11
|
-
|
12
|
-
def destroy(event)
|
13
|
-
info "#{color("Docker Destroy (#{event.duration.round(1)}ms)", CYAN)} "\
|
14
|
-
"#{event.payload[:container]}"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
Coursemology::Evaluator::Logging::DockerLogSubscriber.attach_to(:'docker.evaluator.coursemology')
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::Logging::DockerLogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def pull(event)
|
4
|
+
info "#{color("Docker Pull (#{event.duration.round(1)}ms)", GREEN)} #{event.payload[:image]}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def create(event)
|
8
|
+
info "#{color("Docker Create (#{event.duration.round(1)}ms)", MAGENTA)} "\
|
9
|
+
"#{event.payload[:image]} => #{event.payload[:container].id}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy(event)
|
13
|
+
info "#{color("Docker Destroy (#{event.duration.round(1)}ms)", CYAN)} "\
|
14
|
+
"#{event.payload[:container]}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Coursemology::Evaluator::Logging::DockerLogSubscriber.attach_to(:'docker.evaluator.coursemology')
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module Coursemology::Evaluator::Models
|
3
|
-
extend ActiveSupport::Autoload
|
4
|
-
|
5
|
-
autoload :Base
|
6
|
-
autoload :ProgrammingEvaluation
|
7
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Coursemology::Evaluator::Models
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Base
|
6
|
+
autoload :ProgrammingEvaluation
|
7
|
+
end
|
@@ -1,50 +1,50 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::Models::Base < Flexirest::Base
|
3
|
-
class << self
|
4
|
-
attr_accessor :api_user_email
|
5
|
-
attr_accessor :api_token
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
Flexirest::Base.perform_caching = false
|
9
|
-
default_config = Flexirest::Base.faraday_config
|
10
|
-
Flexirest::Base.faraday_config do |faraday|
|
11
|
-
# +follow_redirects+ must be added before declaring the adapter. See faraday_middleware#32,
|
12
|
-
# last comment.
|
13
|
-
faraday.response :follow_redirects
|
14
|
-
|
15
|
-
default_config.call(faraday)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
verbose!
|
21
|
-
before_request :add_authentication
|
22
|
-
|
23
|
-
# Sets the key of the model. This is the key that all attributes are nested under, the same as
|
24
|
-
# the +require+ directive in the controller of the web application.
|
25
|
-
#
|
26
|
-
# @param [String] key The key to prefix all attributes with.
|
27
|
-
def self.model_key(key)
|
28
|
-
before_request do |name, param|
|
29
|
-
fix_put_parameters(key, name, param) if [:post, :patch, :put].include?(param.method[:method])
|
30
|
-
end
|
31
|
-
end
|
32
|
-
private_class_method :model_key
|
33
|
-
|
34
|
-
# Fixes the request parameters when executing a POST, PATCH or PUT.
|
35
|
-
#
|
36
|
-
# @param [String] key The key to prefix all attributes with.
|
37
|
-
# @param [Request] param The request parameter to prepend the key with.
|
38
|
-
def self.fix_put_parameters(key, _, param)
|
39
|
-
param.post_params = { key => param.post_params } unless param.post_params.empty?
|
40
|
-
end
|
41
|
-
private_class_method :fix_put_parameters
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
# Adds the authentication email and token to the request.
|
46
|
-
def add_authentication(_, request)
|
47
|
-
request.headers['X-User-Email'] = self.class.api_user_email
|
48
|
-
request.headers['X-User-Token'] = self.class.api_token
|
49
|
-
end
|
50
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::Models::Base < Flexirest::Base
|
3
|
+
class << self
|
4
|
+
attr_accessor :api_user_email
|
5
|
+
attr_accessor :api_token
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
Flexirest::Base.perform_caching = false
|
9
|
+
default_config = Flexirest::Base.faraday_config
|
10
|
+
Flexirest::Base.faraday_config do |faraday|
|
11
|
+
# +follow_redirects+ must be added before declaring the adapter. See faraday_middleware#32,
|
12
|
+
# last comment.
|
13
|
+
faraday.response :follow_redirects
|
14
|
+
|
15
|
+
default_config.call(faraday)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
verbose!
|
21
|
+
before_request :add_authentication
|
22
|
+
|
23
|
+
# Sets the key of the model. This is the key that all attributes are nested under, the same as
|
24
|
+
# the +require+ directive in the controller of the web application.
|
25
|
+
#
|
26
|
+
# @param [String] key The key to prefix all attributes with.
|
27
|
+
def self.model_key(key)
|
28
|
+
before_request do |name, param|
|
29
|
+
fix_put_parameters(key, name, param) if [:post, :patch, :put].include?(param.method[:method])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
private_class_method :model_key
|
33
|
+
|
34
|
+
# Fixes the request parameters when executing a POST, PATCH or PUT.
|
35
|
+
#
|
36
|
+
# @param [String] key The key to prefix all attributes with.
|
37
|
+
# @param [Request] param The request parameter to prepend the key with.
|
38
|
+
def self.fix_put_parameters(key, _, param)
|
39
|
+
param.post_params = { key => param.post_params } unless param.post_params.empty?
|
40
|
+
end
|
41
|
+
private_class_method :fix_put_parameters
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Adds the authentication email and token to the request.
|
46
|
+
def add_authentication(_, request)
|
47
|
+
request.headers['X-User-Email'] = self.class.api_user_email
|
48
|
+
request.headers['X-User-Token'] = self.class.api_token
|
49
|
+
end
|
50
|
+
end
|
@@ -1,67 +1,55 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::Models::ProgrammingEvaluation < Coursemology::Evaluator::Models::Base
|
3
|
-
extend ActiveSupport::Autoload
|
4
|
-
autoload :Package
|
5
|
-
|
6
|
-
request_body_type :json
|
7
|
-
model_key :programming_evaluation
|
8
|
-
|
9
|
-
get :find, 'courses/assessment/programming_evaluations/:id'.freeze
|
10
|
-
post :allocate, 'courses/assessment/programming_evaluations/allocate'.freeze
|
11
|
-
put :save, 'courses/assessment/programming_evaluations/:id/result'.freeze
|
12
|
-
|
13
|
-
# Gets the language for the programming evaluation.
|
14
|
-
#
|
15
|
-
# @return [nil] If the language does not exist.
|
16
|
-
# @return [Coursemology::Polyglot::Language] The language that the evaluation uses.
|
17
|
-
def language
|
18
|
-
Coursemology::Polyglot::Language.find_by(type: super)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Sets the language for the programming evaluation.
|
22
|
-
#
|
23
|
-
# @param [String|nil|Coursemology::Polyglot::Language] language The language to set. If this is
|
24
|
-
# a string, it is assumed to be the class name of the language.
|
25
|
-
def language=(language)
|
26
|
-
return super(language) if language.nil? || language.is_a?(String)
|
27
|
-
|
28
|
-
fail ArgumentError unless language.is_a?(Coursemology::Polyglot::Language)
|
29
|
-
super(language.class.name)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Obtains the package.
|
33
|
-
#
|
34
|
-
# @return [Coursemology::Evaluator::Models::ProgrammingEvaluation::Package]
|
35
|
-
def package
|
36
|
-
@package ||= begin
|
37
|
-
body =
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
self.
|
51
|
-
self.
|
52
|
-
self.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# Performs a plain request.
|
58
|
-
#
|
59
|
-
# @param [String] url The URL to request.
|
60
|
-
# @param [Hash] params The parameter to be part of the request.
|
61
|
-
# @return [String] The response body.
|
62
|
-
def plain_request(url, params = {})
|
63
|
-
request = Flexirest::Request.new({ url: url, method: :get, options: { plain: true } },
|
64
|
-
self.class)
|
65
|
-
request.call(params)
|
66
|
-
end
|
67
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::Models::ProgrammingEvaluation < Coursemology::Evaluator::Models::Base
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
autoload :Package
|
5
|
+
|
6
|
+
request_body_type :json
|
7
|
+
model_key :programming_evaluation
|
8
|
+
|
9
|
+
get :find, 'courses/assessment/programming_evaluations/:id'.freeze
|
10
|
+
post :allocate, 'courses/assessment/programming_evaluations/allocate'.freeze
|
11
|
+
put :save, 'courses/assessment/programming_evaluations/:id/result'.freeze
|
12
|
+
|
13
|
+
# Gets the language for the programming evaluation.
|
14
|
+
#
|
15
|
+
# @return [nil] If the language does not exist.
|
16
|
+
# @return [Coursemology::Polyglot::Language] The language that the evaluation uses.
|
17
|
+
def language
|
18
|
+
Coursemology::Polyglot::Language.find_by(type: super)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets the language for the programming evaluation.
|
22
|
+
#
|
23
|
+
# @param [String|nil|Coursemology::Polyglot::Language] language The language to set. If this is
|
24
|
+
# a string, it is assumed to be the class name of the language.
|
25
|
+
def language=(language)
|
26
|
+
return super(language) if language.nil? || language.is_a?(String)
|
27
|
+
|
28
|
+
fail ArgumentError unless language.is_a?(Coursemology::Polyglot::Language)
|
29
|
+
super(language.class.name)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Obtains the package.
|
33
|
+
#
|
34
|
+
# @return [Coursemology::Evaluator::Models::ProgrammingEvaluation::Package]
|
35
|
+
def package
|
36
|
+
@package ||= begin
|
37
|
+
body = self.class._plain_request('courses/assessment/programming_evaluations/:id/package',
|
38
|
+
:get, id: id)
|
39
|
+
Package.new(Coursemology::Evaluator::StringIO.new(body))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Evaluates the package, and stores the result in this record.
|
44
|
+
#
|
45
|
+
# Call {Coursemology::Evaluator::Models::ProgrammingEvaluation#save} to save the record to the
|
46
|
+
# server.
|
47
|
+
def evaluate
|
48
|
+
result = Coursemology::Evaluator::Services::EvaluateProgrammingPackageService.
|
49
|
+
execute(self)
|
50
|
+
self.stdout = result.stdout
|
51
|
+
self.stderr = result.stderr
|
52
|
+
self.test_report = result.test_report
|
53
|
+
self.exit_code = result.exit_code
|
54
|
+
end
|
55
|
+
end
|