1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::JobWaiter, :redis, feature_category: :shared do
describe '.notify' do
let(:key) { described_class.new.key }
it 'pushes the jid to the named queue', :freeze_time do
described_class.notify(key, 123)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.ttl(key)).to eq(described_class::DEFAULT_TTL)
end
end
it 'can be passed a custom TTL', :freeze_time do
described_class.notify(key, 123, ttl: 5.minutes)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.ttl(key)).to eq(5.minutes.to_i)
end
end
end
describe '.generate_key' do
it 'generates and return a new key' do
key = described_class.generate_key
expect(key).to include('gitlab:job_waiter:')
end
end
describe '.delete_key' do
let(:key) { described_class.generate_key }
it 'deletes the key' do
described_class.notify(key, '1')
described_class.delete_key(key)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.llen(key)).to eq(0)
end
end
context 'when key is not a JobWaiter key' do
let(:key) { 'foo' }
it 'does not delete the key' do
described_class.notify(key, '1')
described_class.delete_key(key)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.llen(key)).to eq(1)
end
end
end
end
describe '#wait' do
let(:waiter) { described_class.new(2) }
before do
allow_any_instance_of(described_class).to receive(:wait).and_call_original
stub_feature_flags(
use_primary_and_secondary_stores_for_shared_state: false,
use_primary_store_as_default_for_shared_state: false
)
end
it 'returns when all jobs have been completed' do
described_class.notify(waiter.key, 'a')
described_class.notify(waiter.key, 'b')
result = nil
expect { Timeout.timeout(1) { result = waiter.wait(2) } }.not_to raise_error
expect(result).to contain_exactly('a', 'b')
end
it 'times out if not all jobs complete' do
described_class.notify(waiter.key, 'a')
result = nil
expect { Timeout.timeout(2) { result = waiter.wait(1) } }.not_to raise_error
expect(result).to contain_exactly('a')
end
context 'when migration is ongoing' do
let(:waiter) { described_class.new(3) }
shared_examples 'returns all jobs' do
it 'returns all jobs' do
result = nil
expect { Timeout.timeout(6) { result = waiter.wait(5) } }.not_to raise_error
expect(result).to contain_exactly('a', 'b', 'c')
end
end
context 'when using both stores' do
context 'with existing jobs in old store' do
before do
described_class.notify(waiter.key, 'a')
described_class.notify(waiter.key, 'b')
described_class.notify(waiter.key, 'c')
stub_feature_flags(use_primary_and_secondary_stores_for_shared_state: true)
end
it_behaves_like 'returns all jobs'
end
context 'with jobs in both stores' do
before do
stub_feature_flags(use_primary_and_secondary_stores_for_shared_state: true)
described_class.notify(waiter.key, 'a')
described_class.notify(waiter.key, 'b')
described_class.notify(waiter.key, 'c')
end
it_behaves_like 'returns all jobs'
end
context 'when using primary store as default store' do
before do
stub_feature_flags(
use_primary_and_secondary_stores_for_shared_state: true,
use_primary_store_as_default_for_shared_state: true
)
described_class.notify(waiter.key, 'a')
described_class.notify(waiter.key, 'b')
described_class.notify(waiter.key, 'c')
end
it_behaves_like 'returns all jobs'
end
end
end
end
end
|