flipper 0.22.0 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +26 -20
- data/.github/workflows/examples.yml +62 -0
- data/.rspec +1 -0
- data/.tool-versions +1 -0
- data/Changelog.md +152 -3
- data/Dockerfile +1 -1
- data/Gemfile +9 -6
- data/README.md +15 -67
- data/Rakefile +4 -2
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +19 -0
- data/docker-compose.yml +37 -34
- data/docs/README.md +1 -0
- data/docs/images/banner.jpg +0 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/examples/dsl.rb +3 -3
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/instrumentation.rb +1 -0
- data/examples/instrumentation_last_accessed_at.rb +38 -0
- data/flipper.gemspec +2 -2
- data/lib/flipper/actor.rb +4 -0
- data/lib/flipper/adapter.rb +23 -7
- data/lib/flipper/adapters/dual_write.rb +10 -16
- data/lib/flipper/adapters/failover.rb +83 -0
- data/lib/flipper/adapters/failsafe.rb +76 -0
- data/lib/flipper/adapters/http/client.rb +18 -12
- data/lib/flipper/adapters/http/error.rb +19 -1
- data/lib/flipper/adapters/http.rb +14 -4
- data/lib/flipper/adapters/instrumented.rb +25 -2
- data/lib/flipper/adapters/memoizable.rb +27 -18
- data/lib/flipper/adapters/memory.rb +56 -39
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/adapters/poll/poller.rb +2 -0
- data/lib/flipper/adapters/poll.rb +39 -0
- data/lib/flipper/adapters/pstore.rb +2 -5
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
- data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
- data/lib/flipper/adapters/sync.rb +9 -15
- data/lib/flipper/dsl.rb +9 -11
- data/lib/flipper/errors.rb +3 -20
- data/lib/flipper/export.rb +26 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- data/lib/flipper/feature.rb +32 -26
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gate_values.rb +0 -16
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/group.rb +5 -7
- data/lib/flipper/gates/percentage_of_actors.rb +10 -13
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/instrumenters/memory.rb +6 -2
- data/lib/flipper/metadata.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +2 -12
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/railtie.rb +23 -22
- data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
- data/lib/flipper/test/shared_adapter_test.rb +24 -0
- data/lib/flipper/typecast.rb +28 -15
- data/lib/flipper/types/actor.rb +19 -13
- data/lib/flipper/types/group.rb +12 -5
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +6 -4
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/actor_spec.rb +10 -2
- data/spec/flipper/adapter_spec.rb +29 -4
- data/spec/flipper/adapters/dual_write_spec.rb +0 -2
- data/spec/flipper/adapters/failover_spec.rb +129 -0
- data/spec/flipper/adapters/failsafe_spec.rb +58 -0
- data/spec/flipper/adapters/http_spec.rb +64 -6
- data/spec/flipper/adapters/instrumented_spec.rb +28 -12
- data/spec/flipper/adapters/memoizable_spec.rb +30 -12
- data/spec/flipper/adapters/memory_spec.rb +3 -4
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
- data/spec/flipper/adapters/pstore_spec.rb +0 -2
- data/spec/flipper/adapters/read_only_spec.rb +0 -1
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync_spec.rb +0 -2
- data/spec/flipper/configuration_spec.rb +0 -1
- data/spec/flipper/dsl_spec.rb +38 -12
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- data/spec/flipper/feature_check_context_spec.rb +17 -19
- data/spec/flipper/feature_spec.rb +76 -33
- data/spec/flipper/gate_spec.rb +0 -2
- data/spec/flipper/gate_values_spec.rb +2 -34
- data/spec/flipper/gates/actor_spec.rb +0 -2
- data/spec/flipper/gates/boolean_spec.rb +1 -3
- data/spec/flipper/gates/group_spec.rb +2 -5
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
- data/spec/flipper/identifier_spec.rb +0 -1
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
- data/spec/flipper/instrumenters/memory_spec.rb +18 -1
- data/spec/flipper/instrumenters/noop_spec.rb +14 -8
- data/spec/flipper/middleware/memoizer_spec.rb +0 -23
- data/spec/flipper/middleware/setup_env_spec.rb +0 -2
- data/spec/flipper/poller_spec.rb +47 -0
- data/spec/flipper/railtie_spec.rb +73 -33
- data/spec/flipper/registry_spec.rb +0 -1
- data/spec/flipper/typecast_spec.rb +82 -4
- data/spec/flipper/types/actor_spec.rb +45 -46
- data/spec/flipper/types/boolean_spec.rb +0 -1
- data/spec/flipper/types/group_spec.rb +2 -3
- data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
- data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
- data/spec/flipper/types/percentage_spec.rb +0 -1
- data/spec/flipper_integration_spec.rb +62 -51
- data/spec/flipper_spec.rb +7 -2
- data/spec/{helper.rb → spec_helper.rb} +4 -2
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +2 -6
- metadata +63 -19
- data/docs/Adapters.md +0 -124
- data/docs/Caveats.md +0 -4
- data/docs/Gates.md +0 -167
- data/docs/Instrumentation.md +0 -27
- data/docs/Optimization.md +0 -137
- data/docs/api/README.md +0 -884
- data/docs/http/README.md +0 -36
- data/docs/read-only/README.md +0 -24
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/feature'
|
3
2
|
require 'flipper/instrumenters/memory'
|
4
3
|
|
@@ -33,6 +32,52 @@ RSpec.describe Flipper::Feature do
|
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
35
|
+
describe "#enabled?" do
|
36
|
+
context "for an actor" do
|
37
|
+
let(:actor) { Flipper::Actor.new("User;1") }
|
38
|
+
|
39
|
+
it 'returns true if feature is enabled' do
|
40
|
+
subject.enable
|
41
|
+
expect(subject.enabled?(actor)).to be(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns false if feature is disabled' do
|
45
|
+
subject.disable
|
46
|
+
expect(subject.enabled?(actor)).to be(false)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "for multiple actors" do
|
51
|
+
let(:actors) {
|
52
|
+
[
|
53
|
+
Flipper::Actor.new("User;1"),
|
54
|
+
Flipper::Actor.new("User;2"),
|
55
|
+
Flipper::Actor.new("User;3"),
|
56
|
+
]
|
57
|
+
}
|
58
|
+
|
59
|
+
it 'returns true if feature is enabled' do
|
60
|
+
subject.enable
|
61
|
+
expect(subject.enabled?(actors)).to be(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns true if feature is enabled for any actor' do
|
65
|
+
subject.enable_actor actors.first
|
66
|
+
expect(subject.enabled?(actors)).to be(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns true if feature is enabled for any actor with multiple arguments' do
|
70
|
+
subject.enable_actor actors.last
|
71
|
+
expect(subject.enabled?(*actors)).to be(true)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns false if feature is disabled for all actors' do
|
75
|
+
subject.disable
|
76
|
+
expect(subject.enabled?(actors)).to be(false)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
36
81
|
describe '#to_s' do
|
37
82
|
it 'returns name as string' do
|
38
83
|
feature = described_class.new(:search, adapter)
|
@@ -149,29 +194,29 @@ RSpec.describe Flipper::Feature do
|
|
149
194
|
end
|
150
195
|
|
151
196
|
it 'is recorded for enable' do
|
152
|
-
|
153
|
-
gate = subject.gate_for(
|
197
|
+
actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
|
198
|
+
gate = subject.gate_for(actor)
|
154
199
|
|
155
|
-
subject.enable(
|
200
|
+
subject.enable(actor)
|
156
201
|
|
157
202
|
event = instrumenter.events.last
|
158
203
|
expect(event).not_to be_nil
|
159
204
|
expect(event.name).to eq('feature_operation.flipper')
|
160
205
|
expect(event.payload[:feature_name]).to eq(:search)
|
161
206
|
expect(event.payload[:operation]).to eq(:enable)
|
162
|
-
expect(event.payload[:thing]).to eq(
|
207
|
+
expect(event.payload[:thing]).to eq(actor)
|
163
208
|
expect(event.payload[:result]).not_to be_nil
|
164
209
|
end
|
165
210
|
|
166
211
|
it 'always instruments flipper type instance for enable' do
|
167
|
-
|
168
|
-
gate = subject.gate_for(
|
212
|
+
actor = Flipper::Actor.new('1')
|
213
|
+
gate = subject.gate_for(actor)
|
169
214
|
|
170
|
-
subject.enable(
|
215
|
+
subject.enable(actor)
|
171
216
|
|
172
217
|
event = instrumenter.events.last
|
173
218
|
expect(event).not_to be_nil
|
174
|
-
expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(
|
219
|
+
expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
|
175
220
|
end
|
176
221
|
|
177
222
|
it 'is recorded for disable' do
|
@@ -220,15 +265,15 @@ RSpec.describe Flipper::Feature do
|
|
220
265
|
end
|
221
266
|
|
222
267
|
it 'always instruments flipper type instance for disable' do
|
223
|
-
|
224
|
-
gate = subject.gate_for(
|
268
|
+
actor = Flipper::Actor.new('1')
|
269
|
+
gate = subject.gate_for(actor)
|
225
270
|
|
226
|
-
subject.disable(
|
271
|
+
subject.disable(actor)
|
227
272
|
|
228
273
|
event = instrumenter.events.last
|
229
274
|
expect(event).not_to be_nil
|
230
275
|
expect(event.payload[:operation]).to eq(:disable)
|
231
|
-
expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(
|
276
|
+
expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
|
232
277
|
end
|
233
278
|
|
234
279
|
it 'is recorded for add' do
|
@@ -276,17 +321,15 @@ RSpec.describe Flipper::Feature do
|
|
276
321
|
end
|
277
322
|
|
278
323
|
it 'is recorded for enabled?' do
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
subject.enabled?(thing)
|
324
|
+
actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
|
325
|
+
subject.enabled?(actor)
|
283
326
|
|
284
327
|
event = instrumenter.events.last
|
285
328
|
expect(event).not_to be_nil
|
286
329
|
expect(event.name).to eq('feature_operation.flipper')
|
287
330
|
expect(event.payload[:feature_name]).to eq(:search)
|
288
331
|
expect(event.payload[:operation]).to eq(:enabled?)
|
289
|
-
expect(event.payload[:
|
332
|
+
expect(event.payload[:actors]).to eq([actor])
|
290
333
|
expect(event.payload[:result]).to eq(false)
|
291
334
|
end
|
292
335
|
|
@@ -294,8 +337,8 @@ RSpec.describe Flipper::Feature do
|
|
294
337
|
actor = Flipper::Types::Actor.new(user)
|
295
338
|
{
|
296
339
|
nil => nil,
|
297
|
-
user => actor,
|
298
|
-
actor => actor,
|
340
|
+
user => [actor],
|
341
|
+
actor => [actor],
|
299
342
|
}.each do |thing, wrapped_thing|
|
300
343
|
it "always instruments #{thing.inspect} as #{wrapped_thing.class} for enabled?" do
|
301
344
|
subject.enabled?(thing)
|
@@ -303,7 +346,7 @@ RSpec.describe Flipper::Feature do
|
|
303
346
|
event = instrumenter.events.last
|
304
347
|
expect(event).not_to be_nil
|
305
348
|
expect(event.payload[:operation]).to eq(:enabled?)
|
306
|
-
expect(event.payload[:
|
349
|
+
expect(event.payload[:actors]).to eq(wrapped_thing)
|
307
350
|
end
|
308
351
|
end
|
309
352
|
end
|
@@ -429,10 +472,10 @@ RSpec.describe Flipper::Feature do
|
|
429
472
|
|
430
473
|
context 'when one or more groups enabled' do
|
431
474
|
before do
|
432
|
-
@staff = Flipper.register(:staff) { |
|
433
|
-
@preview_features = Flipper.register(:preview_features) { |
|
434
|
-
@not_enabled = Flipper.register(:not_enabled) { |
|
435
|
-
@disabled = Flipper.register(:disabled) { |
|
475
|
+
@staff = Flipper.register(:staff) { |actor| true }
|
476
|
+
@preview_features = Flipper.register(:preview_features) { |actor| true }
|
477
|
+
@not_enabled = Flipper.register(:not_enabled) { |actor| true }
|
478
|
+
@disabled = Flipper.register(:disabled) { |actor| true }
|
436
479
|
subject.enable @staff
|
437
480
|
subject.enable @preview_features
|
438
481
|
subject.disable @disabled
|
@@ -468,10 +511,10 @@ RSpec.describe Flipper::Feature do
|
|
468
511
|
|
469
512
|
context 'when one or more groups enabled' do
|
470
513
|
before do
|
471
|
-
@staff = Flipper.register(:staff) { |
|
472
|
-
@preview_features = Flipper.register(:preview_features) { |
|
473
|
-
@not_enabled = Flipper.register(:not_enabled) { |
|
474
|
-
@disabled = Flipper.register(:disabled) { |
|
514
|
+
@staff = Flipper.register(:staff) { |actor| true }
|
515
|
+
@preview_features = Flipper.register(:preview_features) { |actor| true }
|
516
|
+
@not_enabled = Flipper.register(:not_enabled) { |actor| true }
|
517
|
+
@disabled = Flipper.register(:disabled) { |actor| true }
|
475
518
|
subject.enable @staff
|
476
519
|
subject.enable @preview_features
|
477
520
|
subject.disable @disabled
|
@@ -495,10 +538,10 @@ RSpec.describe Flipper::Feature do
|
|
495
538
|
|
496
539
|
context 'when one or more groups enabled' do
|
497
540
|
before do
|
498
|
-
@staff = Flipper.register(:staff) { |
|
499
|
-
@preview_features = Flipper.register(:preview_features) { |
|
500
|
-
@not_enabled = Flipper.register(:not_enabled) { |
|
501
|
-
@disabled = Flipper.register(:disabled) { |
|
541
|
+
@staff = Flipper.register(:staff) { |actor| true }
|
542
|
+
@preview_features = Flipper.register(:preview_features) { |actor| true }
|
543
|
+
@not_enabled = Flipper.register(:not_enabled) { |actor| true }
|
544
|
+
@disabled = Flipper.register(:disabled) { |actor| true }
|
502
545
|
subject.enable @staff
|
503
546
|
subject.enable @preview_features
|
504
547
|
subject.disable @disabled
|
data/spec/flipper/gate_spec.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/gate_values'
|
3
2
|
|
4
3
|
RSpec.describe Flipper::GateValues do
|
@@ -81,13 +80,13 @@ RSpec.describe Flipper::GateValues do
|
|
81
80
|
it 'raises argument error for percentage of time value that cannot be converted to an integer' do
|
82
81
|
expect do
|
83
82
|
described_class.new(percentage_of_time: ['asdf'])
|
84
|
-
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to
|
83
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a percentage))
|
85
84
|
end
|
86
85
|
|
87
86
|
it 'raises argument error for percentage of actors value that cannot be converted to an int' do
|
88
87
|
expect do
|
89
88
|
described_class.new(percentage_of_actors: ['asdf'])
|
90
|
-
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to
|
89
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a percentage))
|
91
90
|
end
|
92
91
|
|
93
92
|
it 'raises argument error for actors value that cannot be converted to a set' do
|
@@ -101,35 +100,4 @@ RSpec.describe Flipper::GateValues do
|
|
101
100
|
described_class.new(groups: 'asdf')
|
102
101
|
end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
|
103
102
|
end
|
104
|
-
|
105
|
-
describe '#[]' do
|
106
|
-
it 'can read the boolean value' do
|
107
|
-
expect(described_class.new(boolean: true)[:boolean]).to be(true)
|
108
|
-
expect(described_class.new(boolean: true)['boolean']).to be(true)
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'can read the actors value' do
|
112
|
-
expect(described_class.new(actors: Set[1, 2])[:actors]).to eq(Set[1, 2])
|
113
|
-
expect(described_class.new(actors: Set[1, 2])['actors']).to eq(Set[1, 2])
|
114
|
-
end
|
115
|
-
|
116
|
-
it 'can read the groups value' do
|
117
|
-
expect(described_class.new(groups: Set[:admins])[:groups]).to eq(Set[:admins])
|
118
|
-
expect(described_class.new(groups: Set[:admins])['groups']).to eq(Set[:admins])
|
119
|
-
end
|
120
|
-
|
121
|
-
it 'can read the percentage of time value' do
|
122
|
-
expect(described_class.new(percentage_of_time: 15)[:percentage_of_time]).to eq(15)
|
123
|
-
expect(described_class.new(percentage_of_time: 15)['percentage_of_time']).to eq(15)
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'can read the percentage of actors value' do
|
127
|
-
expect(described_class.new(percentage_of_actors: 15)[:percentage_of_actors]).to eq(15)
|
128
|
-
expect(described_class.new(percentage_of_actors: 15)['percentage_of_actors']).to eq(15)
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'returns nil for value that is not present' do
|
132
|
-
expect(described_class.new({})['not legit']).to be(nil)
|
133
|
-
end
|
134
|
-
end
|
135
103
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
1
|
RSpec.describe Flipper::Gates::Boolean do
|
4
2
|
let(:feature_name) { :search }
|
5
3
|
|
@@ -11,7 +9,7 @@ RSpec.describe Flipper::Gates::Boolean do
|
|
11
9
|
Flipper::FeatureCheckContext.new(
|
12
10
|
feature_name: feature_name,
|
13
11
|
values: Flipper::GateValues.new(boolean: bool),
|
14
|
-
|
12
|
+
actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
|
15
13
|
)
|
16
14
|
end
|
17
15
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
1
|
RSpec.describe Flipper::Gates::Group do
|
4
2
|
let(:feature_name) { :search }
|
5
3
|
|
@@ -11,18 +9,17 @@ RSpec.describe Flipper::Gates::Group do
|
|
11
9
|
Flipper::FeatureCheckContext.new(
|
12
10
|
feature_name: feature_name,
|
13
11
|
values: Flipper::GateValues.new(groups: set),
|
14
|
-
|
12
|
+
actors: [Flipper::Types::Actor.new(Flipper::Actor.new('5'))]
|
15
13
|
)
|
16
14
|
end
|
17
15
|
|
18
16
|
describe '#open?' do
|
19
17
|
context 'with a group in adapter, but not registered' do
|
20
18
|
before do
|
21
|
-
Flipper.register(:staff) { |
|
19
|
+
Flipper.register(:staff) { |actor| true }
|
22
20
|
end
|
23
21
|
|
24
22
|
it 'ignores group' do
|
25
|
-
thing = Flipper::Actor.new('5')
|
26
23
|
expect(subject.open?(context(Set[:newbs, :staff]))).to be(true)
|
27
24
|
end
|
28
25
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
1
|
RSpec.describe Flipper::Gates::PercentageOfActors do
|
4
2
|
let(:feature_name) { :search }
|
5
3
|
|
@@ -7,11 +5,11 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
|
|
7
5
|
described_class.new
|
8
6
|
end
|
9
7
|
|
10
|
-
def context(percentage_of_actors_value, feature = feature_name,
|
8
|
+
def context(percentage_of_actors_value, feature = feature_name, actors = nil)
|
11
9
|
Flipper::FeatureCheckContext.new(
|
12
10
|
feature_name: feature,
|
13
11
|
values: Flipper::GateValues.new(percentage_of_actors: percentage_of_actors_value),
|
14
|
-
|
12
|
+
actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
|
15
13
|
)
|
16
14
|
end
|
17
15
|
|
@@ -22,7 +20,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
|
|
22
20
|
let(:number_of_actors) { 10_000 }
|
23
21
|
|
24
22
|
let(:actors) do
|
25
|
-
(1..number_of_actors).map { |n| Flipper::Actor.new(n) }
|
23
|
+
(1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new(n.to_s)) }
|
26
24
|
end
|
27
25
|
|
28
26
|
let(:feature_one_enabled_actors) do
|
@@ -50,13 +48,69 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
|
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
51
|
+
context "with an array of actors" do
|
52
|
+
let(:percentage) { 0.05 }
|
53
|
+
let(:percentage_as_integer) { percentage * 100 }
|
54
|
+
let(:number_of_actors) { 3_000 }
|
55
|
+
|
56
|
+
let(:user_actors) do
|
57
|
+
(1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("User;#{n}")) }
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:team_actors) do
|
61
|
+
(1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("Team;#{n}")) }
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:org_actors) do
|
65
|
+
(1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("Org;#{n}")) }
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:actors) { user_actors + team_actors + org_actors }
|
69
|
+
|
70
|
+
let(:feature_one_enabled_actors) do
|
71
|
+
actors.each_slice(3).select do |group|
|
72
|
+
context = context(percentage_as_integer, :name_one, group)
|
73
|
+
subject.open?(context)
|
74
|
+
end.flatten
|
75
|
+
end
|
76
|
+
|
77
|
+
let(:feature_two_enabled_actors) do
|
78
|
+
actors.each_slice(3).select do |group|
|
79
|
+
context = context(percentage_as_integer, :name_two, group)
|
80
|
+
subject.open?(context)
|
81
|
+
end.flatten
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'does not enable both features for same set of actors' do
|
85
|
+
expect(feature_one_enabled_actors).not_to eq(feature_two_enabled_actors)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'enables feature for accurate number of actors for each feature' do
|
89
|
+
margin_of_error = 0.02 * actors.size # 2 percent margin of error
|
90
|
+
expected_enabled_size = actors.size * percentage
|
91
|
+
|
92
|
+
[
|
93
|
+
feature_one_enabled_actors.size,
|
94
|
+
feature_two_enabled_actors.size,
|
95
|
+
].each do |size|
|
96
|
+
expect(size).to be_within(margin_of_error).of(expected_enabled_size)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "is consistent regardless of order of actors" do
|
101
|
+
actors = user_actors.first(10)
|
102
|
+
results = 100.times.map { |n| subject.open?(context(75, :some_feature, actors.shuffle)) }
|
103
|
+
expect(results.uniq).to eq([true])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
53
107
|
context 'for fractional percentage' do
|
54
108
|
let(:decimal) { 0.001 }
|
55
109
|
let(:percentage) { decimal * 100 }
|
56
110
|
let(:number_of_actors) { 10_000 }
|
57
111
|
|
58
112
|
let(:actors) do
|
59
|
-
(1..number_of_actors).map { |n| Flipper::Actor.new(n) }
|
113
|
+
(1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new(n.to_s)) }
|
60
114
|
end
|
61
115
|
|
62
116
|
subject { described_class.new }
|
@@ -66,7 +120,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
|
|
66
120
|
expected_open_count = number_of_actors * decimal
|
67
121
|
|
68
122
|
open_count = actors.select do |actor|
|
69
|
-
context = context(percentage, :feature, actor)
|
123
|
+
context = context(percentage, :feature, [actor])
|
70
124
|
subject.open?(context)
|
71
125
|
end.size
|
72
126
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
1
|
RSpec.describe Flipper::Gates::PercentageOfTime do
|
4
2
|
let(:feature_name) { :search }
|
5
3
|
|
@@ -7,11 +5,11 @@ RSpec.describe Flipper::Gates::PercentageOfTime do
|
|
7
5
|
described_class.new
|
8
6
|
end
|
9
7
|
|
10
|
-
def context(percentage_of_time_value, feature = feature_name,
|
8
|
+
def context(percentage_of_time_value, feature = feature_name, actors = nil)
|
11
9
|
Flipper::FeatureCheckContext.new(
|
12
10
|
feature_name: feature,
|
13
11
|
values: Flipper::GateValues.new(percentage_of_time: percentage_of_time_value),
|
14
|
-
|
12
|
+
actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
|
15
13
|
)
|
16
14
|
end
|
17
15
|
|
@@ -1,8 +1,13 @@
|
|
1
1
|
require 'logger'
|
2
|
-
require 'helper'
|
3
2
|
require 'flipper/adapters/instrumented'
|
4
3
|
require 'flipper/instrumentation/log_subscriber'
|
5
4
|
|
5
|
+
begin
|
6
|
+
require 'active_support/isolated_execution_state'
|
7
|
+
rescue LoadError
|
8
|
+
# ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
|
9
|
+
end
|
10
|
+
|
6
11
|
RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
7
12
|
let(:adapter) do
|
8
13
|
memory = Flipper::Adapters::Memory.new
|
@@ -13,8 +18,8 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
13
18
|
end
|
14
19
|
|
15
20
|
before do
|
16
|
-
Flipper.register(:admins) do |
|
17
|
-
|
21
|
+
Flipper.register(:admins) do |actor|
|
22
|
+
actor.respond_to?(:admin?) && actor.admin?
|
18
23
|
end
|
19
24
|
|
20
25
|
@io = StringIO.new
|
@@ -27,6 +32,10 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
27
32
|
described_class.logger = nil
|
28
33
|
end
|
29
34
|
|
35
|
+
after(:all) do
|
36
|
+
ActiveSupport::Notifications.unsubscribe("flipper")
|
37
|
+
end
|
38
|
+
|
30
39
|
let(:log) { @io.string }
|
31
40
|
|
32
41
|
context 'feature enabled checks' do
|
@@ -37,7 +46,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
37
46
|
|
38
47
|
it 'logs feature calls with result after operation' do
|
39
48
|
feature_line = find_line('Flipper feature(search) enabled? false')
|
40
|
-
expect(feature_line).to include('[
|
49
|
+
expect(feature_line).to include('[ actors=nil ]')
|
41
50
|
end
|
42
51
|
|
43
52
|
it 'logs adapter calls' do
|
@@ -47,7 +56,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
47
56
|
end
|
48
57
|
end
|
49
58
|
|
50
|
-
context 'feature enabled checks with
|
59
|
+
context 'feature enabled checks with an actor' do
|
51
60
|
let(:user) { Flipper::Types::Actor.new(Flipper::Actor.new('1')) }
|
52
61
|
|
53
62
|
before do
|
@@ -55,7 +64,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
|
|
55
64
|
flipper[:search].enabled?(user)
|
56
65
|
end
|
57
66
|
|
58
|
-
it 'logs
|
67
|
+
it 'logs actors for feature' do
|
59
68
|
feature_line = find_line('Flipper feature(search) enabled?')
|
60
69
|
expect(feature_line).to include(user.inspect)
|
61
70
|
end
|
@@ -1,8 +1,13 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/adapters/instrumented'
|
3
2
|
require 'flipper/instrumentation/statsd'
|
4
3
|
require 'statsd'
|
5
4
|
|
5
|
+
begin
|
6
|
+
require 'active_support/isolated_execution_state'
|
7
|
+
rescue LoadError
|
8
|
+
# ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
|
9
|
+
end
|
10
|
+
|
6
11
|
RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
7
12
|
let(:statsd_client) { Statsd.new }
|
8
13
|
let(:socket) { FakeUDPSocket.new }
|
@@ -26,6 +31,10 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
|
26
31
|
Thread.current[:statsd_socket] = nil
|
27
32
|
end
|
28
33
|
|
34
|
+
after(:all) do
|
35
|
+
ActiveSupport::Notifications.unsubscribe("flipper")
|
36
|
+
end
|
37
|
+
|
29
38
|
def assert_timer(metric)
|
30
39
|
regex = /#{Regexp.escape metric}\:\d+\|ms/
|
31
40
|
result = socket.buffer.detect { |op| op.first =~ regex }
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'flipper/instrumenters/memory'
|
3
2
|
|
4
3
|
RSpec.describe Flipper::Instrumenters::Memory do
|
@@ -22,5 +21,23 @@ RSpec.describe Flipper::Instrumenters::Memory do
|
|
22
21
|
event = described_class::Event.new(name, payload, block_result)
|
23
22
|
expect(instrumenter.events).to eq([event])
|
24
23
|
end
|
24
|
+
|
25
|
+
context 'when an error is raised' do
|
26
|
+
subject do
|
27
|
+
instrumenter.instrument(:name) { raise IOError }
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:instrumenter) { described_class.new }
|
31
|
+
|
32
|
+
it 'captures and propagates the error' do
|
33
|
+
expect { subject }.to raise_error(IOError)
|
34
|
+
|
35
|
+
expect(instrumenter.events.count).to be 1
|
36
|
+
|
37
|
+
payload = instrumenter.events[0].payload
|
38
|
+
expect(payload.keys).to include(:exception, :exception_object)
|
39
|
+
expect(payload[:exception_object]).to be_a IOError
|
40
|
+
end
|
41
|
+
end
|
25
42
|
end
|
26
43
|
end
|
@@ -1,20 +1,26 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
1
|
RSpec.describe Flipper::Instrumenters::Noop do
|
4
2
|
describe '.instrument' do
|
5
3
|
context 'with name' do
|
6
4
|
it 'yields block' do
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
expect { |block|
|
6
|
+
described_class.instrument(:foo, &block)
|
7
|
+
}.to yield_control
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
13
11
|
context 'with name and payload' do
|
12
|
+
let(:payload) { { pay: :load } }
|
13
|
+
|
14
14
|
it 'yields block' do
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
expect { |block|
|
16
|
+
described_class.instrument(:foo, payload, &block)
|
17
|
+
}.to yield_control
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'yields the payload' do
|
21
|
+
described_class.instrument(:foo, payload) do |block_payload|
|
22
|
+
expect(block_payload).to eq payload
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
20
26
|
end
|
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'helper'
|
2
1
|
require 'rack/test'
|
3
2
|
require 'active_support/cache'
|
4
|
-
require 'active_support/cache/dalli_store'
|
5
3
|
require 'flipper/adapters/active_support_cache_store'
|
6
4
|
require 'flipper/adapters/operation_logger'
|
7
5
|
|
@@ -40,26 +38,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
40
38
|
expect(called).to eq(true)
|
41
39
|
end
|
42
40
|
|
43
|
-
it 'disables local cache after body close' do
|
44
|
-
app = ->(_env) { [200, {}, []] }
|
45
|
-
middleware = described_class.new(app)
|
46
|
-
body = middleware.call(env).last
|
47
|
-
|
48
|
-
expect(flipper.memoizing?).to eq(true)
|
49
|
-
body.close
|
50
|
-
expect(flipper.memoizing?).to eq(false)
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'clears local cache after body close' do
|
54
|
-
app = ->(_env) { [200, {}, []] }
|
55
|
-
middleware = described_class.new(app)
|
56
|
-
body = middleware.call(env).last
|
57
|
-
|
58
|
-
flipper.adapter.cache['hello'] = 'world'
|
59
|
-
body.close
|
60
|
-
expect(flipper.adapter.cache).to be_empty
|
61
|
-
end
|
62
|
-
|
63
41
|
it 'clears the local cache with a successful request' do
|
64
42
|
flipper.adapter.cache['hello'] = 'world'
|
65
43
|
get '/', {}, 'flipper' => flipper
|
@@ -362,7 +340,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
362
340
|
end.to_app
|
363
341
|
end
|
364
342
|
|
365
|
-
|
366
343
|
context 'and unless option' do
|
367
344
|
before do
|
368
345
|
options[:unless] = ->(request) { request.path.start_with?("/assets") }
|