Files
codeocean/spec/models/execution_environment_spec.rb
Sebastian Serth 8fc5123bae Exclusively lock Runners during code executions
Previously, the same runner could be used multiple times with different submissions simultaneously. This, however, yielded errors, for example when one submission time oud (causing the running to be deleted) while another submission was still executed.

Admin actions, such as the shell, can be still executed regardless of any other code execution.

Fixes CODEOCEAN-HG
Fixes openHPI/poseidon#423
2023-10-31 12:35:24 +01:00

214 lines
8.0 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ExecutionEnvironment do
let(:execution_environment) { described_class.create.tap {|execution_environment| execution_environment.update(network_enabled: nil, privileged_execution: nil) } }
it 'validates that the Docker image works' do
allow(execution_environment).to receive_messages(validate_docker_image?: true, working_docker_image?: true)
execution_environment.assign_attributes(build(:ruby).attributes)
execution_environment.save(validate: false)
expect(execution_environment).to have_received(:working_docker_image?)
end
it 'validates the presence of a Docker image name' do
expect(execution_environment.errors[:docker_image]).to be_present
end
it 'validates the minimum value of the memory limit' do
execution_environment.update(memory_limit: ExecutionEnvironment::MINIMUM_MEMORY_LIMIT / 2)
expect(execution_environment.errors[:memory_limit]).to be_present
end
it 'validates the numericality of the memory limit' do
execution_environment.update(memory_limit: Math::PI)
expect(execution_environment.errors[:memory_limit]).to be_present
end
it 'validates the presence of a memory limit' do
execution_environment.update(memory_limit: nil)
expect(execution_environment.errors[:memory_limit]).to be_present
end
it 'validates the minimum value of the cpu limit' do
execution_environment.update(cpu_limit: 0)
expect(execution_environment.errors[:cpu_limit]).to be_present
end
it 'validates that cpu limit is an integer' do
execution_environment.update(cpu_limit: Math::PI)
expect(execution_environment.errors[:cpu_limit]).to be_present
end
it 'validates the presence of a cpu limit' do
execution_environment.update(cpu_limit: nil)
expect(execution_environment.errors[:cpu_limit]).to be_present
end
it 'validates the presence of a name' do
expect(execution_environment.errors[:name]).to be_present
end
it 'validates the presence of the network enabled flag' do
expect(execution_environment.errors[:network_enabled]).to be_present
execution_environment.update(network_enabled: false)
expect(execution_environment.errors[:network_enabled]).to be_blank
end
it 'validates the presence of the privileged_execution enabled flag' do
expect(execution_environment.errors[:privileged_execution]).to be_present
execution_environment.update(privileged_execution: false)
expect(execution_environment.errors[:privileged_execution]).to be_blank
end
it 'validates the numericality of the permitted run time' do
execution_environment.update(permitted_execution_time: Math::PI)
expect(execution_environment.errors[:permitted_execution_time]).to be_present
end
it 'validates the presence of a permitted run time' do
execution_environment.update(permitted_execution_time: nil)
expect(execution_environment.errors[:permitted_execution_time]).to be_present
end
it 'validates the numericality of the pool size' do
execution_environment.update(pool_size: Math::PI)
expect(execution_environment.errors[:pool_size]).to be_present
end
it 'validates the presence of a pool size' do
execution_environment.update(pool_size: nil)
expect(execution_environment.errors[:pool_size]).to be_present
end
it 'validates the presence of a run command' do
expect(execution_environment.errors[:run_command]).to be_present
end
it 'validates the presence of a user' do
expect(execution_environment.errors[:user]).to be_present
end
it 'validates the format of the exposed ports' do
execution_environment.update(exposed_ports: '1,')
expect(execution_environment.errors[:exposed_ports]).to be_present
execution_environment.update(exposed_ports: '1,a')
expect(execution_environment.errors[:exposed_ports]).to be_present
end
describe '#valid_test_setup?' do
context 'with a test command and a testing framework' do
before { execution_environment.update(test_command: attributes_for(:ruby)[:test_command], testing_framework: attributes_for(:ruby)[:testing_framework]) }
it 'is valid' do
expect(execution_environment.errors[:test_command]).to be_blank
end
end
context 'with a test command but no testing framework' do
before { execution_environment.update(test_command: attributes_for(:ruby)[:test_command], testing_framework: nil) }
it 'is invalid' do
expect(execution_environment.errors[:test_command]).to be_present
end
end
context 'with no test command but a testing framework' do
before { execution_environment.update(test_command: nil, testing_framework: attributes_for(:ruby)[:testing_framework]) }
it 'is invalid' do
expect(execution_environment.errors[:test_command]).to be_present
end
end
context 'with no test command and no testing framework' do
before { execution_environment.update(test_command: nil, testing_framework: nil) }
it 'is valid' do
expect(execution_environment.errors[:test_command]).to be_blank
end
end
end
describe '#validate_docker_image?' do
it 'is false in the test environment' do
expect(Rails.env.test?).to be true
expect(execution_environment.send(:validate_docker_image?)).to be false
end
it 'is false without a Docker image' do
expect(execution_environment.docker_image).to be_blank
expect(execution_environment.send(:validate_docker_image?)).to be false
end
it 'is false when the pool size is empty' do
expect(execution_environment.pool_size).to be 0
expect(execution_environment.send(:validate_docker_image?)).to be false
end
it 'is true otherwise' do
execution_environment.docker_image = attributes_for(:ruby)[:docker_image]
execution_environment.pool_size = 1
allow(Rails.env).to receive(:test?).and_return(false)
expect(execution_environment.send(:validate_docker_image?)).to be true
end
end
describe '#working_docker_image?' do
let(:execution_environment) { create(:ruby) }
let(:working_docker_image?) { execution_environment.send(:working_docker_image?) }
let(:runner) { instance_double Runner }
before do
allow(execution_environment).to receive(:sync_runner_environment).and_return(true)
allow(Runner).to receive(:for).with(execution_environment.author, execution_environment).and_return runner
end
it 'instantiates a Runner' do
allow(runner).to receive(:execute_command).and_return({})
working_docker_image?
expect(runner).to have_received(:execute_command).once
end
it 'executes the validation command' do
allow(runner).to receive(:execute_command).and_return({})
working_docker_image?
expect(runner).to have_received(:execute_command).with(ExecutionEnvironment::VALIDATION_COMMAND, exclusive: false)
end
context 'when the command produces an error' do
it 'adds an error' do
allow(runner).to receive(:execute_command).and_return(stderr: 'command not found')
working_docker_image?
expect(execution_environment.errors[:docker_image]).to be_present
end
end
context 'when the Docker client produces an error' do
it 'adds an error' do
allow(runner).to receive(:execute_command).and_raise(Runner::Error)
expect { working_docker_image? }.to raise_error(ActiveRecord::RecordInvalid)
expect(execution_environment.errors[:docker_image]).to be_present
end
end
end
describe '#exposed_ports_list' do
it 'returns an empty string if no ports are exposed' do
execution_environment.exposed_ports = []
expect(execution_environment.exposed_ports_list).to eq('')
end
it 'returns an string with comma-separated integers representing the exposed ports' do
execution_environment.exposed_ports = [1, 2, 3]
expect(execution_environment.exposed_ports_list).to eq('1, 2, 3')
execution_environment.exposed_ports.each do |port|
expect(execution_environment.exposed_ports_list).to include(port.to_s)
end
end
end
end