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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.env +3 -3
  3. data/.gitignore +23 -22
  4. data/.hound.yml +8 -8
  5. data/.idea/Coursemology Evaluator.iml +8 -8
  6. data/.rspec +2 -2
  7. data/.rubocop.unhound.yml +109 -109
  8. data/.rubocop.yml +46 -46
  9. data/.travis.yml +17 -17
  10. data/Gemfile +4 -4
  11. data/Procfile +1 -1
  12. data/README.md +29 -29
  13. data/Rakefile +6 -6
  14. data/bin/evaluator +5 -5
  15. data/coursemology-evaluator.gemspec +37 -37
  16. data/lib/coursemology/evaluator.rb +36 -36
  17. data/lib/coursemology/evaluator/cli.rb +52 -52
  18. data/lib/coursemology/evaluator/client.rb +81 -75
  19. data/lib/coursemology/evaluator/docker_container.rb +59 -59
  20. data/lib/coursemology/evaluator/logging.rb +12 -12
  21. data/lib/coursemology/evaluator/logging/client_log_subscriber.rb +25 -25
  22. data/lib/coursemology/evaluator/logging/docker_log_subscriber.rb +18 -18
  23. data/lib/coursemology/evaluator/models.rb +7 -7
  24. data/lib/coursemology/evaluator/models/base.rb +50 -50
  25. data/lib/coursemology/evaluator/models/programming_evaluation.rb +55 -67
  26. data/lib/coursemology/evaluator/models/programming_evaluation/package.rb +12 -12
  27. data/lib/coursemology/evaluator/services.rb +6 -6
  28. data/lib/coursemology/evaluator/services/evaluate_programming_package_service.rb +151 -151
  29. data/lib/coursemology/evaluator/string_io.rb +14 -14
  30. data/lib/coursemology/evaluator/utils.rb +42 -42
  31. data/lib/coursemology/evaluator/version.rb +5 -5
  32. data/lib/coursemology/polyglot/extensions.rb +3 -3
  33. data/lib/coursemology/polyglot/extensions/language.rb +24 -24
  34. metadata +15 -4
  35. 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 = plain_request('courses/assessment/programming_evaluations/:id/package', id: id)
38
- Package.new(Coursemology::Evaluator::StringIO.new(body))
39
- end
40
- end
41
-
42
- # Evaluates the package, and stores the result in this record.
43
- #
44
- # Call {Coursemology::Evaluator::Models::ProgrammingEvaluation#save} to save the record to the
45
- # server.
46
- def evaluate
47
- result = Coursemology::Evaluator::Services::EvaluateProgrammingPackageService.
48
- execute(self)
49
- self.stdout = result.stdout
50
- self.stderr = result.stderr
51
- self.test_report = result.test_report
52
- self.exit_code = result.exit_code
53
- end
54
-
55
- private
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