flipper 0.4.0 → 0.5.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.
- data/Guardfile +3 -8
- data/README.md +26 -38
- data/examples/percentage_of_actors.rb +17 -12
- data/examples/percentage_of_random.rb +3 -7
- data/lib/flipper.rb +8 -1
- data/lib/flipper/adapter.rb +2 -208
- data/lib/flipper/adapters/decorator.rb +9 -0
- data/lib/flipper/adapters/instrumented.rb +92 -0
- data/lib/flipper/adapters/memoizable.rb +88 -0
- data/lib/flipper/adapters/memory.rb +89 -7
- data/lib/flipper/adapters/operation_logger.rb +31 -45
- data/lib/flipper/decorator.rb +6 -0
- data/lib/flipper/dsl.rb +29 -2
- data/lib/flipper/feature.rb +83 -49
- data/lib/flipper/gate.rb +24 -41
- data/lib/flipper/gates/actor.rb +24 -24
- data/lib/flipper/gates/boolean.rb +28 -15
- data/lib/flipper/gates/group.rb +25 -34
- data/lib/flipper/gates/percentage_of_actors.rb +21 -13
- data/lib/flipper/gates/percentage_of_random.rb +20 -12
- data/lib/flipper/instrumentation/log_subscriber.rb +14 -22
- data/lib/flipper/middleware/memoizer.rb +23 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +141 -92
- data/lib/flipper/types/boolean.rb +5 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/instrumented_spec.rb +92 -0
- data/spec/flipper/adapters/memoizable_spec.rb +184 -0
- data/spec/flipper/adapters/memory_spec.rb +1 -11
- data/spec/flipper/adapters/operation_logger_spec.rb +93 -0
- data/spec/flipper/dsl_spec.rb +18 -43
- data/spec/flipper/feature_spec.rb +25 -9
- data/spec/flipper/gate_spec.rb +8 -20
- data/spec/flipper/gates/actor_spec.rb +6 -14
- data/spec/flipper/gates/boolean_spec.rb +80 -13
- data/spec/flipper/gates/group_spec.rb +8 -18
- data/spec/flipper/gates/percentage_of_actors_spec.rb +12 -28
- data/spec/flipper/gates/percentage_of_random_spec.rb +6 -14
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -8
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +3 -6
- data/spec/flipper/middleware/{local_cache_spec.rb → memoizer_spec.rb} +25 -55
- data/spec/flipper/types/boolean_spec.rb +13 -3
- data/spec/flipper_spec.rb +7 -0
- data/spec/helper.rb +21 -3
- data/spec/integration_spec.rb +115 -116
- metadata +17 -27
- data/lib/flipper/adapters/memoized.rb +0 -55
- data/lib/flipper/key.rb +0 -38
- data/lib/flipper/middleware/local_cache.rb +0 -36
- data/lib/flipper/toggle.rb +0 -54
- data/lib/flipper/toggles/boolean.rb +0 -54
- data/lib/flipper/toggles/set.rb +0 -25
- data/lib/flipper/toggles/value.rb +0 -25
- data/spec/flipper/adapter_spec.rb +0 -463
- data/spec/flipper/adapters/memoized_spec.rb +0 -93
- data/spec/flipper/key_spec.rb +0 -23
- data/spec/flipper/toggle_spec.rb +0 -22
- data/spec/flipper/toggles/boolean_spec.rb +0 -40
- data/spec/flipper/toggles/set_spec.rb +0 -35
- data/spec/flipper/toggles/value_spec.rb +0 -55
data/spec/flipper/gate_spec.rb
CHANGED
@@ -1,33 +1,26 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
describe Flipper::Gate do
|
4
|
-
let(:
|
5
|
-
let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
|
4
|
+
let(:feature_name) { :stats }
|
6
5
|
|
7
6
|
subject {
|
8
|
-
|
9
|
-
# implemented in subclass
|
10
|
-
gate.stub({
|
11
|
-
:key => :actors,
|
12
|
-
:description => 'enabled',
|
13
|
-
})
|
14
|
-
gate
|
7
|
+
described_class.new(feature_name)
|
15
8
|
}
|
16
9
|
|
17
10
|
describe "#initialize" do
|
18
|
-
it "sets
|
19
|
-
gate = described_class.new(
|
20
|
-
gate.
|
11
|
+
it "sets feature_name" do
|
12
|
+
gate = described_class.new(feature_name)
|
13
|
+
gate.feature_name.should be(feature_name)
|
21
14
|
end
|
22
15
|
|
23
16
|
it "defaults instrumenter" do
|
24
|
-
gate = described_class.new(
|
17
|
+
gate = described_class.new(feature_name)
|
25
18
|
gate.instrumenter.should be(Flipper::Instrumenters::Noop)
|
26
19
|
end
|
27
20
|
|
28
21
|
it "allows overriding instrumenter" do
|
29
22
|
instrumenter = double('Instrumentor')
|
30
|
-
gate = described_class.new(
|
23
|
+
gate = described_class.new(feature_name, :instrumenter => instrumenter)
|
31
24
|
gate.instrumenter.should be(instrumenter)
|
32
25
|
end
|
33
26
|
end
|
@@ -36,12 +29,7 @@ describe Flipper::Gate do
|
|
36
29
|
it "returns easy to read string representation" do
|
37
30
|
string = subject.inspect
|
38
31
|
string.should include('Flipper::Gate')
|
39
|
-
string.should include('
|
40
|
-
string.should include('description="enabled"')
|
41
|
-
string.should include("adapter=#{subject.adapter.name.inspect}")
|
42
|
-
string.should include('adapter_key=#<Flipper::Key:')
|
43
|
-
string.should include('toggle_class=Flipper::Toggles::Value')
|
44
|
-
string.should include('toggle_value="22"')
|
32
|
+
string.should include('feature_name=:stats')
|
45
33
|
end
|
46
34
|
end
|
47
35
|
end
|
@@ -2,18 +2,17 @@ require 'helper'
|
|
2
2
|
require 'flipper/instrumenters/memory'
|
3
3
|
|
4
4
|
describe Flipper::Gates::Actor do
|
5
|
-
let(:adapter) { double('Adapter', :set_members => []) }
|
6
|
-
let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
|
7
5
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
|
+
let(:feature_name) { :search }
|
8
7
|
|
9
8
|
subject {
|
10
|
-
described_class.new(
|
9
|
+
described_class.new(feature_name, :instrumenter => instrumenter)
|
11
10
|
}
|
12
11
|
|
13
12
|
describe "instrumentation" do
|
14
13
|
it "is recorded for open" do
|
15
14
|
thing = Struct.new(:flipper_id).new('22')
|
16
|
-
subject.open?(thing)
|
15
|
+
subject.open?(thing, Set.new)
|
17
16
|
|
18
17
|
event = instrumenter.events.last
|
19
18
|
event.should_not be_nil
|
@@ -30,22 +29,15 @@ describe Flipper::Gates::Actor do
|
|
30
29
|
|
31
30
|
describe "#description" do
|
32
31
|
context "with actors in set" do
|
33
|
-
before do
|
34
|
-
adapter.stub(:set_members => Set['bacon', 'ham'])
|
35
|
-
end
|
36
|
-
|
37
32
|
it "returns text" do
|
38
|
-
|
33
|
+
values = Set['bacon', 'ham']
|
34
|
+
subject.description(values).should eq('actors ("bacon", "ham")')
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
42
38
|
context "with no actors in set" do
|
43
|
-
before do
|
44
|
-
adapter.stub(:set_members => Set.new)
|
45
|
-
end
|
46
|
-
|
47
39
|
it "returns disabled" do
|
48
|
-
subject.description.should eq('disabled')
|
40
|
+
subject.description(Set.new).should eq('disabled')
|
49
41
|
end
|
50
42
|
end
|
51
43
|
end
|
@@ -2,32 +2,99 @@ require 'helper'
|
|
2
2
|
require 'flipper/instrumenters/memory'
|
3
3
|
|
4
4
|
describe Flipper::Gates::Boolean do
|
5
|
-
let(:adapter) { double('Adapter', :read => nil) }
|
6
|
-
let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
|
7
5
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
|
+
let(:feature_name) { :search }
|
8
7
|
|
9
8
|
subject {
|
10
|
-
described_class.new(
|
9
|
+
described_class.new(feature_name, :instrumenter => instrumenter)
|
11
10
|
}
|
12
11
|
|
13
12
|
describe "#description" do
|
14
13
|
context "for enabled" do
|
15
|
-
before do
|
16
|
-
subject.stub(:enabled? => true)
|
17
|
-
end
|
18
|
-
|
19
14
|
it "returns Enabled" do
|
20
|
-
subject.description.should eq('Enabled')
|
15
|
+
subject.description(true).should eq('Enabled')
|
21
16
|
end
|
22
17
|
end
|
23
18
|
|
24
19
|
context "for disabled" do
|
25
|
-
|
26
|
-
subject.
|
20
|
+
it "returns Disabled" do
|
21
|
+
subject.description(false).should eq('Disabled')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#enabled?" do
|
27
|
+
context "for true value" do
|
28
|
+
it "returns true" do
|
29
|
+
subject.enabled?(true).should be_true
|
27
30
|
end
|
31
|
+
end
|
28
32
|
|
29
|
-
|
30
|
-
|
33
|
+
context "for false value" do
|
34
|
+
it "returns false" do
|
35
|
+
subject.enabled?(false).should be_false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "for nil value" do
|
40
|
+
it "returns false" do
|
41
|
+
subject.enabled?(nil).should be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "for empty string value" do
|
46
|
+
it "returns false" do
|
47
|
+
subject.enabled?('').should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "for the string true value" do
|
52
|
+
it "returns true" do
|
53
|
+
subject.enabled?('true').should be_true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "for the string false value" do
|
58
|
+
it "returns false" do
|
59
|
+
subject.enabled?('false').should be_false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#open?" do
|
65
|
+
context "for true value" do
|
66
|
+
it "returns true" do
|
67
|
+
subject.open?(Object.new, true).should be_true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "for false value" do
|
72
|
+
it "returns false" do
|
73
|
+
subject.open?(Object.new, false).should be_false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "for nil value" do
|
78
|
+
it "returns false" do
|
79
|
+
subject.open?(Object.new, nil).should be_false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "for string true value" do
|
84
|
+
it "returns true" do
|
85
|
+
subject.open?(Object.new, 'true').should be_true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "for string false value" do
|
90
|
+
it "returns false" do
|
91
|
+
subject.open?(Object.new, 'false').should be_false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "for an empty string value" do
|
96
|
+
it "returns false" do
|
97
|
+
subject.open?(Object.new, '').should be_false
|
31
98
|
end
|
32
99
|
end
|
33
100
|
end
|
@@ -35,7 +102,7 @@ describe Flipper::Gates::Boolean do
|
|
35
102
|
describe "instrumentation" do
|
36
103
|
it "is recorded for open" do
|
37
104
|
thing = nil
|
38
|
-
subject.open?(thing)
|
105
|
+
subject.open?(thing, false)
|
39
106
|
|
40
107
|
event = instrumenter.events.last
|
41
108
|
event.should_not be_nil
|
@@ -2,18 +2,17 @@ require 'helper'
|
|
2
2
|
require 'flipper/instrumenters/memory'
|
3
3
|
|
4
4
|
describe Flipper::Gates::Group do
|
5
|
-
let(:adapter) { double('Adapter', :set_members => []) }
|
6
|
-
let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
|
7
5
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
|
+
let(:feature_name) { :search }
|
8
7
|
|
9
8
|
subject {
|
10
|
-
described_class.new(
|
9
|
+
described_class.new(feature_name, :instrumenter => instrumenter)
|
11
10
|
}
|
12
11
|
|
13
12
|
describe "instrumentation" do
|
14
13
|
it "is recorded for open" do
|
15
14
|
thing = Struct.new(:flipper_id).new('22')
|
16
|
-
subject.open?(thing)
|
15
|
+
subject.open?(thing, Set.new)
|
17
16
|
|
18
17
|
event = instrumenter.events.last
|
19
18
|
event.should_not be_nil
|
@@ -30,22 +29,15 @@ describe Flipper::Gates::Group do
|
|
30
29
|
|
31
30
|
describe "#description" do
|
32
31
|
context "with groups in set" do
|
33
|
-
before do
|
34
|
-
adapter.stub(:set_members => Set['bacon', 'ham'])
|
35
|
-
end
|
36
|
-
|
37
32
|
it "returns text" do
|
38
|
-
|
33
|
+
values = Set['bacon', 'ham']
|
34
|
+
subject.description(values).should eq('groups (:bacon, :ham)')
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
42
38
|
context "with no groups in set" do
|
43
|
-
before do
|
44
|
-
adapter.stub(:set_members => Set.new)
|
45
|
-
end
|
46
|
-
|
47
39
|
it "returns disabled" do
|
48
|
-
subject.description.should eq('disabled')
|
40
|
+
subject.description(Set.new).should eq('disabled')
|
49
41
|
end
|
50
42
|
end
|
51
43
|
end
|
@@ -54,24 +46,22 @@ describe Flipper::Gates::Group do
|
|
54
46
|
context "with a group in adapter, but not registered" do
|
55
47
|
before do
|
56
48
|
Flipper.register(:staff) { |thing| true }
|
57
|
-
adapter.stub(:set_members => Set[:newbs, :staff])
|
58
49
|
end
|
59
50
|
|
60
51
|
it "ignores group" do
|
61
52
|
thing = Struct.new(:flipper_id).new('5')
|
62
|
-
subject.open?(thing).should be_true
|
53
|
+
subject.open?(thing, Set[:newbs, :staff]).should be_true
|
63
54
|
end
|
64
55
|
end
|
65
56
|
|
66
57
|
context "thing that does not respond to method in group block" do
|
67
58
|
before do
|
68
59
|
Flipper.register(:stinkers) { |thing| thing.stinker? }
|
69
|
-
adapter.stub(:set_members => Set[:stinkers])
|
70
60
|
end
|
71
61
|
|
72
62
|
it "raises error" do
|
73
63
|
expect {
|
74
|
-
subject.open?(Object.new)
|
64
|
+
subject.open?(Object.new, Set[:stinkers])
|
75
65
|
}.to raise_error(NoMethodError)
|
76
66
|
end
|
77
67
|
end
|
@@ -2,18 +2,17 @@ require 'helper'
|
|
2
2
|
require 'flipper/instrumenters/memory'
|
3
3
|
|
4
4
|
describe Flipper::Gates::PercentageOfActors do
|
5
|
-
let(:adapter) { double('Adapter', :read => nil) }
|
6
|
-
let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
|
7
5
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
|
+
let(:feature_name) { :search }
|
8
7
|
|
9
8
|
subject {
|
10
|
-
described_class.new(
|
9
|
+
described_class.new(feature_name, :instrumenter => instrumenter)
|
11
10
|
}
|
12
11
|
|
13
12
|
describe "instrumentation" do
|
14
13
|
it "is recorded for open" do
|
15
14
|
thing = Struct.new(:flipper_id).new('22')
|
16
|
-
subject.open?(thing)
|
15
|
+
subject.open?(thing, 0)
|
17
16
|
|
18
17
|
event = instrumenter.events.last
|
19
18
|
event.should_not be_nil
|
@@ -30,22 +29,15 @@ describe Flipper::Gates::PercentageOfActors do
|
|
30
29
|
|
31
30
|
describe "#description" do
|
32
31
|
context "when enabled" do
|
33
|
-
before do
|
34
|
-
adapter.stub(:read => 22)
|
35
|
-
end
|
36
|
-
|
37
32
|
it "returns text" do
|
38
|
-
subject.description.should eq('22% of actors')
|
33
|
+
subject.description(22).should eq('22% of actors')
|
39
34
|
end
|
40
35
|
end
|
41
36
|
|
42
37
|
context "when disabled" do
|
43
|
-
before do
|
44
|
-
adapter.stub(:read => nil)
|
45
|
-
end
|
46
|
-
|
47
38
|
it "returns disabled" do
|
48
|
-
subject.description.should eq('disabled')
|
39
|
+
subject.description(nil).should eq('disabled')
|
40
|
+
subject.description(0).should eq('disabled')
|
49
41
|
end
|
50
42
|
end
|
51
43
|
end
|
@@ -53,29 +45,21 @@ describe Flipper::Gates::PercentageOfActors do
|
|
53
45
|
describe "#open?" do
|
54
46
|
context "when compared against two features" do
|
55
47
|
let(:percentage) { 0.05 }
|
48
|
+
let(:percentage_as_integer) { percentage * 100 }
|
56
49
|
let(:number_of_actors) { 100 }
|
57
50
|
|
58
51
|
let(:actors) {
|
59
|
-
(1..number_of_actors).map { |n|
|
60
|
-
Struct.new(:flipper_id).new(n)
|
61
|
-
}
|
52
|
+
(1..number_of_actors).map { |n| Struct.new(:flipper_id).new(n) }
|
62
53
|
}
|
63
54
|
|
64
55
|
let(:feature_one_enabled_actors) do
|
65
|
-
|
66
|
-
gate
|
67
|
-
actors.select { |actor| gate.open? actor }
|
56
|
+
gate = described_class.new(:name_one)
|
57
|
+
actors.select { |actor| gate.open? actor, percentage_as_integer }
|
68
58
|
end
|
69
59
|
|
70
60
|
let(:feature_two_enabled_actors) do
|
71
|
-
|
72
|
-
gate
|
73
|
-
actors.select { |actor| gate.open? actor }
|
74
|
-
end
|
75
|
-
|
76
|
-
before do
|
77
|
-
percentage_as_integer = percentage * 100
|
78
|
-
adapter.stub(:read => percentage_as_integer)
|
61
|
+
gate = described_class.new(:name_two)
|
62
|
+
actors.select { |actor| gate.open? actor, percentage_as_integer }
|
79
63
|
end
|
80
64
|
|
81
65
|
it "does not enable both features for same set of actors" do
|
@@ -2,18 +2,17 @@ require 'helper'
|
|
2
2
|
require 'flipper/instrumenters/memory'
|
3
3
|
|
4
4
|
describe Flipper::Gates::PercentageOfRandom do
|
5
|
-
let(:adapter) { double('Adapter', :read => 5) }
|
6
|
-
let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
|
7
5
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
|
+
let(:feature_name) { :search }
|
8
7
|
|
9
8
|
subject {
|
10
|
-
described_class.new(
|
9
|
+
described_class.new(feature_name, :instrumenter => instrumenter)
|
11
10
|
}
|
12
11
|
|
13
12
|
describe "instrumentation" do
|
14
13
|
it "is recorded for open" do
|
15
14
|
thing = Struct.new(:flipper_id).new('22')
|
16
|
-
subject.open?(thing)
|
15
|
+
subject.open?(thing, 0)
|
17
16
|
|
18
17
|
event = instrumenter.events.last
|
19
18
|
event.should_not be_nil
|
@@ -32,22 +31,15 @@ describe Flipper::Gates::PercentageOfRandom do
|
|
32
31
|
|
33
32
|
describe "#description" do
|
34
33
|
context "when enabled" do
|
35
|
-
before do
|
36
|
-
adapter.stub(:read => 22)
|
37
|
-
end
|
38
|
-
|
39
34
|
it "returns text" do
|
40
|
-
subject.description.should eq('22% of the time')
|
35
|
+
subject.description(22).should eq('22% of the time')
|
41
36
|
end
|
42
37
|
end
|
43
38
|
|
44
39
|
context "when disabled" do
|
45
|
-
before do
|
46
|
-
adapter.stub(:read => nil)
|
47
|
-
end
|
48
|
-
|
49
40
|
it "returns disabled" do
|
50
|
-
subject.description.should eq('disabled')
|
41
|
+
subject.description(nil).should eq('disabled')
|
42
|
+
subject.description(0).should eq('disabled')
|
51
43
|
end
|
52
44
|
end
|
53
45
|
end
|
@@ -37,8 +37,9 @@ describe Flipper::Instrumentation::LogSubscriber do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it "logs adapter calls" do
|
40
|
-
adapter_line = find_line('Flipper feature(search) adapter(memory)
|
41
|
-
adapter_line.should include('[ result=
|
40
|
+
adapter_line = find_line('Flipper feature(search) adapter(memory) get')
|
41
|
+
adapter_line.should include('[ result={')
|
42
|
+
adapter_line.should include('} ]')
|
42
43
|
end
|
43
44
|
|
44
45
|
it "logs gate calls" do
|
@@ -80,14 +81,20 @@ describe Flipper::Instrumentation::LogSubscriber do
|
|
80
81
|
end
|
81
82
|
|
82
83
|
it "logs adapter value" do
|
83
|
-
adapter_line = find_line('Flipper feature(search) adapter(memory)
|
84
|
-
adapter_line.should include("
|
84
|
+
adapter_line = find_line('Flipper feature(search) adapter(memory) enable')
|
85
|
+
adapter_line.should include("[ result=")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "getting all the features from the adapter" do
|
90
|
+
before do
|
91
|
+
clear_logs
|
92
|
+
flipper.features
|
85
93
|
end
|
86
94
|
|
87
|
-
it "logs adapter calls
|
88
|
-
adapter_line = find_line('Flipper adapter(memory)
|
89
|
-
|
90
|
-
log.should_not include('NoMethodError: undefined method')
|
95
|
+
it "logs adapter calls" do
|
96
|
+
adapter_line = find_line('Flipper adapter(memory) features')
|
97
|
+
adapter_line.should include('[ result=')
|
91
98
|
end
|
92
99
|
end
|
93
100
|
|