coursemology-evaluator 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|