scientist 1.1.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -6
- data/README.md +42 -15
- data/doc/changelog.md +23 -0
- data/lib/scientist/experiment.rb +50 -28
- data/lib/scientist/observation.rb +4 -6
- data/lib/scientist/version.rb +1 -1
- data/scientist.gemspec +2 -0
- data/script/test +1 -1
- data/test/scientist/experiment_test.rb +71 -1
- data/test/scientist_test.rb +10 -8
- metadata +7 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f03841ae9bccb8d7b979971d5f6a38482f57bbc81af82d0a710479ccc3f16fbb
|
4
|
+
data.tar.gz: c0f81d696918e302462baf1bd918cd25179a99045d48082fb162e1fdb2fa2280
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1680e6564a6e83b3dde55f6757f3e7c953a70b3d748aa0e7ca2a54b5a33a4b3a947a8f20aa52790a41e6a66e36627e4e851f627625c0aaf9fd4cf41b94ace8c
|
7
|
+
data.tar.gz: 75ba381053b73e9418ed2ab5608c70c92a55990c938a1c25f4324169ff560a8feea6827ef909e50b9fc3baf5a5eb49f3b7b26ed4e3f9799007b80a71c7543c86
|
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
|
@@ -206,6 +207,8 @@ class MyExperiment
|
|
206
207
|
end
|
207
208
|
```
|
208
209
|
|
210
|
+
Note that the `#clean` method will discard the previous cleaner block if you call it again. If for some reason you need to access the currently configured cleaner block, `Scientist::Experiment#cleaner` will return the block without further ado. _(This probably won't come up in normal usage, but comes in handy if you're writing, say, a custom experiment runner that provides default cleaners.)_
|
211
|
+
|
209
212
|
### Ignoring mismatches
|
210
213
|
|
211
214
|
During the early stages of an experiment, it's possible that some of your code will always generate a mismatch for reasons you know and understand but haven't yet fixed. Instead of these known cases always showing up as mismatches in your metrics or analysis, you can tell an experiment whether or not to ignore a mismatch using the `ignore` method. You may include more than one block if needed:
|
@@ -254,7 +257,7 @@ class MyExperiment
|
|
254
257
|
|
255
258
|
attr_accessor :name, :percent_enabled
|
256
259
|
|
257
|
-
def initialize(name
|
260
|
+
def initialize(name)
|
258
261
|
@name = name
|
259
262
|
@percent_enabled = 100
|
260
263
|
end
|
@@ -327,7 +330,7 @@ class MyExperiment
|
|
327
330
|
}
|
328
331
|
else
|
329
332
|
{
|
330
|
-
# see "Keeping it clean"
|
333
|
+
# see "Keeping it clean" above
|
331
334
|
:value => observation.cleaned_value
|
332
335
|
}
|
333
336
|
end
|
@@ -491,6 +494,22 @@ science "various-ways", run: "first-way" do |e|
|
|
491
494
|
end
|
492
495
|
```
|
493
496
|
|
497
|
+
#### Providing fake timing data
|
498
|
+
|
499
|
+
If you're writing tests that depend on specific timing values, you can provide canned durations using the `fabricate_durations_for_testing_purposes` method, and Scientist will report these in `Scientist::Observation#duration` instead of the actual execution times.
|
500
|
+
|
501
|
+
```ruby
|
502
|
+
science "absolutely-nothing-suspicious-happening-here" do |e|
|
503
|
+
e.use { ... } # "control"
|
504
|
+
e.try { ... } # "candidate"
|
505
|
+
e.fabricate_durations_for_testing_purposes( "control" => 1.0, "candidate" => 0.5 )
|
506
|
+
end
|
507
|
+
```
|
508
|
+
|
509
|
+
`fabricate_durations_for_testing_purposes` takes a Hash of duration values, keyed by behavior names. (By default, Scientist uses `"control"` and `"candidate"`, but if you override these as shown in [Trying more than one thing](#trying-more-than-one-thing) or [No control, just candidates](#no-control-just-candidates), use matching names here.) If a name is not provided, the actual execution time will be reported instead.
|
510
|
+
|
511
|
+
_Like `Scientist::Experiment#cleaner`, this probably won't come up in normal usage. It's here to make it easier to test code that extends Scientist._
|
512
|
+
|
494
513
|
### Without including Scientist
|
495
514
|
|
496
515
|
If you need to use Scientist in a place where you aren't able to include the Scientist module, you can call `Scientist.run`:
|
@@ -504,17 +523,23 @@ end
|
|
504
523
|
|
505
524
|
## Hacking
|
506
525
|
|
507
|
-
Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs the unit tests. All development dependencies are installed automatically.
|
526
|
+
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.
|
527
|
+
|
528
|
+
## Wrappers
|
529
|
+
|
530
|
+
- [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.
|
508
531
|
|
509
532
|
## Alternatives
|
510
533
|
|
511
534
|
- [daylerees/scientist](https://github.com/daylerees/scientist) (PHP)
|
512
|
-
- [
|
535
|
+
- [scientistproject/scientist.net](https://github.com/scientistproject/Scientist.net) (.NET)
|
513
536
|
- [joealcorn/laboratory](https://github.com/joealcorn/laboratory) (Python)
|
514
537
|
- [rawls238/Scientist4J](https://github.com/rawls238/Scientist4J) (Java)
|
515
538
|
- [tomiaijo/scientist](https://github.com/tomiaijo/scientist) (C++)
|
516
539
|
- [trello/scientist](https://github.com/trello/scientist) (node.js)
|
517
540
|
- [ziyasal/scientist.js](https://github.com/ziyasal/scientist.js) (node.js, ES6)
|
541
|
+
- [TrueWill/tzientist](https://github.com/TrueWill/tzientist) (node.js, TypeScript)
|
542
|
+
- [TrueWill/paleontologist](https://github.com/TrueWill/paleontologist) (Deno, TypeScript)
|
518
543
|
- [yeller/laboratory](https://github.com/yeller/laboratory) (Clojure)
|
519
544
|
- [lancew/Scientist](https://github.com/lancew/Scientist) (Perl 5)
|
520
545
|
- [lancew/ScientistP6](https://github.com/lancew/ScientistP6) (Perl 6)
|
@@ -523,7 +548,9 @@ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs t
|
|
523
548
|
- [calavera/go-scientist](https://github.com/calavera/go-scientist) (Go)
|
524
549
|
- [jelmersnoeck/experiment](https://github.com/jelmersnoeck/experiment) (Go)
|
525
550
|
- [spoptchev/scientist](https://github.com/spoptchev/scientist) (Kotlin / Java)
|
526
|
-
|
551
|
+
- [junkpiano/scientist](https://github.com/junkpiano/scientist) (Swift)
|
552
|
+
- [serverless scientist](http://serverlessscientist.com/) (AWS Lambda)
|
553
|
+
- [fightmegg/scientist](https://github.com/fightmegg/scientist) (TypeScript, Browser / Node.js)
|
527
554
|
|
528
555
|
## Maintainers
|
529
556
|
|
data/doc/changelog.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## v1.3.0 (2 April 2019)
|
4
|
+
|
5
|
+
- New: Drop support for ruby <2.3
|
6
|
+
- Fix: Build new strings instead of modifying frozen ones
|
7
|
+
- New: Add an accessor for the configured clean block
|
8
|
+
- New: Add a hook to use fabricated durations instead of actual timing data.
|
9
|
+
|
10
|
+
## v1.2.0 (5 July 2018)
|
11
|
+
|
12
|
+
- New: Use monotonic clock for duration calculations
|
13
|
+
- New: Drop support for ruby <2.1 to support monotonic clock
|
14
|
+
- New: Run CI on Ruby 2.5
|
15
|
+
|
16
|
+
## v1.1.2 (9 May 2018)
|
17
|
+
|
18
|
+
- New: Add `raise_with` option to allow for custom mismatch errors to be raised
|
19
|
+
|
20
|
+
## v1.1.1 (6 February 2018)
|
21
|
+
|
22
|
+
- Fix: default experiment no longer runs all `try` paths
|
23
|
+
- New: Add `Scientist.run` module method for running experiments when an included module isn't available
|
24
|
+
- New: Add [Noise and error rates](https://github.com/github/scientist#noise-and-error-rates) to `README.md`
|
25
|
+
|
3
26
|
## v1.1.0 (29 August 2017)
|
4
27
|
|
5
28
|
- New: [Specify which exception types to rescue](https://github.com/github/scientist#in-candidate-code)
|
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)
|
@@ -41,10 +51,10 @@ module Scientist::Experiment
|
|
41
51
|
def format_observation(observation)
|
42
52
|
observation.name + ":\n" +
|
43
53
|
if observation.raised?
|
44
|
-
observation.exception.
|
45
|
-
|
54
|
+
lines = observation.exception.backtrace.map { |line| " #{line}" }.join("\n")
|
55
|
+
" #{observation.exception.inspect}" + "\n" + lines
|
46
56
|
else
|
47
|
-
observation.cleaned_value.inspect
|
57
|
+
" #{observation.cleaned_value.inspect}"
|
48
58
|
end
|
49
59
|
end
|
50
60
|
end
|
@@ -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
|
#
|
@@ -96,6 +102,13 @@ module Scientist::Experiment
|
|
96
102
|
@_scientist_cleaner = block
|
97
103
|
end
|
98
104
|
|
105
|
+
# Accessor for the clean block, if one is available.
|
106
|
+
#
|
107
|
+
# Returns the configured block, or nil.
|
108
|
+
def cleaner
|
109
|
+
@_scientist_cleaner
|
110
|
+
end
|
111
|
+
|
99
112
|
# Internal: Clean a value with the configured clean block, or return the value
|
100
113
|
# if no clean block is configured.
|
101
114
|
#
|
@@ -209,16 +222,7 @@ module Scientist::Experiment
|
|
209
222
|
@_scientist_before_run.call
|
210
223
|
end
|
211
224
|
|
212
|
-
|
213
|
-
|
214
|
-
behaviors.keys.shuffle.each do |key|
|
215
|
-
block = behaviors[key]
|
216
|
-
observations << Scientist::Observation.new(key, self, &block)
|
217
|
-
end
|
218
|
-
|
219
|
-
control = observations.detect { |o| o.name == name }
|
220
|
-
|
221
|
-
result = Scientist::Result.new self, observations, control
|
225
|
+
result = generate_result(name)
|
222
226
|
|
223
227
|
begin
|
224
228
|
publish(result)
|
@@ -234,11 +238,9 @@ module Scientist::Experiment
|
|
234
238
|
end
|
235
239
|
end
|
236
240
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
control.value
|
241
|
-
end
|
241
|
+
control = result.control
|
242
|
+
raise control.exception if control.raised?
|
243
|
+
control.value
|
242
244
|
end
|
243
245
|
|
244
246
|
# Define a block that determines whether or not the experiment should run.
|
@@ -290,4 +292,24 @@ module Scientist::Experiment
|
|
290
292
|
!!raise_on_mismatches
|
291
293
|
end
|
292
294
|
end
|
295
|
+
|
296
|
+
# Provide predefined durations to use instead of actual timing data.
|
297
|
+
# This is here solely as a convenience for developers of libraries that extend Scientist.
|
298
|
+
def fabricate_durations_for_testing_purposes(fabricated_durations = {})
|
299
|
+
@_scientist_fabricated_durations = fabricated_durations
|
300
|
+
end
|
301
|
+
|
302
|
+
# Internal: Generate the observations and create the result from those and the control.
|
303
|
+
def generate_result(name)
|
304
|
+
observations = []
|
305
|
+
|
306
|
+
behaviors.keys.shuffle.each do |key|
|
307
|
+
block = behaviors[key]
|
308
|
+
fabricated_duration = @_scientist_fabricated_durations && @_scientist_fabricated_durations[key]
|
309
|
+
observations << Scientist::Observation.new(key, self, fabricated_duration: fabricated_duration, &block)
|
310
|
+
end
|
311
|
+
|
312
|
+
control = observations.detect { |o| o.name == name }
|
313
|
+
Scientist::Result.new(self, observations, control)
|
314
|
+
end
|
293
315
|
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
|
|
@@ -23,18 +20,19 @@ class Scientist::Observation
|
|
23
20
|
# The Float seconds elapsed.
|
24
21
|
attr_reader :duration
|
25
22
|
|
26
|
-
def initialize(name, experiment, &block)
|
23
|
+
def initialize(name, experiment, fabricated_duration: nil, &block)
|
27
24
|
@name = name
|
28
25
|
@experiment = experiment
|
29
|
-
@now = Time.now
|
30
26
|
|
27
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) unless fabricated_duration
|
31
28
|
begin
|
32
29
|
@value = block.call
|
33
30
|
rescue *RESCUES => e
|
34
31
|
@exception = e
|
35
32
|
end
|
36
33
|
|
37
|
-
@duration =
|
34
|
+
@duration = fabricated_duration ||
|
35
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) - starting
|
38
36
|
|
39
37
|
freeze
|
40
38
|
end
|
data/lib/scientist/version.rb
CHANGED
data/scientist.gemspec
CHANGED
@@ -10,6 +10,8 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.homepage = "https://github.com/github/scientist"
|
11
11
|
gem.license = "MIT"
|
12
12
|
|
13
|
+
gem.required_ruby_version = '>= 2.3'
|
14
|
+
|
13
15
|
gem.files = `git ls-files`.split($/)
|
14
16
|
gem.executables = []
|
15
17
|
gem.test_files = gem.files.grep(/^test/)
|
data/script/test
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
|
@@ -239,6 +260,13 @@ describe Scientist::Experiment do
|
|
239
260
|
assert_equal 10, @ex.clean_value(10)
|
240
261
|
end
|
241
262
|
|
263
|
+
it "provides the clean block when asked for it, in case subclasses wish to override and provide defaults" do
|
264
|
+
assert_nil @ex.cleaner
|
265
|
+
cleaner = ->(value) { value.upcase }
|
266
|
+
@ex.clean(&cleaner)
|
267
|
+
assert_equal cleaner, @ex.cleaner
|
268
|
+
end
|
269
|
+
|
242
270
|
it "calls the configured clean block with a value when configured" do
|
243
271
|
@ex.clean do |value|
|
244
272
|
value.upcase
|
@@ -437,6 +465,20 @@ describe Scientist::Experiment do
|
|
437
465
|
assert_raises(Scientist::Experiment::MismatchError) { @ex.run }
|
438
466
|
end
|
439
467
|
|
468
|
+
it "allows MismatchError to bubble up through bare rescues" do
|
469
|
+
Fake.raise_on_mismatches = true
|
470
|
+
@ex.use { "control" }
|
471
|
+
@ex.try { "candidate" }
|
472
|
+
runner = -> {
|
473
|
+
begin
|
474
|
+
@ex.run
|
475
|
+
rescue
|
476
|
+
# StandardError handled
|
477
|
+
end
|
478
|
+
}
|
479
|
+
assert_raises(Scientist::Experiment::MismatchError) { runner.call }
|
480
|
+
end
|
481
|
+
|
440
482
|
describe "#raise_on_mismatches?" do
|
441
483
|
it "raises when there is a mismatch if the experiment instance's raise on mismatches is enabled" do
|
442
484
|
Fake.raise_on_mismatches = false
|
@@ -525,7 +567,7 @@ candidate:
|
|
525
567
|
assert_equal " \"value\"", lines[2]
|
526
568
|
assert_equal "candidate:", lines[3]
|
527
569
|
assert_equal " #<RuntimeError: error>", lines[4]
|
528
|
-
assert_match %r(
|
570
|
+
assert_match %r(test/scientist/experiment_test.rb:\d+:in `block), lines[5]
|
529
571
|
end
|
530
572
|
end
|
531
573
|
end
|
@@ -559,4 +601,32 @@ candidate:
|
|
559
601
|
refute before, "before_run should not have run"
|
560
602
|
end
|
561
603
|
end
|
604
|
+
|
605
|
+
describe "testing hooks for extending code" do
|
606
|
+
it "allows a user to provide fabricated durations for testing purposes" do
|
607
|
+
@ex.use { true }
|
608
|
+
@ex.try { true }
|
609
|
+
@ex.fabricate_durations_for_testing_purposes( "control" => 0.5, "candidate" => 1.0 )
|
610
|
+
|
611
|
+
@ex.run
|
612
|
+
|
613
|
+
cont = @ex.published_result.control
|
614
|
+
cand = @ex.published_result.candidates.first
|
615
|
+
assert_in_delta 0.5, cont.duration, 0.01
|
616
|
+
assert_in_delta 1.0, cand.duration, 0.01
|
617
|
+
end
|
618
|
+
|
619
|
+
it "returns actual durations if fabricated ones are omitted for some blocks" do
|
620
|
+
@ex.use { true }
|
621
|
+
@ex.try { sleep 0.1; true }
|
622
|
+
@ex.fabricate_durations_for_testing_purposes( "control" => 0.5 )
|
623
|
+
|
624
|
+
@ex.run
|
625
|
+
|
626
|
+
cont = @ex.published_result.control
|
627
|
+
cand = @ex.published_result.candidates.first
|
628
|
+
assert_in_delta 0.5, cont.duration, 0.01
|
629
|
+
assert_in_delta 0.1, cand.duration, 0.01
|
630
|
+
end
|
631
|
+
end
|
562
632
|
end
|
data/test/scientist_test.rb
CHANGED
@@ -56,27 +56,29 @@ describe Scientist do
|
|
56
56
|
obj = Object.new
|
57
57
|
obj.extend(Scientist)
|
58
58
|
|
59
|
-
|
60
|
-
experiment = e
|
59
|
+
behaviors_executed = []
|
61
60
|
|
62
|
-
|
63
|
-
e.try("
|
61
|
+
result = obj.science "test", run: "first-way" do |e|
|
62
|
+
e.try("first-way") { behaviors_executed << "first-way" ; true }
|
63
|
+
e.try("second-way") { behaviors_executed << "second-way" ; true }
|
64
64
|
end
|
65
65
|
|
66
66
|
assert_equal true, result
|
67
|
+
assert_equal [ "first-way" ], behaviors_executed
|
67
68
|
end
|
68
69
|
|
69
70
|
it "runs control when there is a nil named test" do
|
70
71
|
obj = Object.new
|
71
72
|
obj.extend(Scientist)
|
72
73
|
|
73
|
-
|
74
|
-
experiment = e
|
74
|
+
behaviors_executed = []
|
75
75
|
|
76
|
-
|
77
|
-
e.
|
76
|
+
result = obj.science "test", nil do |e|
|
77
|
+
e.use { behaviors_executed << "control" ; true }
|
78
|
+
e.try("second-way") { behaviors_executed << "second-way" ; true }
|
78
79
|
end
|
79
80
|
|
80
81
|
assert_equal true, result
|
82
|
+
assert_equal [ "control" ], behaviors_executed
|
81
83
|
end
|
82
84
|
end
|
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.0
|
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-03-08 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
|
@@ -88,16 +88,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
88
88
|
requirements:
|
89
89
|
- - ">="
|
90
90
|
- !ruby/object:Gem::Version
|
91
|
-
version: '
|
91
|
+
version: '2.3'
|
92
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
requirements: []
|
98
|
-
|
99
|
-
|
100
|
-
signing_key:
|
98
|
+
rubygems_version: 3.1.4
|
99
|
+
signing_key:
|
101
100
|
specification_version: 4
|
102
101
|
summary: Carefully test, measure, and track refactored code.
|
103
102
|
test_files:
|