verdict 0.11.0 → 0.12.0
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/verdict.rb +1 -0
- data/lib/verdict/experiment.rb +8 -9
- data/lib/verdict/storage/base_storage.rb +22 -4
- data/lib/verdict/storage/memory_storage.rb +2 -0
- data/lib/verdict/storage/mock_storage.rb +5 -3
- data/lib/verdict/storage/redis_storage.rb +6 -4
- data/lib/verdict/version.rb +1 -1
- data/test/experiment_test.rb +16 -1
- data/test/experiments_repository_test.rb +1 -0
- data/test/fake_app.rb +1 -1
- data/test/storage/memory_storage_test.rb +2 -2
- data/test/storage/redis_storage_test.rb +13 -1
- data/test/verdict_rails_test.rb +8 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e99d3cb09e2dbfb3cafeb6f05171978721cd5a79fd4570f8b6bd6c6e4975e17
|
4
|
+
data.tar.gz: 4a885cb9d8a991046e3a22e428a35982bf7565b03aaa49fa0da0b9963e34bc56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/lib/verdict/experiment.rb
CHANGED
@@ -2,7 +2,7 @@ class Verdict::Experiment
|
|
2
2
|
|
3
3
|
include Verdict::Metadata
|
4
4
|
|
5
|
-
attr_reader :handle, :qualifiers, :
|
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
|
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(
|
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
|
-
|
197
|
+
{
|
197
198
|
handle: handle,
|
198
199
|
has_qualifier: has_qualifier?,
|
199
|
-
groups: segmenter.groups.values.map { |
|
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
|
-
|
49
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
@@ -1,14 +1,16 @@
|
|
1
1
|
module Verdict
|
2
2
|
module Storage
|
3
3
|
class MockStorage < BaseStorage
|
4
|
-
|
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
|
32
|
-
|
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
|
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
|
-
|
51
|
+
scrub(scope, cursor: cursor) unless cursor.to_i.zero?
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
data/lib/verdict/version.rb
CHANGED
data/test/experiment_test.rb
CHANGED
@@ -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
|
data/test/fake_app.rb
CHANGED
@@ -35,9 +35,9 @@ class MemoryStorageTest < Minitest::Test
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_started_at
|
38
|
-
assert @storage.get
|
38
|
+
assert @storage.send(:get, @experiment.handle.to_s, 'started_at').nil?
|
39
39
|
@experiment.send(:ensure_experiment_has_started)
|
40
|
-
refute @storage.get
|
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
|
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
|
data/test/verdict_rails_test.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2019-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|