123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'rails_helper'
- class SampleService
- def self.pre_run; end
- def initialize(manager:, fork_id: nil)
- # no-op
- end
- def run
- sleep 1
- end
- def self.service_name
- 'Sample'
- end
- def self.skip?(manager:)
- false
- end
- def self.max_workers
- 1
- end
- end
- class ProcessService < SampleService
- def run
- f = File.new self.class.path, 'w'
- f.write 'run'
- f.close
- sleep 1
- end
- def self.path
- Rails.root.join('tmp/process.service')
- end
- end
- RSpec.describe BackgroundServices do
- let(:instance) { described_class.new(config) }
- let(:config) { [] }
- describe '.available_services' do
- it 'matches existing classes' do
- expect(described_class.available_services).to contain_exactly(
- BackgroundServices::Service::ManageSessionsJobs,
- BackgroundServices::Service::ProcessScheduledJobs,
- BackgroundServices::Service::ProcessSessionsJobs,
- BackgroundServices::Service::ProcessDelayedJobs
- )
- end
- end
- describe '#run', ensure_threads_exited: true do
- let(:config) { described_class::ServiceConfig.new(service: SampleService, disabled: false, workers: 0) }
- it 'runs given services' do
- allow(instance).to receive(:run_service)
- instance.run
- expect(instance).to have_received(:run_service).with(config)
- end
- context 'when config has multiple services' do
- let(:forked) { described_class::ServiceConfig.new(service: SampleService, disabled: false, workers: 3) }
- let(:threaded) { described_class::ServiceConfig.new(service: SampleService, disabled: false, workers: 0) }
- let(:config) { [threaded, forked] }
- it 'runs forked services before threaded', aggregate_failures: true do
- allow(instance).to receive(:run_service)
- instance.run
- expect(instance).to have_received(:run_service).with(forked).ordered
- expect(instance).to have_received(:run_service).with(threaded).ordered
- end
- end
- end
- describe '#run_service' do
- let(:config) { described_class::ServiceConfig.new(service: SampleService, disabled: is_disabled, workers: workers_count) }
- let(:is_disabled) { false }
- before do
- allow(instance).to receive(:start_as_forks)
- allow(instance).to receive(:start_as_thread)
- end
- shared_examples 'stops early if disabled' do
- context 'when disabled' do
- let(:is_disabled) { true }
- it 'stops early if disabled', :aggregate_failures do
- allow(Rails.logger).to receive(:info)
- instance.send(:run_service, config)
- expect(Rails.logger).to have_received(:info).with(no_args) do |&block|
- expect(block.call).to match(%r{Skipping disabled service})
- end
- end
- end
- end
- shared_examples 'calls pre_run' do
- it 'calls pre_run' do
- allow(config.service).to receive(:pre_run)
- instance.send(:run_service, config)
- expect(config.service).to have_received(:pre_run)
- end
- end
- context 'when workers present' do
- let(:workers_count) { 1 }
- include_examples 'stops early if disabled'
- include_examples 'calls pre_run'
- it 'starts as fork' do
- instance.send(:run_service, config)
- expect(instance).to have_received(:start_as_forks).with(config.service, config.workers)
- end
- end
- context 'when workers not present' do
- let(:workers_count) { 0 }
- include_examples 'stops early if disabled'
- include_examples 'calls pre_run'
- it 'starts as thread' do
- instance.send(:run_service, config)
- expect(instance).to have_received(:start_as_thread).with(config.service)
- end
- end
- end
- describe '#start_as_forks' do
- context 'with a file check' do
- after do
- File.delete ProcessService.path
- end
- it 'runs Service#run' do
- instance.send(:start_as_forks, ProcessService, 1)
- sleep 0.1 until File.exist? ProcessService.path
- expect(File.read(ProcessService.path)).to eq('run')
- end
- end
- it 'forks a new process' do
- process_pids = instance.send(:start_as_forks, SampleService, 1)
- Process.wait process_pids.first
- expect($CHILD_STATUS).to be_success
- end
- end
- describe '#start_as_thread', ensure_threads_exited: true do
- let(:config) { described_class::ServiceConfig.new(service: SampleService, disabled: false, workers: 0) }
- context 'with logging' do
- let(:log) { [] }
- before do
- allow_any_instance_of(SampleService).to receive(:run) do
- log << :run_called
- end
- end
- it 'runs Service#run' do
- instance.send(:start_as_thread, SampleService)
- sleep 0.1 until log.any?
- expect(log).to be_present
- end
- end
- it 'starts a new thread' do
- thread = instance.send(:start_as_thread, SampleService)
- expect(thread).to be_alive
- end
- end
- end
|