flipper 0.22.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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") }
|