split 3.3.2 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.eslintrc +1 -1
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -1155
- data/.rubocop_todo.yml +679 -0
- data/.travis.yml +8 -14
- data/Appraisals +1 -1
- data/CHANGELOG.md +22 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/Gemfile +1 -0
- data/README.md +15 -13
- data/Rakefile +1 -0
- data/gemfiles/6.0.gemfile +1 -1
- data/lib/split/algorithms/block_randomization.rb +1 -0
- data/lib/split/alternative.rb +3 -3
- data/lib/split/combined_experiments_helper.rb +1 -1
- data/lib/split/configuration.rb +5 -2
- data/lib/split/dashboard.rb +4 -1
- data/lib/split/dashboard/pagination_helpers.rb +2 -3
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/engine.rb +6 -4
- data/lib/split/experiment.rb +29 -18
- data/lib/split/goals_collection.rb +1 -0
- data/lib/split/helper.rb +2 -1
- data/lib/split/persistence/dual_adapter.rb +54 -12
- data/lib/split/redis_interface.rb +1 -0
- data/lib/split/trial.rb +1 -1
- data/lib/split/user.rb +5 -1
- data/lib/split/version.rb +2 -2
- data/spec/dashboard/pagination_helpers_spec.rb +3 -1
- data/spec/dashboard_helpers_spec.rb +2 -2
- data/spec/dashboard_spec.rb +37 -16
- data/spec/encapsulated_helper_spec.rb +1 -1
- data/spec/experiment_spec.rb +44 -5
- data/spec/helper_spec.rb +118 -80
- data/spec/persistence/dual_adapter_spec.rb +160 -68
- data/spec/user_spec.rb +11 -0
- data/split.gemspec +1 -2
- metadata +6 -4
data/lib/split/helper.rb
CHANGED
@@ -44,6 +44,7 @@ module Split
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def finish_experiment(experiment, options = {:reset => true})
|
47
|
+
return false if active_experiments[experiment.name].nil?
|
47
48
|
return true if experiment.has_winner?
|
48
49
|
should_reset = experiment.resettable? && options[:reset]
|
49
50
|
if ab_user[experiment.finished_key] && !should_reset
|
@@ -79,7 +80,7 @@ module Split
|
|
79
80
|
|
80
81
|
def ab_record_extra_info(metric_descriptor, key, value = 1)
|
81
82
|
return if exclude_visitor? || Split.configuration.disabled?
|
82
|
-
metric_descriptor,
|
83
|
+
metric_descriptor, _ = normalize_metric(metric_descriptor)
|
83
84
|
experiments = Metric.possible_experiments(metric_descriptor)
|
84
85
|
|
85
86
|
if experiments.any?
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
3
|
module Split
|
6
4
|
module Persistence
|
7
5
|
class DualAdapter
|
8
|
-
|
9
|
-
|
6
|
+
def self.with_config(options={})
|
7
|
+
self.config.merge!(options)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.config
|
12
|
+
@config ||= {}
|
13
|
+
end
|
10
14
|
|
11
15
|
def initialize(context)
|
12
16
|
if logged_in = self.class.config[:logged_in]
|
@@ -22,22 +26,60 @@ module Split
|
|
22
26
|
raise "Please configure :logged_out_adapter"
|
23
27
|
end
|
24
28
|
|
25
|
-
|
26
|
-
|
29
|
+
@fallback_to_logged_out_adapter =
|
30
|
+
self.class.config[:fallback_to_logged_out_adapter] || false
|
31
|
+
@logged_in = logged_in.call(context)
|
32
|
+
@logged_in_adapter = logged_in_adapter.new(context)
|
33
|
+
@logged_out_adapter = logged_out_adapter.new(context)
|
34
|
+
@active_adapter = @logged_in ? @logged_in_adapter : @logged_out_adapter
|
35
|
+
end
|
36
|
+
|
37
|
+
def keys
|
38
|
+
if @fallback_to_logged_out_adapter
|
39
|
+
(@logged_in_adapter.keys + @logged_out_adapter.keys).uniq
|
27
40
|
else
|
28
|
-
@
|
41
|
+
@active_adapter.keys
|
29
42
|
end
|
30
43
|
end
|
31
44
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
45
|
+
def [](key)
|
46
|
+
if @fallback_to_logged_out_adapter
|
47
|
+
@logged_in && @logged_in_adapter[key] || @logged_out_adapter[key]
|
48
|
+
else
|
49
|
+
@active_adapter[key]
|
50
|
+
end
|
35
51
|
end
|
36
52
|
|
37
|
-
def
|
38
|
-
@
|
53
|
+
def []=(key, value)
|
54
|
+
if @fallback_to_logged_out_adapter
|
55
|
+
@logged_in_adapter[key] = value if @logged_in
|
56
|
+
old_value = @logged_out_adapter[key]
|
57
|
+
@logged_out_adapter[key] = value
|
58
|
+
|
59
|
+
decrement_participation(key, old_value) if decrement_participation?(old_value, value)
|
60
|
+
else
|
61
|
+
@active_adapter[key] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(key)
|
66
|
+
if @fallback_to_logged_out_adapter
|
67
|
+
@logged_in_adapter.delete(key)
|
68
|
+
@logged_out_adapter.delete(key)
|
69
|
+
else
|
70
|
+
@active_adapter.delete(key)
|
71
|
+
end
|
39
72
|
end
|
40
73
|
|
74
|
+
private
|
75
|
+
|
76
|
+
def decrement_participation?(old_value, value)
|
77
|
+
!old_value.nil? && !value.nil? && old_value != value
|
78
|
+
end
|
79
|
+
|
80
|
+
def decrement_participation(key, value)
|
81
|
+
Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1)
|
82
|
+
end
|
41
83
|
end
|
42
84
|
end
|
43
85
|
end
|
data/lib/split/trial.rb
CHANGED
data/lib/split/user.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'forwardable'
|
2
3
|
|
3
4
|
module Split
|
@@ -8,9 +9,11 @@ module Split
|
|
8
9
|
|
9
10
|
def initialize(context, adapter=nil)
|
10
11
|
@user = adapter || Split::Persistence.adapter.new(context)
|
12
|
+
@cleaned_up = false
|
11
13
|
end
|
12
14
|
|
13
15
|
def cleanup_old_experiments!
|
16
|
+
return if @cleaned_up
|
14
17
|
keys_without_finished(user.keys).each do |key|
|
15
18
|
experiment = ExperimentCatalog.find key_without_version(key)
|
16
19
|
if experiment.nil? || experiment.has_winner? || experiment.start_time.nil?
|
@@ -18,6 +21,7 @@ module Split
|
|
18
21
|
user.delete Experiment.finished_key(key)
|
19
22
|
end
|
20
23
|
end
|
24
|
+
@cleaned_up = true
|
21
25
|
end
|
22
26
|
|
23
27
|
def max_experiments_reached?(experiment_key)
|
@@ -38,7 +42,7 @@ module Split
|
|
38
42
|
|
39
43
|
def active_experiments
|
40
44
|
experiment_pairs = {}
|
41
|
-
user.keys.each do |key|
|
45
|
+
keys_without_finished(user.keys).each do |key|
|
42
46
|
Metric.possible_experiments(key_without_version(key)).each do |experiment|
|
43
47
|
if !experiment.has_winner?
|
44
48
|
experiment_pairs[key_without_version(key)] = user[key]
|
data/lib/split/version.rb
CHANGED
@@ -10,7 +10,9 @@ describe Split::DashboardPaginationHelpers do
|
|
10
10
|
context 'when params empty' do
|
11
11
|
let(:params) { Hash[] }
|
12
12
|
|
13
|
-
it 'returns 10' do
|
13
|
+
it 'returns the default (10)' do
|
14
|
+
default_per_page = Split.configuration.dashboard_pagination_default_per_page
|
15
|
+
expect(pagination_per).to eql default_per_page
|
14
16
|
expect(pagination_per).to eql 10
|
15
17
|
end
|
16
18
|
end
|
@@ -27,11 +27,11 @@ describe Split::DashboardHelpers do
|
|
27
27
|
|
28
28
|
describe '#round' do
|
29
29
|
it 'can round number strings' do
|
30
|
-
expect(round('3.1415')).to eq BigDecimal
|
30
|
+
expect(round('3.1415')).to eq BigDecimal('3.14')
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'can round number strings for precsion' do
|
34
|
-
expect(round('3.1415', 1)).to eq BigDecimal
|
34
|
+
expect(round('3.1415', 1)).to eq BigDecimal('3.1')
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'can handle invalid number strings' do
|
data/spec/dashboard_spec.rb
CHANGED
@@ -29,6 +29,10 @@ describe Split::Dashboard do
|
|
29
29
|
let(:red_link) { link("red") }
|
30
30
|
let(:blue_link) { link("blue") }
|
31
31
|
|
32
|
+
before(:each) do
|
33
|
+
Split.configuration.beta_probability_simulations = 1
|
34
|
+
end
|
35
|
+
|
32
36
|
it "should respond to /" do
|
33
37
|
get '/'
|
34
38
|
expect(last_response).to be_ok
|
@@ -74,17 +78,39 @@ describe Split::Dashboard do
|
|
74
78
|
end
|
75
79
|
|
76
80
|
describe "force alternative" do
|
77
|
-
|
78
|
-
|
79
|
-
|
81
|
+
context "initial version" do
|
82
|
+
let!(:user) do
|
83
|
+
Split::User.new(@app, { experiment.name => 'red' })
|
84
|
+
end
|
80
85
|
|
81
|
-
|
82
|
-
|
86
|
+
before do
|
87
|
+
allow(Split::User).to receive(:new).and_return(user)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should set current user's alternative" do
|
91
|
+
blue_link.participant_count = 7
|
92
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
93
|
+
expect(user[experiment.key]).to eq("blue")
|
94
|
+
expect(blue_link.participant_count).to eq(8)
|
95
|
+
end
|
83
96
|
end
|
84
97
|
|
85
|
-
|
86
|
-
|
87
|
-
|
98
|
+
context "incremented version" do
|
99
|
+
let!(:user) do
|
100
|
+
experiment.increment_version
|
101
|
+
Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => 'red' })
|
102
|
+
end
|
103
|
+
|
104
|
+
before do
|
105
|
+
allow(Split::User).to receive(:new).and_return(user)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should set current user's alternative" do
|
109
|
+
blue_link.participant_count = 7
|
110
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
111
|
+
expect(user[experiment.key]).to eq("blue")
|
112
|
+
expect(blue_link.participant_count).to eq(8)
|
113
|
+
end
|
88
114
|
end
|
89
115
|
end
|
90
116
|
|
@@ -120,7 +146,7 @@ describe Split::Dashboard do
|
|
120
146
|
it "removes winner" do
|
121
147
|
post "/reopen?experiment=#{experiment.name}"
|
122
148
|
|
123
|
-
expect(experiment).to_not have_winner
|
149
|
+
expect(Split::ExperimentCatalog.find(experiment.name)).to_not have_winner
|
124
150
|
end
|
125
151
|
|
126
152
|
it "keeps existing stats" do
|
@@ -167,19 +193,14 @@ describe Split::Dashboard do
|
|
167
193
|
end
|
168
194
|
|
169
195
|
it "should display the start date" do
|
170
|
-
|
171
|
-
expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
|
172
|
-
experiment
|
196
|
+
experiment.start
|
173
197
|
|
174
198
|
get '/'
|
175
199
|
|
176
|
-
expect(last_response.body).to include(
|
200
|
+
expect(last_response.body).to include("<small>#{experiment.start_time.strftime('%Y-%m-%d')}</small>")
|
177
201
|
end
|
178
202
|
|
179
203
|
it "should handle experiments without a start date" do
|
180
|
-
experiment_start_time = Time.parse('2011-07-07')
|
181
|
-
expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
|
182
|
-
|
183
204
|
Split.redis.hdel(:experiment_start_times, experiment.name)
|
184
205
|
|
185
206
|
get '/'
|
data/spec/experiment_spec.rb
CHANGED
@@ -35,6 +35,12 @@ describe Split::Experiment do
|
|
35
35
|
expect(experiment.resettable).to be_truthy
|
36
36
|
end
|
37
37
|
|
38
|
+
it "should be resettable when loading from configuration" do
|
39
|
+
allow(Split.configuration).to receive(:experiment_for).with('some_experiment') { { alternatives: %w(a b) } }
|
40
|
+
|
41
|
+
expect(Split::Experiment.new('some_experiment')).to be_resettable
|
42
|
+
end
|
43
|
+
|
38
44
|
it "should save to redis" do
|
39
45
|
experiment.save
|
40
46
|
expect(Split.redis.exists('basket_text')).to be true
|
@@ -86,7 +92,7 @@ describe Split::Experiment do
|
|
86
92
|
experiment.save
|
87
93
|
experiment.save
|
88
94
|
expect(Split.redis.exists('basket_text')).to be true
|
89
|
-
expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['Basket', "Cart"])
|
95
|
+
expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
|
90
96
|
end
|
91
97
|
|
92
98
|
describe 'new record?' do
|
@@ -213,12 +219,41 @@ describe Split::Experiment do
|
|
213
219
|
it "should have no winner initially" do
|
214
220
|
expect(experiment.winner).to be_nil
|
215
221
|
end
|
222
|
+
end
|
216
223
|
|
224
|
+
describe 'winner=' do
|
217
225
|
it "should allow you to specify a winner" do
|
218
226
|
experiment.save
|
219
227
|
experiment.winner = 'red'
|
220
228
|
expect(experiment.winner.name).to eq('red')
|
221
229
|
end
|
230
|
+
|
231
|
+
context 'when has_winner state is memoized' do
|
232
|
+
before { expect(experiment).to_not have_winner }
|
233
|
+
|
234
|
+
it 'should keep has_winner state consistent' do
|
235
|
+
experiment.winner = 'red'
|
236
|
+
expect(experiment).to have_winner
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe 'reset_winner' do
|
242
|
+
before { experiment.winner = 'green' }
|
243
|
+
|
244
|
+
it 'should reset the winner' do
|
245
|
+
experiment.reset_winner
|
246
|
+
expect(experiment.winner).to be_nil
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'when has_winner state is memoized' do
|
250
|
+
before { expect(experiment).to have_winner }
|
251
|
+
|
252
|
+
it 'should keep has_winner state consistent' do
|
253
|
+
experiment.reset_winner
|
254
|
+
expect(experiment).to_not have_winner
|
255
|
+
end
|
256
|
+
end
|
222
257
|
end
|
223
258
|
|
224
259
|
describe 'has_winner?' do
|
@@ -235,6 +270,12 @@ describe Split::Experiment do
|
|
235
270
|
expect(experiment).to_not have_winner
|
236
271
|
end
|
237
272
|
end
|
273
|
+
|
274
|
+
it 'memoizes has_winner state' do
|
275
|
+
expect(experiment).to receive(:winner).once
|
276
|
+
expect(experiment).to_not have_winner
|
277
|
+
expect(experiment).to_not have_winner
|
278
|
+
end
|
238
279
|
end
|
239
280
|
|
240
281
|
describe 'reset' do
|
@@ -414,9 +455,7 @@ describe Split::Experiment do
|
|
414
455
|
}
|
415
456
|
|
416
457
|
context "saving experiment" do
|
417
|
-
|
418
|
-
Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green')
|
419
|
-
end
|
458
|
+
let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green') }
|
420
459
|
|
421
460
|
before { experiment.save }
|
422
461
|
|
@@ -425,7 +464,7 @@ describe Split::Experiment do
|
|
425
464
|
end
|
426
465
|
|
427
466
|
it "should reset an experiment if it is loaded with different goals" do
|
428
|
-
|
467
|
+
same_but_different_goals
|
429
468
|
expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"])
|
430
469
|
end
|
431
470
|
|
data/spec/helper_spec.rb
CHANGED
@@ -183,8 +183,7 @@ describe Split::Helper do
|
|
183
183
|
ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)
|
184
184
|
experiment = Split::ExperimentCatalog.find('link_color')
|
185
185
|
expect(experiment.alternatives.map(&:name)).to eq(['blue', 'red'])
|
186
|
-
|
187
|
-
# expect(experiment.alternatives.collect{|a| a.weight}).to eq([0.01, 0.2])
|
186
|
+
expect(experiment.alternatives.collect{|a| a.weight}).to match_array([0.01, 0.2])
|
188
187
|
end
|
189
188
|
|
190
189
|
it "should only let a user participate in one experiment at a time" do
|
@@ -297,98 +296,126 @@ describe Split::Helper do
|
|
297
296
|
end
|
298
297
|
|
299
298
|
describe 'ab_finished' do
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
299
|
+
context 'for an experiment that the user participates in' do
|
300
|
+
before(:each) do
|
301
|
+
@experiment_name = 'link_color'
|
302
|
+
@alternatives = ['blue', 'red']
|
303
|
+
@experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
|
304
|
+
@alternative_name = ab_test(@experiment_name, *@alternatives)
|
305
|
+
@previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
306
|
+
end
|
307
307
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
308
|
+
it 'should increment the counter for the completed alternative' do
|
309
|
+
ab_finished(@experiment_name)
|
310
|
+
new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
311
|
+
expect(new_completion_count).to eq(@previous_completion_count + 1)
|
312
|
+
end
|
313
313
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
314
|
+
it "should set experiment's finished key if reset is false" do
|
315
|
+
ab_finished(@experiment_name, {:reset => false})
|
316
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
317
|
+
expect(ab_user[@experiment.finished_key]).to eq(true)
|
318
|
+
end
|
319
319
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
320
|
+
it 'should not increment the counter if reset is false and the experiment has been already finished' do
|
321
|
+
2.times { ab_finished(@experiment_name, {:reset => false}) }
|
322
|
+
new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
323
|
+
expect(new_completion_count).to eq(@previous_completion_count + 1)
|
324
|
+
end
|
325
325
|
|
326
|
-
|
327
|
-
|
326
|
+
it 'should not increment the counter for an ended experiment' do
|
327
|
+
e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
|
328
|
+
e.winner = 'small'
|
329
|
+
a = ab_test('button_size', 'small', 'big')
|
330
|
+
expect(a).to eq('small')
|
331
|
+
expect(lambda {
|
332
|
+
ab_finished('button_size')
|
333
|
+
}).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
|
334
|
+
end
|
328
335
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
ab_finished('button_size')
|
335
|
-
}).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
|
336
|
-
end
|
336
|
+
it "should clear out the user's participation from their session" do
|
337
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
338
|
+
ab_finished(@experiment_name)
|
339
|
+
expect(ab_user.keys).to be_empty
|
340
|
+
end
|
337
341
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
ab_finished('button_size')
|
345
|
-
}).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
|
346
|
-
end
|
342
|
+
it "should not clear out the users session if reset is false" do
|
343
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
344
|
+
ab_finished(@experiment_name, {:reset => false})
|
345
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
346
|
+
expect(ab_user[@experiment.finished_key]).to eq(true)
|
347
|
+
end
|
347
348
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
349
|
+
it "should reset the users session when experiment is not versioned" do
|
350
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
351
|
+
ab_finished(@experiment_name)
|
352
|
+
expect(ab_user.keys).to be_empty
|
353
|
+
end
|
353
354
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
358
|
-
expect(ab_user[@experiment.finished_key]).to eq(true)
|
359
|
-
end
|
355
|
+
it "should reset the users session when experiment is versioned" do
|
356
|
+
@experiment.increment_version
|
357
|
+
@alternative_name = ab_test(@experiment_name, *@alternatives)
|
360
358
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
end
|
359
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
360
|
+
ab_finished(@experiment_name)
|
361
|
+
expect(ab_user.keys).to be_empty
|
362
|
+
end
|
366
363
|
|
367
|
-
|
368
|
-
|
369
|
-
|
364
|
+
context "when on_trial_complete is set" do
|
365
|
+
before { Split.configuration.on_trial_complete = :some_method }
|
366
|
+
it "should call the method" do
|
367
|
+
expect(self).to receive(:some_method)
|
368
|
+
ab_finished(@experiment_name)
|
369
|
+
end
|
370
370
|
|
371
|
-
|
372
|
-
|
373
|
-
|
371
|
+
it "should not call the method without alternative" do
|
372
|
+
ab_user[@experiment.key] = nil
|
373
|
+
expect(self).not_to receive(:some_method)
|
374
|
+
ab_finished(@experiment_name)
|
375
|
+
end
|
376
|
+
end
|
374
377
|
end
|
375
378
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
+
context 'for an experiment that the user is excluded from' do
|
380
|
+
before do
|
381
|
+
alternative = ab_test('link_color', 'blue', 'red')
|
382
|
+
expect(Split::Alternative.new(alternative, 'link_color').participant_count).to eq(1)
|
383
|
+
alternative = ab_test('button_size', 'small', 'big')
|
384
|
+
expect(Split::Alternative.new(alternative, 'button_size').participant_count).to eq(0)
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'should not increment the completed counter' do
|
388
|
+
# So, user should be participating in the link_color experiment and
|
389
|
+
# receive the control for button_size. As the user is not participating in
|
390
|
+
# the button size experiment, finishing it should not increase the
|
391
|
+
# completion count for that alternative.
|
392
|
+
expect(lambda {
|
393
|
+
ab_finished('button_size')
|
394
|
+
}).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
|
395
|
+
end
|
379
396
|
end
|
380
397
|
|
381
|
-
context
|
382
|
-
before
|
383
|
-
|
384
|
-
|
385
|
-
|
398
|
+
context 'for an experiment that the user does not participate in' do
|
399
|
+
before do
|
400
|
+
Split::ExperimentCatalog.find_or_create(:not_started_experiment, 'control', 'alt')
|
401
|
+
end
|
402
|
+
it 'should not raise an exception' do
|
403
|
+
expect { ab_finished(:not_started_experiment) }.not_to raise_exception
|
386
404
|
end
|
387
405
|
|
388
|
-
it
|
389
|
-
ab_user[
|
390
|
-
|
391
|
-
|
406
|
+
it 'should not change the user state when reset is false' do
|
407
|
+
expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys}.from([])
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'should not change the user state when reset is true' do
|
411
|
+
expect(self).not_to receive(:reset!)
|
412
|
+
ab_finished(:not_started_experiment)
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'should not increment the completed counter' do
|
416
|
+
ab_finished(:not_started_experiment)
|
417
|
+
expect(Split::Alternative.new('control', :not_started_experiment).completed_count).to eq(0)
|
418
|
+
expect(Split::Alternative.new('alt', :not_started_experiment).completed_count).to eq(0)
|
392
419
|
end
|
393
420
|
end
|
394
421
|
end
|
@@ -528,6 +555,17 @@ describe Split::Helper do
|
|
528
555
|
expect(active_experiments.first[0]).to eq "link_color"
|
529
556
|
end
|
530
557
|
|
558
|
+
it 'should show versioned tests properly' do
|
559
|
+
10.times { experiment.reset }
|
560
|
+
|
561
|
+
alternative = ab_test(experiment.name, 'blue', 'red')
|
562
|
+
ab_finished(experiment.name, reset: false)
|
563
|
+
|
564
|
+
expect(experiment.version).to eq(10)
|
565
|
+
expect(active_experiments.count).to eq 1
|
566
|
+
expect(active_experiments).to eq({'link_color' => alternative })
|
567
|
+
end
|
568
|
+
|
531
569
|
it 'should show multiple tests' do
|
532
570
|
Split.configure do |config|
|
533
571
|
config.allow_multiple_experiments = true
|
@@ -545,7 +583,7 @@ describe Split::Helper do
|
|
545
583
|
end
|
546
584
|
e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6')
|
547
585
|
e.winner = '4'
|
548
|
-
|
586
|
+
ab_test('def', '4', '5', '6')
|
549
587
|
another_alternative = ab_test('ghi', '7', '8', '9')
|
550
588
|
expect(active_experiments.count).to eq 1
|
551
589
|
expect(active_experiments.first[0]).to eq "ghi"
|
@@ -1021,8 +1059,8 @@ describe Split::Helper do
|
|
1021
1059
|
|
1022
1060
|
it 'should handle multiple experiments correctly' do
|
1023
1061
|
experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red')
|
1024
|
-
|
1025
|
-
|
1062
|
+
ab_test('link_color', 'blue', 'red')
|
1063
|
+
ab_test('link_color2', 'blue', 'red')
|
1026
1064
|
ab_finished('link_color2')
|
1027
1065
|
|
1028
1066
|
experiment2.alternatives.each do |alt|
|