spec/lib/runit_spec.rb (232 lines of code) (raw):
# frozen_string_literal: true
RSpec.describe Runit do
include ShelloutHelper
let(:log_dir) { Pathname.new('/home/git/gdk/log') }
describe 'ALL_DATA_ORIENTED_SERVICE_NAMES' do
it 'returns all data service names only' do
expect(described_class::ALL_DATA_ORIENTED_SERVICE_NAMES).to match_array(%w[minio openldap gitaly praefect redis redis-cluster postgresql-geo postgresql])
end
end
context 'with stubbed services' do
let(:data_service_names) { %w[praefect redis postgresql redis-cluster] }
let(:non_data_service_names) { %w[gitlab-workhorse rails-background-jobs rails-web webpack] }
before do
stub_services
end
describe '.data_oriented_service_names' do
subject(:data_oriented_service_names) { described_class.data_oriented_service_names }
it 'returns data service names only' do
expect(data_oriented_service_names).to match_array(data_service_names)
expect(data_oriented_service_names).not_to include(*non_data_service_names)
end
end
describe '.non_data_oriented_service_names' do
subject(:non_data_oriented_service_names) { described_class.non_data_oriented_service_names }
it 'returns non-data service names only' do
expect(non_data_oriented_service_names).to match_array(non_data_service_names)
expect(non_data_oriented_service_names).not_to include(*data_service_names)
end
end
describe '.all_service_names' do
subject(:all_service_names) { described_class.all_service_names }
it 'returns all service names' do
expect(all_service_names).to include(*data_service_names + non_data_service_names)
end
it 'excludes praefect-gitaly-* service names' do
expect(all_service_names).not_to include('praefect-gitaly-0')
end
end
describe '.start' do
subject(:start) { described_class.start(services, **args) }
shared_examples 'starts services' do |quiet|
context 'with empty args array' do
let(:services) { [] }
it 'starts data services first and then non-data services last' do
allow(described_class).to receive_messages(data_oriented_service_names: data_service_names, non_data_oriented_service_names: non_data_service_names)
data_service_names.reverse_each do |service_name|
expect(described_class).to receive(:sv).with('start', [service_name], quiet: quiet).and_return(true).ordered
end
expect(described_class).to receive(:sv).with('start', non_data_service_names, quiet: quiet).and_return(true).ordered
start
end
end
context 'with args array' do
let(:services) { data_service_names }
it 'starts the requested services' do
expect(described_class).to receive(:sv).with('start', data_service_names, quiet: quiet)
start
end
end
context 'with a string' do
let(:services) { 'postgresql' }
it 'starts the requested service' do
expect(described_class).to receive(:sv).with('start', [services], quiet: quiet)
start
end
end
end
context 'with default operation' do
it_behaves_like 'starts services', false do
let(:args) { {} }
end
end
context 'with quiet operation' do
it_behaves_like 'starts services', true do
let(:args) { { quiet: true } }
end
end
end
describe '.stop' do
subject(:stop) { described_class.stop(**args) }
shared_examples 'stops all services' do |quiet|
it 'stops all services', :hide_output do
allow(described_class).to receive_messages(data_oriented_service_names: data_service_names, non_data_oriented_service_names: non_data_service_names)
allow(described_class).to receive(:unload_runsvdir!)
expect(described_class).to receive(:sv).with('force-stop', non_data_service_names, quiet: quiet).and_return(true).ordered
data_service_names.each do |service_name|
expect(described_class).to receive(:sv).with('force-stop', [service_name], quiet: quiet).and_return(true).ordered
end
stop
end
end
context 'with default operation' do
it_behaves_like 'stops all services', false do
let(:args) { {} }
end
end
context 'with quiet operation' do
it_behaves_like 'stops all services', true do
let(:args) { { quiet: true } }
end
end
end
describe '.sv_shellout' do
it 'returns a shellout with the sv command' do
expect(described_class).to receive(:start_runsvdir).and_return(nil)
expect(described_class).to receive(:ensure_services_are_supervised)
sh = described_class.sv_shellout('status', ['redis'])
expect(sh).to be_instance_of(GDK::Shellout)
expect(sh.args).to eq(%w[sv -w 20 status /tmp/random-dir123/gdk/services/redis])
expect(sh.opts).to eq({})
end
end
describe '.sv' do
shared_examples 'send sv command' do |shellarg|
subject(:sv) { described_class.sv(command, services, **args) }
let(:command) { 'stop' }
let(:services) { data_service_names }
let(:args) { {} }
it 'sends the command to services' do
expect(described_class).to receive(:start_runsvdir).and_return(nil)
expect(described_class).to receive(:ensure_services_are_supervised)
shellout_double2 = gdk_shellout_double(run: '', stream: '', success?: true)
expect_gdk_shellout_command(shellarg).and_return(shellout_double2)
expect(sv).to be_truthy
end
end
it_behaves_like 'send sv command', %w[sv -w 20 stop /tmp/random-dir123/gdk/services/postgresql /tmp/random-dir123/gdk/services/praefect /tmp/random-dir123/gdk/services/redis]
context 'when redis_cluster.enabled is true' do
before do
config = {
'redis_cluster' => {
'enabled' => true
}
}
stub_gdk_yaml(config)
end
it_behaves_like 'send sv command', %w[sv -w 20 stop /tmp/random-dir123/gdk/services/postgresql /tmp/random-dir123/gdk/services/praefect /tmp/random-dir123/gdk/services/redis /tmp/random-dir123/gdk/services/redis-cluster]
end
end
describe '.unload_runsvdir!' do
subject(:unload_runsvdir!) { described_class.unload_runsvdir! }
it 'send the HUP signal to the runsvdir PID' do
pid = 99999999999
allow(described_class).to receive(:runsvdir_pid).and_return(pid)
expect(Process).to receive(:kill).with('HUP', pid)
unload_runsvdir!
end
end
describe '.tail' do
subject(:tail) { described_class.tail(services) }
let(:services) { %w[postgresql redis] }
context 'when there are no logs to tail' do
it 'returns true' do
allow(described_class).to receive(:log_files).and_return([])
expect(GDK::Output).to receive(:warn).with(<<~MSG)
No matching services to tail.
To view a list of services and shortcuts, run `gdk tail --help`.
MSG
expect(tail).to be_truthy
end
end
context 'when there are logs to tail' do
let(:log_files) { services.map { |service| Pathname.new(log_dir.join(service, 'current')) } }
it 'attempts to tail service log files' do
allow(described_class).to receive(:log_files).and_return(log_files)
expect(described_class).to receive(:exec).with('tail', '-qF', '/home/git/gdk/log/postgresql/current', '/home/git/gdk/log/redis/current')
tail
end
end
end
describe '.log_files' do
subject(:log_files) { described_class.log_files(services) }
let(:services) { %w[redis] }
before do
stub_const('Runit::LOG_DIR', log_dir)
end
context 'when there are no matching logs files' do
it 'returns an empty array' do
expect(log_files).to eq([])
end
end
context 'when there are matching log_files' do
let(:redis_log) { log_dir.join('redis/current') }
before do
allow(log_dir).to receive(:join).with(services.first, 'current').and_return(redis_log)
allow(redis_log).to receive(:exist?).and_return(true)
end
it 'returns the list of log files' do
expect(log_files).to contain_exactly(redis_log)
end
end
context 'when there are matching shortcuts' do
let(:services) { %w[rails] }
let(:rails_web_log) { log_dir.join('rails-web/current') }
before do
allow(log_dir)
.to receive(:glob).with('rails-*/current')
.and_return(rails_web_log)
end
it 'returns the list of log files' do
expect(log_files).to contain_exactly(rails_web_log)
end
end
end
end
describe 'SERVICE_SHORTCUTS' do
describe 'db' do
it 'contains all service shortcuts' do
expect(described_class::SERVICE_SHORTCUTS['db']).to eq('{redis,redis-cluster,postgresql,postgresql-geo,clickhouse}')
end
end
describe 'workhorse' do
it 'contains all service shortcuts' do
expect(described_class::SERVICE_SHORTCUTS['workhorse']).to eq('gitlab-workhorse')
end
end
end
def stub_services
stub_const('Runit::SERVICES_DIR', Pathname.new('/tmp/random-dir123/gdk/services'))
stub_const('Runit::ALL_DATA_ORIENTED_SERVICE_NAMES', data_service_names)
allow(described_class::SERVICES_DIR).to receive(:join).and_call_original
allow(described_class::SERVICES_DIR).to receive(:exist?).and_return(true)
children_doubles = (data_service_names + non_data_service_names).map do |service_name|
pathname_double = Pathname.new(described_class::SERVICES_DIR.join(service_name))
allow(pathname_double).to receive_messages(exist?: true, directory?: true)
allow(described_class::SERVICES_DIR).to receive(:join).with(service_name).and_return(pathname_double)
pathname_double
end
allow(Pathname).to receive(:new).and_call_original
services_dir_double = instance_double(Pathname, children: children_doubles)
allow(Pathname).to receive(:new).with(described_class::SERVICES_DIR).and_return(services_dir_double)
end
end