scientist 1.3.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 +4 -4
- data/README.md +56 -13
- data/lib/scientist/experiment.rb +46 -32
- data/lib/scientist/observation.rb +17 -12
- data/lib/scientist/version.rb +1 -1
- data/test/scientist/experiment_test.rb +48 -1
- data/test/scientist/observation_test.rb +28 -4
- metadata +6 -6
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
@@ -24,9 +24,9 @@ Wrap a `use` block around the code's original behavior, and wrap `try` around th
|
|
24
24
|
|
25
25
|
* It decides whether or not to run the `try` block,
|
26
26
|
* Randomizes the order in which `use` and `try` blocks are run,
|
27
|
-
* Measures the durations of all behaviors,
|
27
|
+
* Measures the durations of all behaviors in seconds,
|
28
28
|
* Compares the result of `try` to the result of `use`,
|
29
|
-
*
|
29
|
+
* Swallow and record exceptions raised in the `try` block when overriding `raised`, and
|
30
30
|
* Publishes all this information.
|
31
31
|
|
32
32
|
The `use` block is called the **control**. The `try` block is called the **candidate**.
|
@@ -62,7 +62,7 @@ class MyExperiment
|
|
62
62
|
|
63
63
|
attr_accessor :name
|
64
64
|
|
65
|
-
def initialize(name
|
65
|
+
def initialize(name)
|
66
66
|
@name = name
|
67
67
|
end
|
68
68
|
|
@@ -71,20 +71,21 @@ class MyExperiment
|
|
71
71
|
true
|
72
72
|
end
|
73
73
|
|
74
|
+
def raised(operation, error)
|
75
|
+
# see "In a Scientist callback" below
|
76
|
+
p "Operation '#{operation}' failed with error '#{error.inspect}'"
|
77
|
+
super # will re-raise
|
78
|
+
end
|
79
|
+
|
74
80
|
def publish(result)
|
75
81
|
# see "Publishing results" below
|
76
82
|
p result
|
77
83
|
end
|
78
84
|
end
|
79
|
-
|
80
|
-
# replace `Scientist::Default` as the default implementation
|
81
|
-
module Scientist::Experiment
|
82
|
-
def self.new(name)
|
83
|
-
MyExperiment.new(name: name)
|
84
|
-
end
|
85
|
-
end
|
86
85
|
```
|
87
86
|
|
87
|
+
When `Scientist::Experiment` is included in a class, it automatically sets it as the default implementation via `Scientist::Experiment.set_default`. This `set_default` call is is skipped if you include `Scientist::Experiment` in a module.
|
88
|
+
|
88
89
|
Now calls to the `science` helper will load instances of `MyExperiment`.
|
89
90
|
|
90
91
|
### Controlling comparison
|
@@ -108,6 +109,38 @@ class MyWidget
|
|
108
109
|
end
|
109
110
|
```
|
110
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
|
+
|
111
144
|
### Adding context
|
112
145
|
|
113
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:
|
@@ -227,7 +260,7 @@ def admin?(user)
|
|
227
260
|
end
|
228
261
|
```
|
229
262
|
|
230
|
-
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.
|
231
264
|
|
232
265
|
### Enabling/disabling experiments
|
233
266
|
|
@@ -256,7 +289,7 @@ class MyExperiment
|
|
256
289
|
|
257
290
|
attr_accessor :name, :percent_enabled
|
258
291
|
|
259
|
-
def initialize(name
|
292
|
+
def initialize(name)
|
260
293
|
@name = name
|
261
294
|
@percent_enabled = 100
|
262
295
|
end
|
@@ -394,6 +427,8 @@ Scientist rescues and tracks _all_ exceptions raised in a `try` or `use` block,
|
|
394
427
|
Scientist::Observation::RESCUES.replace [StandardError]
|
395
428
|
```
|
396
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
|
+
|
397
432
|
#### In a Scientist callback
|
398
433
|
|
399
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:
|
@@ -524,6 +559,10 @@ end
|
|
524
559
|
|
525
560
|
Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs the unit tests. All development dependencies are installed automatically. Scientist requires Ruby 2.3 or newer.
|
526
561
|
|
562
|
+
## Wrappers
|
563
|
+
|
564
|
+
- [RealGeeks/lab_tech](https://github.com/RealGeeks/lab_tech) is a Rails engine for using this library by controlling, storing, and analyzing experiment results with ActiveRecord.
|
565
|
+
|
527
566
|
## Alternatives
|
528
567
|
|
529
568
|
- [daylerees/scientist](https://github.com/daylerees/scientist) (PHP)
|
@@ -533,6 +572,8 @@ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs t
|
|
533
572
|
- [tomiaijo/scientist](https://github.com/tomiaijo/scientist) (C++)
|
534
573
|
- [trello/scientist](https://github.com/trello/scientist) (node.js)
|
535
574
|
- [ziyasal/scientist.js](https://github.com/ziyasal/scientist.js) (node.js, ES6)
|
575
|
+
- [TrueWill/tzientist](https://github.com/TrueWill/tzientist) (node.js, TypeScript)
|
576
|
+
- [TrueWill/paleontologist](https://github.com/TrueWill/paleontologist) (Deno, TypeScript)
|
536
577
|
- [yeller/laboratory](https://github.com/yeller/laboratory) (Clojure)
|
537
578
|
- [lancew/Scientist](https://github.com/lancew/Scientist) (Perl 5)
|
538
579
|
- [lancew/ScientistP6](https://github.com/lancew/ScientistP6) (Perl 6)
|
@@ -542,7 +583,9 @@ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs t
|
|
542
583
|
- [jelmersnoeck/experiment](https://github.com/jelmersnoeck/experiment) (Go)
|
543
584
|
- [spoptchev/scientist](https://github.com/spoptchev/scientist) (Kotlin / Java)
|
544
585
|
- [junkpiano/scientist](https://github.com/junkpiano/scientist) (Swift)
|
545
|
-
|
586
|
+
- [serverless scientist](http://serverlessscientist.com/) (AWS Lambda)
|
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)
|
546
589
|
|
547
590
|
## Maintainers
|
548
591
|
|
data/lib/scientist/experiment.rb
CHANGED
@@ -9,16 +9,26 @@ module Scientist::Experiment
|
|
9
9
|
# If this is nil, raise_on_mismatches class attribute is used instead.
|
10
10
|
attr_accessor :raise_on_mismatches
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def self.included(base)
|
13
|
+
self.set_default(base) if base.instance_of?(Class)
|
14
|
+
base.extend RaiseOnMismatch
|
15
|
+
end
|
16
|
+
|
17
|
+
# Instantiate a new experiment (using the class given to the .set_default method).
|
16
18
|
def self.new(name)
|
17
|
-
Scientist::Default.new(name)
|
19
|
+
(@experiment_klass || Scientist::Default).new(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Configure Scientist to use the given class for all future experiments
|
23
|
+
# (must implement the Scientist::Experiment interface).
|
24
|
+
#
|
25
|
+
# Called automatically when new experiments are defined.
|
26
|
+
def self.set_default(klass)
|
27
|
+
@experiment_klass = klass
|
18
28
|
end
|
19
29
|
|
20
30
|
# A mismatch, raised when raise_on_mismatches is enabled.
|
21
|
-
class MismatchError <
|
31
|
+
class MismatchError < Exception
|
22
32
|
attr_reader :name, :result
|
23
33
|
|
24
34
|
def initialize(name, result)
|
@@ -67,10 +77,6 @@ module Scientist::Experiment
|
|
67
77
|
end
|
68
78
|
end
|
69
79
|
|
70
|
-
def self.included(base)
|
71
|
-
base.extend RaiseOnMismatch
|
72
|
-
end
|
73
|
-
|
74
80
|
# Define a block of code to run before an experiment begins, if the experiment
|
75
81
|
# is enabled.
|
76
82
|
#
|
@@ -128,6 +134,16 @@ module Scientist::Experiment
|
|
128
134
|
@_scientist_comparator = block
|
129
135
|
end
|
130
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
|
+
|
131
147
|
# A Symbol-keyed Hash of extra experiment data.
|
132
148
|
def context(context = nil)
|
133
149
|
@_scientist_context ||= {}
|
@@ -171,13 +187,9 @@ module Scientist::Experiment
|
|
171
187
|
"experiment"
|
172
188
|
end
|
173
189
|
|
174
|
-
# Internal: compare two observations, using the configured compare
|
190
|
+
# Internal: compare two observations, using the configured compare and compare_errors lambdas if present.
|
175
191
|
def observations_are_equivalent?(a, b)
|
176
|
-
|
177
|
-
a.equivalent_to?(b, &@_scientist_comparator)
|
178
|
-
else
|
179
|
-
a.equivalent_to? b
|
180
|
-
end
|
192
|
+
a.equivalent_to? b, @_scientist_comparator, @_scientist_error_comparator
|
181
193
|
rescue StandardError => ex
|
182
194
|
raised :compare, ex
|
183
195
|
false
|
@@ -216,17 +228,7 @@ module Scientist::Experiment
|
|
216
228
|
@_scientist_before_run.call
|
217
229
|
end
|
218
230
|
|
219
|
-
|
220
|
-
|
221
|
-
behaviors.keys.shuffle.each do |key|
|
222
|
-
block = behaviors[key]
|
223
|
-
fabricated_duration = @_scientist_fabricated_durations && @_scientist_fabricated_durations[key]
|
224
|
-
observations << Scientist::Observation.new(key, self, fabricated_duration: fabricated_duration, &block)
|
225
|
-
end
|
226
|
-
|
227
|
-
control = observations.detect { |o| o.name == name }
|
228
|
-
|
229
|
-
result = Scientist::Result.new self, observations, control
|
231
|
+
result = generate_result(name)
|
230
232
|
|
231
233
|
begin
|
232
234
|
publish(result)
|
@@ -242,11 +244,9 @@ module Scientist::Experiment
|
|
242
244
|
end
|
243
245
|
end
|
244
246
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
control.value
|
249
|
-
end
|
247
|
+
control = result.control
|
248
|
+
raise control.exception if control.raised?
|
249
|
+
control.value
|
250
250
|
end
|
251
251
|
|
252
252
|
# Define a block that determines whether or not the experiment should run.
|
@@ -304,4 +304,18 @@ module Scientist::Experiment
|
|
304
304
|
def fabricate_durations_for_testing_purposes(fabricated_durations = {})
|
305
305
|
@_scientist_fabricated_durations = fabricated_durations
|
306
306
|
end
|
307
|
+
|
308
|
+
# Internal: Generate the observations and create the result from those and the control.
|
309
|
+
def generate_result(name)
|
310
|
+
observations = []
|
311
|
+
|
312
|
+
behaviors.keys.shuffle.each do |key|
|
313
|
+
block = behaviors[key]
|
314
|
+
fabricated_duration = @_scientist_fabricated_durations && @_scientist_fabricated_durations[key]
|
315
|
+
observations << Scientist::Observation.new(key, self, fabricated_duration: fabricated_duration, &block)
|
316
|
+
end
|
317
|
+
|
318
|
+
control = observations.detect { |o| o.name == name }
|
319
|
+
Scientist::Result.new(self, observations, control)
|
320
|
+
end
|
307
321
|
end
|
@@ -8,9 +8,6 @@ class Scientist::Observation
|
|
8
8
|
# The experiment this observation is for
|
9
9
|
attr_reader :experiment
|
10
10
|
|
11
|
-
# The instant observation began.
|
12
|
-
attr_reader :now
|
13
|
-
|
14
11
|
# The String name of the behavior.
|
15
12
|
attr_reader :name
|
16
13
|
|
@@ -26,7 +23,6 @@ class Scientist::Observation
|
|
26
23
|
def initialize(name, experiment, fabricated_duration: nil, &block)
|
27
24
|
@name = name
|
28
25
|
@experiment = experiment
|
29
|
-
@now = Time.now
|
30
26
|
|
31
27
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) unless fabricated_duration
|
32
28
|
begin
|
@@ -49,25 +45,34 @@ class Scientist::Observation
|
|
49
45
|
|
50
46
|
# Is this observation equivalent to another?
|
51
47
|
#
|
52
|
-
# other
|
53
|
-
# comparator
|
54
|
-
#
|
55
|
-
#
|
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.
|
56
55
|
#
|
57
56
|
# Returns true if:
|
58
57
|
#
|
59
58
|
# * The values of the observation are equal (using `==`)
|
60
59
|
# * The values of the observations are equal according to a comparison
|
61
|
-
#
|
60
|
+
# proc, if given
|
61
|
+
# * The exceptions raised by the obeservations are equal according to the
|
62
|
+
# error comparison proc, if given.
|
62
63
|
# * Both observations raised an exception with the same class and message.
|
63
64
|
#
|
64
65
|
# Returns false otherwise.
|
65
|
-
def equivalent_to?(other,
|
66
|
+
def equivalent_to?(other, comparator=nil, error_comparator=nil)
|
66
67
|
return false unless other.is_a?(Scientist::Observation)
|
67
68
|
|
68
69
|
if raised? || other.raised?
|
69
|
-
|
70
|
-
|
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
|
71
76
|
end
|
72
77
|
|
73
78
|
if comparator
|
data/lib/scientist/version.rb
CHANGED
@@ -2,6 +2,9 @@ describe Scientist::Experiment do
|
|
2
2
|
class Fake
|
3
3
|
include Scientist::Experiment
|
4
4
|
|
5
|
+
# Undo auto-config magic / preserve default behavior of Scientist::Experiment.new
|
6
|
+
Scientist::Experiment.set_default(nil)
|
7
|
+
|
5
8
|
def initialize(*args)
|
6
9
|
end
|
7
10
|
|
@@ -28,6 +31,24 @@ describe Scientist::Experiment do
|
|
28
31
|
@ex = Fake.new
|
29
32
|
end
|
30
33
|
|
34
|
+
it "sets the default on inclusion" do
|
35
|
+
klass = Class.new do
|
36
|
+
include Scientist::Experiment
|
37
|
+
|
38
|
+
def initialize(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_kind_of klass, Scientist::Experiment.new("hello")
|
43
|
+
|
44
|
+
Scientist::Experiment.set_default(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't set the default on inclusion when it's a module" do
|
48
|
+
Module.new { include Scientist::Experiment }
|
49
|
+
assert_kind_of Scientist::Default, Scientist::Experiment.new("hello")
|
50
|
+
end
|
51
|
+
|
31
52
|
it "has a default implementation" do
|
32
53
|
ex = Scientist::Experiment.new("hello")
|
33
54
|
assert_kind_of Scientist::Default, ex
|
@@ -180,6 +201,18 @@ describe Scientist::Experiment do
|
|
180
201
|
assert @ex.published_result.matched?
|
181
202
|
end
|
182
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
|
+
|
183
216
|
it "knows how to compare two experiments" do
|
184
217
|
a = Scientist::Observation.new(@ex, "a") { 1 }
|
185
218
|
b = Scientist::Observation.new(@ex, "b") { 2 }
|
@@ -444,6 +477,20 @@ describe Scientist::Experiment do
|
|
444
477
|
assert_raises(Scientist::Experiment::MismatchError) { @ex.run }
|
445
478
|
end
|
446
479
|
|
480
|
+
it "allows MismatchError to bubble up through bare rescues" do
|
481
|
+
Fake.raise_on_mismatches = true
|
482
|
+
@ex.use { "control" }
|
483
|
+
@ex.try { "candidate" }
|
484
|
+
runner = -> {
|
485
|
+
begin
|
486
|
+
@ex.run
|
487
|
+
rescue
|
488
|
+
# StandardError handled
|
489
|
+
end
|
490
|
+
}
|
491
|
+
assert_raises(Scientist::Experiment::MismatchError) { runner.call }
|
492
|
+
end
|
493
|
+
|
447
494
|
describe "#raise_on_mismatches?" do
|
448
495
|
it "raises when there is a mismatch if the experiment instance's raise on mismatches is enabled" do
|
449
496
|
Fake.raise_on_mismatches = false
|
@@ -532,7 +579,7 @@ candidate:
|
|
532
579
|
assert_equal " \"value\"", lines[2]
|
533
580
|
assert_equal "candidate:", lines[3]
|
534
581
|
assert_equal " #<RuntimeError: error>", lines[4]
|
535
|
-
assert_match %r(
|
582
|
+
assert_match %r(test/scientist/experiment_test.rb:\d+:in `block), lines[5]
|
536
583
|
end
|
537
584
|
end
|
538
585
|
end
|
@@ -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.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
- Rick Bradley
|
10
10
|
- Jesse Toth
|
11
11
|
- Nathan Witmer
|
12
|
-
autorequire:
|
12
|
+
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2021-10-22 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: minitest
|
@@ -80,7 +80,7 @@ homepage: https://github.com/github/scientist
|
|
80
80
|
licenses:
|
81
81
|
- MIT
|
82
82
|
metadata: {}
|
83
|
-
post_install_message:
|
83
|
+
post_install_message:
|
84
84
|
rdoc_options: []
|
85
85
|
require_paths:
|
86
86
|
- lib
|
@@ -95,8 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
requirements: []
|
98
|
-
rubygems_version: 3.
|
99
|
-
signing_key:
|
98
|
+
rubygems_version: 3.2.22
|
99
|
+
signing_key:
|
100
100
|
specification_version: 4
|
101
101
|
summary: Carefully test, measure, and track refactored code.
|
102
102
|
test_files:
|