
Timeouts are now handled correctly and the Runner automatically creates the execution environment if it could not be found in Poseidon. The runner is deleted locally if Poseidon returns a bad request error.
178 lines
6.8 KiB
Ruby
178 lines
6.8 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 }
|
|
|
|
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
|
|
|
|
{poseidon: Runner::Strategy::Poseidon, docker: Runner::Strategy::Docker}.each do |strategy, strategy_class|
|
|
include_examples 'uses the strategy defined in the constant', strategy, strategy_class
|
|
end
|
|
|
|
shared_examples 'delegates method sends to its strategy' do |method, *args|
|
|
context "when sending #{method}" do
|
|
let(:strategy) { instance_double(strategy_class) }
|
|
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 the method #{method}" do
|
|
expect(strategy).to receive(method)
|
|
runner.send(method, *args)
|
|
end
|
|
end
|
|
end
|
|
|
|
include_examples 'delegates method sends to its strategy', :destroy_at_management
|
|
include_examples 'delegates method sends to its strategy', :copy_files, nil
|
|
include_examples 'delegates method sends to its strategy', :attach_to_execution, nil
|
|
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::NotFound) }
|
|
|
|
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::NotFound
|
|
# 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::NotFound, /#{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::NotFound, /#{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
|