
These tests were blocking because of the newly introduced EventLoop. The messages sent to the EventLoop are now mocked and the EventLoop isn't blocking anymore in the tests.
251 lines
8.9 KiB
Ruby
251 lines
8.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe Runner do
|
|
let(:runner_id) { FactoryBot.attributes_for(:runner)[:runner_id] }
|
|
let(:strategy_class) { described_class.strategy_class }
|
|
let(:strategy) { instance_double(strategy_class) }
|
|
|
|
describe 'attribute validation' do
|
|
let(:runner) { FactoryBot.create :runner }
|
|
|
|
it 'validates the presence of the runner id' do
|
|
described_class.skip_callback(:validation, :before, :request_id)
|
|
runner.update(runner_id: nil)
|
|
expect(runner.errors[:runner_id]).to be_present
|
|
described_class.set_callback(:validation, :before, :request_id)
|
|
end
|
|
|
|
it 'validates the presence of an execution environment' do
|
|
runner.update(execution_environment: nil)
|
|
expect(runner.errors[:execution_environment]).to be_present
|
|
end
|
|
|
|
it 'validates the presence of a user' do
|
|
runner.update(user: nil)
|
|
expect(runner.errors[:user]).to be_present
|
|
end
|
|
end
|
|
|
|
describe '::strategy_class' do
|
|
shared_examples 'uses the strategy defined in the constant' do |strategy, strategy_class|
|
|
it "uses #{strategy_class} as strategy class for constant #{strategy}" do
|
|
stub_const('Runner::STRATEGY_NAME', strategy)
|
|
expect(described_class.strategy_class).to eq(strategy_class)
|
|
end
|
|
end
|
|
|
|
available_strategies = {
|
|
poseidon: Runner::Strategy::Poseidon,
|
|
docker_container_pool: Runner::Strategy::DockerContainerPool,
|
|
}
|
|
available_strategies.each do |strategy, strategy_class|
|
|
include_examples 'uses the strategy defined in the constant', strategy, strategy_class
|
|
end
|
|
end
|
|
|
|
describe '#destroy_at_management' do
|
|
let(:runner) { described_class.create }
|
|
|
|
before do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
allow(strategy_class).to receive(:new).and_return(strategy)
|
|
end
|
|
|
|
it 'delegates to its strategy' do
|
|
expect(strategy).to receive(:destroy_at_management)
|
|
runner.destroy_at_management
|
|
end
|
|
end
|
|
|
|
describe '#attach to execution' do
|
|
let(:runner) { described_class.create }
|
|
let(:command) { 'ls' }
|
|
let(:event_loop) { instance_double(Runner::EventLoop) }
|
|
|
|
before do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
allow(strategy_class).to receive(:new).and_return(strategy)
|
|
allow(event_loop).to receive(:wait)
|
|
allow(Runner::EventLoop).to receive(:new).and_return(event_loop)
|
|
end
|
|
|
|
it 'delegates to its strategy' do
|
|
expect(strategy).to receive(:attach_to_execution)
|
|
runner.attach_to_execution(command)
|
|
end
|
|
|
|
it 'returns the execution time' do
|
|
allow(strategy).to receive(:attach_to_execution)
|
|
starting_time = Time.zone.now
|
|
execution_time = runner.attach_to_execution(command)
|
|
test_time = Time.zone.now - starting_time
|
|
expect(execution_time).to be_between(0.0, test_time).exclusive
|
|
end
|
|
|
|
context 'when a runner error is raised' do
|
|
before { allow(strategy).to receive(:attach_to_execution).and_raise(Runner::Error) }
|
|
|
|
it 'attaches the execution time to the error' do
|
|
starting_time = Time.zone.now
|
|
expect { runner.attach_to_execution(command) }.to raise_error do |error|
|
|
test_time = Time.zone.now - starting_time
|
|
expect(error.execution_duration).to be_between(0.0, test_time).exclusive
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#copy_files' do
|
|
let(:runner) { described_class.create }
|
|
|
|
before do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
allow(strategy_class).to receive(:new).and_return(strategy)
|
|
end
|
|
|
|
it 'delegates to its strategy' do
|
|
expect(strategy).to receive(:copy_files).once
|
|
runner.copy_files(nil)
|
|
end
|
|
|
|
context 'when a RunnerNotFound exception is raised' do
|
|
before do
|
|
was_called = false
|
|
allow(strategy).to receive(:copy_files) do
|
|
unless was_called
|
|
was_called = true
|
|
raise Runner::Error::RunnerNotFound.new
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'requests a new id' do
|
|
expect(runner).to receive(:request_new_id)
|
|
runner.copy_files(nil)
|
|
end
|
|
|
|
it 'calls copy_file twice' do
|
|
# copy_files is called again after a new runner was requested.
|
|
expect(strategy).to receive(:copy_files).twice
|
|
runner.copy_files(nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'creation' do
|
|
let(:user) { FactoryBot.create :external_user }
|
|
let(:execution_environment) { FactoryBot.create :ruby }
|
|
let(:create_action) { -> { described_class.create(user: user, execution_environment: execution_environment) } }
|
|
|
|
it 'requests a runner id from the runner management' do
|
|
expect(strategy_class).to receive(:request_from_management)
|
|
create_action.call
|
|
end
|
|
|
|
it 'returns a valid runner' do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
expect(create_action.call).to be_valid
|
|
end
|
|
|
|
it 'sets the strategy' do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
strategy = strategy_class.new(runner_id, execution_environment)
|
|
allow(strategy_class).to receive(:new).with(runner_id, execution_environment).and_return(strategy)
|
|
runner = create_action.call
|
|
expect(runner.strategy).to eq(strategy)
|
|
end
|
|
|
|
it 'does not call the runner management again while a runner id is set' do
|
|
expect(strategy_class).to receive(:request_from_management).and_return(runner_id).once
|
|
runner = create_action.call
|
|
runner.update(user: FactoryBot.create(:external_user))
|
|
end
|
|
end
|
|
|
|
describe '#request_new_id' do
|
|
let(:runner) { FactoryBot.create :runner }
|
|
|
|
context 'when the environment is available in the runner management' do
|
|
it 'requests the runner management' do
|
|
expect(strategy_class).to receive(:request_from_management)
|
|
runner.send(:request_new_id)
|
|
end
|
|
|
|
it 'updates the runner id' do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
runner.send(:request_new_id)
|
|
expect(runner.runner_id).to eq(runner_id)
|
|
end
|
|
|
|
it 'updates the strategy' do
|
|
allow(strategy_class).to receive(:request_from_management).and_return(runner_id)
|
|
strategy = strategy_class.new(runner_id, runner.execution_environment)
|
|
allow(strategy_class).to receive(:new).with(runner_id, runner.execution_environment).and_return(strategy)
|
|
runner.send(:request_new_id)
|
|
expect(runner.strategy).to eq(strategy)
|
|
end
|
|
end
|
|
|
|
context 'when the environment could not be found in the runner management' do
|
|
let(:environment_id) { runner.execution_environment.id }
|
|
|
|
before { allow(strategy_class).to receive(:request_from_management).and_raise(Runner::Error::EnvironmentNotFound) }
|
|
|
|
it 'syncs the execution environment' do
|
|
expect(strategy_class).to receive(:sync_environment).with(runner.execution_environment)
|
|
runner.send(:request_new_id)
|
|
rescue Runner::Error::EnvironmentNotFound
|
|
# Ignored because this error is expected (see tests below).
|
|
end
|
|
|
|
it 'raises an error when the environment could be synced' do
|
|
allow(strategy_class).to receive(:sync_environment).with(runner.execution_environment).and_return(true)
|
|
expect { runner.send(:request_new_id) }.to raise_error(Runner::Error::EnvironmentNotFound, /#{environment_id}.*successfully synced/)
|
|
end
|
|
|
|
it 'raises an error when the environment could not be synced' do
|
|
allow(strategy_class).to receive(:sync_environment).with(runner.execution_environment).and_return(false)
|
|
expect { runner.send(:request_new_id) }.to raise_error(Runner::Error::EnvironmentNotFound, /#{environment_id}.*could not be synced/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '::for' do
|
|
let(:user) { FactoryBot.create :external_user }
|
|
let(:exercise) { FactoryBot.create :fibonacci }
|
|
|
|
context 'when the runner could not be saved' do
|
|
before { allow(strategy_class).to receive(:request_from_management).and_return(nil) }
|
|
|
|
it 'raises an error' do
|
|
expect { described_class.for(user, exercise) }.to raise_error(Runner::Error::Unknown, /could not be saved/)
|
|
end
|
|
end
|
|
|
|
context 'when a runner already exists' do
|
|
let!(:existing_runner) { FactoryBot.create(:runner, user: user, execution_environment: exercise.execution_environment) }
|
|
|
|
it 'returns the existing runner' do
|
|
new_runner = described_class.for(user, exercise)
|
|
expect(new_runner).to eq(existing_runner)
|
|
end
|
|
|
|
it 'sets the strategy' do
|
|
runner = described_class.for(user, exercise)
|
|
expect(runner.strategy).to be_present
|
|
end
|
|
end
|
|
|
|
context 'when no runner exists' do
|
|
before { allow(strategy_class).to receive(:request_from_management).and_return(runner_id) }
|
|
|
|
it 'returns a new runner' do
|
|
runner = described_class.for(user, exercise)
|
|
expect(runner).to be_valid
|
|
end
|
|
end
|
|
end
|
|
end
|