scientist 1.6.0 → 1.6.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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -4
- data/README.md +36 -1
- data/lib/scientist/experiment.rb +12 -6
- data/lib/scientist/observation.rb +17 -8
- data/lib/scientist/version.rb +1 -1
- data/test/scientist/experiment_test.rb +12 -0
- data/test/scientist/observation_test.rb +28 -4
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eee7969d8abea0f0fa0a53dea180d2f786d67a589b8694b2ccf6c70bdcedcbb4
|
4
|
+
data.tar.gz: 87bb457307fb452731efed8413770abed9f3d687e9718dd54edce106d745d389
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 882c44d39b595fe4c660abd619b5889552fe5d48c8bf2a2ff57e8b62fa221853a3af8727d04318068da888b6a8dd59e23f78c0eda71237261e409cb4038a5524
|
7
|
+
data.tar.gz: a6397a830792dda87404c7775d804d19a8b229a488180bba633501ff80ca889b9e61d44cf06287e48059664f8d6dd5097bf82e2ac6c4d33e478e02ea5e88c78f
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -109,6 +109,38 @@ class MyWidget
|
|
109
109
|
end
|
110
110
|
```
|
111
111
|
|
112
|
+
If either the control block or candidate block raises an error, Scientist compares the two observations' classes and messages using `==`. To override this behavior, use `compare_error` to define how to compare observed errors instead:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class MyWidget
|
116
|
+
include Scientist
|
117
|
+
|
118
|
+
def slug_from_login(login)
|
119
|
+
science "slug_from_login" do |e|
|
120
|
+
e.use { User.slug_from_login login } # returns String instance or ArgumentError
|
121
|
+
e.try { UserService.slug_from_login login } # returns String instance or ArgumentError
|
122
|
+
|
123
|
+
compare_error_message_and_class = -> (control, candidate) do
|
124
|
+
control.class == candidate.class &&
|
125
|
+
control.message == candidate.message
|
126
|
+
end
|
127
|
+
|
128
|
+
compare_argument_errors = -> (control, candidate) do
|
129
|
+
control.class == ArgumentError &&
|
130
|
+
candidate.class == ArgumentError &&
|
131
|
+
control.message.start_with?("Input has invalid characters") &&
|
132
|
+
candidate.message.star_with?("Invalid characters in input")
|
133
|
+
end
|
134
|
+
|
135
|
+
e.compare_error do |control, candidate|
|
136
|
+
compare_error_message_and_class.call(control, candidate) ||
|
137
|
+
compare_argument_errors.call(control, candidate)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
112
144
|
### Adding context
|
113
145
|
|
114
146
|
Results aren't very useful without some way to identify them. Use the `context` method to add to or retrieve the context for an experiment:
|
@@ -228,7 +260,7 @@ def admin?(user)
|
|
228
260
|
end
|
229
261
|
```
|
230
262
|
|
231
|
-
The ignore blocks are only called if the *values* don't match.
|
263
|
+
The ignore blocks are only called if the *values* don't match. Unless a `compare_error` comparator is defined, two cases are considered mismatches: a) one observation raising an exception and the other not, b) observations raising exceptions with different classes or messages.
|
232
264
|
|
233
265
|
### Enabling/disabling experiments
|
234
266
|
|
@@ -395,6 +427,8 @@ Scientist rescues and tracks _all_ exceptions raised in a `try` or `use` block,
|
|
395
427
|
Scientist::Observation::RESCUES.replace [StandardError]
|
396
428
|
```
|
397
429
|
|
430
|
+
**Timeout ⏲️**: If you're introducing a candidate that could possibly timeout, use caution. ⚠️ While Scientist rescues all exceptions that occur in the candidate block, it *does not* protect you from timeouts, as doing so would be complicated. It would likely require running the candidate code in a background job and tracking the time of a request. We feel the cost of this complexity would outweigh the benefit, so make sure that your code doesn't cause timeouts. This risk can be reduced by running the experiment on a low percentage so that users can (most likely) bypass the experiment by refreshing the page if they hit a timeout. See [Ramping up experiments](#ramping-up-experiments) below for how details on how to set the percentage for your experiment.
|
431
|
+
|
398
432
|
#### In a Scientist callback
|
399
433
|
|
400
434
|
If an exception is raised within any of Scientist's internal helpers, like `publish`, `compare`, or `clean`, the `raised` method is called with the symbol name of the internal operation that failed and the exception that was raised. The default behavior of `Scientist::Default` is to simply re-raise the exception. Since this halts the experiment entirely, it's often a better idea to handle this error and continue so the experiment as a whole isn't canceled entirely:
|
@@ -551,6 +585,7 @@ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs t
|
|
551
585
|
- [junkpiano/scientist](https://github.com/junkpiano/scientist) (Swift)
|
552
586
|
- [serverless scientist](http://serverlessscientist.com/) (AWS Lambda)
|
553
587
|
- [fightmegg/scientist](https://github.com/fightmegg/scientist) (TypeScript, Browser / Node.js)
|
588
|
+
- [MisterSpex/misterspex-scientist](https://github.com/MisterSpex/misterspex-scientist) (Java, no dependencies)
|
554
589
|
|
555
590
|
## Maintainers
|
556
591
|
|
data/lib/scientist/experiment.rb
CHANGED
@@ -134,6 +134,16 @@ module Scientist::Experiment
|
|
134
134
|
@_scientist_comparator = block
|
135
135
|
end
|
136
136
|
|
137
|
+
# A block which compares two experimental errors.
|
138
|
+
#
|
139
|
+
# The block must take two arguments, the control Error and a candidate Error,
|
140
|
+
# and return true or false.
|
141
|
+
#
|
142
|
+
# Returns the block.
|
143
|
+
def compare_errors(*args, &block)
|
144
|
+
@_scientist_error_comparator = block
|
145
|
+
end
|
146
|
+
|
137
147
|
# A Symbol-keyed Hash of extra experiment data.
|
138
148
|
def context(context = nil)
|
139
149
|
@_scientist_context ||= {}
|
@@ -177,13 +187,9 @@ module Scientist::Experiment
|
|
177
187
|
"experiment"
|
178
188
|
end
|
179
189
|
|
180
|
-
# Internal: compare two observations, using the configured compare
|
190
|
+
# Internal: compare two observations, using the configured compare and compare_errors lambdas if present.
|
181
191
|
def observations_are_equivalent?(a, b)
|
182
|
-
|
183
|
-
a.equivalent_to?(b, &@_scientist_comparator)
|
184
|
-
else
|
185
|
-
a.equivalent_to? b
|
186
|
-
end
|
192
|
+
a.equivalent_to? b, @_scientist_comparator, @_scientist_error_comparator
|
187
193
|
rescue StandardError => ex
|
188
194
|
raised :compare, ex
|
189
195
|
false
|
@@ -45,25 +45,34 @@ class Scientist::Observation
|
|
45
45
|
|
46
46
|
# Is this observation equivalent to another?
|
47
47
|
#
|
48
|
-
# other
|
49
|
-
# comparator
|
50
|
-
#
|
51
|
-
#
|
48
|
+
# other - the other Observation in question
|
49
|
+
# comparator - an optional comparison proc. This observation's value and the
|
50
|
+
# other observation's value are passed to this to determine
|
51
|
+
# their equivalency. Proc should return true/false.
|
52
|
+
# error_comparator - an optional comparison proc. This observation's Error and the
|
53
|
+
# other observation's Error are passed to this to determine
|
54
|
+
# their equivalency. Proc should return true/false.
|
52
55
|
#
|
53
56
|
# Returns true if:
|
54
57
|
#
|
55
58
|
# * The values of the observation are equal (using `==`)
|
56
59
|
# * The values of the observations are equal according to a comparison
|
57
|
-
#
|
60
|
+
# proc, if given
|
61
|
+
# * The exceptions raised by the obeservations are equal according to the
|
62
|
+
# error comparison proc, if given.
|
58
63
|
# * Both observations raised an exception with the same class and message.
|
59
64
|
#
|
60
65
|
# Returns false otherwise.
|
61
|
-
def equivalent_to?(other,
|
66
|
+
def equivalent_to?(other, comparator=nil, error_comparator=nil)
|
62
67
|
return false unless other.is_a?(Scientist::Observation)
|
63
68
|
|
64
69
|
if raised? || other.raised?
|
65
|
-
|
66
|
-
|
70
|
+
if error_comparator
|
71
|
+
return error_comparator.call(exception, other.exception)
|
72
|
+
else
|
73
|
+
return other.exception.class == exception.class &&
|
74
|
+
other.exception.message == exception.message
|
75
|
+
end
|
67
76
|
end
|
68
77
|
|
69
78
|
if comparator
|
data/lib/scientist/version.rb
CHANGED
@@ -201,6 +201,18 @@ describe Scientist::Experiment do
|
|
201
201
|
assert @ex.published_result.matched?
|
202
202
|
end
|
203
203
|
|
204
|
+
it "compares errors with an error comparator block if provided" do
|
205
|
+
@ex.compare_errors { |a, b| a.class == b.class }
|
206
|
+
@ex.use { raise "foo" }
|
207
|
+
@ex.try { raise "bar" }
|
208
|
+
|
209
|
+
resulting_error = assert_raises RuntimeError do
|
210
|
+
@ex.run
|
211
|
+
end
|
212
|
+
assert_equal "foo", resulting_error.message
|
213
|
+
assert @ex.published_result.matched?
|
214
|
+
end
|
215
|
+
|
204
216
|
it "knows how to compare two experiments" do
|
205
217
|
a = Scientist::Observation.new(@ex, "a") { 1 }
|
206
218
|
b = Scientist::Observation.new(@ex, "b") { 2 }
|
@@ -80,7 +80,7 @@ describe Scientist::Observation do
|
|
80
80
|
refute x.equivalent_to?(y)
|
81
81
|
end
|
82
82
|
|
83
|
-
|
83
|
+
FirstError = Class.new(StandardError)
|
84
84
|
SecondError = Class.new(StandardError)
|
85
85
|
|
86
86
|
it "compares exception classes" do
|
@@ -92,22 +92,46 @@ describe Scientist::Observation do
|
|
92
92
|
refute x.equivalent_to?(y)
|
93
93
|
end
|
94
94
|
|
95
|
-
it "compares values using a comparator
|
95
|
+
it "compares values using a comparator proc" do
|
96
96
|
a = Scientist::Observation.new("test", @experiment) { 1 }
|
97
97
|
b = Scientist::Observation.new("test", @experiment) { "1" }
|
98
98
|
|
99
99
|
refute a.equivalent_to?(b)
|
100
|
-
|
100
|
+
|
101
|
+
compare_on_string = -> (x, y) { x.to_s == y.to_s }
|
102
|
+
|
103
|
+
assert a.equivalent_to?(b, compare_on_string)
|
101
104
|
|
102
105
|
yielded = []
|
103
|
-
|
106
|
+
compare_appends = -> (x, y) do
|
104
107
|
yielded << x
|
105
108
|
yielded << y
|
106
109
|
true
|
107
110
|
end
|
111
|
+
a.equivalent_to?(b, compare_appends)
|
112
|
+
|
108
113
|
assert_equal [a.value, b.value], yielded
|
109
114
|
end
|
110
115
|
|
116
|
+
it "compares exceptions using an error comparator proc" do
|
117
|
+
x = Scientist::Observation.new("test", @experiment) { raise FirstError, "error" }
|
118
|
+
y = Scientist::Observation.new("test", @experiment) { raise SecondError, "error" }
|
119
|
+
z = Scientist::Observation.new("test", @experiment) { raise FirstError, "ERROR" }
|
120
|
+
|
121
|
+
refute x.equivalent_to?(z)
|
122
|
+
refute x.equivalent_to?(y)
|
123
|
+
|
124
|
+
compare_on_class = -> (error, other_error) {
|
125
|
+
error.class == other_error.class
|
126
|
+
}
|
127
|
+
compare_on_message = -> (error, other_error) {
|
128
|
+
error.message == other_error.message
|
129
|
+
}
|
130
|
+
|
131
|
+
assert x.equivalent_to?(z, nil, compare_on_class)
|
132
|
+
assert x.equivalent_to?(y, nil, compare_on_message)
|
133
|
+
end
|
134
|
+
|
111
135
|
describe "#cleaned_value" do
|
112
136
|
it "returns the observation's value by default" do
|
113
137
|
a = Scientist::Observation.new("test", @experiment) { 1 }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scientist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.6.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2021-
|
15
|
+
date: 2021-10-22 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: minitest
|
@@ -95,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
requirements: []
|
98
|
-
rubygems_version: 3.
|
98
|
+
rubygems_version: 3.2.22
|
99
99
|
signing_key:
|
100
100
|
specification_version: 4
|
101
101
|
summary: Carefully test, measure, and track refactored code.
|