flipper 0.6.3 → 0.7.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/Gemfile +10 -10
- data/Guardfile +2 -1
- data/README.md +54 -23
- data/Rakefile +1 -1
- data/examples/dsl.rb +2 -2
- data/examples/{percentage_of_random.rb → percentage_of_time.rb} +1 -1
- data/lib/flipper.rb +25 -12
- data/lib/flipper/dsl.rb +96 -8
- data/lib/flipper/feature.rb +216 -37
- data/lib/flipper/gate.rb +6 -38
- data/lib/flipper/gate_values.rb +42 -0
- data/lib/flipper/gates/actor.rb +11 -13
- data/lib/flipper/gates/boolean.rb +3 -12
- data/lib/flipper/gates/group.rb +14 -16
- data/lib/flipper/gates/percentage_of_actors.rb +12 -13
- data/lib/flipper/gates/{percentage_of_random.rb → percentage_of_time.rb} +8 -11
- data/lib/flipper/instrumentation/log_subscriber.rb +1 -1
- data/lib/flipper/instrumentation/subscriber.rb +1 -1
- data/lib/flipper/spec/shared_adapter_specs.rb +16 -16
- data/lib/flipper/type.rb +1 -1
- data/lib/flipper/typecast.rb +44 -0
- data/lib/flipper/types/actor.rb +2 -5
- data/lib/flipper/types/group.rb +6 -0
- data/lib/flipper/types/percentage.rb +7 -1
- data/lib/flipper/types/{percentage_of_random.rb → percentage_of_time.rb} +1 -1
- data/lib/flipper/version.rb +1 -1
- data/script/bootstrap +21 -0
- data/script/guard +15 -0
- data/script/release +15 -0
- data/script/test +30 -0
- data/spec/flipper/dsl_spec.rb +67 -8
- data/spec/flipper/feature_spec.rb +424 -12
- data/spec/flipper/gate_spec.rb +1 -20
- data/spec/flipper/gate_values_spec.rb +134 -0
- data/spec/flipper/gates/actor_spec.rb +1 -21
- data/spec/flipper/gates/boolean_spec.rb +3 -71
- data/spec/flipper/gates/group_spec.rb +3 -23
- data/spec/flipper/gates/percentage_of_actors_spec.rb +5 -26
- data/spec/flipper/gates/percentage_of_time_spec.rb +23 -0
- data/spec/flipper/middleware/memoizer_spec.rb +1 -2
- data/spec/flipper/typecast_spec.rb +63 -0
- data/spec/flipper/types/group_spec.rb +21 -1
- data/spec/flipper/types/percentage_of_time_spec.rb +6 -0
- data/spec/flipper/types/percentage_spec.rb +20 -0
- data/spec/flipper_spec.rb +31 -9
- data/spec/helper.rb +1 -3
- data/spec/integration_spec.rb +22 -22
- metadata +21 -11
- data/spec/flipper/gates/percentage_of_random_spec.rb +0 -46
- data/spec/flipper/types/percentage_of_random_spec.rb +0 -6
data/spec/flipper/gate_spec.rb
CHANGED
@@ -4,32 +4,13 @@ describe Flipper::Gate do
|
|
4
4
|
let(:feature_name) { :stats }
|
5
5
|
|
6
6
|
subject {
|
7
|
-
described_class.new
|
7
|
+
described_class.new
|
8
8
|
}
|
9
9
|
|
10
|
-
describe "#initialize" do
|
11
|
-
it "sets feature_name" do
|
12
|
-
gate = described_class.new(feature_name)
|
13
|
-
gate.feature_name.should be(feature_name)
|
14
|
-
end
|
15
|
-
|
16
|
-
it "defaults instrumenter" do
|
17
|
-
gate = described_class.new(feature_name)
|
18
|
-
gate.instrumenter.should be(Flipper::Instrumenters::Noop)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "allows overriding instrumenter" do
|
22
|
-
instrumenter = double('Instrumentor')
|
23
|
-
gate = described_class.new(feature_name, :instrumenter => instrumenter)
|
24
|
-
gate.instrumenter.should be(instrumenter)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
10
|
describe "#inspect" do
|
29
11
|
it "returns easy to read string representation" do
|
30
12
|
string = subject.inspect
|
31
13
|
string.should include('Flipper::Gate')
|
32
|
-
string.should include('feature_name=:stats')
|
33
14
|
end
|
34
15
|
end
|
35
16
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'flipper/gate_values'
|
3
|
+
|
4
|
+
describe Flipper::GateValues do
|
5
|
+
{
|
6
|
+
nil => false,
|
7
|
+
"" => false,
|
8
|
+
0 => false,
|
9
|
+
1 => true,
|
10
|
+
"0" => false,
|
11
|
+
"1" => true,
|
12
|
+
true => true,
|
13
|
+
false => false,
|
14
|
+
"true" => true,
|
15
|
+
"false" => false,
|
16
|
+
}.each do |value, expected|
|
17
|
+
context "with #{value.inspect} boolean" do
|
18
|
+
it "returns #{expected}" do
|
19
|
+
described_class.new(boolean: value).boolean.should be(expected)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
{
|
25
|
+
nil => 0,
|
26
|
+
"" => 0,
|
27
|
+
0 => 0,
|
28
|
+
1 => 1,
|
29
|
+
"1" => 1,
|
30
|
+
"99" => 99,
|
31
|
+
}.each do |value, expected|
|
32
|
+
context "with #{value.inspect} percentage of time" do
|
33
|
+
it "returns #{expected}" do
|
34
|
+
described_class.new(percentage_of_time: value).percentage_of_time.should be(expected)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
{
|
40
|
+
nil => 0,
|
41
|
+
"" => 0,
|
42
|
+
0 => 0,
|
43
|
+
1 => 1,
|
44
|
+
"1" => 1,
|
45
|
+
"99" => 99,
|
46
|
+
}.each do |value, expected|
|
47
|
+
context "with #{value.inspect} percentage of actors" do
|
48
|
+
it "returns #{expected}" do
|
49
|
+
described_class.new(percentage_of_actors: value).percentage_of_actors.should be(expected)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
{
|
55
|
+
nil => Set.new,
|
56
|
+
"" => Set.new,
|
57
|
+
Set.new([1, 2]) => Set.new([1, 2]),
|
58
|
+
[1, 2] => Set.new([1, 2])
|
59
|
+
}.each do |value, expected|
|
60
|
+
context "with #{value.inspect} actors" do
|
61
|
+
it "returns #{expected}" do
|
62
|
+
described_class.new(actors: value).actors.should eq(expected)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
{
|
68
|
+
nil => Set.new,
|
69
|
+
"" => Set.new,
|
70
|
+
Set.new([:admins, :preview_features]) => Set.new([:admins, :preview_features]),
|
71
|
+
[:admins, :preview_features] => Set.new([:admins, :preview_features])
|
72
|
+
}.each do |value, expected|
|
73
|
+
context "with #{value.inspect} groups" do
|
74
|
+
it "returns #{expected}" do
|
75
|
+
described_class.new(groups: value).groups.should eq(expected)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "raises argument error for percentage of time value that cannot be converted to an integer" do
|
81
|
+
expect {
|
82
|
+
described_class.new(percentage_of_time: ["asdf"])
|
83
|
+
}.to raise_error(ArgumentError, %Q(["asdf"] cannot be converted to an integer))
|
84
|
+
end
|
85
|
+
|
86
|
+
it "raises argument error for percentage of actors value that cannot be converted to an integer" do
|
87
|
+
expect {
|
88
|
+
described_class.new(percentage_of_actors: ["asdf"])
|
89
|
+
}.to raise_error(ArgumentError, %Q(["asdf"] cannot be converted to an integer))
|
90
|
+
end
|
91
|
+
|
92
|
+
it "raises argument error for actors value that cannot be converted to a set" do
|
93
|
+
expect {
|
94
|
+
described_class.new(actors: "asdf")
|
95
|
+
}.to raise_error(ArgumentError, %Q("asdf" cannot be converted to a set))
|
96
|
+
end
|
97
|
+
|
98
|
+
it "raises argument error for groups value that cannot be converted to a set" do
|
99
|
+
expect {
|
100
|
+
described_class.new(groups: "asdf")
|
101
|
+
}.to raise_error(ArgumentError, %Q("asdf" cannot be converted to a set))
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#[]" do
|
105
|
+
it "can read the boolean value" do
|
106
|
+
described_class.new(boolean: true)[:boolean].should be(true)
|
107
|
+
described_class.new(boolean: true)["boolean"].should be(true)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "can read the actors value" do
|
111
|
+
described_class.new(actors: Set[1, 2])[:actors].should eq(Set[1, 2])
|
112
|
+
described_class.new(actors: Set[1, 2])["actors"].should eq(Set[1, 2])
|
113
|
+
end
|
114
|
+
|
115
|
+
it "can read the groups value" do
|
116
|
+
described_class.new(groups: Set[:admins])[:groups].should eq(Set[:admins])
|
117
|
+
described_class.new(groups: Set[:admins])["groups"].should eq(Set[:admins])
|
118
|
+
end
|
119
|
+
|
120
|
+
it "can read the percentage of time value" do
|
121
|
+
described_class.new(percentage_of_time: 15)[:percentage_of_time].should eq(15)
|
122
|
+
described_class.new(percentage_of_time: 15)["percentage_of_time"].should eq(15)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "can read the percentage of actors value" do
|
126
|
+
described_class.new(percentage_of_actors: 15)[:percentage_of_actors].should eq(15)
|
127
|
+
described_class.new(percentage_of_actors: 15)["percentage_of_actors"].should eq(15)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns nil for value that is not present" do
|
131
|
+
described_class.new({})["not legit"].should be(nil)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -1,32 +1,12 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'flipper/instrumenters/memory'
|
3
2
|
|
4
3
|
describe Flipper::Gates::Actor do
|
5
|
-
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
4
|
let(:feature_name) { :search }
|
7
5
|
|
8
6
|
subject {
|
9
|
-
described_class.new
|
7
|
+
described_class.new
|
10
8
|
}
|
11
9
|
|
12
|
-
describe "instrumentation" do
|
13
|
-
it "is recorded for open" do
|
14
|
-
thing = Struct.new(:flipper_id).new('22')
|
15
|
-
subject.open?(thing, Set.new)
|
16
|
-
|
17
|
-
event = instrumenter.events.last
|
18
|
-
event.should_not be_nil
|
19
|
-
event.name.should eq('gate_operation.flipper')
|
20
|
-
event.payload.should eq({
|
21
|
-
:thing => thing,
|
22
|
-
:operation => :open?,
|
23
|
-
:result => false,
|
24
|
-
:gate_name => :actor,
|
25
|
-
:feature_name => :search,
|
26
|
-
})
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
10
|
describe "#description" do
|
31
11
|
context "with actors in set" do
|
32
12
|
it "returns text" do
|
@@ -1,12 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'flipper/instrumenters/memory'
|
3
2
|
|
4
3
|
describe Flipper::Gates::Boolean do
|
5
|
-
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
4
|
let(:feature_name) { :search }
|
7
5
|
|
8
6
|
subject {
|
9
|
-
described_class.new
|
7
|
+
described_class.new
|
10
8
|
}
|
11
9
|
|
12
10
|
describe "#description" do
|
@@ -35,85 +33,19 @@ describe Flipper::Gates::Boolean do
|
|
35
33
|
subject.enabled?(false).should eq(false)
|
36
34
|
end
|
37
35
|
end
|
38
|
-
|
39
|
-
context "for nil value" do
|
40
|
-
it "returns false" do
|
41
|
-
subject.enabled?(nil).should eq(false)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context "for empty string value" do
|
46
|
-
it "returns false" do
|
47
|
-
subject.enabled?('').should eq(false)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
context "for the string true value" do
|
52
|
-
it "returns true" do
|
53
|
-
subject.enabled?('true').should eq(true)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
context "for the string false value" do
|
58
|
-
it "returns false" do
|
59
|
-
subject.enabled?('false').should eq(false)
|
60
|
-
end
|
61
|
-
end
|
62
36
|
end
|
63
37
|
|
64
38
|
describe "#open?" do
|
65
39
|
context "for true value" do
|
66
40
|
it "returns true" do
|
67
|
-
subject.open?(Object.new, true).should eq(true)
|
41
|
+
subject.open?(Object.new, true, feature_name: feature_name).should eq(true)
|
68
42
|
end
|
69
43
|
end
|
70
44
|
|
71
45
|
context "for false value" do
|
72
46
|
it "returns false" do
|
73
|
-
subject.open?(Object.new, false).should eq(false)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
context "for nil value" do
|
78
|
-
it "returns false" do
|
79
|
-
subject.open?(Object.new, nil).should eq(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 eq(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 eq(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 eq(false)
|
47
|
+
subject.open?(Object.new, false, feature_name: feature_name).should eq(false)
|
98
48
|
end
|
99
49
|
end
|
100
50
|
end
|
101
|
-
|
102
|
-
describe "instrumentation" do
|
103
|
-
it "is recorded for open" do
|
104
|
-
thing = nil
|
105
|
-
subject.open?(thing, false)
|
106
|
-
|
107
|
-
event = instrumenter.events.last
|
108
|
-
event.should_not be_nil
|
109
|
-
event.name.should eq('gate_operation.flipper')
|
110
|
-
event.payload.should eq({
|
111
|
-
:thing => thing,
|
112
|
-
:operation => :open?,
|
113
|
-
:result => false,
|
114
|
-
:gate_name => :boolean,
|
115
|
-
:feature_name => :search,
|
116
|
-
})
|
117
|
-
end
|
118
|
-
end
|
119
51
|
end
|
@@ -1,32 +1,12 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'flipper/instrumenters/memory'
|
3
2
|
|
4
3
|
describe Flipper::Gates::Group do
|
5
|
-
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
4
|
let(:feature_name) { :search }
|
7
5
|
|
8
6
|
subject {
|
9
|
-
described_class.new
|
7
|
+
described_class.new
|
10
8
|
}
|
11
9
|
|
12
|
-
describe "instrumentation" do
|
13
|
-
it "is recorded for open" do
|
14
|
-
thing = Struct.new(:flipper_id).new('22')
|
15
|
-
subject.open?(thing, Set.new)
|
16
|
-
|
17
|
-
event = instrumenter.events.last
|
18
|
-
event.should_not be_nil
|
19
|
-
event.name.should eq('gate_operation.flipper')
|
20
|
-
event.payload.should eq({
|
21
|
-
:thing => thing,
|
22
|
-
:operation => :open?,
|
23
|
-
:result => false,
|
24
|
-
:gate_name => :group,
|
25
|
-
:feature_name => :search,
|
26
|
-
})
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
10
|
describe "#description" do
|
31
11
|
context "with groups in set" do
|
32
12
|
it "returns text" do
|
@@ -50,7 +30,7 @@ describe Flipper::Gates::Group do
|
|
50
30
|
|
51
31
|
it "ignores group" do
|
52
32
|
thing = Struct.new(:flipper_id).new('5')
|
53
|
-
subject.open?(thing, Set[:newbs, :staff]).should eq(true)
|
33
|
+
subject.open?(thing, Set[:newbs, :staff], feature_name: feature_name).should eq(true)
|
54
34
|
end
|
55
35
|
end
|
56
36
|
|
@@ -61,7 +41,7 @@ describe Flipper::Gates::Group do
|
|
61
41
|
|
62
42
|
it "raises error" do
|
63
43
|
expect {
|
64
|
-
subject.open?(Object.new, Set[:stinkers])
|
44
|
+
subject.open?(Object.new, Set[:stinkers], feature_name: feature_name)
|
65
45
|
}.to raise_error(NoMethodError)
|
66
46
|
end
|
67
47
|
end
|
@@ -1,32 +1,12 @@
|
|
1
1
|
require 'helper'
|
2
|
-
require 'flipper/instrumenters/memory'
|
3
2
|
|
4
3
|
describe Flipper::Gates::PercentageOfActors do
|
5
|
-
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
6
4
|
let(:feature_name) { :search }
|
7
5
|
|
8
6
|
subject {
|
9
|
-
described_class.new
|
7
|
+
described_class.new
|
10
8
|
}
|
11
9
|
|
12
|
-
describe "instrumentation" do
|
13
|
-
it "is recorded for open" do
|
14
|
-
thing = Struct.new(:flipper_id).new('22')
|
15
|
-
subject.open?(thing, 0)
|
16
|
-
|
17
|
-
event = instrumenter.events.last
|
18
|
-
event.should_not be_nil
|
19
|
-
event.name.should eq('gate_operation.flipper')
|
20
|
-
event.payload.should eq({
|
21
|
-
:thing => thing,
|
22
|
-
:operation => :open?,
|
23
|
-
:result => false,
|
24
|
-
:gate_name => :percentage_of_actors,
|
25
|
-
:feature_name => :search,
|
26
|
-
})
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
10
|
describe "#description" do
|
31
11
|
context "when enabled" do
|
32
12
|
it "returns text" do
|
@@ -36,7 +16,6 @@ describe Flipper::Gates::PercentageOfActors do
|
|
36
16
|
|
37
17
|
context "when disabled" do
|
38
18
|
it "returns disabled" do
|
39
|
-
subject.description(nil).should eq('disabled')
|
40
19
|
subject.description(0).should eq('disabled')
|
41
20
|
end
|
42
21
|
end
|
@@ -53,13 +32,13 @@ describe Flipper::Gates::PercentageOfActors do
|
|
53
32
|
}
|
54
33
|
|
55
34
|
let(:feature_one_enabled_actors) do
|
56
|
-
gate = described_class.new
|
57
|
-
actors.select { |actor| gate.open? actor, percentage_as_integer }
|
35
|
+
gate = described_class.new
|
36
|
+
actors.select { |actor| gate.open? actor, percentage_as_integer, feature_name: :name_one }
|
58
37
|
end
|
59
38
|
|
60
39
|
let(:feature_two_enabled_actors) do
|
61
|
-
gate = described_class.new
|
62
|
-
actors.select { |actor| gate.open? actor, percentage_as_integer }
|
40
|
+
gate = described_class.new
|
41
|
+
actors.select { |actor| gate.open? actor, percentage_as_integer, feature_name: :name_two }
|
63
42
|
end
|
64
43
|
|
65
44
|
it "does not enable both features for same set of actors" do
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Flipper::Gates::PercentageOfTime do
|
4
|
+
let(:feature_name) { :search }
|
5
|
+
|
6
|
+
subject {
|
7
|
+
described_class.new
|
8
|
+
}
|
9
|
+
|
10
|
+
describe "#description" do
|
11
|
+
context "when enabled" do
|
12
|
+
it "returns text" do
|
13
|
+
subject.description(22).should eq('22% of the time')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when disabled" do
|
18
|
+
it "returns disabled" do
|
19
|
+
subject.description(0).should eq('disabled')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -7,8 +7,7 @@ require 'flipper/adapters/memory'
|
|
7
7
|
describe Flipper::Middleware::Memoizer do
|
8
8
|
include Rack::Test::Methods
|
9
9
|
|
10
|
-
let(:
|
11
|
-
let(:memory_adapter) { Flipper::Adapters::Memory.new(source) }
|
10
|
+
let(:memory_adapter) { Flipper::Adapters::Memory.new }
|
12
11
|
let(:adapter) {
|
13
12
|
Flipper::Adapters::OperationLogger.new(memory_adapter)
|
14
13
|
}
|