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