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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/ci.yml +26 -20
  5. data/.github/workflows/examples.yml +62 -0
  6. data/.rspec +1 -0
  7. data/.tool-versions +1 -0
  8. data/Changelog.md +152 -3
  9. data/Dockerfile +1 -1
  10. data/Gemfile +9 -6
  11. data/README.md +15 -67
  12. data/Rakefile +4 -2
  13. data/benchmark/enabled_ips.rb +10 -0
  14. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  15. data/benchmark/enabled_profile.rb +20 -0
  16. data/benchmark/instrumentation_ips.rb +21 -0
  17. data/benchmark/typecast_ips.rb +19 -0
  18. data/docker-compose.yml +37 -34
  19. data/docs/README.md +1 -0
  20. data/docs/images/banner.jpg +0 -0
  21. data/examples/api/basic.ru +3 -4
  22. data/examples/api/custom_memoized.ru +3 -4
  23. data/examples/api/memoized.ru +3 -4
  24. data/examples/dsl.rb +3 -3
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/instrumentation.rb +1 -0
  27. data/examples/instrumentation_last_accessed_at.rb +38 -0
  28. data/flipper.gemspec +2 -2
  29. data/lib/flipper/actor.rb +4 -0
  30. data/lib/flipper/adapter.rb +23 -7
  31. data/lib/flipper/adapters/dual_write.rb +10 -16
  32. data/lib/flipper/adapters/failover.rb +83 -0
  33. data/lib/flipper/adapters/failsafe.rb +76 -0
  34. data/lib/flipper/adapters/http/client.rb +18 -12
  35. data/lib/flipper/adapters/http/error.rb +19 -1
  36. data/lib/flipper/adapters/http.rb +14 -4
  37. data/lib/flipper/adapters/instrumented.rb +25 -2
  38. data/lib/flipper/adapters/memoizable.rb +27 -18
  39. data/lib/flipper/adapters/memory.rb +56 -39
  40. data/lib/flipper/adapters/operation_logger.rb +16 -3
  41. data/lib/flipper/adapters/poll/poller.rb +2 -0
  42. data/lib/flipper/adapters/poll.rb +39 -0
  43. data/lib/flipper/adapters/pstore.rb +2 -5
  44. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
  45. data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
  46. data/lib/flipper/adapters/sync.rb +9 -15
  47. data/lib/flipper/dsl.rb +9 -11
  48. data/lib/flipper/errors.rb +3 -20
  49. data/lib/flipper/export.rb +26 -0
  50. data/lib/flipper/exporter.rb +17 -0
  51. data/lib/flipper/exporters/json/export.rb +32 -0
  52. data/lib/flipper/exporters/json/v1.rb +33 -0
  53. data/lib/flipper/feature.rb +32 -26
  54. data/lib/flipper/feature_check_context.rb +10 -6
  55. data/lib/flipper/gate.rb +12 -11
  56. data/lib/flipper/gate_values.rb +0 -16
  57. data/lib/flipper/gates/actor.rb +10 -17
  58. data/lib/flipper/gates/boolean.rb +1 -1
  59. data/lib/flipper/gates/group.rb +5 -7
  60. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  61. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  62. data/lib/flipper/identifier.rb +2 -2
  63. data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
  64. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  65. data/lib/flipper/instrumenters/memory.rb +6 -2
  66. data/lib/flipper/metadata.rb +1 -1
  67. data/lib/flipper/middleware/memoizer.rb +2 -12
  68. data/lib/flipper/poller.rb +117 -0
  69. data/lib/flipper/railtie.rb +23 -22
  70. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  71. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  72. data/lib/flipper/typecast.rb +28 -15
  73. data/lib/flipper/types/actor.rb +19 -13
  74. data/lib/flipper/types/group.rb +12 -5
  75. data/lib/flipper/version.rb +1 -1
  76. data/lib/flipper.rb +6 -4
  77. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  78. data/spec/flipper/actor_spec.rb +10 -2
  79. data/spec/flipper/adapter_spec.rb +29 -4
  80. data/spec/flipper/adapters/dual_write_spec.rb +0 -2
  81. data/spec/flipper/adapters/failover_spec.rb +129 -0
  82. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  83. data/spec/flipper/adapters/http_spec.rb +64 -6
  84. data/spec/flipper/adapters/instrumented_spec.rb +28 -12
  85. data/spec/flipper/adapters/memoizable_spec.rb +30 -12
  86. data/spec/flipper/adapters/memory_spec.rb +3 -4
  87. data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
  88. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  89. data/spec/flipper/adapters/read_only_spec.rb +0 -1
  90. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
  91. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
  92. data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
  93. data/spec/flipper/adapters/sync_spec.rb +0 -2
  94. data/spec/flipper/configuration_spec.rb +0 -1
  95. data/spec/flipper/dsl_spec.rb +38 -12
  96. data/spec/flipper/export_spec.rb +13 -0
  97. data/spec/flipper/exporter_spec.rb +16 -0
  98. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  99. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  100. data/spec/flipper/feature_check_context_spec.rb +17 -19
  101. data/spec/flipper/feature_spec.rb +76 -33
  102. data/spec/flipper/gate_spec.rb +0 -2
  103. data/spec/flipper/gate_values_spec.rb +2 -34
  104. data/spec/flipper/gates/actor_spec.rb +0 -2
  105. data/spec/flipper/gates/boolean_spec.rb +1 -3
  106. data/spec/flipper/gates/group_spec.rb +2 -5
  107. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
  108. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
  109. data/spec/flipper/identifier_spec.rb +0 -1
  110. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
  111. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
  112. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  113. data/spec/flipper/instrumenters/noop_spec.rb +14 -8
  114. data/spec/flipper/middleware/memoizer_spec.rb +0 -23
  115. data/spec/flipper/middleware/setup_env_spec.rb +0 -2
  116. data/spec/flipper/poller_spec.rb +47 -0
  117. data/spec/flipper/railtie_spec.rb +73 -33
  118. data/spec/flipper/registry_spec.rb +0 -1
  119. data/spec/flipper/typecast_spec.rb +82 -4
  120. data/spec/flipper/types/actor_spec.rb +45 -46
  121. data/spec/flipper/types/boolean_spec.rb +0 -1
  122. data/spec/flipper/types/group_spec.rb +2 -3
  123. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  124. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  125. data/spec/flipper/types/percentage_spec.rb +0 -1
  126. data/spec/flipper_integration_spec.rb +62 -51
  127. data/spec/flipper_spec.rb +7 -2
  128. data/spec/{helper.rb → spec_helper.rb} +4 -2
  129. data/spec/support/skippable.rb +18 -0
  130. data/spec/support/spec_helpers.rb +2 -6
  131. metadata +63 -19
  132. data/docs/Adapters.md +0 -124
  133. data/docs/Caveats.md +0 -4
  134. data/docs/Gates.md +0 -167
  135. data/docs/Instrumentation.md +0 -27
  136. data/docs/Optimization.md +0 -137
  137. data/docs/api/README.md +0 -884
  138. data/docs/http/README.md +0 -36
  139. 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
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
153
- gate = subject.gate_for(thing)
197
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
198
+ gate = subject.gate_for(actor)
154
199
 
155
- subject.enable(thing)
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(thing)
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
- thing = Flipper::Actor.new('1')
168
- gate = subject.gate_for(thing)
212
+ actor = Flipper::Actor.new('1')
213
+ gate = subject.gate_for(actor)
169
214
 
170
- subject.enable(thing)
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(thing))
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
- thing = Flipper::Actor.new('1')
224
- gate = subject.gate_for(thing)
268
+ actor = Flipper::Actor.new('1')
269
+ gate = subject.gate_for(actor)
225
270
 
226
- subject.disable(thing)
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(thing))
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
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
280
- gate = subject.gate_for(thing)
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[:thing]).to eq(thing)
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[:thing]).to eq(wrapped_thing)
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) { |_thing| true }
433
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
434
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
435
- @disabled = Flipper.register(:disabled) { |_thing| true }
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) { |_thing| true }
472
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
473
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
474
- @disabled = Flipper.register(:disabled) { |_thing| true }
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) { |_thing| true }
499
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
500
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
501
- @disabled = Flipper.register(:disabled) { |_thing| true }
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
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gate do
4
2
  let(:feature_name) { :stats }
5
3
 
@@ -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 an integer))
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 an integer))
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::Actor do
4
2
  let(:feature_name) { :search }
5
3
 
@@ -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
- thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
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
- thing: Flipper::Types::Actor.new(Flipper::Actor.new('5'))
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) { |_thing| true }
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, thing = nil)
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
- thing: thing || Flipper::Types::Actor.new(Flipper::Actor.new(1))
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, thing = nil)
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
- thing: thing || Flipper::Types::Actor.new(Flipper::Actor.new(1))
12
+ actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
15
13
  )
16
14
  end
17
15
 
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/identifier'
3
2
 
4
3
  RSpec.describe Flipper::Identifier do
@@ -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 |thing|
17
- thing.respond_to?(:admin?) && thing.admin?
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('[ thing=nil ]')
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 a thing' do
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 thing for feature' do
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
- yielded = false
8
- described_class.instrument(:foo) { yielded = true }
9
- expect(yielded).to eq(true)
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
- yielded = false
16
- described_class.instrument(:foo, pay: :load) { yielded = true }
17
- expect(yielded).to eq(true)
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") }
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Middleware::SetupEnv do
4
2
  context 'with flipper instance' do
5
3
  let(:app) do