flipper 0.22.0 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
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