scientist 0.0.3 → 0.0.4
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/README.md +20 -1
- data/lib/scientist/experiment.rb +49 -8
- data/lib/scientist/version.rb +1 -1
- data/test/scientist/experiment_test.rb +105 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89ebfbe0cbc1c0de8e8247f59fac21a7ac547e96
|
4
|
+
data.tar.gz: 50b3ee5ed061c80af3ed1e10098acab524d82dc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c35cd1392cca5da51729d825f9b4b9e58ed3439247141438070ad5137de59600549e12a3eff4a0ed73ee19fa220de4cd66fe37eb2207df5f1a200b7cde532d4
|
7
|
+
data.tar.gz: 80cf84df09f69c2fb3f3f51b6fd72ca3a7d569b0a57c6d5a7dc48d48a597b7add4bbebea149d5dc2bef3aedc9ce5e02897c24285596d9cbbd912f68ec1919e0f
|
data/README.md
CHANGED
@@ -144,6 +144,25 @@ end
|
|
144
144
|
|
145
145
|
The `widget-permissions` and `widget-destruction` experiments will both have a `:widget` key in their contexts.
|
146
146
|
|
147
|
+
### Expensive setup
|
148
|
+
|
149
|
+
If an experiment requires expensive setup that should only occur when the experiment is going to be run, define it with the `before_run` method:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
# Code under test modifies this in-place. We want to copy it for the
|
153
|
+
# candidate code, but only when needed:
|
154
|
+
value_for_original_code = big_object
|
155
|
+
value_for_new_code = nil
|
156
|
+
|
157
|
+
science "expensive-but-worthwhile" do |e|
|
158
|
+
e.before_run do
|
159
|
+
value_for_new_code = big_object.deep_copy
|
160
|
+
end
|
161
|
+
e.use { original_code(value_for_original_code) }
|
162
|
+
e.try { new_code(value_for_new_code) }
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
147
166
|
### Keeping it clean
|
148
167
|
|
149
168
|
Sometimes you don't want to store the full value for later analysis. For example, an experiment may return `User` instances, but when researching a mismatch, all you care about is the logins. You can define how to clean these values in an experiment:
|
@@ -410,7 +429,7 @@ end
|
|
410
429
|
|
411
430
|
## Hacking
|
412
431
|
|
413
|
-
Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs the unit tests. All development dependencies are
|
432
|
+
Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs the unit tests. All development dependencies are installed automatically. Science requires Ruby 1.9 or newer.
|
414
433
|
|
415
434
|
## Maintainers
|
416
435
|
|
data/lib/scientist/experiment.rb
CHANGED
@@ -15,8 +15,33 @@ module Scientist::Experiment
|
|
15
15
|
|
16
16
|
# A mismatch, raised when raise_on_mismatches is enabled.
|
17
17
|
class MismatchError < StandardError
|
18
|
+
attr_reader :name, :result
|
19
|
+
|
18
20
|
def initialize(name, result)
|
19
|
-
|
21
|
+
@name = name
|
22
|
+
@result = result
|
23
|
+
super "experiment '#{name}' observations mismatched"
|
24
|
+
end
|
25
|
+
|
26
|
+
# The default formatting is nearly unreadable, so make it useful.
|
27
|
+
#
|
28
|
+
# The assumption here is that errors raised in a test environment are
|
29
|
+
# printed out as strings, rather than using #inspect.
|
30
|
+
def to_s
|
31
|
+
super + ":\n" +
|
32
|
+
format_observation(result.control) + "\n" +
|
33
|
+
result.candidates.map { |candidate| format_observation(candidate) }.join("\n") +
|
34
|
+
"\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_observation(observation)
|
38
|
+
observation.name + ":\n" +
|
39
|
+
if observation.raised?
|
40
|
+
observation.exception.inspect.prepend(" ") + "\n" +
|
41
|
+
observation.exception.backtrace.map { |line| line.prepend(" ") }.join("\n")
|
42
|
+
else
|
43
|
+
observation.value.inspect.prepend(" ")
|
44
|
+
end
|
20
45
|
end
|
21
46
|
end
|
22
47
|
|
@@ -42,6 +67,16 @@ module Scientist::Experiment
|
|
42
67
|
base.extend RaiseOnMismatch
|
43
68
|
end
|
44
69
|
|
70
|
+
# Define a block of code to run before an experiment begins, if the experiment
|
71
|
+
# is enabled.
|
72
|
+
#
|
73
|
+
# The block takes no arguments.
|
74
|
+
#
|
75
|
+
# Returns the configured block.
|
76
|
+
def before_run(&block)
|
77
|
+
@_scientist_before_run = block
|
78
|
+
end
|
79
|
+
|
45
80
|
# A Hash of behavior blocks, keyed by String name. Register behavior blocks
|
46
81
|
# with the `try` and `use` methods.
|
47
82
|
def behaviors
|
@@ -158,7 +193,13 @@ module Scientist::Experiment
|
|
158
193
|
raise Scientist::BehaviorMissing.new(self, name)
|
159
194
|
end
|
160
195
|
|
161
|
-
|
196
|
+
unless should_experiment_run?
|
197
|
+
return block.call
|
198
|
+
end
|
199
|
+
|
200
|
+
if @_scientist_before_run
|
201
|
+
@_scientist_before_run.call
|
202
|
+
end
|
162
203
|
|
163
204
|
observations = []
|
164
205
|
|
@@ -177,15 +218,15 @@ module Scientist::Experiment
|
|
177
218
|
raised :publish, ex
|
178
219
|
end
|
179
220
|
|
180
|
-
if control.raised?
|
181
|
-
raise control.exception
|
182
|
-
end
|
183
|
-
|
184
221
|
if self.class.raise_on_mismatches? && result.mismatched?
|
185
|
-
raise MismatchError.new(name, result)
|
222
|
+
raise MismatchError.new(self.name, result)
|
186
223
|
end
|
187
224
|
|
188
|
-
control.
|
225
|
+
if control.raised?
|
226
|
+
raise control.exception
|
227
|
+
else
|
228
|
+
control.value
|
229
|
+
end
|
189
230
|
end
|
190
231
|
|
191
232
|
# Define a block that determines whether or not the experiment should run.
|
data/lib/scientist/version.rb
CHANGED
@@ -392,7 +392,112 @@ describe Scientist::Experiment do
|
|
392
392
|
@ex.use { "fine" }
|
393
393
|
@ex.try { "not fine" }
|
394
394
|
|
395
|
+
assert_equal "fine", @ex.run
|
396
|
+
end
|
397
|
+
|
398
|
+
it "raises a mismatch error if the control raises and candidate doesn't" do
|
399
|
+
Fake.raise_on_mismatches = true
|
400
|
+
@ex.use { raise "control" }
|
401
|
+
@ex.try { "candidate" }
|
402
|
+
# assert_raises(Scientist::Experiment::MismatchError) { @ex.run }
|
403
|
+
@ex.run
|
404
|
+
end
|
405
|
+
|
406
|
+
it "raises a mismatch error if the candidate raises and the control doesn't" do
|
407
|
+
Fake.raise_on_mismatches = true
|
408
|
+
@ex.use { "control" }
|
409
|
+
@ex.try { raise "candidate" }
|
410
|
+
assert_raises(Scientist::Experiment::MismatchError) { @ex.run }
|
411
|
+
end
|
412
|
+
|
413
|
+
describe "MismatchError" do
|
414
|
+
before do
|
415
|
+
Fake.raise_on_mismatches = true
|
416
|
+
@ex.use { :foo }
|
417
|
+
@ex.try { :bar }
|
418
|
+
begin
|
419
|
+
@ex.run
|
420
|
+
rescue Scientist::Experiment::MismatchError => e
|
421
|
+
@mismatch = e
|
422
|
+
end
|
423
|
+
assert @mismatch
|
424
|
+
end
|
425
|
+
|
426
|
+
it "has the name of the experiment" do
|
427
|
+
assert_equal @ex.name, @mismatch.name
|
428
|
+
end
|
429
|
+
|
430
|
+
it "includes the experiments' results" do
|
431
|
+
assert_equal @ex.published_result, @mismatch.result
|
432
|
+
end
|
433
|
+
|
434
|
+
it "formats nicely as a string" do
|
435
|
+
assert_equal <<-STR, @mismatch.to_s
|
436
|
+
experiment 'experiment' observations mismatched:
|
437
|
+
control:
|
438
|
+
:foo
|
439
|
+
candidate:
|
440
|
+
:bar
|
441
|
+
STR
|
442
|
+
end
|
443
|
+
|
444
|
+
it "includes the backtrace when an observation raises" do
|
445
|
+
mismatch = nil
|
446
|
+
ex = Fake.new
|
447
|
+
ex.use { "value" }
|
448
|
+
ex.try { raise "error" }
|
449
|
+
|
450
|
+
begin
|
451
|
+
ex.run
|
452
|
+
rescue Scientist::Experiment::MismatchError => e
|
453
|
+
mismatch = e
|
454
|
+
end
|
455
|
+
|
456
|
+
# Should look like this:
|
457
|
+
# experiment 'experiment' observations mismatched:
|
458
|
+
# control:
|
459
|
+
# "value"
|
460
|
+
# candidate:
|
461
|
+
# #<RuntimeError: error>
|
462
|
+
# test/scientist/experiment_test.rb:447:in `block (5 levels) in <top (required)>'
|
463
|
+
# ... (more backtrace)
|
464
|
+
lines = mismatch.to_s.split("\n")
|
465
|
+
assert_equal "control:", lines[1]
|
466
|
+
assert_equal " \"value\"", lines[2]
|
467
|
+
assert_equal "candidate:", lines[3]
|
468
|
+
assert_equal " #<RuntimeError: error>", lines[4]
|
469
|
+
assert_match %r( test/scientist/experiment_test.rb:\d+:in `block), lines[5]
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
describe "before run block" do
|
475
|
+
it "runs when an experiment is enabled" do
|
476
|
+
control_ok = candidate_ok = false
|
477
|
+
before = false
|
478
|
+
@ex.before_run { before = true }
|
479
|
+
@ex.use { control_ok = before }
|
480
|
+
@ex.try { candidate_ok = before }
|
481
|
+
|
395
482
|
@ex.run
|
483
|
+
|
484
|
+
assert before, "before_run should have run"
|
485
|
+
assert control_ok, "control should have run after before_run"
|
486
|
+
assert candidate_ok, "candidate should have run after before_run"
|
487
|
+
end
|
488
|
+
|
489
|
+
it "does not run when an experiment is disabled" do
|
490
|
+
before = false
|
491
|
+
|
492
|
+
def @ex.enabled?
|
493
|
+
false
|
494
|
+
end
|
495
|
+
@ex.before_run { before = true }
|
496
|
+
@ex.use { "value" }
|
497
|
+
@ex.try { "value" }
|
498
|
+
@ex.run
|
499
|
+
|
500
|
+
refute before, "before_run should not have run"
|
396
501
|
end
|
397
502
|
end
|
398
503
|
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: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Barnette
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|