verdict 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e98209c988365064ac1570a47b2dc5c0b0663f673ec62cb8d2640bdf38c9f3d
4
- data.tar.gz: 1111714a3fbfffc8ee878c49db5dbaacc5e0c8d6c53f8425dfe69cfdcde96e38
3
+ metadata.gz: 7e99d3cb09e2dbfb3cafeb6f05171978721cd5a79fd4570f8b6bd6c6e4975e17
4
+ data.tar.gz: 4a885cb9d8a991046e3a22e428a35982bf7565b03aaa49fa0da0b9963e34bc56
5
5
  SHA512:
6
- metadata.gz: 71b77a462635384229bcce809954d15fac8e75fe3d307172eb1f31a5950e92e36f0a51c686c0cdb22d89fafcb0c5d6e10ba9a51b708580ac28e7ab480698fde7
7
- data.tar.gz: 48b4b96643a1235479061ceec0954700a7997105f37b03b51833dff7becd5f6db11582f177df81fcc79c56ed5a6b9b2ecd038a33c2e2233f74f69185cbe49a7a
6
+ metadata.gz: 89c2d20c1892c630942099ac0b0ea1d3db012a6aaa9031a7bbf8d834fad295adfaf4cc0194d09930608c4416bbeb8efba89852446e02b091c237d851d8266704
7
+ data.tar.gz: cdb0271d66903d1f39316c2665c68bf8deae733c105e3aecc8bb665adaef4981129bb0f7cce6baf001eb7f246af50e40d85115bf3105525a44b2111fb2c41fbc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## v0.12.0
2
+
3
+ * Allow options to be passed to `Experiment#cleanup` so they can be forwarded to storage.
4
+
5
+ * Changed `Experiment#cleanup` to accept an argument of type `Verdict::Experiment`.
6
+ Passing a `String`/`Symbol` argument is still supported, but will log a deprecation warning.
7
+
1
8
  ## v0.11.0
2
9
 
3
10
  * Automatic eager loading when inside a Rails app.
data/lib/verdict.rb CHANGED
@@ -58,3 +58,4 @@ require "verdict/event_logger"
58
58
 
59
59
  Verdict.default_logger ||= Logger.new("/dev/null")
60
60
  Verdict.directory = nil
61
+ Verdict.clear_repository_cache
@@ -2,7 +2,7 @@ class Verdict::Experiment
2
2
 
3
3
  include Verdict::Metadata
4
4
 
5
- attr_reader :handle, :qualifiers, :storage, :event_logger
5
+ attr_reader :handle, :qualifiers, :event_logger
6
6
 
7
7
  def self.define(handle, *args, &block)
8
8
  experiment = self.new(handle, *args, &block)
@@ -11,6 +11,7 @@ class Verdict::Experiment
11
11
  end
12
12
 
13
13
  def initialize(handle, options = {}, &block)
14
+ @started_at = nil
14
15
  @handle = handle.to_s
15
16
 
16
17
  options = default_options.merge(options)
@@ -88,7 +89,7 @@ class Verdict::Experiment
88
89
 
89
90
  def started_at
90
91
  @started_at ||= @storage.retrieve_start_timestamp(self)
91
- rescue Verdict::StorageError => e
92
+ rescue Verdict::StorageError
92
93
  nil
93
94
  end
94
95
 
@@ -162,8 +163,8 @@ class Verdict::Experiment
162
163
  assignment
163
164
  end
164
165
 
165
- def cleanup
166
- @storage.cleanup(handle.to_s)
166
+ def cleanup(options = {})
167
+ @storage.cleanup(self, options)
167
168
  end
168
169
 
169
170
  def remove_subject_assignment(subject)
@@ -193,15 +194,13 @@ class Verdict::Experiment
193
194
  end
194
195
 
195
196
  def as_json(options = {})
196
- data = {
197
+ {
197
198
  handle: handle,
198
199
  has_qualifier: has_qualifier?,
199
- groups: segmenter.groups.values.map { |g| g.as_json(options) },
200
+ groups: segmenter.groups.values.map { |group| group.as_json(options) },
200
201
  metadata: metadata,
201
202
  started_at: started_at.nil? ? nil : started_at.utc.strftime('%FT%TZ')
202
- }
203
-
204
- data.tap do |data|
203
+ }.tap do |data|
205
204
  data[:subject_type] = subject_type.to_s unless subject_type.nil?
206
205
  end
207
206
  end
@@ -45,11 +45,21 @@ module Verdict
45
45
  set(experiment.handle.to_s, 'started_at', timestamp.utc.strftime('%FT%TZ'))
46
46
  end
47
47
 
48
- def cleanup(_scope)
49
- raise NotImplementedError
48
+ # Deletes all assignments (and any other stored data) for the given experiment
49
+ def cleanup(experiment_or_scope, options = {})
50
+ if experiment_or_scope.is_a?(Symbol) || experiment_or_scope.is_a?(String)
51
+ Verdict.default_logger.warn(
52
+ "Passing a scope string/symbol to #{self.class}#cleanup is deprecated, " \
53
+ 'pass a Verdict::Experiment instance instead.'
54
+ )
55
+ clear(experiment_or_scope, options)
56
+ else
57
+ clear(experiment_or_scope.handle.to_s, options)
58
+ end
50
59
  end
51
60
 
52
61
  protected
62
+
53
63
  # Retrieves a key in a given scope from storage.
54
64
  # - The scope and key are both provided as string.
55
65
  # - Should return a string value if the key is found in the scope, nil otherwise.
@@ -58,7 +68,7 @@ module Verdict
58
68
  raise NotImplementedError
59
69
  end
60
70
 
61
- # Retrieves a key in a given scope from storage.
71
+ # Sets the value of a key in a given scope from storage.
62
72
  # - The scope, key, and value are all provided as string.
63
73
  # - Should return true if the item was successfully stored.
64
74
  # - Should raise Verdict::StorageError if anything goes wrong.
@@ -66,13 +76,21 @@ module Verdict
66
76
  raise NotImplementedError
67
77
  end
68
78
 
69
- # Retrieves a key in a given scope from storage.
79
+ # Removes a key in a given scope from storage.
70
80
  # - The scope and key are both provided as string.
71
81
  # - Should return true if the item was successfully removed from storage.
72
82
  # - Should raise Verdict::StorageError if anything goes wrong.
73
83
  def remove(scope, key)
74
84
  raise NotImplementedError
75
85
  end
86
+
87
+ # Removes all keys in a given scope from storage.
88
+ # - The scope is provided as string.
89
+ # - Should return true if all items were successfully removed from storage.
90
+ # - Should raise Verdict::StorageError if anything goes wrong.
91
+ def clear(scope, options)
92
+ raise NotImplementedError
93
+ end
76
94
  end
77
95
  end
78
96
  end
@@ -7,6 +7,8 @@ module Verdict
7
7
  @storage = {}
8
8
  end
9
9
 
10
+ protected
11
+
10
12
  def get(scope, key)
11
13
  @storage[scope] ||= {}
12
14
  @storage[scope][key]
@@ -1,14 +1,16 @@
1
1
  module Verdict
2
2
  module Storage
3
3
  class MockStorage < BaseStorage
4
- def set(scope, key, value)
5
- false
6
- end
4
+ protected
7
5
 
8
6
  def get(scope, key)
9
7
  nil
10
8
  end
11
9
 
10
+ def set(scope, key, value)
11
+ false
12
+ end
13
+
12
14
  def remove(scope, key)
13
15
  end
14
16
  end
@@ -10,6 +10,8 @@ module Verdict
10
10
  @key_prefix = options[:key_prefix] || 'experiments/'
11
11
  end
12
12
 
13
+ protected
14
+
13
15
  def get(scope, key)
14
16
  redis.hget(scope_key(scope), key)
15
17
  rescue ::Redis::BaseError => e
@@ -28,8 +30,8 @@ module Verdict
28
30
  raise Verdict::StorageError, "Redis error: #{e.message}"
29
31
  end
30
32
 
31
- def cleanup(scope)
32
- clear(scope)
33
+ def clear(scope, options)
34
+ scrub(scope)
33
35
  redis.del(scope_key(scope))
34
36
  rescue ::Redis::BaseError => e
35
37
  raise Verdict::StorageError, "Redis error: #{e.message}"
@@ -41,12 +43,12 @@ module Verdict
41
43
  "#{@key_prefix}#{scope}"
42
44
  end
43
45
 
44
- def clear(scope, cursor: 0)
46
+ def scrub(scope, cursor: 0)
45
47
  cursor, results = redis.hscan(scope_key(scope), cursor, count: PAGE_SIZE)
46
48
  results.map(&:first).each do |key|
47
49
  remove(scope, key)
48
50
  end
49
- clear(scope, cursor: cursor) unless cursor.to_i.zero?
51
+ scrub(scope, cursor: cursor) unless cursor.to_i.zero?
50
52
  end
51
53
  end
52
54
  end
@@ -1,3 +1,3 @@
1
1
  module Verdict
2
- VERSION = "0.11.0"
2
+ VERSION = "0.12.0"
3
3
  end
@@ -424,7 +424,6 @@ class ExperimentTest < Minitest::Test
424
424
  end
425
425
 
426
426
  def test_cleanup
427
- redis = ::Redis.new(host: REDIS_HOST, port: REDIS_PORT)
428
427
  storage = Verdict::Storage::RedisStorage.new(redis)
429
428
  experiment = Verdict::Experiment.new(:cleanup_test) do
430
429
  groups { group :all, 100 }
@@ -440,6 +439,16 @@ class ExperimentTest < Minitest::Test
440
439
  redis.del("experiments/cleanup_test")
441
440
  end
442
441
 
442
+ def test_cleanup_options
443
+ experiment = Verdict::Experiment.new(:cleanup_test) do
444
+ groups { group :all, 100 }
445
+ end
446
+
447
+ experiment.storage.expects(:clear).with(experiment.handle, some: :thing)
448
+ experiment.assign("something")
449
+ experiment.cleanup(some: :thing)
450
+ end
451
+
443
452
  def test_cleanup_without_redis
444
453
  experiment = Verdict::Experiment.new(:cleanup_test) do
445
454
  groups { group :all, 100 }
@@ -450,4 +459,10 @@ class ExperimentTest < Minitest::Test
450
459
  experiment.cleanup
451
460
  end
452
461
  end
462
+
463
+ private
464
+
465
+ def redis
466
+ @redis ||= ::Redis.new(host: REDIS_HOST, port: REDIS_PORT)
467
+ end
453
468
  end
@@ -28,5 +28,6 @@ class ExperimentTest < Minitest::Test
28
28
  json = JSON.parse(Verdict.repository.to_json)
29
29
  assert_equal ['test_1', 'test_2'], json.keys
30
30
  assert_equal json['test_1'], JSON.parse(e1.to_json)
31
+ assert_equal json['test_2'], JSON.parse(e2.to_json)
31
32
  end
32
33
  end
data/test/fake_app.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'rails'
1
+ require "rails"
2
2
  require "active_model/railtie"
3
3
  require "action_controller/railtie"
4
4
  require "action_view/railtie"
@@ -35,9 +35,9 @@ class MemoryStorageTest < Minitest::Test
35
35
  end
36
36
 
37
37
  def test_started_at
38
- assert @storage.get(@experiment.handle.to_s, 'started_at').nil?
38
+ assert @storage.send(:get, @experiment.handle.to_s, 'started_at').nil?
39
39
  @experiment.send(:ensure_experiment_has_started)
40
- refute @storage.get(@experiment.handle.to_s, 'started_at').nil?
40
+ refute @storage.send(:get, @experiment.handle.to_s, 'started_at').nil?
41
41
  assert_instance_of Time, @experiment.started_at
42
42
  end
43
43
  end
@@ -71,17 +71,29 @@ class RedisStorageTest < Minitest::Test
71
71
  assert_equal a, @experiment.started_at
72
72
  end
73
73
 
74
- def test_cleanup
74
+ def test_cleanup_with_scope_argument
75
75
  1000.times do |n|
76
76
  @experiment.assign("something_#{n}")
77
77
  end
78
78
 
79
79
  assert_operator @redis, :exists, experiment_key
80
80
 
81
+ Verdict.default_logger.expects(:warn).with(regexp_matches(/deprecated/))
81
82
  @storage.cleanup(:redis_storage)
82
83
  refute_operator @redis, :exists, experiment_key
83
84
  end
84
85
 
86
+ def test_cleanup
87
+ 1000.times do |n|
88
+ @experiment.assign("something_#{n}")
89
+ end
90
+
91
+ assert_operator @redis, :exists, experiment_key
92
+
93
+ @storage.cleanup(@experiment)
94
+ refute_operator @redis, :exists, experiment_key
95
+ end
96
+
85
97
  private
86
98
 
87
99
  def experiment_key
@@ -5,9 +5,16 @@ class VerdictRailsTest < Minitest::Test
5
5
  def setup
6
6
  Verdict.clear_repository_cache
7
7
  new_rails_app = Dummy::Application.new
8
+ new_rails_app.config.eager_load = false
8
9
  new_rails_app.initialize!
9
10
  end
10
11
 
12
+ def teardown
13
+ Verdict.default_logger = Logger.new("/dev/null")
14
+ Verdict.directory = nil
15
+ Verdict.clear_repository_cache
16
+ end
17
+
11
18
  def test_verdict_railtie_should_find_directory_path
12
19
  assert_equal Verdict.directory, Rails.root.join('app', 'experiments')
13
20
  end
@@ -16,4 +23,4 @@ class VerdictRailsTest < Minitest::Test
16
23
  expected_experiment = Verdict.instance_variable_get('@repository')
17
24
  assert expected_experiment.include?("test_rails_app_experiment")
18
25
  end
19
- end
26
+ 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.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-03 00:00:00.000000000 Z
11
+ date: 2019-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest