verdict 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZDNjZTFjMmI3OTZlMDQyOTlkYTdjYzU5ZmNhZjMxYjcxODVhYmZmMQ==
5
- data.tar.gz: !binary |-
6
- MGU5YWNkZDY2YmIzNjhmZDAwNjFkODIzZGI0MGRmZjMwMWEwYzFlZg==
2
+ SHA1:
3
+ metadata.gz: bc058be7df27d778fc5c3d4333c1570c64edccc3
4
+ data.tar.gz: 7095d5c3a2e6408f29018ea2490f978debe05ed9
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MDI2ZDI3OTVhYjljZjU1Y2NiNDAyMWMwYTIyMTU1NTgwZGNhNTEzNWI0Nzdl
10
- ZWYwNGM2MDA1ODE3ZWE5ZjFkOGZlZmI5MWRkY2IxOTI2ZDQ4MzZjNTA4N2Ex
11
- MmYxZmNhMmYzOGM3Y2FkNTFiMDhmMTQ1ZjUwMzBjZjE2ZTViMGU=
12
- data.tar.gz: !binary |-
13
- YTYxYTM4NGMxNjUwOWUyM2ZkYjI2NTZhOTFmOGRlYjMzMWQ1ZjU4YmIzOTUy
14
- ZDQ2OTA0ZjNmZmY1OWJjMWVlOTI3NjliNzFmNmRkZjBkYzQ3NjNhNDIyODE1
15
- NjQxYmM0YjI3MGE1MTc0N2E4NWQ2MjczMDRhYmM0N2I1YTAxZDk=
6
+ metadata.gz: 0a74b192fd5d4a03a6524e9c8f88d761b8eb2e83e6eb33957f46d429d526d9506eaf9787ae6d3665a80424d077f32d1dbb72498b1c7a81bb6a5380d581662155
7
+ data.tar.gz: c73434af18dcaf65c05689daf3686ddc42a9bd6334018be0d7369fd577742c92ad6630ca4b82d539ba7ddb3aaa6a379a0a0204aa35cb9375dac3e09bd14b7229
@@ -2,12 +2,13 @@ class Verdict::Assignment
2
2
 
3
3
  attr_reader :experiment, :subject_identifier, :group, :created_at
4
4
 
5
- def initialize(experiment, subject_identifier, group, originally_created_at)
5
+ def initialize(experiment, subject_identifier, group, originally_created_at, temporary = false)
6
6
  @experiment = experiment
7
7
  @subject_identifier = subject_identifier
8
8
  @group = group
9
9
  @returning = !originally_created_at.nil?
10
10
  @created_at = originally_created_at || Time.now.utc
11
+ @temporary = temporary
11
12
  end
12
13
 
13
14
  def subject
@@ -18,6 +19,14 @@ class Verdict::Assignment
18
19
  !group.nil?
19
20
  end
20
21
 
22
+ def permanent?
23
+ !@temporary
24
+ end
25
+
26
+ def temporary?
27
+ @temporary
28
+ end
29
+
21
30
  def returning
22
31
  self.class.new(@experiment, @subject_identifier, @group, @created_at)
23
32
  end
@@ -38,14 +38,20 @@ class Verdict::Experiment
38
38
  segmenter.groups[handle.to_s]
39
39
  end
40
40
 
41
- def groups(segmenter_class = Verdict::Segmenter::StaticPercentage, &block)
42
- return @segmenter.groups unless block_given?
41
+ def groups(segmenter_class = Verdict::FixedPercentageSegmenter, &block)
42
+ return segmenter.groups unless block_given?
43
43
  @segmenter ||= segmenter_class.new(self)
44
44
  @segmenter.instance_eval(&block)
45
45
  @segmenter.verify!
46
46
  return self
47
47
  end
48
48
 
49
+ def rollout_percentage(percentage, rollout_group_name = :enabled)
50
+ groups(Verdict::RolloutSegmenter) do
51
+ group rollout_group_name, percentage
52
+ end
53
+ end
54
+
49
55
  def qualify(&block)
50
56
  @qualifier = block
51
57
  end
@@ -72,8 +78,8 @@ class Verdict::Experiment
72
78
  segmenter.groups.keys
73
79
  end
74
80
 
75
- def subject_assignment(subject_identifier, group, originally_created_at = nil)
76
- Verdict::Assignment.new(self, subject_identifier, group, originally_created_at)
81
+ def subject_assignment(subject_identifier, group, originally_created_at, temporary = false)
82
+ Verdict::Assignment.new(self, subject_identifier, group, originally_created_at, temporary)
77
83
  end
78
84
 
79
85
  def subject_conversion(subject_identifier, goal, created_at = Time.now.utc)
@@ -84,6 +90,7 @@ class Verdict::Experiment
84
90
  identifier = retrieve_subject_identifier(subject)
85
91
  conversion = subject_conversion(identifier, goal)
86
92
  event_logger.log_conversion(conversion)
93
+ segmenter.conversion_feedback(identifier, subject, conversion)
87
94
  conversion
88
95
  rescue Verdict::EmptySubjectIdentifier
89
96
  raise unless disqualify_empty_identifier?
@@ -194,25 +201,30 @@ class Verdict::Experiment
194
201
  end
195
202
 
196
203
  def should_store_assignment?(assignment)
197
- !assignment.returning? && (store_unqualified? || assignment.qualified?)
204
+ assignment.permanent? && !assignment.returning? && (store_unqualified? || assignment.qualified?)
198
205
  end
199
206
 
200
207
  def assignment_with_unqualified_persistence(subject_identifier, subject, context)
201
- fetch_assignment(subject_identifier) || (
202
- subject_qualifies?(subject, context) ?
203
- subject_assignment(subject_identifier, @segmenter.assign(subject_identifier, subject, context), nil) :
204
- subject_assignment(subject_identifier, nil, nil)
205
- )
208
+ previous_assignment = fetch_assignment(subject_identifier)
209
+ return previous_assignment unless previous_assignment.nil?
210
+ if subject_qualifies?(subject, context)
211
+ group = segmenter.assign(subject_identifier, subject, context)
212
+ subject_assignment(subject_identifier, group, nil, group.nil?)
213
+ else
214
+ subject_assignment(subject_identifier, nil, nil)
215
+ end
206
216
  end
207
217
 
208
218
  def assignment_without_unqualified_persistence(subject_identifier, subject, context)
209
219
  if subject_qualifies?(subject, context)
210
- fetch_assignment(subject_identifier) ||
211
- subject_assignment(subject_identifier, @segmenter.assign(subject_identifier, subject, context), nil)
220
+ previous_assignment = fetch_assignment(subject_identifier)
221
+ return previous_assignment unless previous_assignment.nil?
222
+ group = segmenter.assign(subject_identifier, subject, context)
223
+ subject_assignment(subject_identifier, group, nil, group.nil?)
212
224
  else
213
225
  subject_assignment(subject_identifier, nil, nil)
214
226
  end
215
- end
227
+ end
216
228
 
217
229
  def subject_identifier(subject)
218
230
  subject.respond_to?(:id) ? subject.id : subject.to_s
@@ -0,0 +1,52 @@
1
+ class Verdict::FixedPercentageSegmenter < Verdict::Segmenter
2
+
3
+ class Group < Verdict::Group
4
+
5
+ attr_reader :percentile_range
6
+
7
+ def initialize(experiment, handle, percentile_range)
8
+ super(experiment, handle)
9
+ @percentile_range = percentile_range
10
+ end
11
+
12
+ def percentage_size
13
+ percentile_range.end - percentile_range.begin
14
+ end
15
+
16
+ def to_s
17
+ "#{handle} (#{percentage_size}%)"
18
+ end
19
+
20
+ def as_json(options = {})
21
+ super(options).merge(percentage: percentage_size)
22
+ end
23
+ end
24
+
25
+ def initialize(experiment)
26
+ super
27
+ @total_percentage_segmented = 0
28
+ end
29
+
30
+ def verify!
31
+ raise Verdict::SegmentationError, "Should segment exactly 100% of the cases, but segments add up to #{@total_percentage_segmented}%." if @total_percentage_segmented != 100
32
+ end
33
+
34
+ def register_group(handle, size)
35
+ percentage = size.kind_of?(Hash) && size[:percentage] ? size[:percentage] : size
36
+ n = case percentage
37
+ when :rest; 100 - @total_percentage_segmented
38
+ when :half; 50
39
+ when Integer; percentage
40
+ else Integer(percentage)
41
+ end
42
+
43
+ group = Group.new(experiment, handle, @total_percentage_segmented ... (@total_percentage_segmented + n))
44
+ @total_percentage_segmented += n
45
+ return group
46
+ end
47
+
48
+ def assign(identifier, subject, context)
49
+ percentile = Digest::MD5.hexdigest("#{@experiment.handle}#{identifier}").to_i(16) % 100
50
+ groups.values.find { |group| group.percentile_range.include?(percentile) }
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ class Verdict::RolloutSegmenter < Verdict::Segmenter
2
+
3
+ class Group < Verdict::Group
4
+
5
+ attr_reader :percentile_range
6
+
7
+ def initialize(experiment, handle, percentile_range)
8
+ super(experiment, handle)
9
+ @percentile_range = percentile_range
10
+ end
11
+
12
+ def percentage_size
13
+ percentile_range.end - percentile_range.begin
14
+ end
15
+
16
+ def to_s
17
+ "#{handle} (#{percentage_size}%)"
18
+ end
19
+
20
+ def as_json(options = {})
21
+ super(options).merge(percentage: percentage_size)
22
+ end
23
+ end
24
+
25
+ def initialize(experiment)
26
+ super
27
+ @total_percentage_segmented = 0
28
+ end
29
+
30
+ def verify!
31
+ raise Verdict::SegmentationError, "Should segment less than 100% of the cases, but segments add up to #{@total_percentage_segmented}%." if @total_percentage_segmented >= 100
32
+ end
33
+
34
+ def register_group(handle, size)
35
+ percentage = size.kind_of?(Hash) && size[:percentage] ? size[:percentage] : size
36
+ n = case percentage
37
+ when :rest; 100 - @total_percentage_segmented
38
+ when :half; 50
39
+ when Integer; percentage
40
+ else Integer(percentage)
41
+ end
42
+
43
+ group = Group.new(experiment, handle, @total_percentage_segmented ... (@total_percentage_segmented + n))
44
+ @total_percentage_segmented += n
45
+ return group
46
+ end
47
+
48
+ def assign(identifier, subject, context)
49
+ percentile = Digest::MD5.hexdigest("#{@experiment.handle}#{identifier}").to_i(16) % 100
50
+ groups.values.find { |group| group.percentile_range.include?(percentile) }
51
+ end
52
+
53
+ end
@@ -1,78 +1,87 @@
1
1
  require 'digest/md5'
2
2
 
3
- module Verdict::Segmenter
4
-
5
- class Base
6
-
7
- attr_reader :experiment, :groups
8
-
9
- def initialize(experiment)
10
- @experiment = experiment
11
- @groups = {}
12
- end
13
-
14
- def verify!
15
- end
16
-
17
- def group(identifier, subject, context)
18
- raise NotImplementedError
19
- end
3
+ # Base class of all segmenters.
4
+ #
5
+ # The segmenter is responsible for assigning subjects to groups. You can
6
+ # implement any assignment strategy you like by subclassing this class and
7
+ # using it in your experiment.
8
+ #
9
+ # - You should implement the register_group method for the experiment definition DSL
10
+ # to make the system aware of the groups that the segmenter could return.
11
+ # - The verify! method is called after all the groups have been defined, so it can
12
+ # detect internal inconsistencies in the group definitions.
13
+ # - The assign method is where your assignment magic lives.
14
+ class Verdict::Segmenter
15
+
16
+ # The experiment to which this segmenter is associated
17
+ attr_reader :experiment
18
+
19
+ # A hash of the groups that are defined in this experiment, indexed by their
20
+ # handle. The assign method should return one of the groups in this hash
21
+ attr_reader :groups
22
+
23
+ def initialize(experiment)
24
+ @experiment = experiment
25
+ @groups = {}
20
26
  end
21
27
 
22
- class StaticPercentage < Base
23
-
24
- class Group < Verdict::Group
25
-
26
- attr_reader :percentile_range
27
-
28
- def initialize(experiment, handle, percentile_range)
29
- super(experiment, handle)
30
- @percentile_range = percentile_range
31
- end
32
-
33
- def percentage_size
34
- percentile_range.end - percentile_range.begin
35
- end
36
-
37
- def to_s
38
- "#{handle} (#{percentage_size}%)"
39
- end
40
-
41
- def as_json(options = {})
42
- super(options).merge(percentage: percentage_size)
43
- end
44
- end
28
+ # DSL method to register a group. It calls the register_group method of the
29
+ # segmenter implementation
30
+ def group(handle, *args, &block)
31
+ group = register_group(handle, *args)
32
+ @groups[group.handle] = group
33
+ group.instance_eval(&block) if block_given?
34
+ end
45
35
 
46
- def initialize(experiment)
47
- super
48
- @total_percentage_segmented = 0
49
- end
36
+ # The group method is called from the experiment definition DSL.
37
+ # It should register a new group to the segmenter, with the given handle.
38
+ #
39
+ # - The handle parameter is a symbol that uniquely identifies the group within
40
+ # this experiment.
41
+ # - The return value of this method should be a Verdict::Group instance.
42
+ def register_group(handle, *args)
43
+ raise NotImplementedError
44
+ end
50
45
 
51
- def verify!
52
- raise Verdict::SegmentationError, "Should segment exactly 100% of the cases, but segments add up to #{@total_percentage_segmented}%." if @total_percentage_segmented != 100
53
- end
46
+ # The verify! method is called after all the groups have been defined in the
47
+ # experiment definition DSL. You can run any consistency checks in this method,
48
+ # and if anything is off, you can raise a Verdict::SegmentationError to
49
+ # signify the problem.
50
+ def verify!
51
+ # noop by default
52
+ end
54
53
 
55
- def group(handle, size, &block)
56
- percentage = size.kind_of?(Hash) && size[:percentage] ? size[:percentage] : size
57
- n = case percentage
58
- when :rest; 100 - @total_percentage_segmented
59
- when :half; 50
60
- when Integer; percentage
61
- else Integer(percentage)
62
- end
54
+ # The assign method is called to assign a subject to one of the groups that have been defined
55
+ # in the segmenter implementation.
56
+ #
57
+ # - The identifier parameter is a string that uniquely identifies the subject.
58
+ # - The subject paramater is the subject instance that was passed to the framework,
59
+ # when the application code calls Experiment#assign or Experiment#switch.
60
+ # - The context parameter is an object that was passed to the framework, you can use this
61
+ # object any way you like in your segmenting logic.
62
+ #
63
+ # This method should return the Verdict::Group instance to which the subject should be assigned.
64
+ # This instance should be one of the group instance that was registered in the definition DSL.
65
+ def assign(identifier, subject, context)
66
+ raise NotImplementedError
67
+ end
63
68
 
64
- group = Group.new(experiment, handle, @total_percentage_segmented ... (@total_percentage_segmented + n))
65
- @groups[group.handle] = group
66
- @total_percentage_segmented += n
67
- group.instance_eval(&block) if block_given?
68
- return group
69
- end
70
69
 
71
- def assign(identifier, subject, context)
72
- percentile = Digest::MD5.hexdigest("#{@experiment.handle}#{identifier}").to_i(16) % 100
73
- _, group = groups.find { |_, group| group.percentile_range.include?(percentile) }
74
- raise Verdict::SegmentationError, "Could not get segment for subject #{identifier.inspect}!" unless group
75
- group
76
- end
70
+ # This method is called whenever a subjects converts to a goal, i.e., when Experiment#convert
71
+ # is called. You can use this to implement a feedback loop in your segmenter.
72
+ #
73
+ # - The identifier parameter is a string that uniquely identifies the subject.
74
+ # - The subject paramater is the subject instance that was passed to the framework,
75
+ # when the application code calls Experiment#assign or Experiment#switch.
76
+ # - The conversion parameter is a Verdict::Conversion instance that describes what
77
+ # goal the subject converted to.
78
+ #
79
+ # The return value of this method is not used.
80
+ def conversion_feedback(identifier, subject, conversion)
81
+ # noop by default
77
82
  end
78
83
  end
84
+
85
+ require 'verdict/static_segmenter'
86
+ require 'verdict/fixed_percentage_segmenter'
87
+ require 'verdict/rollout_segmenter'
@@ -0,0 +1,24 @@
1
+ class Verdict::StaticSegmenter < Verdict::Segmenter
2
+
3
+ class Group < Verdict::Group
4
+
5
+ attr_reader :subject_identifiers
6
+
7
+ def initialize(experiment, handle, subject_identifiers)
8
+ super(experiment, handle)
9
+ @subject_identifiers = subject_identifiers
10
+ end
11
+
12
+ def as_json(options = {})
13
+ super(options).merge(subject_identifiers: subject_identifiers)
14
+ end
15
+ end
16
+
17
+ def register_group(handle, subject_identifiers)
18
+ Group.new(experiment, handle, subject_identifiers)
19
+ end
20
+
21
+ def assign(identifier, subject, context)
22
+ groups.values.find { |group| group.subject_identifiers.include?(identifier) }
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Verdict
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -153,6 +153,20 @@ class ExperimentTest < MiniTest::Unit::TestCase
153
153
  e.assign(mock('subject'))
154
154
  end
155
155
 
156
+ def test_dont_store_when_segmenter_returns_nil
157
+ mock_store = Verdict::Storage::MockStorage.new
158
+ e = Verdict::Experiment.new('test') do
159
+ groups { group :all, 100 }
160
+ storage mock_store, store_unqualified: true
161
+ end
162
+
163
+ e.segmenter.stubs(:assign).returns(nil)
164
+ mock_store.expects(:store_assignment).never
165
+
166
+ assignment = e.assign(mock('subject'))
167
+ assert !assignment.qualified?
168
+ end
169
+
156
170
  def test_disqualify
157
171
  e = Verdict::Experiment.new('test') do
158
172
  groups { group :all, 100 }
@@ -177,12 +191,16 @@ class ExperimentTest < MiniTest::Unit::TestCase
177
191
  end
178
192
 
179
193
  def test_conversion_event_logging
180
- e = Verdict::Experiment.new('test')
194
+ e = Verdict::Experiment.new('test')do
195
+ groups { group :all, 100 }
196
+ end
181
197
 
198
+ subject = stub(id: 'test_subject')
182
199
  e.stubs(:event_logger).returns(logger = mock('logger'))
183
200
  logger.expects(:log_conversion).with(kind_of(Verdict::Conversion))
201
+ e.segmenter.expects(:conversion_feedback).with('test_subject', subject, kind_of(Verdict::Conversion))
184
202
 
185
- conversion = e.convert(subject = stub(id: 'test_subject'), :my_goal)
203
+ conversion = e.convert(subject, :my_goal)
186
204
  assert_equal 'test_subject', conversion.subject_identifier
187
205
  assert_equal :my_goal, conversion.goal
188
206
  end
@@ -229,7 +247,7 @@ class ExperimentTest < MiniTest::Unit::TestCase
229
247
  storage storage_mock
230
248
  end
231
249
 
232
- storage_mock.expects(:retrieve_assignment).returns(e.subject_assignment(mock('subject_identifier'), e.group(:all)))
250
+ storage_mock.expects(:retrieve_assignment).returns(e.subject_assignment(mock('subject_identifier'), e.group(:all), nil))
233
251
  storage_mock.expects(:store_assignment).raises(Verdict::StorageError, 'storage write issues')
234
252
  rescued_assignment = e.assign(stub(id: 456))
235
253
  assert !rescued_assignment.qualified?
@@ -1,15 +1,9 @@
1
1
  require 'test_helper'
2
2
 
3
- class StaticPercentageSegmenterTest < MiniTest::Unit::TestCase
4
-
5
- MockExperiment = Struct.new(:handle)
6
-
7
- def setup
8
- Verdict.repository.clear
9
- end
3
+ class FixedPercentageSegmenterTest < MiniTest::Unit::TestCase
10
4
 
11
5
  def test_add_up_to_100_percent
12
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
6
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
13
7
  s.group :segment1, 1
14
8
  s.group :segment2, 54
15
9
  s.group :segment3, 27
@@ -24,7 +18,7 @@ class StaticPercentageSegmenterTest < MiniTest::Unit::TestCase
24
18
  end
25
19
 
26
20
  def test_definition_ofhalf_and_rest
27
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
21
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
28
22
  s.group :first_half, :half
29
23
  s.group :second_half, :rest
30
24
  s.verify!
@@ -36,7 +30,7 @@ class StaticPercentageSegmenterTest < MiniTest::Unit::TestCase
36
30
 
37
31
  def test_raises_if_less_than_100_percent
38
32
  assert_raises(Verdict::SegmentationError) do
39
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
33
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
40
34
  s.group :too_little, 99
41
35
  s.verify!
42
36
  end
@@ -44,14 +38,14 @@ class StaticPercentageSegmenterTest < MiniTest::Unit::TestCase
44
38
 
45
39
  def test_raises_if_greather_than_100_percent
46
40
  assert_raises(Verdict::SegmentationError) do
47
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
41
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
48
42
  s.group :too_much, 101
49
43
  s.verify!
50
44
  end
51
45
  end
52
46
 
53
47
  def test_consistent_assignment_for_subjects
54
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
48
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
55
49
  s.group :first_half, :half
56
50
  s.group :second_half, :rest
57
51
  s.verify!
@@ -63,7 +57,7 @@ class StaticPercentageSegmenterTest < MiniTest::Unit::TestCase
63
57
  end
64
58
 
65
59
  def test_fair_segmenting
66
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
60
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
67
61
  s.group :first_third, 33
68
62
  s.group :second_third, 33
69
63
  s.group :final_third, :rest
@@ -82,7 +76,7 @@ class StaticPercentageSegmenterTest < MiniTest::Unit::TestCase
82
76
  end
83
77
 
84
78
  def test_group_json_export
85
- s = Verdict::Segmenter::StaticPercentage.new(MockExperiment.new('test'))
79
+ s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
86
80
  s.group :first_third, 33
87
81
  s.group :rest, :rest
88
82
  s.verify!
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ class RolloutPercentageSegmenterTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @experiment = Verdict::Experiment.new('test') do
7
+ rollout_percentage 50
8
+ end
9
+ end
10
+
11
+ def test_assignment
12
+ included_subject = stub(id: 1)
13
+ excluded_subject = stub(id: 2)
14
+
15
+ included_assignment = @experiment.assign(included_subject)
16
+ assert included_assignment.qualified?
17
+ assert included_assignment.permanent?
18
+
19
+ excluded_assignment = @experiment.assign(excluded_subject)
20
+ assert !excluded_assignment.qualified?
21
+ assert excluded_assignment.temporary?
22
+ end
23
+
24
+ def test_group_json_representation
25
+ json = JSON.parse(@experiment.segmenter.groups['enabled'].to_json)
26
+ assert_equal 'enabled', json['handle']
27
+ assert_equal 50, json['percentage']
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class StaticSegmenterTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @segmenter = Verdict::StaticSegmenter.new(Verdict::Experiment.new('test'))
7
+ @segmenter.group :beta, ['id1', 'id2']
8
+ end
9
+
10
+ def test_group_definition
11
+ assert_equal ['beta'], @segmenter.groups.keys
12
+ assert_equal ['id1', 'id2'], @segmenter.groups['beta'].subject_identifiers
13
+ end
14
+
15
+ def test_group_json_representation
16
+ json = JSON.parse(@segmenter.groups['beta'].to_json)
17
+ assert_equal 'beta', json['handle']
18
+ assert_equal ['id1', 'id2'], json['subject_identifiers']
19
+ end
20
+
21
+ def test_assigment
22
+ assert_equal @segmenter.groups['beta'], @segmenter.assign('id2', stub(id: 'id2'), nil)
23
+ assert_equal nil, @segmenter.assign('id3', stub(id: 'id3'), nil)
24
+ end
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verdict
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-17 00:00:00.000000000 Z
11
+ date: 2014-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -28,56 +28,56 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mocha
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: timecop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: redis
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ! '>='
73
+ - - '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ! '>='
80
+ - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  description: Shopify Experiments classes
@@ -99,10 +99,13 @@ files:
99
99
  - lib/verdict/conversion.rb
100
100
  - lib/verdict/event_logger.rb
101
101
  - lib/verdict/experiment.rb
102
+ - lib/verdict/fixed_percentage_segmenter.rb
102
103
  - lib/verdict/group.rb
103
104
  - lib/verdict/metadata.rb
104
105
  - lib/verdict/railtie.rb
106
+ - lib/verdict/rollout_segmenter.rb
105
107
  - lib/verdict/segmenter.rb
108
+ - lib/verdict/static_segmenter.rb
106
109
  - lib/verdict/storage.rb
107
110
  - lib/verdict/tasks.rake
108
111
  - lib/verdict/version.rb
@@ -111,11 +114,13 @@ files:
111
114
  - test/event_logger_test.rb
112
115
  - test/experiment_test.rb
113
116
  - test/experiments_repository_test.rb
117
+ - test/fixed_percentage_segmenter_test.rb
114
118
  - test/group_test.rb
115
119
  - test/memory_subject_storage_test.rb
116
120
  - test/metadata_test.rb
117
121
  - test/redis_subject_storage_test.rb
118
- - test/static_percentage_segmenter_test.rb
122
+ - test/rollout_segmenter_test.rb
123
+ - test/static_segmenter_test.rb
119
124
  - test/test_helper.rb
120
125
  - verdict.gemspec
121
126
  homepage: http://github.com/Shopify/verdict
@@ -127,17 +132,17 @@ require_paths:
127
132
  - lib
128
133
  required_ruby_version: !ruby/object:Gem::Requirement
129
134
  requirements:
130
- - - ! '>='
135
+ - - '>='
131
136
  - !ruby/object:Gem::Version
132
137
  version: '0'
133
138
  required_rubygems_version: !ruby/object:Gem::Requirement
134
139
  requirements:
135
- - - ! '>='
140
+ - - '>='
136
141
  - !ruby/object:Gem::Version
137
142
  version: '0'
138
143
  requirements: []
139
144
  rubyforge_project:
140
- rubygems_version: 2.1.4
145
+ rubygems_version: 2.0.14
141
146
  signing_key:
142
147
  specification_version: 4
143
148
  summary: A library to centrally define experiments for your application, and collect
@@ -148,10 +153,11 @@ test_files:
148
153
  - test/event_logger_test.rb
149
154
  - test/experiment_test.rb
150
155
  - test/experiments_repository_test.rb
156
+ - test/fixed_percentage_segmenter_test.rb
151
157
  - test/group_test.rb
152
158
  - test/memory_subject_storage_test.rb
153
159
  - test/metadata_test.rb
154
160
  - test/redis_subject_storage_test.rb
155
- - test/static_percentage_segmenter_test.rb
161
+ - test/rollout_segmenter_test.rb
162
+ - test/static_segmenter_test.rb
156
163
  - test/test_helper.rb
157
- has_rdoc: