class Coursemology::Evaluator::Services::EvaluateProgrammingPackageService

Constants

HOME_PATH

The path to the Coursemology user home directory.

MEMORY_LIMIT_RATIO

The ratio to multiply the memory limits from our evaluation to the container by.

PACKAGE_PATH

The path to where the package will be extracted.

REPORT_PATH

The path to where the test report will be at.

Result

Public Class Methods

execute(evaluation) click to toggle source

Executes the given package in a container.

@param [Coursemology::Evaluator::Models::ProgrammingEvaluation] evaluation The evaluation

from the server.

@return [Coursemology::Evaluator::Services::EvaluateProgrammingPackageService::Result] The

result of the evaluation.
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 23
def self.execute(evaluation)
  new(evaluation).send(:execute)
end
new(evaluation) click to toggle source

Creates a new service object.

# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 28
def initialize(evaluation)
  @evaluation = evaluation
  @package = evaluation.package
end

Private Instance Methods

container_arguments() click to toggle source
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 53
def container_arguments
  result = []
  result.push("-c#{@evaluation.time_limit}") if @evaluation.time_limit
  result.push("-m#{@evaluation.memory_limit * MEMORY_LIMIT_RATIO}") if @evaluation.memory_limit

  result
end
copy_archive(zip_file, tar_file, prefix = nil) click to toggle source

Copies every entry from the zip archive to the tar archive, adding the optional prefix to the start of each file name.

@param [Zip::File] zip_file The zip file to read from. @param [Gem::Package::TarWriter] tar_file The tar file to write to. @param [String] prefix The prefix to add to every file name in the tar.

# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 97
def copy_archive(zip_file, tar_file, prefix = nil)
  zip_file.each do |entry|
    next unless entry.file?

    zip_entry_stream = entry.get_input_stream
    new_entry_name = prefix ? File.join(prefix, entry.name) : entry.name
    tar_file.add_file(new_entry_name, 0664) do |tar_entry_stream|
      IO.copy_stream(zip_entry_stream, tar_entry_stream)
    end

    zip_entry_stream.close
  end
end
copy_package(container) click to toggle source

Copies the contents of the package to the container.

@param [Docker::Container] container The container to copy the package into.

# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 64
def copy_package(container)
  tar = tar_package(@package)
  container.archive_in_stream(HOME_PATH) do
    tar.read(Excon.defaults[:chunk_size]).to_s
  end
end
create_container(image) click to toggle source
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 48
def create_container(image)
  image_identifier = "coursemology/evaluator-image-#{image}"
  Coursemology::Evaluator::DockerContainer.create(image_identifier, argv: container_arguments)
end
destroy_container(container) click to toggle source
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 149
def destroy_container(container)
  container.delete
end
execute() click to toggle source

Evaluates the package.

@return [Coursemology::Evaluator::Services::EvaluateProgrammingPackageService::Result]

# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 38
def execute
  container = create_container(@evaluation.language.class.docker_image)
  copy_package(container)
  execute_package(container)

  extract_result(container)
ensure
  destroy_container(container) if container
end
execute_package(container) click to toggle source
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 111
def execute_package(container)
  container.start!
  container.wait
end
extract_result(container) click to toggle source
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 116
def extract_result(container)
  logs = container.logs(stdout: true, stderr: true)

  _, stdout, stderr = Coursemology::Evaluator::Utils.parse_docker_stream(logs)
  Result.new(stdout, stderr, extract_test_report(container), container.exit_code)
end
extract_test_report(container) click to toggle source
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 123
def extract_test_report(container)
  stream = extract_test_report_archive(container)

  tar_file = Gem::Package::TarReader.new(stream)
  tar_file.each do |file|
    test_report = file.read
    return test_report.force_encoding(Encoding::UTF_8) if test_report
  end
rescue Docker::Error::NotFoundError
  return nil
end
extract_test_report_archive(container) click to toggle source

Extracts the test report from the container.

@return [StringIO] The stream containing the archive, the pointer is reset to the start of the

stream.
# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 139
def extract_test_report_archive(container)
  stream = StringIO.new
  container.archive_out(REPORT_PATH) do |bytes|
    stream.write(bytes)
  end

  stream.seek(0)
  stream
end
tar_package(package) click to toggle source

Converts the zip package into a tar package for the container.

This also adds an additional package directory to the start of the path, following tar convention.

@param [Coursemology::Evaluator::Models::ProgrammingEvaluation::Package] package The package

to convert to a tar.

@return [IO] A stream containing the tar.

# File lib/coursemology/evaluator/services/evaluate_programming_package_service.rb, line 79
def tar_package(package)
  tar_file_stream = StringIO.new
  tar_file = Gem::Package::TarWriter.new(tar_file_stream)
  Zip::File.open_buffer(package.stream) do |zip_file|
    copy_archive(zip_file, tar_file, File.basename(PACKAGE_PATH))
    tar_file.close
  end

  tar_file_stream.seek(0)
  tar_file_stream
end