scientist 1.2.0 → 1.3.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 +4 -5
- data/README.md +21 -2
- data/doc/changelog.md +7 -0
- data/lib/scientist/experiment.rb +18 -4
- data/lib/scientist/observation.rb +4 -3
- data/lib/scientist/version.rb +1 -1
- data/scientist.gemspec +1 -1
- data/script/release +2 -3
- data/script/test +1 -1
- data/test/scientist/experiment_test.rb +35 -0
- data/test/scientist_test.rb +10 -8
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2c73b090777600d31576b815584cba175ba2061ef223b5ec6775038b03828ee
|
4
|
+
data.tar.gz: 0336cebef463c2c7b92246d7124a395f7351a75452071595ec3c7edac63050f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e97b6ba1d1f6a07d518b8d86b1b60dbfc0a978e3ef77e95490f733e290fcdef7a42c73728b7f17657a379706bfbfb5ff2d8dbc24334fdb0f7847069c46c5629f
|
7
|
+
data.tar.gz: 3c2b414b032e96eebf4fe476078dc170384efaf67622a32f24db44a52b1dec1c0c4da2706151d06a9dbd3e30da1655ad06d4add6db0af25dd9d85f540014452a
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -206,6 +206,8 @@ class MyExperiment
|
|
206
206
|
end
|
207
207
|
```
|
208
208
|
|
209
|
+
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.)_
|
210
|
+
|
209
211
|
### Ignoring mismatches
|
210
212
|
|
211
213
|
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:
|
@@ -491,6 +493,22 @@ science "various-ways", run: "first-way" do |e|
|
|
491
493
|
end
|
492
494
|
```
|
493
495
|
|
496
|
+
#### Providing fake timing data
|
497
|
+
|
498
|
+
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.
|
499
|
+
|
500
|
+
```ruby
|
501
|
+
science "absolutely-nothing-suspicious-happening-here" do |e|
|
502
|
+
e.use { ... } # "control"
|
503
|
+
e.try { ... } # "candidate"
|
504
|
+
e.fabricate_durations_for_testing_purposes( "control" => 1.0, "candidate" => 0.5 )
|
505
|
+
end
|
506
|
+
```
|
507
|
+
|
508
|
+
`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.
|
509
|
+
|
510
|
+
_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._
|
511
|
+
|
494
512
|
### Without including Scientist
|
495
513
|
|
496
514
|
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,12 +522,12 @@ end
|
|
504
522
|
|
505
523
|
## Hacking
|
506
524
|
|
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. Scientist requires Ruby 2.
|
525
|
+
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.
|
508
526
|
|
509
527
|
## Alternatives
|
510
528
|
|
511
529
|
- [daylerees/scientist](https://github.com/daylerees/scientist) (PHP)
|
512
|
-
- [
|
530
|
+
- [scientistproject/scientist.net](https://github.com/scientistproject/Scientist.net) (.NET)
|
513
531
|
- [joealcorn/laboratory](https://github.com/joealcorn/laboratory) (Python)
|
514
532
|
- [rawls238/Scientist4J](https://github.com/rawls238/Scientist4J) (Java)
|
515
533
|
- [tomiaijo/scientist](https://github.com/tomiaijo/scientist) (C++)
|
@@ -523,6 +541,7 @@ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs t
|
|
523
541
|
- [calavera/go-scientist](https://github.com/calavera/go-scientist) (Go)
|
524
542
|
- [jelmersnoeck/experiment](https://github.com/jelmersnoeck/experiment) (Go)
|
525
543
|
- [spoptchev/scientist](https://github.com/spoptchev/scientist) (Kotlin / Java)
|
544
|
+
- [junkpiano/scientist](https://github.com/junkpiano/scientist) (Swift)
|
526
545
|
|
527
546
|
|
528
547
|
## Maintainers
|
data/doc/changelog.md
CHANGED
@@ -1,5 +1,12 @@
|
|
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
|
+
|
3
10
|
## v1.2.0 (5 July 2018)
|
4
11
|
|
5
12
|
- New: Use monotonic clock for duration calculations
|
data/lib/scientist/experiment.rb
CHANGED
@@ -41,10 +41,10 @@ module Scientist::Experiment
|
|
41
41
|
def format_observation(observation)
|
42
42
|
observation.name + ":\n" +
|
43
43
|
if observation.raised?
|
44
|
-
observation.exception.
|
45
|
-
|
44
|
+
lines = observation.exception.backtrace.map { |line| " #{line}" }.join("\n")
|
45
|
+
" #{observation.exception.inspect}" + "\n" + lines
|
46
46
|
else
|
47
|
-
observation.cleaned_value.inspect
|
47
|
+
" #{observation.cleaned_value.inspect}"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -96,6 +96,13 @@ module Scientist::Experiment
|
|
96
96
|
@_scientist_cleaner = block
|
97
97
|
end
|
98
98
|
|
99
|
+
# Accessor for the clean block, if one is available.
|
100
|
+
#
|
101
|
+
# Returns the configured block, or nil.
|
102
|
+
def cleaner
|
103
|
+
@_scientist_cleaner
|
104
|
+
end
|
105
|
+
|
99
106
|
# Internal: Clean a value with the configured clean block, or return the value
|
100
107
|
# if no clean block is configured.
|
101
108
|
#
|
@@ -213,7 +220,8 @@ module Scientist::Experiment
|
|
213
220
|
|
214
221
|
behaviors.keys.shuffle.each do |key|
|
215
222
|
block = behaviors[key]
|
216
|
-
|
223
|
+
fabricated_duration = @_scientist_fabricated_durations && @_scientist_fabricated_durations[key]
|
224
|
+
observations << Scientist::Observation.new(key, self, fabricated_duration: fabricated_duration, &block)
|
217
225
|
end
|
218
226
|
|
219
227
|
control = observations.detect { |o| o.name == name }
|
@@ -290,4 +298,10 @@ module Scientist::Experiment
|
|
290
298
|
!!raise_on_mismatches
|
291
299
|
end
|
292
300
|
end
|
301
|
+
|
302
|
+
# Provide predefined durations to use instead of actual timing data.
|
303
|
+
# This is here solely as a convenience for developers of libraries that extend Scientist.
|
304
|
+
def fabricate_durations_for_testing_purposes(fabricated_durations = {})
|
305
|
+
@_scientist_fabricated_durations = fabricated_durations
|
306
|
+
end
|
293
307
|
end
|
@@ -23,19 +23,20 @@ class Scientist::Observation
|
|
23
23
|
# The Float seconds elapsed.
|
24
24
|
attr_reader :duration
|
25
25
|
|
26
|
-
def initialize(name, experiment, &block)
|
26
|
+
def initialize(name, experiment, fabricated_duration: nil, &block)
|
27
27
|
@name = name
|
28
28
|
@experiment = experiment
|
29
29
|
@now = Time.now
|
30
30
|
|
31
|
-
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
|
31
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) unless fabricated_duration
|
32
32
|
begin
|
33
33
|
@value = block.call
|
34
34
|
rescue *RESCUES => e
|
35
35
|
@exception = e
|
36
36
|
end
|
37
37
|
|
38
|
-
@duration =
|
38
|
+
@duration = fabricated_duration ||
|
39
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) - starting
|
39
40
|
|
40
41
|
freeze
|
41
42
|
end
|
data/lib/scientist/version.rb
CHANGED
data/scientist.gemspec
CHANGED
@@ -10,7 +10,7 @@ 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.
|
13
|
+
gem.required_ruby_version = '>= 2.3'
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
16
|
gem.executables = []
|
data/script/release
CHANGED
@@ -33,7 +33,6 @@ git fetch -t origin
|
|
33
33
|
}
|
34
34
|
|
35
35
|
# Tag it and bag it.
|
36
|
-
echo TAG $tag
|
37
36
|
|
38
|
-
gem push scientist-*.gem && git tag "$tag"
|
39
|
-
|
37
|
+
gem push scientist-*.gem && git tag "$tag" &&
|
38
|
+
git push origin master && git push origin "$tag"
|
data/script/test
CHANGED
@@ -239,6 +239,13 @@ describe Scientist::Experiment do
|
|
239
239
|
assert_equal 10, @ex.clean_value(10)
|
240
240
|
end
|
241
241
|
|
242
|
+
it "provides the clean block when asked for it, in case subclasses wish to override and provide defaults" do
|
243
|
+
assert_nil @ex.cleaner
|
244
|
+
cleaner = ->(value) { value.upcase }
|
245
|
+
@ex.clean(&cleaner)
|
246
|
+
assert_equal cleaner, @ex.cleaner
|
247
|
+
end
|
248
|
+
|
242
249
|
it "calls the configured clean block with a value when configured" do
|
243
250
|
@ex.clean do |value|
|
244
251
|
value.upcase
|
@@ -559,4 +566,32 @@ candidate:
|
|
559
566
|
refute before, "before_run should not have run"
|
560
567
|
end
|
561
568
|
end
|
569
|
+
|
570
|
+
describe "testing hooks for extending code" do
|
571
|
+
it "allows a user to provide fabricated durations for testing purposes" do
|
572
|
+
@ex.use { true }
|
573
|
+
@ex.try { true }
|
574
|
+
@ex.fabricate_durations_for_testing_purposes( "control" => 0.5, "candidate" => 1.0 )
|
575
|
+
|
576
|
+
@ex.run
|
577
|
+
|
578
|
+
cont = @ex.published_result.control
|
579
|
+
cand = @ex.published_result.candidates.first
|
580
|
+
assert_in_delta 0.5, cont.duration, 0.01
|
581
|
+
assert_in_delta 1.0, cand.duration, 0.01
|
582
|
+
end
|
583
|
+
|
584
|
+
it "returns actual durations if fabricated ones are omitted for some blocks" do
|
585
|
+
@ex.use { true }
|
586
|
+
@ex.try { sleep 0.1; true }
|
587
|
+
@ex.fabricate_durations_for_testing_purposes( "control" => 0.5 )
|
588
|
+
|
589
|
+
@ex.run
|
590
|
+
|
591
|
+
cont = @ex.published_result.control
|
592
|
+
cand = @ex.published_result.candidates.first
|
593
|
+
assert_in_delta 0.5, cont.duration, 0.01
|
594
|
+
assert_in_delta 0.1, cand.duration, 0.01
|
595
|
+
end
|
596
|
+
end
|
562
597
|
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.3.0
|
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:
|
15
|
+
date: 2019-04-02 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: minitest
|
@@ -88,15 +88,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
88
88
|
requirements:
|
89
89
|
- - ">="
|
90
90
|
- !ruby/object:Gem::Version
|
91
|
-
version: '2.
|
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
|
-
rubygems_version: 2.7.3
|
98
|
+
rubygems_version: 3.0.3
|
100
99
|
signing_key:
|
101
100
|
specification_version: 4
|
102
101
|
summary: Carefully test, measure, and track refactored code.
|