split 3.2.0 → 4.0.5
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 +5 -5
- data/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +63 -0
- data/.rspec +1 -0
- data/.rubocop.yml +67 -1043
- data/CHANGELOG.md +174 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +6 -1
- data/README.md +79 -33
- data/Rakefile +6 -5
- data/lib/split/algorithms/block_randomization.rb +7 -6
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +17 -18
- data/lib/split/algorithms.rb +14 -0
- data/lib/split/alternative.rb +25 -25
- data/lib/split/cache.rb +27 -0
- data/lib/split/combined_experiments_helper.rb +6 -5
- data/lib/split/configuration.rb +94 -91
- data/lib/split/dashboard/helpers.rb +9 -9
- data/lib/split/dashboard/pagination_helpers.rb +86 -0
- data/lib/split/dashboard/paginator.rb +17 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +19 -2
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/_experiment.erb +2 -1
- data/lib/split/dashboard/views/index.erb +24 -5
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +47 -20
- data/lib/split/encapsulated_helper.rb +15 -8
- data/lib/split/engine.rb +7 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +160 -122
- data/lib/split/experiment_catalog.rb +7 -8
- data/lib/split/extensions/string.rb +2 -1
- data/lib/split/goals_collection.rb +10 -10
- data/lib/split/helper.rb +56 -24
- data/lib/split/metric.rb +6 -6
- data/lib/split/persistence/cookie_adapter.rb +52 -15
- data/lib/split/persistence/dual_adapter.rb +53 -12
- data/lib/split/persistence/redis_adapter.rb +8 -4
- data/lib/split/persistence/session_adapter.rb +1 -2
- data/lib/split/persistence.rb +8 -6
- data/lib/split/redis_interface.rb +16 -31
- data/lib/split/trial.rb +48 -41
- data/lib/split/user.rb +30 -15
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +2 -3
- data/lib/split.rb +39 -25
- data/spec/algorithms/block_randomization_spec.rb +6 -5
- data/spec/algorithms/weighted_sample_spec.rb +6 -5
- data/spec/algorithms/whiplash_spec.rb +4 -5
- data/spec/alternative_spec.rb +35 -36
- data/spec/cache_spec.rb +84 -0
- data/spec/combined_experiments_helper_spec.rb +18 -17
- data/spec/configuration_spec.rb +41 -45
- data/spec/dashboard/pagination_helpers_spec.rb +202 -0
- data/spec/dashboard/paginator_spec.rb +38 -0
- data/spec/dashboard_helpers_spec.rb +19 -18
- data/spec/dashboard_spec.rb +153 -48
- data/spec/encapsulated_helper_spec.rb +47 -23
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +224 -111
- data/spec/goals_collection_spec.rb +18 -16
- data/spec/helper_spec.rb +539 -419
- data/spec/metric_spec.rb +14 -14
- data/spec/persistence/cookie_adapter_spec.rb +105 -27
- data/spec/persistence/dual_adapter_spec.rb +158 -66
- data/spec/persistence/redis_adapter_spec.rb +35 -27
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +25 -82
- data/spec/spec_helper.rb +38 -24
- data/spec/split_spec.rb +18 -18
- data/spec/support/cookies_mock.rb +1 -2
- data/spec/trial_spec.rb +117 -70
- data/spec/user_spec.rb +69 -27
- data/split.gemspec +26 -22
- metadata +85 -37
- data/.travis.yml +0 -41
- data/Appraisals +0 -13
- data/gemfiles/4.2.gemfile +0 -9
- data/gemfiles/5.0.gemfile +0 -10
- data/gemfiles/5.1.gemfile +0 -10
|
@@ -1,111 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
2
4
|
|
|
3
5
|
describe Split::RedisInterface do
|
|
4
|
-
let(:list_name) {
|
|
5
|
-
let(:set_name) {
|
|
6
|
+
let(:list_name) { "list_name" }
|
|
7
|
+
let(:set_name) { "set_name" }
|
|
6
8
|
let(:interface) { described_class.new }
|
|
7
9
|
|
|
8
|
-
describe
|
|
10
|
+
describe "#persist_list" do
|
|
9
11
|
subject(:persist_list) do
|
|
10
12
|
interface.persist_list(list_name, %w(a b c d))
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
specify do
|
|
14
16
|
expect(persist_list).to eq %w(a b c d)
|
|
15
|
-
expect(Split.redis.lindex(list_name, 0)).to eq
|
|
16
|
-
expect(Split.redis.lindex(list_name, 1)).to eq
|
|
17
|
-
expect(Split.redis.lindex(list_name, 2)).to eq
|
|
18
|
-
expect(Split.redis.lindex(list_name, 3)).to eq
|
|
17
|
+
expect(Split.redis.lindex(list_name, 0)).to eq "a"
|
|
18
|
+
expect(Split.redis.lindex(list_name, 1)).to eq "b"
|
|
19
|
+
expect(Split.redis.lindex(list_name, 2)).to eq "c"
|
|
20
|
+
expect(Split.redis.lindex(list_name, 3)).to eq "d"
|
|
19
21
|
expect(Split.redis.llen(list_name)).to eq 4
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
context
|
|
24
|
+
context "list is overwritten but not deleted" do
|
|
23
25
|
specify do
|
|
24
26
|
expect(persist_list).to eq %w(a b c d)
|
|
25
|
-
interface.persist_list(list_name, [
|
|
26
|
-
expect(Split.redis.lindex(list_name, 0)).to eq
|
|
27
|
+
interface.persist_list(list_name, ["z"])
|
|
28
|
+
expect(Split.redis.lindex(list_name, 0)).to eq "z"
|
|
27
29
|
expect(Split.redis.llen(list_name)).to eq 1
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
describe
|
|
33
|
-
subject(:
|
|
34
|
-
interface.
|
|
35
|
-
interface.add_to_list(list_name, 'z')
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
specify do
|
|
39
|
-
add_to_list
|
|
40
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'y'
|
|
41
|
-
expect(Split.redis.lindex(list_name, 1)).to eq 'z'
|
|
42
|
-
expect(Split.redis.llen(list_name)).to eq 2
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
describe '#set_list_index' do
|
|
47
|
-
subject(:set_list_index) do
|
|
48
|
-
interface.add_to_list(list_name, 'y')
|
|
49
|
-
interface.add_to_list(list_name, 'z')
|
|
50
|
-
interface.set_list_index(list_name, 0, 'a')
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
specify do
|
|
54
|
-
set_list_index
|
|
55
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'a'
|
|
56
|
-
expect(Split.redis.lindex(list_name, 1)).to eq 'z'
|
|
57
|
-
expect(Split.redis.llen(list_name)).to eq 2
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
describe '#list_length' do
|
|
62
|
-
subject(:list_length) do
|
|
63
|
-
interface.add_to_list(list_name, 'y')
|
|
64
|
-
interface.add_to_list(list_name, 'z')
|
|
65
|
-
interface.list_length(list_name)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
specify do
|
|
69
|
-
expect(list_length).to eq 2
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
describe '#remove_last_item_from_list' do
|
|
74
|
-
subject(:remove_last_item_from_list) do
|
|
75
|
-
interface.add_to_list(list_name, 'y')
|
|
76
|
-
interface.add_to_list(list_name, 'z')
|
|
77
|
-
interface.remove_last_item_from_list(list_name)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
specify do
|
|
81
|
-
remove_last_item_from_list
|
|
82
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'y'
|
|
83
|
-
expect(Split.redis.llen(list_name)).to eq 1
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
describe '#make_list_length' do
|
|
88
|
-
subject(:make_list_length) do
|
|
89
|
-
interface.add_to_list(list_name, 'y')
|
|
90
|
-
interface.add_to_list(list_name, 'z')
|
|
91
|
-
interface.make_list_length(list_name, 1)
|
|
34
|
+
describe "#add_to_set" do
|
|
35
|
+
subject(:add_to_set) do
|
|
36
|
+
interface.add_to_set(set_name, "something")
|
|
92
37
|
end
|
|
93
38
|
|
|
94
39
|
specify do
|
|
95
|
-
|
|
96
|
-
expect(Split.redis.
|
|
97
|
-
expect(Split.redis.llen(list_name)).to eq 1
|
|
40
|
+
add_to_set
|
|
41
|
+
expect(Split.redis.sismember(set_name, "something")).to be true
|
|
98
42
|
end
|
|
99
|
-
end
|
|
100
43
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
interface.add_to_set(set_name, 'something')
|
|
104
|
-
end
|
|
44
|
+
context "when a Redis version is used that supports the 'sadd?' method" do
|
|
45
|
+
before { expect(Split.redis).to receive(:respond_to?).with(:sadd?).and_return(true) }
|
|
105
46
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
47
|
+
it "will use this method instead of 'sadd'" do
|
|
48
|
+
expect(Split.redis).to receive(:sadd?).with(set_name, "something")
|
|
49
|
+
expect(Split.redis).not_to receive(:sadd).with(set_name, "something")
|
|
50
|
+
add_to_set
|
|
51
|
+
end
|
|
109
52
|
end
|
|
110
53
|
end
|
|
111
54
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
ENV['RACK_ENV'] = "test"
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
require 'bundler/setup'
|
|
3
|
+
ENV["RACK_ENV"] = "test"
|
|
6
4
|
|
|
7
|
-
require
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
require 'split'
|
|
11
|
-
require 'ostruct'
|
|
12
|
-
require 'yaml'
|
|
5
|
+
require "rubygems"
|
|
6
|
+
require "bundler/setup"
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
require "simplecov"
|
|
9
|
+
SimpleCov.start
|
|
15
10
|
|
|
16
|
-
require "
|
|
11
|
+
require "split"
|
|
12
|
+
require "yaml"
|
|
13
|
+
require "pry"
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
Dir["./spec/support/*.rb"].each { |f| require f }
|
|
19
16
|
|
|
20
17
|
module GlobalSharedContext
|
|
21
18
|
extend RSpec::SharedContext
|
|
22
|
-
let(:mock_user){ Split::User.new(double(session: {})) }
|
|
19
|
+
let(:mock_user) { Split::User.new(double(session: {})) }
|
|
20
|
+
|
|
23
21
|
before(:each) do
|
|
24
22
|
Split.configuration = Split::Configuration.new
|
|
25
|
-
Split.redis =
|
|
26
|
-
Split.redis.
|
|
23
|
+
Split.redis = Redis.new
|
|
24
|
+
Split.redis.select(10)
|
|
25
|
+
Split.redis.flushdb
|
|
26
|
+
Split::Cache.clear
|
|
27
27
|
@ab_user = mock_user
|
|
28
|
-
params = nil
|
|
28
|
+
@params = nil
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
RSpec.configure do |config|
|
|
33
|
-
config.order =
|
|
33
|
+
config.order = "random"
|
|
34
34
|
config.include GlobalSharedContext
|
|
35
|
+
config.raise_errors_for_deprecations!
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def session
|
|
@@ -42,11 +43,24 @@ def params
|
|
|
42
43
|
@params ||= {}
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
def request
|
|
46
|
-
@request ||=
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
def request
|
|
47
|
+
@request ||= build_request
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
DummyRequest = Struct.new(:user_agent, :ip, :params, :cookies, :headers)
|
|
51
|
+
|
|
52
|
+
def build_request(
|
|
53
|
+
user_agent: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
|
|
54
|
+
ip: "192.168.1.1",
|
|
55
|
+
params: {},
|
|
56
|
+
cookies: {},
|
|
57
|
+
headers: {}
|
|
58
|
+
)
|
|
59
|
+
r = DummyRequest.new
|
|
60
|
+
r.user_agent = user_agent
|
|
61
|
+
r.ip = ip
|
|
62
|
+
r.params = params
|
|
63
|
+
r.cookies = cookies
|
|
64
|
+
r.headers = headers
|
|
65
|
+
r
|
|
52
66
|
end
|
data/spec/split_spec.rb
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'spec_helper'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
require "spec_helper"
|
|
5
4
|
|
|
5
|
+
RSpec.describe Split do
|
|
6
6
|
around(:each) do |ex|
|
|
7
|
-
old_env, old_redis = [ENV.delete(
|
|
7
|
+
old_env, old_redis = [ENV.delete("REDIS_URL"), Split.redis]
|
|
8
8
|
ex.run
|
|
9
|
-
ENV[
|
|
9
|
+
ENV["REDIS_URL"] = old_env
|
|
10
10
|
Split.redis = old_redis
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
describe
|
|
14
|
-
it
|
|
15
|
-
Split.redis =
|
|
13
|
+
describe "#redis=" do
|
|
14
|
+
it "accepts a url string" do
|
|
15
|
+
Split.redis = "redis://localhost:6379"
|
|
16
16
|
expect(Split.redis).to be_a(Redis)
|
|
17
17
|
|
|
18
|
-
client = Split.redis.
|
|
19
|
-
expect(client
|
|
20
|
-
expect(client
|
|
18
|
+
client = Split.redis.connection
|
|
19
|
+
expect(client[:host]).to eq("localhost")
|
|
20
|
+
expect(client[:port]).to eq(6379)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
it
|
|
24
|
-
Split.redis = {host:
|
|
23
|
+
it "accepts an options hash" do
|
|
24
|
+
Split.redis = { host: "localhost", port: 6379, db: 12 }
|
|
25
25
|
expect(Split.redis).to be_a(Redis)
|
|
26
26
|
|
|
27
|
-
client = Split.redis.
|
|
28
|
-
expect(client
|
|
29
|
-
expect(client
|
|
30
|
-
expect(client
|
|
27
|
+
client = Split.redis.connection
|
|
28
|
+
expect(client[:host]).to eq("localhost")
|
|
29
|
+
expect(client[:port]).to eq(6379)
|
|
30
|
+
expect(client[:db]).to eq(12)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
it
|
|
33
|
+
it "accepts a valid Redis instance" do
|
|
34
34
|
other_redis = Redis.new(url: "redis://localhost:6379")
|
|
35
35
|
Split.redis = other_redis
|
|
36
36
|
expect(Split.redis).to eq(other_redis)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
it
|
|
39
|
+
it "raises an ArgumentError when server cannot be determined" do
|
|
40
40
|
expect { Split.redis = Object.new }.to raise_error(ArgumentError)
|
|
41
41
|
end
|
|
42
42
|
end
|
data/spec/trial_spec.rb
CHANGED
|
@@ -1,54 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "split/trial"
|
|
4
5
|
|
|
5
6
|
describe Split::Trial do
|
|
6
7
|
let(:user) { mock_user }
|
|
7
|
-
let(:alternatives) { [
|
|
8
|
+
let(:alternatives) { ["basket", "cart"] }
|
|
8
9
|
let(:experiment) do
|
|
9
|
-
Split::Experiment.new(
|
|
10
|
+
Split::Experiment.new("basket_text", alternatives: alternatives).save
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
it "should be initializeable" do
|
|
13
|
-
experiment = double(
|
|
14
|
-
alternative = double(
|
|
15
|
-
trial = Split::Trial.new(:
|
|
14
|
+
experiment = double("experiment")
|
|
15
|
+
alternative = double("alternative", kind_of?: Split::Alternative)
|
|
16
|
+
trial = Split::Trial.new(experiment: experiment, alternative: alternative)
|
|
16
17
|
expect(trial.experiment).to eq(experiment)
|
|
17
18
|
expect(trial.alternative).to eq(alternative)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
describe "alternative" do
|
|
21
22
|
it "should use the alternative if specified" do
|
|
22
|
-
alternative = double(
|
|
23
|
-
trial = Split::Trial.new(:
|
|
24
|
-
:
|
|
23
|
+
alternative = double("alternative", kind_of?: Split::Alternative)
|
|
24
|
+
trial = Split::Trial.new(experiment: double("experiment"),
|
|
25
|
+
alternative: alternative, user: user)
|
|
25
26
|
expect(trial).not_to receive(:choose)
|
|
26
27
|
expect(trial.alternative).to eq(alternative)
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
it "should load the alternative when the alternative name is set" do
|
|
30
|
-
experiment = Split::Experiment.new(
|
|
31
|
+
experiment = Split::Experiment.new("basket_text", alternatives: ["basket", "cart"])
|
|
31
32
|
experiment.save
|
|
32
33
|
|
|
33
|
-
trial = Split::Trial.new(:
|
|
34
|
-
expect(trial.alternative.name).to eq(
|
|
34
|
+
trial = Split::Trial.new(experiment: experiment, alternative: "basket")
|
|
35
|
+
expect(trial.alternative.name).to eq("basket")
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
describe "metadata" do
|
|
39
40
|
let(:metadata) { Hash[alternatives.map { |k| [k, "Metadata for #{k}"] }] }
|
|
40
41
|
let(:experiment) do
|
|
41
|
-
Split::Experiment.new(
|
|
42
|
+
Split::Experiment.new("basket_text", alternatives: alternatives, metadata: metadata).save
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
it
|
|
45
|
-
trial = Split::Trial.new(:
|
|
46
|
-
:
|
|
47
|
-
expect(trial.metadata).to eq(metadata[
|
|
45
|
+
it "has metadata on each trial" do
|
|
46
|
+
trial = Split::Trial.new(experiment: experiment, user: user, metadata: metadata["cart"],
|
|
47
|
+
override: "cart")
|
|
48
|
+
expect(trial.metadata).to eq(metadata["cart"])
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
it
|
|
51
|
-
trial = Split::Trial.new(:
|
|
51
|
+
it "has metadata on each trial from the experiment" do
|
|
52
|
+
trial = Split::Trial.new(experiment: experiment, user: user)
|
|
52
53
|
trial.choose!
|
|
53
54
|
expect(trial.metadata).to eq(metadata[trial.alternative.name])
|
|
54
55
|
expect(trial.metadata).to match(/#{trial.alternative.name}/)
|
|
@@ -56,24 +57,24 @@ describe Split::Trial do
|
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
describe "#choose!" do
|
|
59
|
-
let(:context) { double(on_trial_callback:
|
|
60
|
+
let(:context) { double(on_trial_callback: "test callback") }
|
|
60
61
|
let(:trial) do
|
|
61
|
-
Split::Trial.new(:
|
|
62
|
+
Split::Trial.new(user: user, experiment: experiment)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
shared_examples_for
|
|
65
|
-
it
|
|
65
|
+
shared_examples_for "a trial with callbacks" do
|
|
66
|
+
it "does not run if on_trial callback is not respondable" do
|
|
66
67
|
Split.configuration.on_trial = :foo
|
|
67
68
|
allow(context).to receive(:respond_to?).with(:foo, true).and_return false
|
|
68
69
|
expect(context).to_not receive(:foo)
|
|
69
70
|
trial.choose! context
|
|
70
71
|
end
|
|
71
|
-
it
|
|
72
|
+
it "runs on_trial callback" do
|
|
72
73
|
Split.configuration.on_trial = :on_trial_callback
|
|
73
74
|
expect(context).to receive(:on_trial_callback)
|
|
74
75
|
trial.choose! context
|
|
75
76
|
end
|
|
76
|
-
it
|
|
77
|
+
it "does not run nil on_trial callback" do
|
|
77
78
|
Split.configuration.on_trial = nil
|
|
78
79
|
expect(context).not_to receive(:on_trial_callback)
|
|
79
80
|
trial.choose! context
|
|
@@ -88,12 +89,12 @@ describe Split::Trial do
|
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
context "when override is present" do
|
|
91
|
-
let(:override) {
|
|
92
|
+
let(:override) { "cart" }
|
|
92
93
|
let(:trial) do
|
|
93
|
-
Split::Trial.new(:
|
|
94
|
+
Split::Trial.new(user: user, experiment: experiment, override: override)
|
|
94
95
|
end
|
|
95
96
|
|
|
96
|
-
it_behaves_like
|
|
97
|
+
it_behaves_like "a trial with callbacks"
|
|
97
98
|
|
|
98
99
|
it "picks the override" do
|
|
99
100
|
expect(experiment).to_not receive(:next_alternative)
|
|
@@ -102,7 +103,7 @@ describe Split::Trial do
|
|
|
102
103
|
|
|
103
104
|
context "when alternative doesn't exist" do
|
|
104
105
|
let(:override) { nil }
|
|
105
|
-
it
|
|
106
|
+
it "falls back on next_alternative" do
|
|
106
107
|
expect(experiment).to receive(:next_alternative).and_call_original
|
|
107
108
|
expect_alternative(trial, alternatives)
|
|
108
109
|
end
|
|
@@ -111,7 +112,7 @@ describe Split::Trial do
|
|
|
111
112
|
|
|
112
113
|
context "when disabled option is true" do
|
|
113
114
|
let(:trial) do
|
|
114
|
-
Split::Trial.new(:
|
|
115
|
+
Split::Trial.new(user: user, experiment: experiment, disabled: true)
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
it "picks the control", :aggregate_failures do
|
|
@@ -120,7 +121,7 @@ describe Split::Trial do
|
|
|
120
121
|
|
|
121
122
|
expect(context).not_to receive(:on_trial_callback)
|
|
122
123
|
|
|
123
|
-
expect_alternative(trial,
|
|
124
|
+
expect_alternative(trial, "basket")
|
|
124
125
|
Split.configuration.on_trial = nil
|
|
125
126
|
end
|
|
126
127
|
end
|
|
@@ -132,7 +133,7 @@ describe Split::Trial do
|
|
|
132
133
|
|
|
133
134
|
expect(experiment).to_not receive(:next_alternative)
|
|
134
135
|
expect(context).not_to receive(:on_trial_callback)
|
|
135
|
-
expect_alternative(trial,
|
|
136
|
+
expect_alternative(trial, "basket")
|
|
136
137
|
|
|
137
138
|
Split.configuration.enabled = true
|
|
138
139
|
Split.configuration.on_trial = nil
|
|
@@ -141,40 +142,48 @@ describe Split::Trial do
|
|
|
141
142
|
|
|
142
143
|
context "when experiment has winner" do
|
|
143
144
|
let(:trial) do
|
|
144
|
-
Split::Trial.new(:
|
|
145
|
+
Split::Trial.new(user: user, experiment: experiment)
|
|
145
146
|
end
|
|
146
147
|
|
|
147
|
-
it_behaves_like
|
|
148
|
+
it_behaves_like "a trial with callbacks"
|
|
148
149
|
|
|
149
150
|
it "picks the winner" do
|
|
150
|
-
experiment.winner =
|
|
151
|
+
experiment.winner = "cart"
|
|
151
152
|
expect(experiment).to_not receive(:next_alternative)
|
|
152
153
|
|
|
153
|
-
expect_alternative(trial,
|
|
154
|
+
expect_alternative(trial, "cart")
|
|
154
155
|
end
|
|
155
156
|
end
|
|
156
157
|
|
|
157
158
|
context "when exclude is true" do
|
|
158
159
|
let(:trial) do
|
|
159
|
-
Split::Trial.new(:
|
|
160
|
+
Split::Trial.new(user: user, experiment: experiment, exclude: true)
|
|
160
161
|
end
|
|
161
162
|
|
|
162
|
-
it_behaves_like
|
|
163
|
+
it_behaves_like "a trial with callbacks"
|
|
163
164
|
|
|
164
165
|
it "picks the control" do
|
|
165
166
|
expect(experiment).to_not receive(:next_alternative)
|
|
166
|
-
expect_alternative(trial,
|
|
167
|
+
expect_alternative(trial, "basket")
|
|
167
168
|
end
|
|
168
169
|
end
|
|
169
170
|
|
|
170
171
|
context "when user is already participating" do
|
|
171
|
-
it_behaves_like
|
|
172
|
+
it_behaves_like "a trial with callbacks"
|
|
172
173
|
|
|
173
174
|
it "picks the same alternative" do
|
|
174
|
-
user[experiment.key] =
|
|
175
|
+
user[experiment.key] = "basket"
|
|
175
176
|
expect(experiment).to_not receive(:next_alternative)
|
|
176
177
|
|
|
177
|
-
expect_alternative(trial,
|
|
178
|
+
expect_alternative(trial, "basket")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context "when alternative is not found" do
|
|
182
|
+
it "falls back on next_alternative" do
|
|
183
|
+
user[experiment.key] = "notfound"
|
|
184
|
+
expect(experiment).to receive(:next_alternative).and_call_original
|
|
185
|
+
expect_alternative(trial, alternatives)
|
|
186
|
+
end
|
|
178
187
|
end
|
|
179
188
|
end
|
|
180
189
|
|
|
@@ -190,42 +199,68 @@ describe Split::Trial do
|
|
|
190
199
|
expect(trial.alternative.name).to_not be_empty
|
|
191
200
|
Split.configuration.on_trial_choose = nil
|
|
192
201
|
end
|
|
202
|
+
|
|
203
|
+
it "assigns user to an alternative" do
|
|
204
|
+
trial.choose! context
|
|
205
|
+
|
|
206
|
+
expect(alternatives).to include(user[experiment.name])
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context "when cohorting is disabled" do
|
|
210
|
+
before(:each) { allow(experiment).to receive(:cohorting_disabled?).and_return(true) }
|
|
211
|
+
|
|
212
|
+
it "picks the control and does not run on_trial callbacks" do
|
|
213
|
+
Split.configuration.on_trial = :on_trial_callback
|
|
214
|
+
|
|
215
|
+
expect(experiment).to_not receive(:next_alternative)
|
|
216
|
+
expect(context).not_to receive(:on_trial_callback)
|
|
217
|
+
expect_alternative(trial, "basket")
|
|
218
|
+
|
|
219
|
+
Split.configuration.enabled = true
|
|
220
|
+
Split.configuration.on_trial = nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "user is not assigned an alternative" do
|
|
224
|
+
trial.choose! context
|
|
225
|
+
|
|
226
|
+
expect(user[experiment]).to eq(nil)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
193
229
|
end
|
|
194
230
|
end
|
|
195
231
|
|
|
196
232
|
describe "#complete!" do
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
it
|
|
233
|
+
context "when there are no goals" do
|
|
234
|
+
let(:trial) { Split::Trial.new(user: user, experiment: experiment) }
|
|
235
|
+
it "should complete the trial" do
|
|
200
236
|
trial.choose!
|
|
201
237
|
old_completed_count = trial.alternative.completed_count
|
|
202
238
|
trial.complete!
|
|
203
|
-
expect(trial.alternative.completed_count).to
|
|
239
|
+
expect(trial.alternative.completed_count).to eq(old_completed_count + 1)
|
|
204
240
|
end
|
|
205
241
|
end
|
|
206
242
|
|
|
207
|
-
context
|
|
208
|
-
let(:goals) { [
|
|
209
|
-
let(:trial) { Split::Trial.new(:
|
|
210
|
-
shared_examples_for "goal completion" do
|
|
211
|
-
it 'should not complete the trial' do
|
|
212
|
-
trial.choose!
|
|
213
|
-
old_completed_count = trial.alternative.completed_count
|
|
214
|
-
trial.complete!(goal)
|
|
215
|
-
expect(trial.alternative.completed_count).to_not be(old_completed_count+1)
|
|
216
|
-
end
|
|
217
|
-
end
|
|
243
|
+
context "when there are many goals" do
|
|
244
|
+
let(:goals) { [ "goal1", "goal2" ] }
|
|
245
|
+
let(:trial) { Split::Trial.new(user: user, experiment: experiment, goals: goals) }
|
|
218
246
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
it "increments the completed count corresponding to the goals" do
|
|
248
|
+
trial.choose!
|
|
249
|
+
old_completed_counts = goals.map { |goal| [goal, trial.alternative.completed_count(goal)] }.to_h
|
|
250
|
+
trial.complete!
|
|
251
|
+
goals.each { | goal | expect(trial.alternative.completed_count(goal)).to eq(old_completed_counts[goal] + 1) }
|
|
222
252
|
end
|
|
253
|
+
end
|
|
223
254
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
255
|
+
context "when there is 1 goal of type string" do
|
|
256
|
+
let(:goal) { "goal" }
|
|
257
|
+
let(:trial) { Split::Trial.new(user: user, experiment: experiment, goals: goal) }
|
|
258
|
+
it "increments the completed count corresponding to the goal" do
|
|
259
|
+
trial.choose!
|
|
260
|
+
old_completed_count = trial.alternative.completed_count(goal)
|
|
261
|
+
trial.complete!
|
|
262
|
+
expect(trial.alternative.completed_count(goal)).to eq(old_completed_count + 1)
|
|
227
263
|
end
|
|
228
|
-
|
|
229
264
|
end
|
|
230
265
|
end
|
|
231
266
|
|
|
@@ -234,7 +269,7 @@ describe Split::Trial do
|
|
|
234
269
|
|
|
235
270
|
context "when override is present" do
|
|
236
271
|
it "stores when store_override is true" do
|
|
237
|
-
trial = Split::Trial.new(:
|
|
272
|
+
trial = Split::Trial.new(user: user, experiment: experiment, override: "basket")
|
|
238
273
|
|
|
239
274
|
Split.configuration.store_override = true
|
|
240
275
|
expect(user).to receive("[]=")
|
|
@@ -243,7 +278,7 @@ describe Split::Trial do
|
|
|
243
278
|
end
|
|
244
279
|
|
|
245
280
|
it "does not store when store_override is false" do
|
|
246
|
-
trial = Split::Trial.new(:
|
|
281
|
+
trial = Split::Trial.new(user: user, experiment: experiment, override: "basket")
|
|
247
282
|
|
|
248
283
|
expect(user).to_not receive("[]=")
|
|
249
284
|
trial.choose!
|
|
@@ -252,7 +287,7 @@ describe Split::Trial do
|
|
|
252
287
|
|
|
253
288
|
context "when disabled is present" do
|
|
254
289
|
it "stores when store_override is true" do
|
|
255
|
-
trial = Split::Trial.new(:
|
|
290
|
+
trial = Split::Trial.new(user: user, experiment: experiment, disabled: true)
|
|
256
291
|
|
|
257
292
|
Split.configuration.store_override = true
|
|
258
293
|
expect(user).to receive("[]=")
|
|
@@ -260,7 +295,7 @@ describe Split::Trial do
|
|
|
260
295
|
end
|
|
261
296
|
|
|
262
297
|
it "does not store when store_override is false" do
|
|
263
|
-
trial = Split::Trial.new(:
|
|
298
|
+
trial = Split::Trial.new(user: user, experiment: experiment, disabled: true)
|
|
264
299
|
|
|
265
300
|
expect(user).to_not receive("[]=")
|
|
266
301
|
trial.choose!
|
|
@@ -269,8 +304,20 @@ describe Split::Trial do
|
|
|
269
304
|
|
|
270
305
|
context "when exclude is present" do
|
|
271
306
|
it "does not store" do
|
|
272
|
-
trial = Split::Trial.new(:
|
|
307
|
+
trial = Split::Trial.new(user: user, experiment: experiment, exclude: true)
|
|
308
|
+
|
|
309
|
+
expect(user).to_not receive("[]=")
|
|
310
|
+
trial.choose!
|
|
311
|
+
end
|
|
312
|
+
end
|
|
273
313
|
|
|
314
|
+
context "when experiment has winner" do
|
|
315
|
+
let(:trial) do
|
|
316
|
+
experiment.winner = "cart"
|
|
317
|
+
Split::Trial.new(user: user, experiment: experiment)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it "does not store" do
|
|
274
321
|
expect(user).to_not receive("[]=")
|
|
275
322
|
trial.choose!
|
|
276
323
|
end
|