flipper 0.10.2 → 0.11.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/.rubocop.yml +42 -0
- data/.rubocop_todo.yml +188 -0
- data/Changelog.md +10 -0
- data/Gemfile +6 -3
- data/README.md +4 -3
- data/Rakefile +13 -13
- data/docs/Adapters.md +2 -1
- data/docs/DockerCompose.md +6 -3
- data/docs/Gates.md +25 -3
- data/docs/Optimization.md +27 -5
- data/docs/api/README.md +73 -32
- data/docs/http/README.md +34 -0
- data/docs/read-only/README.md +22 -0
- data/examples/percentage_of_actors_group.rb +49 -0
- data/flipper.gemspec +15 -15
- data/lib/flipper.rb +2 -5
- data/lib/flipper/adapter.rb +10 -0
- data/lib/flipper/adapters/http.rb +147 -0
- data/lib/flipper/adapters/http/client.rb +83 -0
- data/lib/flipper/adapters/http/error.rb +14 -0
- data/lib/flipper/adapters/instrumented.rb +36 -36
- data/lib/flipper/adapters/memoizable.rb +2 -6
- data/lib/flipper/adapters/memory.rb +10 -9
- data/lib/flipper/adapters/operation_logger.rb +1 -1
- data/lib/flipper/adapters/pstore.rb +12 -11
- data/lib/flipper/adapters/read_only.rb +6 -6
- data/lib/flipper/dsl.rb +1 -3
- data/lib/flipper/feature.rb +11 -16
- data/lib/flipper/gate.rb +3 -3
- data/lib/flipper/gate_values.rb +6 -6
- data/lib/flipper/gates/group.rb +2 -2
- data/lib/flipper/gates/percentage_of_actors.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/metriks.rb +1 -1
- data/lib/flipper/instrumentation/statsd.rb +1 -1
- data/lib/flipper/instrumentation/statsd_subscriber.rb +1 -3
- data/lib/flipper/instrumentation/subscriber.rb +11 -10
- data/lib/flipper/instrumenters/memory.rb +1 -5
- data/lib/flipper/instrumenters/noop.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +11 -27
- data/lib/flipper/middleware/setup_env.rb +44 -0
- data/lib/flipper/registry.rb +8 -10
- data/lib/flipper/spec/shared_adapter_specs.rb +45 -67
- data/lib/flipper/test/shared_adapter_test.rb +25 -31
- data/lib/flipper/typecast.rb +2 -2
- data/lib/flipper/types/actor.rb +2 -4
- data/lib/flipper/types/group.rb +1 -1
- data/lib/flipper/types/percentage.rb +2 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/fixtures/feature.json +31 -0
- data/spec/flipper/adapters/http_spec.rb +148 -0
- data/spec/flipper/adapters/instrumented_spec.rb +20 -20
- data/spec/flipper/adapters/memoizable_spec.rb +59 -59
- data/spec/flipper/adapters/operation_logger_spec.rb +16 -16
- data/spec/flipper/adapters/pstore_spec.rb +6 -6
- data/spec/flipper/adapters/read_only_spec.rb +28 -34
- data/spec/flipper/dsl_spec.rb +73 -84
- data/spec/flipper/feature_check_context_spec.rb +27 -27
- data/spec/flipper/feature_spec.rb +186 -196
- data/spec/flipper/gate_spec.rb +11 -11
- data/spec/flipper/gate_values_spec.rb +46 -45
- data/spec/flipper/gates/actor_spec.rb +2 -2
- data/spec/flipper/gates/boolean_spec.rb +24 -23
- data/spec/flipper/gates/group_spec.rb +19 -19
- data/spec/flipper/gates/percentage_of_actors_spec.rb +10 -10
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +11 -11
- data/spec/flipper/instrumenters/memory_spec.rb +5 -5
- data/spec/flipper/instrumenters/noop_spec.rb +6 -6
- data/spec/flipper/middleware/memoizer_spec.rb +83 -100
- data/spec/flipper/middleware/setup_env_spec.rb +76 -0
- data/spec/flipper/registry_spec.rb +35 -39
- data/spec/flipper/typecast_spec.rb +18 -18
- data/spec/flipper/types/actor_spec.rb +30 -29
- data/spec/flipper/types/boolean_spec.rb +8 -8
- data/spec/flipper/types/group_spec.rb +28 -28
- data/spec/flipper/types/percentage_spec.rb +14 -14
- data/spec/flipper_spec.rb +61 -54
- data/spec/helper.rb +26 -21
- data/spec/integration_spec.rb +121 -113
- data/spec/support/fake_udp_socket.rb +1 -1
- data/spec/support/spec_helpers.rb +32 -4
- data/test/adapters/pstore_test.rb +3 -3
- data/test/test_helper.rb +1 -1
- metadata +20 -5
data/spec/flipper/gate_spec.rb
CHANGED
@@ -3,14 +3,14 @@ require 'helper'
|
|
3
3
|
RSpec.describe Flipper::Gate do
|
4
4
|
let(:feature_name) { :stats }
|
5
5
|
|
6
|
-
subject
|
6
|
+
subject do
|
7
7
|
described_class.new
|
8
|
-
|
8
|
+
end
|
9
9
|
|
10
|
-
describe
|
11
|
-
context
|
12
|
-
let(:subclass)
|
13
|
-
Class.new(described_class)
|
10
|
+
describe '#inspect' do
|
11
|
+
context 'for subclass' do
|
12
|
+
let(:subclass) do
|
13
|
+
Class.new(described_class) do
|
14
14
|
def name
|
15
15
|
:name
|
16
16
|
end
|
@@ -22,14 +22,14 @@ RSpec.describe Flipper::Gate do
|
|
22
22
|
def data_type
|
23
23
|
:set
|
24
24
|
end
|
25
|
-
|
26
|
-
|
25
|
+
end
|
26
|
+
end
|
27
27
|
|
28
|
-
subject
|
28
|
+
subject do
|
29
29
|
subclass.new
|
30
|
-
|
30
|
+
end
|
31
31
|
|
32
|
-
it
|
32
|
+
it 'includes attributes' do
|
33
33
|
string = subject.inspect
|
34
34
|
expect(string).to include(subject.object_id.to_s)
|
35
35
|
expect(string).to include('name=:name')
|
@@ -4,15 +4,15 @@ require 'flipper/gate_values'
|
|
4
4
|
RSpec.describe Flipper::GateValues do
|
5
5
|
{
|
6
6
|
nil => false,
|
7
|
-
|
7
|
+
'' => false,
|
8
8
|
0 => false,
|
9
9
|
1 => true,
|
10
|
-
|
11
|
-
|
10
|
+
'0' => false,
|
11
|
+
'1' => true,
|
12
12
|
true => true,
|
13
13
|
false => false,
|
14
|
-
|
15
|
-
|
14
|
+
'true' => true,
|
15
|
+
'false' => false,
|
16
16
|
}.each do |value, expected|
|
17
17
|
context "with #{value.inspect} boolean" do
|
18
18
|
it "returns #{expected}" do
|
@@ -23,11 +23,11 @@ RSpec.describe Flipper::GateValues do
|
|
23
23
|
|
24
24
|
{
|
25
25
|
nil => 0,
|
26
|
-
|
26
|
+
'' => 0,
|
27
27
|
0 => 0,
|
28
28
|
1 => 1,
|
29
|
-
|
30
|
-
|
29
|
+
'1' => 1,
|
30
|
+
'99' => 99,
|
31
31
|
}.each do |value, expected|
|
32
32
|
context "with #{value.inspect} percentage of time" do
|
33
33
|
it "returns #{expected}" do
|
@@ -38,24 +38,25 @@ RSpec.describe Flipper::GateValues do
|
|
38
38
|
|
39
39
|
{
|
40
40
|
nil => 0,
|
41
|
-
|
41
|
+
'' => 0,
|
42
42
|
0 => 0,
|
43
43
|
1 => 1,
|
44
|
-
|
45
|
-
|
44
|
+
'1' => 1,
|
45
|
+
'99' => 99,
|
46
46
|
}.each do |value, expected|
|
47
47
|
context "with #{value.inspect} percentage of actors" do
|
48
48
|
it "returns #{expected}" do
|
49
|
-
expect(described_class.new(percentage_of_actors: value).percentage_of_actors)
|
49
|
+
expect(described_class.new(percentage_of_actors: value).percentage_of_actors)
|
50
|
+
.to be(expected)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
54
55
|
{
|
55
56
|
nil => Set.new,
|
56
|
-
|
57
|
+
'' => Set.new,
|
57
58
|
Set.new([1, 2]) => Set.new([1, 2]),
|
58
|
-
[1, 2] => Set.new([1, 2])
|
59
|
+
[1, 2] => Set.new([1, 2]),
|
59
60
|
}.each do |value, expected|
|
60
61
|
context "with #{value.inspect} actors" do
|
61
62
|
it "returns #{expected}" do
|
@@ -66,9 +67,9 @@ RSpec.describe Flipper::GateValues do
|
|
66
67
|
|
67
68
|
{
|
68
69
|
nil => Set.new,
|
69
|
-
|
70
|
+
'' => Set.new,
|
70
71
|
Set.new([:admins, :preview_features]) => Set.new([:admins, :preview_features]),
|
71
|
-
[:admins, :preview_features] => Set.new([:admins, :preview_features])
|
72
|
+
[:admins, :preview_features] => Set.new([:admins, :preview_features]),
|
72
73
|
}.each do |value, expected|
|
73
74
|
context "with #{value.inspect} groups" do
|
74
75
|
it "returns #{expected}" do
|
@@ -77,58 +78,58 @@ RSpec.describe Flipper::GateValues do
|
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
80
|
-
it
|
81
|
-
expect
|
82
|
-
described_class.new(percentage_of_time: [
|
83
|
-
|
81
|
+
it 'raises argument error for percentage of time value that cannot be converted to an integer' do
|
82
|
+
expect do
|
83
|
+
described_class.new(percentage_of_time: ['asdf'])
|
84
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
|
84
85
|
end
|
85
86
|
|
86
|
-
it
|
87
|
-
expect
|
88
|
-
described_class.new(percentage_of_actors: [
|
89
|
-
|
87
|
+
it 'raises argument error for percentage of actors value that cannot be converted to an int' do
|
88
|
+
expect do
|
89
|
+
described_class.new(percentage_of_actors: ['asdf'])
|
90
|
+
end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
|
90
91
|
end
|
91
92
|
|
92
|
-
it
|
93
|
-
expect
|
94
|
-
described_class.new(actors:
|
95
|
-
|
93
|
+
it 'raises argument error for actors value that cannot be converted to a set' do
|
94
|
+
expect do
|
95
|
+
described_class.new(actors: 'asdf')
|
96
|
+
end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
|
96
97
|
end
|
97
98
|
|
98
|
-
it
|
99
|
-
expect
|
100
|
-
described_class.new(groups:
|
101
|
-
|
99
|
+
it 'raises argument error for groups value that cannot be converted to a set' do
|
100
|
+
expect do
|
101
|
+
described_class.new(groups: 'asdf')
|
102
|
+
end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
|
102
103
|
end
|
103
104
|
|
104
|
-
describe
|
105
|
-
it
|
105
|
+
describe '#[]' do
|
106
|
+
it 'can read the boolean value' do
|
106
107
|
expect(described_class.new(boolean: true)[:boolean]).to be(true)
|
107
|
-
expect(described_class.new(boolean: true)[
|
108
|
+
expect(described_class.new(boolean: true)['boolean']).to be(true)
|
108
109
|
end
|
109
110
|
|
110
|
-
it
|
111
|
+
it 'can read the actors value' do
|
111
112
|
expect(described_class.new(actors: Set[1, 2])[:actors]).to eq(Set[1, 2])
|
112
|
-
expect(described_class.new(actors: Set[1, 2])[
|
113
|
+
expect(described_class.new(actors: Set[1, 2])['actors']).to eq(Set[1, 2])
|
113
114
|
end
|
114
115
|
|
115
|
-
it
|
116
|
+
it 'can read the groups value' do
|
116
117
|
expect(described_class.new(groups: Set[:admins])[:groups]).to eq(Set[:admins])
|
117
|
-
expect(described_class.new(groups: Set[:admins])[
|
118
|
+
expect(described_class.new(groups: Set[:admins])['groups']).to eq(Set[:admins])
|
118
119
|
end
|
119
120
|
|
120
|
-
it
|
121
|
+
it 'can read the percentage of time value' do
|
121
122
|
expect(described_class.new(percentage_of_time: 15)[:percentage_of_time]).to eq(15)
|
122
|
-
expect(described_class.new(percentage_of_time: 15)[
|
123
|
+
expect(described_class.new(percentage_of_time: 15)['percentage_of_time']).to eq(15)
|
123
124
|
end
|
124
125
|
|
125
|
-
it
|
126
|
+
it 'can read the percentage of actors value' do
|
126
127
|
expect(described_class.new(percentage_of_actors: 15)[:percentage_of_actors]).to eq(15)
|
127
|
-
expect(described_class.new(percentage_of_actors: 15)[
|
128
|
+
expect(described_class.new(percentage_of_actors: 15)['percentage_of_actors']).to eq(15)
|
128
129
|
end
|
129
130
|
|
130
|
-
it
|
131
|
-
expect(described_class.new({})[
|
131
|
+
it 'returns nil for value that is not present' do
|
132
|
+
expect(described_class.new({})['not legit']).to be(nil)
|
132
133
|
end
|
133
134
|
end
|
134
135
|
end
|
@@ -3,71 +3,72 @@ require 'helper'
|
|
3
3
|
RSpec.describe Flipper::Gates::Boolean do
|
4
4
|
let(:feature_name) { :search }
|
5
5
|
|
6
|
-
subject
|
6
|
+
subject do
|
7
7
|
described_class.new
|
8
|
-
|
8
|
+
end
|
9
9
|
|
10
10
|
def context(bool)
|
11
11
|
Flipper::FeatureCheckContext.new(
|
12
12
|
feature_name: feature_name,
|
13
|
-
values: Flipper::GateValues.new(
|
14
|
-
thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
|
13
|
+
values: Flipper::GateValues.new(boolean: bool),
|
14
|
+
thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
-
describe
|
19
|
-
context
|
20
|
-
it
|
18
|
+
describe '#enabled?' do
|
19
|
+
context 'for true value' do
|
20
|
+
it 'returns true' do
|
21
21
|
expect(subject.enabled?(true)).to eq(true)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
context
|
26
|
-
it
|
25
|
+
context 'for false value' do
|
26
|
+
it 'returns false' do
|
27
27
|
expect(subject.enabled?(false)).to eq(false)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
describe
|
33
|
-
context
|
34
|
-
it
|
32
|
+
describe '#open?' do
|
33
|
+
context 'for true value' do
|
34
|
+
it 'returns true' do
|
35
35
|
expect(subject.open?(context(true))).to be(true)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
context
|
40
|
-
it
|
39
|
+
context 'for false value' do
|
40
|
+
it 'returns false' do
|
41
41
|
expect(subject.open?(context(false))).to be(false)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
describe
|
47
|
-
it
|
46
|
+
describe '#protects?' do
|
47
|
+
it 'returns true for boolean type' do
|
48
48
|
expect(subject.protects?(Flipper::Types::Boolean.new(true))).to be(true)
|
49
49
|
end
|
50
50
|
|
51
|
-
it
|
51
|
+
it 'returns true for true' do
|
52
52
|
expect(subject.protects?(true)).to be(true)
|
53
53
|
end
|
54
54
|
|
55
|
-
it
|
55
|
+
it 'returns true for false' do
|
56
56
|
expect(subject.protects?(false)).to be(true)
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
describe
|
61
|
-
it
|
62
|
-
expect(subject.wrap(Flipper::Types::Boolean.new(true)))
|
60
|
+
describe '#wrap' do
|
61
|
+
it 'returns boolean type for boolean type' do
|
62
|
+
expect(subject.wrap(Flipper::Types::Boolean.new(true)))
|
63
|
+
.to be_instance_of(Flipper::Types::Boolean)
|
63
64
|
end
|
64
65
|
|
65
|
-
it
|
66
|
+
it 'returns boolean type for true' do
|
66
67
|
expect(subject.wrap(true)).to be_instance_of(Flipper::Types::Boolean)
|
67
68
|
expect(subject.wrap(true).value).to be(true)
|
68
69
|
end
|
69
70
|
|
70
|
-
it
|
71
|
+
it 'returns boolean type for true' do
|
71
72
|
expect(subject.wrap(false)).to be_instance_of(Flipper::Types::Boolean)
|
72
73
|
expect(subject.wrap(false).value).to be(false)
|
73
74
|
end
|
@@ -3,62 +3,62 @@ require 'helper'
|
|
3
3
|
RSpec.describe Flipper::Gates::Group do
|
4
4
|
let(:feature_name) { :search }
|
5
5
|
|
6
|
-
subject
|
6
|
+
subject do
|
7
7
|
described_class.new
|
8
|
-
|
8
|
+
end
|
9
9
|
|
10
10
|
def context(set)
|
11
11
|
Flipper::FeatureCheckContext.new(
|
12
12
|
feature_name: feature_name,
|
13
|
-
values: Flipper::GateValues.new(
|
14
|
-
thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(
|
13
|
+
values: Flipper::GateValues.new(groups: set),
|
14
|
+
thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new('5'))
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
-
describe
|
19
|
-
context
|
18
|
+
describe '#open?' do
|
19
|
+
context 'with a group in adapter, but not registered' do
|
20
20
|
before do
|
21
|
-
Flipper.register(:staff) { |
|
21
|
+
Flipper.register(:staff) { |_thing| true }
|
22
22
|
end
|
23
23
|
|
24
|
-
it
|
24
|
+
it 'ignores group' do
|
25
25
|
thing = Struct.new(:flipper_id).new('5')
|
26
26
|
expect(subject.open?(context(Set[:newbs, :staff]))).to be(true)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
context
|
30
|
+
context 'thing that does not respond to method in group block' do
|
31
31
|
before do
|
32
|
-
Flipper.register(:stinkers
|
32
|
+
Flipper.register(:stinkers, &:stinker?)
|
33
33
|
end
|
34
34
|
|
35
|
-
it
|
36
|
-
expect
|
35
|
+
it 'raises error' do
|
36
|
+
expect do
|
37
37
|
subject.open?(context(Set[:stinkers]))
|
38
|
-
|
38
|
+
end.to raise_error(NoMethodError)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
describe
|
44
|
-
it
|
43
|
+
describe '#wrap' do
|
44
|
+
it 'returns group instance for symbol' do
|
45
45
|
group = Flipper.register(:admins) {}
|
46
46
|
expect(subject.wrap(:admins)).to eq(group)
|
47
47
|
end
|
48
48
|
|
49
|
-
it
|
49
|
+
it 'returns group instance for group instance' do
|
50
50
|
group = Flipper.register(:admins) {}
|
51
51
|
expect(subject.wrap(group)).to eq(group)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
describe
|
56
|
-
it
|
55
|
+
describe '#protects?' do
|
56
|
+
it 'returns true for group' do
|
57
57
|
group = Flipper.register(:admins) {}
|
58
58
|
expect(subject.protects?(group)).to be(true)
|
59
59
|
end
|
60
60
|
|
61
|
-
it
|
61
|
+
it 'returns true for symbol' do
|
62
62
|
expect(subject.protects?(:admins)).to be(true)
|
63
63
|
end
|
64
64
|
end
|
@@ -3,27 +3,27 @@ require 'helper'
|
|
3
3
|
RSpec.describe Flipper::Gates::PercentageOfActors do
|
4
4
|
let(:feature_name) { :search }
|
5
5
|
|
6
|
-
subject
|
6
|
+
subject do
|
7
7
|
described_class.new
|
8
|
-
|
8
|
+
end
|
9
9
|
|
10
10
|
def context(integer, feature = feature_name, thing = nil)
|
11
11
|
Flipper::FeatureCheckContext.new(
|
12
12
|
feature_name: feature,
|
13
|
-
values: Flipper::GateValues.new(
|
14
|
-
thing: thing || Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
|
13
|
+
values: Flipper::GateValues.new(percentage_of_actors: integer),
|
14
|
+
thing: thing || Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
-
describe
|
19
|
-
context
|
18
|
+
describe '#open?' do
|
19
|
+
context 'when compared against two features' do
|
20
20
|
let(:percentage) { 0.05 }
|
21
21
|
let(:percentage_as_integer) { percentage * 100 }
|
22
22
|
let(:number_of_actors) { 100 }
|
23
23
|
|
24
|
-
let(:actors)
|
24
|
+
let(:actors) do
|
25
25
|
(1..number_of_actors).map { |n| Struct.new(:flipper_id).new(n) }
|
26
|
-
|
26
|
+
end
|
27
27
|
|
28
28
|
let(:feature_one_enabled_actors) do
|
29
29
|
gate = described_class.new
|
@@ -35,11 +35,11 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
|
|
35
35
|
actors.select { |actor| gate.open? context(percentage_as_integer, :name_two, actor) }
|
36
36
|
end
|
37
37
|
|
38
|
-
it
|
38
|
+
it 'does not enable both features for same set of actors' do
|
39
39
|
expect(feature_one_enabled_actors).not_to eq(feature_two_enabled_actors)
|
40
40
|
end
|
41
41
|
|
42
|
-
it
|
42
|
+
it 'enables feature for accurate number of actors for each feature' do
|
43
43
|
margin_of_error = 0.02 * number_of_actors # 2 percent margin of error
|
44
44
|
expected_enabled_size = number_of_actors * percentage
|
45
45
|
|