flipper 0.7.5 → 0.8.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +12 -0
  3. data/Gemfile +1 -0
  4. data/Rakefile +9 -4
  5. data/docs/Optimization.md +8 -2
  6. data/lib/flipper/adapters/instrumented.rb +21 -17
  7. data/lib/flipper/adapters/memoizable.rb +20 -13
  8. data/lib/flipper/adapters/memory.rb +1 -1
  9. data/lib/flipper/adapters/operation_logger.rb +49 -12
  10. data/lib/flipper/adapters/pstore.rb +1 -1
  11. data/lib/flipper/adapters/read_only.rb +51 -0
  12. data/lib/flipper/dsl.rb +9 -0
  13. data/lib/flipper/feature.rb +8 -20
  14. data/lib/flipper/gate.rb +0 -4
  15. data/lib/flipper/gate_values.rb +4 -2
  16. data/lib/flipper/gates/actor.rb +1 -1
  17. data/lib/flipper/gates/boolean.rb +1 -1
  18. data/lib/flipper/gates/group.rb +1 -1
  19. data/lib/flipper/gates/percentage_of_actors.rb +3 -5
  20. data/lib/flipper/gates/percentage_of_time.rb +2 -3
  21. data/lib/flipper/instrumentation/log_subscriber.rb +0 -28
  22. data/lib/flipper/instrumentation/subscriber.rb +0 -22
  23. data/lib/flipper/middleware/memoizer.rb +2 -3
  24. data/lib/flipper/spec/shared_adapter_specs.rb +32 -0
  25. data/lib/flipper/test/shared_adapter_test.rb +249 -0
  26. data/lib/flipper/typecast.rb +1 -1
  27. data/lib/flipper/types/group.rb +1 -1
  28. data/lib/flipper/version.rb +1 -1
  29. data/spec/flipper/adapters/instrumented_spec.rb +6 -0
  30. data/spec/flipper/adapters/memoizable_spec.rb +6 -0
  31. data/spec/flipper/adapters/read_only_spec.rb +94 -0
  32. data/spec/flipper/dsl_spec.rb +14 -2
  33. data/spec/flipper/feature_spec.rb +11 -0
  34. data/spec/flipper/instrumentation/log_subscriber_spec.rb +0 -10
  35. data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +0 -10
  36. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +0 -10
  37. data/spec/flipper/types/group_spec.rb +14 -0
  38. data/spec/helper.rb +4 -0
  39. data/spec/integration_spec.rb +11 -0
  40. data/spec/support/spec_helpers.rb +4 -0
  41. data/test/adapters/memory_test.rb +10 -0
  42. data/test/adapters/pstore_test.rb +17 -0
  43. data/test/helper.rb +0 -2
  44. data/test/test_helper.rb +6 -0
  45. metadata +12 -4
  46. data/lib/flipper/adapters/decorator.rb +0 -11
  47. data/lib/flipper/decorator.rb +0 -6
@@ -5,7 +5,7 @@ module Flipper
5
5
  1 => true,
6
6
  "true" => true,
7
7
  "1" => true,
8
- }
8
+ }.freeze
9
9
 
10
10
  # Internal: Convert value to a boolean.
11
11
  #
@@ -15,7 +15,7 @@ module Flipper
15
15
  end
16
16
 
17
17
  def match?(*args)
18
- @block.call(*args) == true
18
+ @block.call(*args)
19
19
  end
20
20
  end
21
21
  end
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = "0.7.5"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -19,6 +19,12 @@ RSpec.describe Flipper::Adapters::Instrumented do
19
19
 
20
20
  it_should_behave_like 'a flipper adapter'
21
21
 
22
+ describe "#name" do
23
+ it "is instrumented" do
24
+ expect(subject.name).to be(:instrumented)
25
+ end
26
+ end
27
+
22
28
  describe "#get" do
23
29
  it "records instrumentation" do
24
30
  result = subject.get(feature)
@@ -13,6 +13,12 @@ RSpec.describe Flipper::Adapters::Memoizable do
13
13
 
14
14
  it_should_behave_like 'a flipper adapter'
15
15
 
16
+ describe "#name" do
17
+ it "is instrumented" do
18
+ expect(subject.name).to be(:memoizable)
19
+ end
20
+ end
21
+
16
22
  describe "#memoize=" do
17
23
  it "sets value" do
18
24
  subject.memoize = true
@@ -0,0 +1,94 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/read_only'
3
+
4
+ RSpec.describe Flipper::Adapters::ReadOnly do
5
+ let(:actor_class) { Struct.new(:flipper_id) }
6
+
7
+ let(:adapter) { Flipper::Adapters::Memory.new }
8
+ let(:flipper) { Flipper.new(subject) }
9
+ let(:feature) { flipper[:stats] }
10
+
11
+ let(:boolean_gate) { feature.gate(:boolean) }
12
+ let(:group_gate) { feature.gate(:group) }
13
+ let(:actor_gate) { feature.gate(:actor) }
14
+ let(:actors_gate) { feature.gate(:percentage_of_actors) }
15
+ let(:time_gate) { feature.gate(:percentage_of_time) }
16
+
17
+ subject { described_class.new(adapter) }
18
+
19
+ before do
20
+ Flipper.register(:admins) { |actor|
21
+ actor.respond_to?(:admin?) && actor.admin?
22
+ }
23
+
24
+ Flipper.register(:early_access) { |actor|
25
+ actor.respond_to?(:early_access?) && actor.early_access?
26
+ }
27
+ end
28
+
29
+ after do
30
+ Flipper.unregister_groups
31
+ end
32
+
33
+ it "has name that is a symbol" do
34
+ expect(subject.name).to_not be_nil
35
+ expect(subject.name).to be_instance_of(Symbol)
36
+ end
37
+
38
+ it "has included the flipper adapter module" do
39
+ expect(subject.class.ancestors).to include(Flipper::Adapter)
40
+ end
41
+
42
+ it "returns correct default values for the gates if none are enabled" do
43
+ expect(subject.get(feature)).to eq({
44
+ :boolean => nil,
45
+ :groups => Set.new,
46
+ :actors => Set.new,
47
+ :percentage_of_actors => nil,
48
+ :percentage_of_time => nil,
49
+ })
50
+ end
51
+
52
+ it "can get feature" do
53
+ actor_22 = actor_class.new('22')
54
+ adapter.enable(feature, boolean_gate, flipper.boolean)
55
+ adapter.enable(feature, group_gate, flipper.group(:admins))
56
+ adapter.enable(feature, actor_gate, flipper.actor(actor_22))
57
+ adapter.enable(feature, actors_gate, flipper.actors(25))
58
+ adapter.enable(feature, time_gate, flipper.time(45))
59
+
60
+ expect(subject.get(feature)).to eq({
61
+ :boolean => "true",
62
+ :groups => Set["admins"],
63
+ :actors => Set["22"],
64
+ :percentage_of_actors => "25",
65
+ :percentage_of_time => "45",
66
+ })
67
+ end
68
+
69
+ it "can get features" do
70
+ expect(subject.features).to eq(Set.new)
71
+ adapter.add(feature)
72
+ expect(subject.features).to eq(Set["stats"])
73
+ end
74
+
75
+ it "raises error on add" do
76
+ expect { subject.add(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
77
+ end
78
+
79
+ it "raises error on remove" do
80
+ expect { subject.remove(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
81
+ end
82
+
83
+ it "raises on clear" do
84
+ expect { subject.clear(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
85
+ end
86
+
87
+ it "raises error on enable" do
88
+ expect { subject.enable(feature, boolean_gate, flipper.boolean) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
89
+ end
90
+
91
+ it "raises error on disable" do
92
+ expect { subject.disable(feature, boolean_gate, flipper.boolean) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
93
+ end
94
+ end
@@ -26,9 +26,11 @@ RSpec.describe Flipper::DSL do
26
26
  expect(dsl.instrumenter).to be(instrumenter)
27
27
  end
28
28
 
29
- it "passes overridden instrumenter to adapter wrapping" do
29
+ it "passes overridden instrumenter to instrumented adapter" do
30
30
  dsl = described_class.new(adapter, :instrumenter => instrumenter)
31
- expect(dsl.adapter.instrumenter).to be(instrumenter)
31
+ memoized = dsl.adapter
32
+ instrumented = memoized.adapter
33
+ expect(instrumented.instrumenter).to be(instrumenter)
32
34
  end
33
35
  end
34
36
  end
@@ -233,4 +235,14 @@ RSpec.describe Flipper::DSL do
233
235
  expect(subject[:stats].percentage_of_actors_value).to be(0)
234
236
  end
235
237
  end
238
+
239
+ describe '#remove' do
240
+ it "removes the feature" do
241
+ subject.enable(:stats)
242
+
243
+ expect { subject.remove(:stats) }.to change { subject.enabled?(:stats) }.to(false)
244
+
245
+ expect(subject.features).to be_empty
246
+ end
247
+ end
236
248
  end
@@ -197,6 +197,17 @@ RSpec.describe Flipper::Feature do
197
197
  expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
198
198
  end
199
199
 
200
+ it "is recorded for remove" do
201
+ subject.remove
202
+
203
+ event = instrumenter.events.last
204
+ expect(event).not_to be_nil
205
+ expect(event.name).to eq('feature_operation.flipper')
206
+ expect(event.payload[:feature_name]).to eq(:search)
207
+ expect(event.payload[:operation]).to eq(:remove)
208
+ expect(event.payload[:result]).not_to be_nil
209
+ end
210
+
200
211
  it "is recorded for enabled?" do
201
212
  thing = Flipper::Types::Actor.new(Struct.new(:flipper_id).new("1"))
202
213
  gate = subject.gate_for(thing)
@@ -42,11 +42,6 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
42
42
  expect(adapter_line).to include('[ result={')
43
43
  expect(adapter_line).to include('} ]')
44
44
  end
45
-
46
- it "logs gate calls" do
47
- gate_line = find_line('Flipper feature(search) gate(boolean) open? false')
48
- expect(gate_line).to include('[ thing=nil ]')
49
- end
50
45
  end
51
46
 
52
47
  context "feature enabled checks with a thing" do
@@ -61,11 +56,6 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
61
56
  feature_line = find_line('Flipper feature(search) enabled?')
62
57
  expect(feature_line).to include(user.inspect)
63
58
  end
64
-
65
- it "logs thing for gate" do
66
- gate_line = find_line('Flipper feature(search) gate(boolean) open')
67
- expect(gate_line).to include(user.inspect)
68
- end
69
59
  end
70
60
 
71
61
  context "changing feature enabled state" do
@@ -46,14 +46,4 @@ RSpec.describe Flipper::Instrumentation::MetriksSubscriber do
46
46
  flipper[:stats].disable(user)
47
47
  expect(Metriks.timer("flipper.adapter.memory.disable").count).to be(1)
48
48
  end
49
-
50
- it "updates gate metrics when calls happen" do
51
- flipper[:stats].enable(user)
52
- flipper[:stats].enabled?(user)
53
-
54
- expect(Metriks.timer("flipper.gate_operation.boolean.open").count).to be(1)
55
- expect(Metriks.timer("flipper.feature.stats.gate_operation.boolean.open").count).to be(1)
56
- expect(Metriks.meter("flipper.feature.stats.gate.actor.open").count).to be(1)
57
- expect(Metriks.meter("flipper.feature.stats.gate.boolean.closed").count).to be(1)
58
- end
59
49
  end
@@ -65,14 +65,4 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
65
65
  flipper[:stats].disable(user)
66
66
  assert_timer 'flipper.adapter.memory.disable'
67
67
  end
68
-
69
- it "updates gate metrics when calls happen" do
70
- flipper[:stats].enable(user)
71
- flipper[:stats].enabled?(user)
72
-
73
- assert_timer 'flipper.gate_operation.boolean.open'
74
- assert_timer 'flipper.feature.stats.gate_operation.boolean.open'
75
- assert_counter 'flipper.feature.stats.gate.actor.open'
76
- assert_counter 'flipper.feature.stats.gate.boolean.closed'
77
- end
78
68
  end
@@ -48,5 +48,19 @@ RSpec.describe Flipper::Types::Group do
48
48
  it "returns false if block does not match" do
49
49
  expect(subject.match?(non_admin_actor)).to eq(false)
50
50
  end
51
+
52
+ it "returns true for truthy block results" do
53
+ group = Flipper::Types::Group.new(:examples) do |actor|
54
+ actor.email =~ /@example\.com/
55
+ end
56
+ expect(group.match?(double('Actor', :email => "foo@example.com"))).to be_truthy
57
+ end
58
+
59
+ it "returns false for falsey block results" do
60
+ group = Flipper::Types::Group.new(:examples) do |actor|
61
+ nil
62
+ end
63
+ expect(group.match?(double('Actor'))).to be_falsey
64
+ end
51
65
  end
52
66
  end
@@ -10,6 +10,7 @@ Bundler.setup(:default)
10
10
 
11
11
  require 'flipper'
12
12
  require 'flipper-ui'
13
+ require 'flipper-api'
13
14
 
14
15
  Dir[FlipperRoot.join("spec/support/**/*.rb")].each { |f| require f }
15
16
 
@@ -17,6 +18,9 @@ RSpec.configure do |config|
17
18
  config.before(:example) do
18
19
  Flipper.unregister_groups
19
20
  end
21
+
22
+ config.filter_run focus: true
23
+ config.run_all_when_everything_filtered = true
20
24
  end
21
25
 
22
26
  RSpec.shared_examples_for 'a percentage' do
@@ -15,6 +15,9 @@ RSpec.describe Flipper do
15
15
  let(:admin_thing) { double 'Non Flipper Thing', :flipper_id => 1, :admin? => true, :dev? => false }
16
16
  let(:dev_thing) { double 'Non Flipper Thing', :flipper_id => 10, :admin? => false, :dev? => true }
17
17
 
18
+ let(:admin_truthy_thing) { double 'Non Flipper Thing', :flipper_id => 1, :admin? => "true-ish", :dev? => false }
19
+ let(:admin_falsey_thing) { double 'Non Flipper Thing', :flipper_id => 1, :admin? => nil, :dev? => false }
20
+
18
21
  let(:pitt) { actor_class.new(1) }
19
22
  let(:clooney) { actor_class.new(10) }
20
23
 
@@ -347,6 +350,10 @@ RSpec.describe Flipper do
347
350
  expect(feature.enabled?(flipper.actor(admin_thing))).to eq(true)
348
351
  expect(feature.enabled?(admin_thing)).to eq(true)
349
352
  end
353
+
354
+ it "returns true for truthy block values" do
355
+ expect(feature.enabled?(flipper.actor(admin_truthy_thing))).to eq(true)
356
+ end
350
357
  end
351
358
 
352
359
  context "for actor in disabled group" do
@@ -354,6 +361,10 @@ RSpec.describe Flipper do
354
361
  expect(feature.enabled?(flipper.actor(dev_thing))).to eq(false)
355
362
  expect(feature.enabled?(dev_thing)).to eq(false)
356
363
  end
364
+
365
+ it "returns false for falsey block values" do
366
+ expect(feature.enabled?(flipper.actor(admin_falsey_thing))).to eq(false)
367
+ end
357
368
  end
358
369
 
359
370
  context "for enabled actor" do
@@ -14,6 +14,10 @@ module SpecHelpers
14
14
  }
15
15
  end
16
16
 
17
+ def build_api(flipper)
18
+ Flipper::Api.app(flipper)
19
+ end
20
+
17
21
  def build_flipper(adapter = build_memory_adapter)
18
22
  Flipper.new(adapter)
19
23
  end
@@ -0,0 +1,10 @@
1
+ require 'test_helper'
2
+ require 'flipper/adapters/memory'
3
+
4
+ class MemoryTest < MiniTest::Test
5
+ prepend SharedAdapterTests
6
+
7
+ def setup
8
+ @adapter = Flipper::Adapters::Memory.new
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+ require 'flipper/adapters/pstore'
3
+
4
+ class PstoreTest < MiniTest::Test
5
+ prepend SharedAdapterTests
6
+
7
+ def setup
8
+ dir = FlipperRoot.join("tmp").tap { |d| d.mkpath }
9
+ pstore_file = dir.join("flipper.pstore")
10
+ pstore_file.unlink if pstore_file.exist?
11
+ @adapter = Flipper::Adapters::PStore.new(pstore_file)
12
+ end
13
+
14
+ def test_defaults_path_to_flipper_pstore
15
+ assert_equal Flipper::Adapters::PStore.new.path, "flipper.pstore"
16
+ end
17
+ end
@@ -1,5 +1,3 @@
1
- $:.unshift(File.expand_path('../../lib', __FILE__))
2
-
3
1
  require 'rubygems'
4
2
  require 'bundler'
5
3
  Bundler.setup(:default)
@@ -0,0 +1,6 @@
1
+ require 'flipper'
2
+ require 'minitest/autorun'
3
+ require 'minitest/unit'
4
+ Dir["./lib/flipper/test/*.rb"].each { |f| require(f) }
5
+
6
+ FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-25 00:00:00.000000000 Z
11
+ date: 2016-06-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Feature flipper is the act of enabling/disabling features in your application,
14
14
  ideally without re-deploying or changing anything in your code base. Flipper makes
@@ -43,13 +43,12 @@ files:
43
43
  - flipper.gemspec
44
44
  - lib/flipper.rb
45
45
  - lib/flipper/adapter.rb
46
- - lib/flipper/adapters/decorator.rb
47
46
  - lib/flipper/adapters/instrumented.rb
48
47
  - lib/flipper/adapters/memoizable.rb
49
48
  - lib/flipper/adapters/memory.rb
50
49
  - lib/flipper/adapters/operation_logger.rb
51
50
  - lib/flipper/adapters/pstore.rb
52
- - lib/flipper/decorator.rb
51
+ - lib/flipper/adapters/read_only.rb
53
52
  - lib/flipper/dsl.rb
54
53
  - lib/flipper/errors.rb
55
54
  - lib/flipper/feature.rb
@@ -71,6 +70,7 @@ files:
71
70
  - lib/flipper/middleware/memoizer.rb
72
71
  - lib/flipper/registry.rb
73
72
  - lib/flipper/spec/shared_adapter_specs.rb
73
+ - lib/flipper/test/shared_adapter_test.rb
74
74
  - lib/flipper/type.rb
75
75
  - lib/flipper/typecast.rb
76
76
  - lib/flipper/types/actor.rb
@@ -85,6 +85,7 @@ files:
85
85
  - spec/flipper/adapters/memory_spec.rb
86
86
  - spec/flipper/adapters/operation_logger_spec.rb
87
87
  - spec/flipper/adapters/pstore_spec.rb
88
+ - spec/flipper/adapters/read_only_spec.rb
88
89
  - spec/flipper/dsl_spec.rb
89
90
  - spec/flipper/feature_spec.rb
90
91
  - spec/flipper/gate_spec.rb
@@ -113,7 +114,10 @@ files:
113
114
  - spec/integration_spec.rb
114
115
  - spec/support/fake_udp_socket.rb
115
116
  - spec/support/spec_helpers.rb
117
+ - test/adapters/memory_test.rb
118
+ - test/adapters/pstore_test.rb
116
119
  - test/helper.rb
120
+ - test/test_helper.rb
117
121
  homepage: https://github.com/jnunemaker/flipper
118
122
  licenses:
119
123
  - MIT
@@ -144,6 +148,7 @@ test_files:
144
148
  - spec/flipper/adapters/memory_spec.rb
145
149
  - spec/flipper/adapters/operation_logger_spec.rb
146
150
  - spec/flipper/adapters/pstore_spec.rb
151
+ - spec/flipper/adapters/read_only_spec.rb
147
152
  - spec/flipper/dsl_spec.rb
148
153
  - spec/flipper/feature_spec.rb
149
154
  - spec/flipper/gate_spec.rb
@@ -172,4 +177,7 @@ test_files:
172
177
  - spec/integration_spec.rb
173
178
  - spec/support/fake_udp_socket.rb
174
179
  - spec/support/spec_helpers.rb
180
+ - test/adapters/memory_test.rb
181
+ - test/adapters/pstore_test.rb
175
182
  - test/helper.rb
183
+ - test/test_helper.rb