split 3.0.0 → 4.0.1

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.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc +1 -1
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  5. data/.github/dependabot.yml +7 -0
  6. data/.github/workflows/ci.yml +71 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +71 -1044
  9. data/.rubocop_todo.yml +226 -0
  10. data/Appraisals +12 -1
  11. data/CHANGELOG.md +157 -0
  12. data/CODE_OF_CONDUCT.md +3 -3
  13. data/CONTRIBUTING.md +54 -5
  14. data/Gemfile +2 -0
  15. data/LICENSE +1 -1
  16. data/README.md +232 -121
  17. data/Rakefile +2 -0
  18. data/gemfiles/5.0.gemfile +1 -2
  19. data/gemfiles/{4.2.gemfile → 5.1.gemfile} +2 -2
  20. data/gemfiles/5.2.gemfile +9 -0
  21. data/gemfiles/6.0.gemfile +9 -0
  22. data/gemfiles/6.1.gemfile +9 -0
  23. data/gemfiles/7.0.gemfile +9 -0
  24. data/lib/split/algorithms/block_randomization.rb +2 -0
  25. data/lib/split/algorithms/weighted_sample.rb +2 -1
  26. data/lib/split/algorithms/whiplash.rb +3 -2
  27. data/lib/split/alternative.rb +7 -3
  28. data/lib/split/cache.rb +28 -0
  29. data/lib/split/combined_experiments_helper.rb +38 -0
  30. data/lib/split/configuration.rb +24 -13
  31. data/lib/split/dashboard/helpers.rb +3 -2
  32. data/lib/split/dashboard/pagination_helpers.rb +87 -0
  33. data/lib/split/dashboard/paginator.rb +17 -0
  34. data/lib/split/dashboard/public/dashboard.js +10 -0
  35. data/lib/split/dashboard/public/style.css +14 -0
  36. data/lib/split/dashboard/views/_controls.erb +13 -0
  37. data/lib/split/dashboard/views/index.erb +5 -1
  38. data/lib/split/dashboard/views/layout.erb +1 -1
  39. data/lib/split/dashboard.rb +21 -1
  40. data/lib/split/encapsulated_helper.rb +3 -2
  41. data/lib/split/engine.rb +7 -2
  42. data/lib/split/exceptions.rb +1 -0
  43. data/lib/split/experiment.rb +103 -69
  44. data/lib/split/experiment_catalog.rb +1 -3
  45. data/lib/split/extensions/string.rb +1 -0
  46. data/lib/split/goals_collection.rb +2 -0
  47. data/lib/split/helper.rb +42 -9
  48. data/lib/split/metric.rb +2 -1
  49. data/lib/split/persistence/cookie_adapter.rb +58 -15
  50. data/lib/split/persistence/dual_adapter.rb +54 -12
  51. data/lib/split/persistence/redis_adapter.rb +5 -0
  52. data/lib/split/persistence/session_adapter.rb +1 -0
  53. data/lib/split/persistence.rb +4 -2
  54. data/lib/split/redis_interface.rb +9 -30
  55. data/lib/split/trial.rb +25 -17
  56. data/lib/split/user.rb +20 -4
  57. data/lib/split/version.rb +2 -4
  58. data/lib/split/zscore.rb +1 -0
  59. data/lib/split.rb +17 -3
  60. data/spec/alternative_spec.rb +13 -1
  61. data/spec/cache_spec.rb +88 -0
  62. data/spec/combined_experiments_helper_spec.rb +57 -0
  63. data/spec/configuration_spec.rb +17 -15
  64. data/spec/dashboard/pagination_helpers_spec.rb +200 -0
  65. data/spec/dashboard/paginator_spec.rb +37 -0
  66. data/spec/dashboard_helpers_spec.rb +2 -2
  67. data/spec/dashboard_spec.rb +78 -17
  68. data/spec/encapsulated_helper_spec.rb +2 -2
  69. data/spec/experiment_spec.rb +117 -13
  70. data/spec/goals_collection_spec.rb +1 -1
  71. data/spec/helper_spec.rb +211 -112
  72. data/spec/persistence/cookie_adapter_spec.rb +90 -23
  73. data/spec/persistence/dual_adapter_spec.rb +160 -68
  74. data/spec/persistence/redis_adapter_spec.rb +9 -0
  75. data/spec/redis_interface_spec.rb +0 -69
  76. data/spec/spec_helper.rb +5 -6
  77. data/spec/split_spec.rb +7 -7
  78. data/spec/trial_spec.rb +65 -19
  79. data/spec/user_spec.rb +45 -3
  80. data/split.gemspec +20 -10
  81. metadata +61 -35
  82. data/.travis.yml +0 -16
@@ -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
- extend Forwardable
9
- def_delegators :@adapter, :keys, :[], :[]=, :delete
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
- if logged_in.call(context)
26
- @adapter = logged_in_adapter.new(context)
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
- @adapter = logged_out_adapter.new(context)
41
+ @active_adapter.keys
29
42
  end
30
43
  end
31
44
 
32
- def self.with_config(options={})
33
- self.config.merge!(options)
34
- self
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 self.config
38
- @config ||= {}
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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  module Persistence
4
5
  class RedisAdapter
@@ -39,6 +40,10 @@ module Split
39
40
  Split.redis.hkeys(redis_key)
40
41
  end
41
42
 
43
+ def self.find(user_id)
44
+ new(nil, user_id)
45
+ end
46
+
42
47
  def self.with_config(options={})
43
48
  self.config.merge!(options)
44
49
  self
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  module Persistence
4
5
  class SessionAdapter
@@ -8,8 +8,10 @@ module Split
8
8
  require 'split/persistence/session_adapter'
9
9
 
10
10
  ADAPTERS = {
11
- :cookie => Split::Persistence::CookieAdapter,
12
- :session => Split::Persistence::SessionAdapter
11
+ cookie: Split::Persistence::CookieAdapter,
12
+ session: Split::Persistence::SessionAdapter,
13
+ redis: Split::Persistence::RedisAdapter,
14
+ dual_adapter: Split::Persistence::DualAdapter
13
15
  }.freeze
14
16
 
15
17
  def self.adapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Split
2
4
  # Simplifies the interface to Redis.
3
5
  class RedisInterface
@@ -6,42 +8,19 @@ module Split
6
8
  end
7
9
 
8
10
  def persist_list(list_name, list_values)
9
- max_index = list_length(list_name) - 1
10
- list_values.each_with_index do |value, index|
11
- if index > max_index
12
- add_to_list(list_name, value)
13
- else
14
- set_list_index(list_name, index, value)
11
+ if list_values.length > 0
12
+ redis.multi do |multi|
13
+ tmp_list = "#{list_name}_tmp"
14
+ multi.rpush(tmp_list, list_values)
15
+ multi.rename(tmp_list, list_name)
15
16
  end
16
17
  end
17
- make_list_length(list_name, list_values.length)
18
- list_values
19
- end
20
-
21
- def add_to_list(list_name, value)
22
- redis.rpush(list_name, value)
23
- end
24
-
25
- def set_list_index(list_name, index, value)
26
- redis.lset(list_name, index, value)
27
- end
28
18
 
29
- def list_length(list_name)
30
- redis.llen(list_name)
31
- end
32
-
33
- def remove_last_item_from_list(list_name)
34
- redis.rpop(list_name)
35
- end
36
-
37
- def make_list_length(list_name, new_length)
38
- while list_length(list_name) > new_length
39
- remove_last_item_from_list(list_name)
40
- end
19
+ list_values
41
20
  end
42
21
 
43
22
  def add_to_set(set_name, value)
44
- redis.sadd(set_name, value) unless redis.sismember(set_name, value)
23
+ redis.sadd(set_name, value)
45
24
  end
46
25
 
47
26
  private
data/lib/split/trial.rb CHANGED
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  class Trial
5
+ attr_accessor :goals
4
6
  attr_accessor :experiment
5
- attr_accessor :metadata
7
+ attr_writer :metadata
6
8
 
7
9
  def initialize(attrs = {})
8
10
  self.experiment = attrs.delete(:experiment)
9
11
  self.alternative = attrs.delete(:alternative)
10
12
  self.metadata = attrs.delete(:metadata)
13
+ self.goals = attrs.delete(:goals) || []
11
14
 
12
15
  @user = attrs.delete(:user)
13
16
  @options = attrs
14
17
 
15
- @alternative_choosen = false
18
+ @alternative_chosen = false
16
19
  end
17
20
 
18
21
  def metadata
@@ -33,7 +36,7 @@ module Split
33
36
  end
34
37
  end
35
38
 
36
- def complete!(goals=[], context = nil)
39
+ def complete!(context = nil)
37
40
  if alternative
38
41
  if Array(goals).empty?
39
42
  alternative.increment_completion
@@ -51,8 +54,9 @@ module Split
51
54
  def choose!(context = nil)
52
55
  @user.cleanup_old_experiments!
53
56
  # Only run the process once
54
- return alternative if @alternative_choosen
57
+ return alternative if @alternative_chosen
55
58
 
59
+ new_participant = @user[@experiment.key].nil?
56
60
  if override_is_alternative?
57
61
  self.alternative = @options[:override]
58
62
  if should_store_alternative? && !@user[@experiment.key]
@@ -68,23 +72,27 @@ module Split
68
72
  if exclude_user?
69
73
  self.alternative = @experiment.control
70
74
  else
71
- value = @user[@experiment.key]
72
- if value
73
- self.alternative = value
74
- else
75
- self.alternative = @experiment.next_alternative
76
-
77
- # Increment the number of participants since we are actually choosing a new alternative
78
- self.alternative.increment_participation
79
-
80
- run_callback context, Split.configuration.on_trial_choose
75
+ self.alternative = @user[@experiment.key]
76
+ if alternative.nil?
77
+ if @experiment.cohorting_disabled?
78
+ self.alternative = @experiment.control
79
+ else
80
+ self.alternative = @experiment.next_alternative
81
+
82
+ # Increment the number of participants since we are actually choosing a new alternative
83
+ self.alternative.increment_participation
84
+
85
+ run_callback context, Split.configuration.on_trial_choose
86
+ end
81
87
  end
82
88
  end
83
89
  end
84
90
 
85
- @user[@experiment.key] = alternative.name if should_store_alternative?
86
- @alternative_choosen = true
87
- run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled?
91
+ new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled?
92
+
93
+ @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled
94
+ @alternative_chosen = true
95
+ run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled
88
96
  alternative
89
97
  end
90
98
 
data/lib/split/user.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Split
@@ -6,11 +8,13 @@ module Split
6
8
  def_delegators :@user, :keys, :[], :[]=, :delete
7
9
  attr_reader :user
8
10
 
9
- def initialize(context, adapter=nil)
11
+ def initialize(context, adapter = nil)
10
12
  @user = adapter || Split::Persistence.adapter.new(context)
13
+ @cleaned_up = false
11
14
  end
12
15
 
13
16
  def cleanup_old_experiments!
17
+ return if @cleaned_up
14
18
  keys_without_finished(user.keys).each do |key|
15
19
  experiment = ExperimentCatalog.find key_without_version(key)
16
20
  if experiment.nil? || experiment.has_winner? || experiment.start_time.nil?
@@ -18,12 +22,14 @@ module Split
18
22
  user.delete Experiment.finished_key(key)
19
23
  end
20
24
  end
25
+ @cleaned_up = true
21
26
  end
22
27
 
23
28
  def max_experiments_reached?(experiment_key)
24
29
  if Split.configuration.allow_multiple_experiments == 'control'
25
30
  experiments = active_experiments
26
- count_control = experiments.count {|k,v| k == experiment_key || v == 'control'}
31
+ experiment_key_without_version = key_without_version(experiment_key)
32
+ count_control = experiments.count {|k, v| k == experiment_key_without_version || v == 'control'}
27
33
  experiments.size > count_control
28
34
  else
29
35
  !Split.configuration.allow_multiple_experiments &&
@@ -32,13 +38,13 @@ module Split
32
38
  end
33
39
 
34
40
  def cleanup_old_versions!(experiment)
35
- keys = user.keys.select { |k| k.match(Regexp.new(experiment.name)) }
41
+ keys = user.keys.select { |k| key_without_version(k) == experiment.name }
36
42
  keys_without_experiment(keys, experiment.key).each { |key| user.delete(key) }
37
43
  end
38
44
 
39
45
  def active_experiments
40
46
  experiment_pairs = {}
41
- user.keys.each do |key|
47
+ keys_without_finished(user.keys).each do |key|
42
48
  Metric.possible_experiments(key_without_version(key)).each do |experiment|
43
49
  if !experiment.has_winner?
44
50
  experiment_pairs[key_without_version(key)] = user[key]
@@ -48,6 +54,16 @@ module Split
48
54
  experiment_pairs
49
55
  end
50
56
 
57
+ def self.find(user_id, adapter)
58
+ adapter = adapter.is_a?(Symbol) ? Split::Persistence::ADAPTERS[adapter] : adapter
59
+
60
+ if adapter.respond_to?(:find)
61
+ User.new(nil, adapter.find(user_id))
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
51
67
  private
52
68
 
53
69
  def keys_without_experiment(keys, experiment_key)
data/lib/split/version.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
- MAJOR = 3
4
- MINOR = 0
5
- PATCH = 0
6
- VERSION = [MAJOR, MINOR, PATCH].join('.')
4
+ VERSION = "4.0.1"
7
5
  end
data/lib/split/zscore.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  class Zscore
4
5
 
data/lib/split.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'redis'
3
4
 
4
5
  require 'split/algorithms/block_randomization'
5
6
  require 'split/algorithms/weighted_sample'
6
7
  require 'split/algorithms/whiplash'
7
8
  require 'split/alternative'
9
+ require 'split/cache'
8
10
  require 'split/configuration'
9
11
  require 'split/encapsulated_helper'
10
12
  require 'split/exceptions'
@@ -13,6 +15,7 @@ require 'split/experiment_catalog'
13
15
  require 'split/extensions/string'
14
16
  require 'split/goals_collection'
15
17
  require 'split/helper'
18
+ require 'split/combined_experiments_helper'
16
19
  require 'split/metric'
17
20
  require 'split/persistence'
18
21
  require 'split/redis_interface'
@@ -34,9 +37,9 @@ module Split
34
37
  # `Redis::DistRedis`, or `Redis::Namespace`.
35
38
  def redis=(server)
36
39
  @redis = if server.is_a?(String)
37
- Redis.new(:url => server, :thread_safe => true)
40
+ Redis.new(url: server)
38
41
  elsif server.is_a?(Hash)
39
- Redis.new(server.merge(:thread_safe => true))
42
+ Redis.new(server)
40
43
  elsif server.respond_to?(:smembers)
41
44
  server
42
45
  else
@@ -63,6 +66,17 @@ module Split
63
66
  self.configuration ||= Configuration.new
64
67
  yield(configuration)
65
68
  end
69
+
70
+ def cache(namespace, key, &block)
71
+ Split::Cache.fetch(namespace, key, &block)
72
+ end
66
73
  end
67
74
 
68
- Split.configure {}
75
+ # Check to see if being run in a Rails application. If so, wait until before_initialize to run configuration so Gems that create ENV variables have the chance to initialize first.
76
+ if defined?(::Rails)
77
+ class Split::Railtie < Rails::Railtie
78
+ config.before_initialize { Split.configure {} }
79
+ end
80
+ else
81
+ Split.configure {}
82
+ end
@@ -126,7 +126,7 @@ describe Split::Alternative do
126
126
 
127
127
  it "should save to redis" do
128
128
  alternative.save
129
- expect(Split.redis.exists('basket_text:Basket')).to be true
129
+ expect(Split.redis.exists?('basket_text:Basket')).to be true
130
130
  end
131
131
 
132
132
  it "should increment participation count" do
@@ -273,6 +273,18 @@ describe Split::Alternative do
273
273
  expect(control.z_score(goal1)).to eq('N/A')
274
274
  expect(control.z_score(goal2)).to eq('N/A')
275
275
  end
276
+
277
+ it "should not blow up for Conversion Rates > 1" do
278
+ control = experiment.control
279
+ control.participant_count = 3474
280
+ control.set_completed_count(4244)
281
+
282
+ alternative2.participant_count = 3434
283
+ alternative2.set_completed_count(4358)
284
+
285
+ expect { control.z_score }.not_to raise_error
286
+ expect { alternative2.z_score }.not_to raise_error
287
+ end
276
288
  end
277
289
 
278
290
  describe "extra_info" do
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ describe Split::Cache do
5
+
6
+ let(:namespace) { :test_namespace }
7
+ let(:key) { :test_key }
8
+ let(:now) { 1606189017 }
9
+
10
+ before { allow(Time).to receive(:now).and_return(now) }
11
+
12
+ describe 'clear' do
13
+
14
+ before { Split.configuration.cache = true }
15
+
16
+ it 'clears the cache' do
17
+ expect(Time).to receive(:now).and_return(now).exactly(2).times
18
+ Split::Cache.fetch(namespace, key) { Time.now }
19
+ Split::Cache.clear
20
+ Split::Cache.fetch(namespace, key) { Time.now }
21
+ end
22
+ end
23
+
24
+ describe 'clear_key' do
25
+ before { Split.configuration.cache = true }
26
+
27
+ it 'clears the cache' do
28
+ expect(Time).to receive(:now).and_return(now).exactly(3).times
29
+ Split::Cache.fetch(namespace, :key1) { Time.now }
30
+ Split::Cache.fetch(namespace, :key2) { Time.now }
31
+ Split::Cache.clear_key(:key1)
32
+
33
+ Split::Cache.fetch(namespace, :key1) { Time.now }
34
+ Split::Cache.fetch(namespace, :key2) { Time.now }
35
+ end
36
+ end
37
+
38
+ describe 'fetch' do
39
+
40
+ subject { Split::Cache.fetch(namespace, key) { Time.now } }
41
+
42
+ context 'when cache disabled' do
43
+
44
+ before { Split.configuration.cache = false }
45
+
46
+ it 'returns the yield' do
47
+ expect(subject).to eql(now)
48
+ end
49
+
50
+ it 'yields every time' do
51
+ expect(Time).to receive(:now).and_return(now).exactly(2).times
52
+ Split::Cache.fetch(namespace, key) { Time.now }
53
+ Split::Cache.fetch(namespace, key) { Time.now }
54
+ end
55
+ end
56
+
57
+ context 'when cache enabled' do
58
+
59
+ before { Split.configuration.cache = true }
60
+
61
+ it 'returns the yield' do
62
+ expect(subject).to eql(now)
63
+ end
64
+
65
+ it 'yields once' do
66
+ expect(Time).to receive(:now).and_return(now).once
67
+ Split::Cache.fetch(namespace, key) { Time.now }
68
+ Split::Cache.fetch(namespace, key) { Time.now }
69
+ end
70
+
71
+ it 'honors namespace' do
72
+ expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
73
+ expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
74
+
75
+ expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
76
+ expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
77
+ end
78
+
79
+ it 'honors key' do
80
+ expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
81
+ expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
82
+
83
+ expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
84
+ expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/combined_experiments_helper'
4
+
5
+ describe Split::CombinedExperimentsHelper do
6
+ include Split::CombinedExperimentsHelper
7
+
8
+ describe 'ab_combined_test' do
9
+ let!(:config_enabled) { true }
10
+ let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
11
+ let!(:allow_multiple_experiments) { true }
12
+
13
+ before do
14
+ Split.configuration.experiments = {
15
+ :combined_exp_1 => {
16
+ :alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ],
17
+ :metric => :my_metric,
18
+ :combined_experiments => combined_experiments
19
+ }
20
+ }
21
+ Split.configuration.enabled = config_enabled
22
+ Split.configuration.allow_multiple_experiments = allow_multiple_experiments
23
+ end
24
+
25
+ context 'without config enabled' do
26
+ let!(:config_enabled) { false }
27
+
28
+ it "raises an error" do
29
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
30
+ end
31
+ end
32
+
33
+ context 'multiple experiments disabled' do
34
+ let!(:allow_multiple_experiments) { false }
35
+
36
+ it "raises an error if multiple experiments is disabled" do
37
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError)
38
+ end
39
+ end
40
+
41
+ context 'without combined experiments' do
42
+ let!(:combined_experiments) { nil }
43
+
44
+ it "raises an error" do
45
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
46
+ end
47
+ end
48
+
49
+ it "uses same alternative for all sub experiments and returns the alternative" do
50
+ allow(self).to receive(:get_alternative) { "test-alt" }
51
+ expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
52
+ expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
53
+
54
+ expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
55
+ end
56
+ end
57
+ end
@@ -212,23 +212,12 @@ describe Split::Configuration do
212
212
  expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}})
213
213
  end
214
214
 
215
- context 'redis_url configuration [DEPRECATED]' do
216
- it 'should warn on set and assign to #redis' do
217
- expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
218
- @config.redis_url = 'example_url'
219
- expect(@config.redis).to eq('example_url')
220
- end
221
-
222
- it 'should warn on get and return #redis' do
223
- expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
224
- @config.redis = 'example_url'
225
- expect(@config.redis_url).to eq('example_url')
226
- end
227
- end
228
-
229
215
  context "redis configuration" do
230
216
  it "should default to local redis server" do
231
- expect(@config.redis).to eq("redis://localhost:6379")
217
+ old_redis_url = ENV['REDIS_URL']
218
+ ENV.delete('REDIS_URL')
219
+ expect(Split::Configuration.new.redis).to eq("redis://localhost:6379")
220
+ ENV['REDIS_URL'] = old_redis_url
232
221
  end
233
222
 
234
223
  it "should allow for redis url to be configured" do
@@ -238,8 +227,10 @@ describe Split::Configuration do
238
227
 
239
228
  context "provided REDIS_URL environment variable" do
240
229
  it "should use the ENV variable" do
230
+ old_redis_url = ENV['REDIS_URL']
241
231
  ENV['REDIS_URL'] = "env_redis_url"
242
232
  expect(Split::Configuration.new.redis).to eq("env_redis_url")
233
+ ENV['REDIS_URL'] = old_redis_url
243
234
  end
244
235
  end
245
236
  end
@@ -255,4 +246,15 @@ describe Split::Configuration do
255
246
  end
256
247
  end
257
248
 
249
+ context "persistence cookie domain" do
250
+ it "should default to nil" do
251
+ expect(@config.persistence_cookie_domain).to eq(nil)
252
+ end
253
+
254
+ it "should allow the persistence cookie domain to be configured" do
255
+ @config.persistence_cookie_domain = '.acme.com'
256
+ expect(@config.persistence_cookie_domain).to eq('.acme.com')
257
+ end
258
+ end
259
+
258
260
  end