coursemology-evaluator 0.1.6 → 0.1.7
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 -23
- 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 +52 -52
- data/Rakefile +6 -6
- data/bin/evaluator +5 -5
- data/coursemology-evaluator.gemspec +38 -38
- data/lib/coursemology/evaluator.rb +41 -41
- data/lib/coursemology/evaluator/cli.rb +74 -74
- data/lib/coursemology/evaluator/client.rb +84 -84
- data/lib/coursemology/evaluator/docker_container.rb +79 -79
- 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 +21 -21
- 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 -55
- 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 +3 -3
@@ -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,55 +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 = 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
|
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
|
@@ -1,12 +1,12 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::Models::ProgrammingEvaluation::Package
|
3
|
-
# The stream comprising the package.
|
4
|
-
attr_reader :stream
|
5
|
-
|
6
|
-
# Constructs a new Package.
|
7
|
-
#
|
8
|
-
# @param [IO] stream The stream comprising the package.
|
9
|
-
def initialize(stream)
|
10
|
-
@stream = stream
|
11
|
-
end
|
12
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::Models::ProgrammingEvaluation::Package
|
3
|
+
# The stream comprising the package.
|
4
|
+
attr_reader :stream
|
5
|
+
|
6
|
+
# Constructs a new Package.
|
7
|
+
#
|
8
|
+
# @param [IO] stream The stream comprising the package.
|
9
|
+
def initialize(stream)
|
10
|
+
@stream = stream
|
11
|
+
end
|
12
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module Coursemology::Evaluator::Services
|
3
|
-
extend ActiveSupport::Autoload
|
4
|
-
|
5
|
-
autoload :EvaluateProgrammingPackageService
|
6
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Coursemology::Evaluator::Services
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :EvaluateProgrammingPackageService
|
6
|
+
end
|
@@ -1,151 +1,151 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
class Coursemology::Evaluator::Services::EvaluateProgrammingPackageService
|
3
|
-
Result = Struct.new(:stdout, :stderr, :test_report, :exit_code)
|
4
|
-
|
5
|
-
# The path to the Coursemology user home directory.
|
6
|
-
HOME_PATH = '/home/coursemology'.freeze
|
7
|
-
|
8
|
-
# The path to where the package will be extracted.
|
9
|
-
PACKAGE_PATH = File.join(HOME_PATH, 'package')
|
10
|
-
|
11
|
-
# The path to where the test report will be at.
|
12
|
-
REPORT_PATH = File.join(PACKAGE_PATH, 'report.xml')
|
13
|
-
|
14
|
-
# The ratio to multiply the memory limits from our evaluation to the container by.
|
15
|
-
MEMORY_LIMIT_RATIO = 1.megabyte / 1.kilobyte
|
16
|
-
|
17
|
-
# Executes the given package in a container.
|
18
|
-
#
|
19
|
-
# @param [Coursemology::Evaluator::Models::ProgrammingEvaluation] evaluation The evaluation
|
20
|
-
# from the server.
|
21
|
-
# @return [Coursemology::Evaluator::Services::EvaluateProgrammingPackageService::Result] The
|
22
|
-
# result of the evaluation.
|
23
|
-
def self.execute(evaluation)
|
24
|
-
new(evaluation).send(:execute)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Creates a new service object.
|
28
|
-
def initialize(evaluation)
|
29
|
-
@evaluation = evaluation
|
30
|
-
@package = evaluation.package
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
# Evaluates the package.
|
36
|
-
#
|
37
|
-
# @return [Coursemology::Evaluator::Services::EvaluateProgrammingPackageService::Result]
|
38
|
-
def execute
|
39
|
-
container = create_container(@evaluation.language.class.docker_image)
|
40
|
-
copy_package(container)
|
41
|
-
execute_package(container)
|
42
|
-
|
43
|
-
extract_result(container)
|
44
|
-
ensure
|
45
|
-
destroy_container(container) if container
|
46
|
-
end
|
47
|
-
|
48
|
-
def create_container(image)
|
49
|
-
image_identifier = "coursemology/evaluator-image-#{image}"
|
50
|
-
Coursemology::Evaluator::DockerContainer.create(image_identifier, argv: container_arguments)
|
51
|
-
end
|
52
|
-
|
53
|
-
def container_arguments
|
54
|
-
result = []
|
55
|
-
result.push("-c#{@evaluation.time_limit}") if @evaluation.time_limit
|
56
|
-
result.push("-m#{@evaluation.memory_limit * MEMORY_LIMIT_RATIO}") if @evaluation.memory_limit
|
57
|
-
|
58
|
-
result
|
59
|
-
end
|
60
|
-
|
61
|
-
# Copies the contents of the package to the container.
|
62
|
-
#
|
63
|
-
# @param [Docker::Container] container The container to copy the package into.
|
64
|
-
def copy_package(container)
|
65
|
-
tar = tar_package(@package)
|
66
|
-
container.archive_in_stream(HOME_PATH) do
|
67
|
-
tar.read(Excon.defaults[:chunk_size]).to_s
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Converts the zip package into a tar package for the container.
|
72
|
-
#
|
73
|
-
# This also adds an additional +package+ directory to the start of the path, following tar
|
74
|
-
# convention.
|
75
|
-
#
|
76
|
-
# @param [Coursemology::Evaluator::Models::ProgrammingEvaluation::Package] package The package
|
77
|
-
# to convert to a tar.
|
78
|
-
# @return [IO] A stream containing the tar.
|
79
|
-
def tar_package(package)
|
80
|
-
tar_file_stream = StringIO.new
|
81
|
-
tar_file = Gem::Package::TarWriter.new(tar_file_stream)
|
82
|
-
Zip::File.open_buffer(package.stream) do |zip_file|
|
83
|
-
copy_archive(zip_file, tar_file, File.basename(PACKAGE_PATH))
|
84
|
-
tar_file.close
|
85
|
-
end
|
86
|
-
|
87
|
-
tar_file_stream.seek(0)
|
88
|
-
tar_file_stream
|
89
|
-
end
|
90
|
-
|
91
|
-
# Copies every entry from the zip archive to the tar archive, adding the optional prefix to the
|
92
|
-
# start of each file name.
|
93
|
-
#
|
94
|
-
# @param [Zip::File] zip_file The zip file to read from.
|
95
|
-
# @param [Gem::Package::TarWriter] tar_file The tar file to write to.
|
96
|
-
# @param [String] prefix The prefix to add to every file name in the tar.
|
97
|
-
def copy_archive(zip_file, tar_file, prefix = nil)
|
98
|
-
zip_file.each do |entry|
|
99
|
-
next unless entry.file?
|
100
|
-
|
101
|
-
zip_entry_stream = entry.get_input_stream
|
102
|
-
new_entry_name = prefix ? File.join(prefix, entry.name) : entry.name
|
103
|
-
tar_file.add_file(new_entry_name, 0664) do |tar_entry_stream|
|
104
|
-
IO.copy_stream(zip_entry_stream, tar_entry_stream)
|
105
|
-
end
|
106
|
-
|
107
|
-
zip_entry_stream.close
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def execute_package(container)
|
112
|
-
container.start!
|
113
|
-
container.wait
|
114
|
-
end
|
115
|
-
|
116
|
-
def extract_result(container)
|
117
|
-
logs = container.logs(stdout: true, stderr: true)
|
118
|
-
|
119
|
-
_, stdout, stderr = Coursemology::Evaluator::Utils.parse_docker_stream(logs)
|
120
|
-
Result.new(stdout, stderr, extract_test_report(container), container.exit_code)
|
121
|
-
end
|
122
|
-
|
123
|
-
def extract_test_report(container)
|
124
|
-
stream = extract_test_report_archive(container)
|
125
|
-
|
126
|
-
tar_file = Gem::Package::TarReader.new(stream)
|
127
|
-
tar_file.each do |file|
|
128
|
-
return file.read
|
129
|
-
end
|
130
|
-
rescue Docker::Error::NotFoundError
|
131
|
-
return nil
|
132
|
-
end
|
133
|
-
|
134
|
-
# Extracts the test report from the container.
|
135
|
-
#
|
136
|
-
# @return [StringIO] The stream containing the archive, the pointer is reset to the start of the
|
137
|
-
# stream.
|
138
|
-
def extract_test_report_archive(container)
|
139
|
-
stream = StringIO.new
|
140
|
-
container.archive_out(REPORT_PATH) do |bytes|
|
141
|
-
stream.write(bytes)
|
142
|
-
end
|
143
|
-
|
144
|
-
stream.seek(0)
|
145
|
-
stream
|
146
|
-
end
|
147
|
-
|
148
|
-
def destroy_container(container)
|
149
|
-
container.delete
|
150
|
-
end
|
151
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Coursemology::Evaluator::Services::EvaluateProgrammingPackageService
|
3
|
+
Result = Struct.new(:stdout, :stderr, :test_report, :exit_code)
|
4
|
+
|
5
|
+
# The path to the Coursemology user home directory.
|
6
|
+
HOME_PATH = '/home/coursemology'.freeze
|
7
|
+
|
8
|
+
# The path to where the package will be extracted.
|
9
|
+
PACKAGE_PATH = File.join(HOME_PATH, 'package')
|
10
|
+
|
11
|
+
# The path to where the test report will be at.
|
12
|
+
REPORT_PATH = File.join(PACKAGE_PATH, 'report.xml')
|
13
|
+
|
14
|
+
# The ratio to multiply the memory limits from our evaluation to the container by.
|
15
|
+
MEMORY_LIMIT_RATIO = 1.megabyte / 1.kilobyte
|
16
|
+
|
17
|
+
# Executes the given package in a container.
|
18
|
+
#
|
19
|
+
# @param [Coursemology::Evaluator::Models::ProgrammingEvaluation] evaluation The evaluation
|
20
|
+
# from the server.
|
21
|
+
# @return [Coursemology::Evaluator::Services::EvaluateProgrammingPackageService::Result] The
|
22
|
+
# result of the evaluation.
|
23
|
+
def self.execute(evaluation)
|
24
|
+
new(evaluation).send(:execute)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a new service object.
|
28
|
+
def initialize(evaluation)
|
29
|
+
@evaluation = evaluation
|
30
|
+
@package = evaluation.package
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Evaluates the package.
|
36
|
+
#
|
37
|
+
# @return [Coursemology::Evaluator::Services::EvaluateProgrammingPackageService::Result]
|
38
|
+
def execute
|
39
|
+
container = create_container(@evaluation.language.class.docker_image)
|
40
|
+
copy_package(container)
|
41
|
+
execute_package(container)
|
42
|
+
|
43
|
+
extract_result(container)
|
44
|
+
ensure
|
45
|
+
destroy_container(container) if container
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_container(image)
|
49
|
+
image_identifier = "coursemology/evaluator-image-#{image}"
|
50
|
+
Coursemology::Evaluator::DockerContainer.create(image_identifier, argv: container_arguments)
|
51
|
+
end
|
52
|
+
|
53
|
+
def container_arguments
|
54
|
+
result = []
|
55
|
+
result.push("-c#{@evaluation.time_limit}") if @evaluation.time_limit
|
56
|
+
result.push("-m#{@evaluation.memory_limit * MEMORY_LIMIT_RATIO}") if @evaluation.memory_limit
|
57
|
+
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
# Copies the contents of the package to the container.
|
62
|
+
#
|
63
|
+
# @param [Docker::Container] container The container to copy the package into.
|
64
|
+
def copy_package(container)
|
65
|
+
tar = tar_package(@package)
|
66
|
+
container.archive_in_stream(HOME_PATH) do
|
67
|
+
tar.read(Excon.defaults[:chunk_size]).to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Converts the zip package into a tar package for the container.
|
72
|
+
#
|
73
|
+
# This also adds an additional +package+ directory to the start of the path, following tar
|
74
|
+
# convention.
|
75
|
+
#
|
76
|
+
# @param [Coursemology::Evaluator::Models::ProgrammingEvaluation::Package] package The package
|
77
|
+
# to convert to a tar.
|
78
|
+
# @return [IO] A stream containing the tar.
|
79
|
+
def tar_package(package)
|
80
|
+
tar_file_stream = StringIO.new
|
81
|
+
tar_file = Gem::Package::TarWriter.new(tar_file_stream)
|
82
|
+
Zip::File.open_buffer(package.stream) do |zip_file|
|
83
|
+
copy_archive(zip_file, tar_file, File.basename(PACKAGE_PATH))
|
84
|
+
tar_file.close
|
85
|
+
end
|
86
|
+
|
87
|
+
tar_file_stream.seek(0)
|
88
|
+
tar_file_stream
|
89
|
+
end
|
90
|
+
|
91
|
+
# Copies every entry from the zip archive to the tar archive, adding the optional prefix to the
|
92
|
+
# start of each file name.
|
93
|
+
#
|
94
|
+
# @param [Zip::File] zip_file The zip file to read from.
|
95
|
+
# @param [Gem::Package::TarWriter] tar_file The tar file to write to.
|
96
|
+
# @param [String] prefix The prefix to add to every file name in the tar.
|
97
|
+
def copy_archive(zip_file, tar_file, prefix = nil)
|
98
|
+
zip_file.each do |entry|
|
99
|
+
next unless entry.file?
|
100
|
+
|
101
|
+
zip_entry_stream = entry.get_input_stream
|
102
|
+
new_entry_name = prefix ? File.join(prefix, entry.name) : entry.name
|
103
|
+
tar_file.add_file(new_entry_name, 0664) do |tar_entry_stream|
|
104
|
+
IO.copy_stream(zip_entry_stream, tar_entry_stream)
|
105
|
+
end
|
106
|
+
|
107
|
+
zip_entry_stream.close
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def execute_package(container)
|
112
|
+
container.start!
|
113
|
+
container.wait
|
114
|
+
end
|
115
|
+
|
116
|
+
def extract_result(container)
|
117
|
+
logs = container.logs(stdout: true, stderr: true)
|
118
|
+
|
119
|
+
_, stdout, stderr = Coursemology::Evaluator::Utils.parse_docker_stream(logs)
|
120
|
+
Result.new(stdout, stderr, extract_test_report(container), container.exit_code)
|
121
|
+
end
|
122
|
+
|
123
|
+
def extract_test_report(container)
|
124
|
+
stream = extract_test_report_archive(container)
|
125
|
+
|
126
|
+
tar_file = Gem::Package::TarReader.new(stream)
|
127
|
+
tar_file.each do |file|
|
128
|
+
return file.read.force_encoding(Encoding::UTF_8)
|
129
|
+
end
|
130
|
+
rescue Docker::Error::NotFoundError
|
131
|
+
return nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Extracts the test report from the container.
|
135
|
+
#
|
136
|
+
# @return [StringIO] The stream containing the archive, the pointer is reset to the start of the
|
137
|
+
# stream.
|
138
|
+
def extract_test_report_archive(container)
|
139
|
+
stream = StringIO.new
|
140
|
+
container.archive_out(REPORT_PATH) do |bytes|
|
141
|
+
stream.write(bytes)
|
142
|
+
end
|
143
|
+
|
144
|
+
stream.seek(0)
|
145
|
+
stream
|
146
|
+
end
|
147
|
+
|
148
|
+
def destroy_container(container)
|
149
|
+
container.delete
|
150
|
+
end
|
151
|
+
end
|