scientist 0.0.0 → 0.0.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/CLA.md +54 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +0 -1
- data/README.md +413 -4
- data/lib/scientist/default.rb +21 -0
- data/lib/scientist/errors.rb +38 -0
- data/lib/scientist/experiment.rb +233 -0
- data/lib/scientist/observation.rb +92 -0
- data/lib/scientist/result.rb +77 -0
- data/lib/scientist/version.rb +1 -1
- data/lib/scientist.rb +39 -2
- data/scientist.gemspec +5 -3
- data/script/test +4 -1
- data/test/scientist/default_test.rb +23 -0
- data/test/scientist/experiment_test.rb +398 -0
- data/test/scientist/observation_test.rb +93 -0
- data/test/scientist/result_test.rb +111 -0
- data/test/scientist_test.rb +42 -4
- metadata +22 -21
@@ -0,0 +1,398 @@
|
|
1
|
+
describe Scientist::Experiment do
|
2
|
+
class Fake
|
3
|
+
include Scientist::Experiment
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def enabled?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :published_result
|
13
|
+
|
14
|
+
def exceptions
|
15
|
+
@exceptions ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def raised(op, exception)
|
19
|
+
exceptions << [op, exception]
|
20
|
+
end
|
21
|
+
|
22
|
+
def publish(result)
|
23
|
+
@published_result = result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
@ex = Fake.new
|
29
|
+
end
|
30
|
+
|
31
|
+
it "has a default implementation" do
|
32
|
+
ex = Scientist::Experiment.new("hello")
|
33
|
+
assert_kind_of Scientist::Default, ex
|
34
|
+
assert_equal "hello", ex.name
|
35
|
+
end
|
36
|
+
|
37
|
+
it "provides a static default name" do
|
38
|
+
assert_equal "experiment", Fake.new.name
|
39
|
+
end
|
40
|
+
|
41
|
+
it "requires includers to implement enabled?" do
|
42
|
+
obj = Object.new
|
43
|
+
obj.extend Scientist::Experiment
|
44
|
+
|
45
|
+
assert_raises NoMethodError do
|
46
|
+
obj.enabled?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "requires includers to implement publish" do
|
51
|
+
obj = Object.new
|
52
|
+
obj.extend Scientist::Experiment
|
53
|
+
|
54
|
+
assert_raises NoMethodError do
|
55
|
+
obj.publish("result")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "can't be run without a control behavior" do
|
60
|
+
e = assert_raises Scientist::BehaviorMissing do
|
61
|
+
@ex.run
|
62
|
+
end
|
63
|
+
|
64
|
+
assert_equal "control", e.name
|
65
|
+
end
|
66
|
+
|
67
|
+
it "is a straight pass-through with only a control behavior" do
|
68
|
+
@ex.use { "control" }
|
69
|
+
assert_equal "control", @ex.run
|
70
|
+
end
|
71
|
+
|
72
|
+
it "runs other behaviors but always returns the control" do
|
73
|
+
@ex.use { "control" }
|
74
|
+
@ex.try { "candidate" }
|
75
|
+
|
76
|
+
assert_equal "control", @ex.run
|
77
|
+
end
|
78
|
+
|
79
|
+
it "complains about duplicate behavior names" do
|
80
|
+
@ex.use { "control" }
|
81
|
+
|
82
|
+
e = assert_raises Scientist::BehaviorNotUnique do
|
83
|
+
@ex.use { "control-again" }
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_equal @ex, e.experiment
|
87
|
+
assert_equal "control", e.name
|
88
|
+
end
|
89
|
+
|
90
|
+
it "swallows exceptions raised by candidate behaviors" do
|
91
|
+
@ex.use { "control" }
|
92
|
+
@ex.try { raise "candidate" }
|
93
|
+
|
94
|
+
assert_equal "control", @ex.run
|
95
|
+
end
|
96
|
+
|
97
|
+
it "passes through exceptions raised by the control behavior" do
|
98
|
+
@ex.use { raise "control" }
|
99
|
+
@ex.try { "candidate" }
|
100
|
+
|
101
|
+
exception = assert_raises RuntimeError do
|
102
|
+
@ex.run
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_equal "control", exception.message
|
106
|
+
end
|
107
|
+
|
108
|
+
it "shuffles behaviors before running" do
|
109
|
+
last = nil
|
110
|
+
runs = []
|
111
|
+
|
112
|
+
@ex.use { last = "control" }
|
113
|
+
@ex.try { last = "candidate" }
|
114
|
+
|
115
|
+
10000.times do
|
116
|
+
@ex.run
|
117
|
+
runs << last
|
118
|
+
end
|
119
|
+
|
120
|
+
assert runs.uniq.size > 1
|
121
|
+
end
|
122
|
+
|
123
|
+
it "re-raises exceptions raised during publish by default" do
|
124
|
+
ex = Scientist::Experiment.new("hello")
|
125
|
+
assert_kind_of Scientist::Default, ex
|
126
|
+
def ex.publish(result)
|
127
|
+
raise "boomtown"
|
128
|
+
end
|
129
|
+
|
130
|
+
ex.use { "control" }
|
131
|
+
ex.try { "candidate" }
|
132
|
+
|
133
|
+
exception = assert_raises RuntimeError do
|
134
|
+
ex.run
|
135
|
+
end
|
136
|
+
|
137
|
+
assert_equal "boomtown", exception.message
|
138
|
+
end
|
139
|
+
|
140
|
+
it "reports publishing errors" do
|
141
|
+
def @ex.publish(result)
|
142
|
+
raise "boomtown"
|
143
|
+
end
|
144
|
+
|
145
|
+
@ex.use { "control" }
|
146
|
+
@ex.try { "candidate" }
|
147
|
+
|
148
|
+
assert_equal "control", @ex.run
|
149
|
+
|
150
|
+
op, exception = @ex.exceptions.pop
|
151
|
+
|
152
|
+
assert_equal :publish, op
|
153
|
+
assert_equal "boomtown", exception.message
|
154
|
+
end
|
155
|
+
|
156
|
+
it "publishes results" do
|
157
|
+
@ex.use { 1 }
|
158
|
+
@ex.try { 1 }
|
159
|
+
assert_equal 1, @ex.run
|
160
|
+
assert @ex.published_result
|
161
|
+
end
|
162
|
+
|
163
|
+
it "does not publish results when there is only a control value" do
|
164
|
+
@ex.use { 1 }
|
165
|
+
assert_equal 1, @ex.run
|
166
|
+
assert_nil @ex.published_result
|
167
|
+
end
|
168
|
+
|
169
|
+
it "compares results with a comparator block if provided" do
|
170
|
+
@ex.compare { |a, b| a == b.to_s }
|
171
|
+
@ex.use { "1" }
|
172
|
+
@ex.try { 1 }
|
173
|
+
|
174
|
+
assert_equal "1", @ex.run
|
175
|
+
assert @ex.published_result.matched?
|
176
|
+
end
|
177
|
+
|
178
|
+
it "knows how to compare two experiments" do
|
179
|
+
a = Scientist::Observation.new(@ex, "a") { 1 }
|
180
|
+
b = Scientist::Observation.new(@ex, "b") { 2 }
|
181
|
+
|
182
|
+
assert @ex.observations_are_equivalent?(a, a)
|
183
|
+
refute @ex.observations_are_equivalent?(a, b)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "uses a compare block to determine if observations are equivalent" do
|
187
|
+
a = Scientist::Observation.new(@ex, "a") { "1" }
|
188
|
+
b = Scientist::Observation.new(@ex, "b") { 1 }
|
189
|
+
@ex.compare { |x, y| x == y.to_s }
|
190
|
+
assert @ex.observations_are_equivalent?(a, b)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "reports errors in a compare block" do
|
194
|
+
@ex.compare { raise "boomtown" }
|
195
|
+
@ex.use { "control" }
|
196
|
+
@ex.try { "candidate" }
|
197
|
+
|
198
|
+
assert_equal "control", @ex.run
|
199
|
+
|
200
|
+
op, exception = @ex.exceptions.pop
|
201
|
+
|
202
|
+
assert_equal :compare, op
|
203
|
+
assert_equal "boomtown", exception.message
|
204
|
+
end
|
205
|
+
|
206
|
+
it "reports errors in the enabled? method" do
|
207
|
+
def @ex.enabled?
|
208
|
+
raise "kaboom"
|
209
|
+
end
|
210
|
+
|
211
|
+
@ex.use { "control" }
|
212
|
+
@ex.try { "candidate" }
|
213
|
+
assert_equal "control", @ex.run
|
214
|
+
|
215
|
+
op, exception = @ex.exceptions.pop
|
216
|
+
|
217
|
+
assert_equal :enabled, op
|
218
|
+
assert_equal "kaboom", exception.message
|
219
|
+
end
|
220
|
+
|
221
|
+
it "reports errors in a run_if block" do
|
222
|
+
@ex.run_if { raise "kaboom" }
|
223
|
+
@ex.use { "control" }
|
224
|
+
@ex.try { "candidate" }
|
225
|
+
assert_equal "control", @ex.run
|
226
|
+
|
227
|
+
op, exception = @ex.exceptions.pop
|
228
|
+
|
229
|
+
assert_equal :run_if, op
|
230
|
+
assert_equal "kaboom", exception.message
|
231
|
+
end
|
232
|
+
|
233
|
+
it "returns the given value when no clean block is configured" do
|
234
|
+
assert_equal 10, @ex.clean_value(10)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "calls the configured clean block with a value when configured" do
|
238
|
+
@ex.clean do |value|
|
239
|
+
value.upcase
|
240
|
+
end
|
241
|
+
|
242
|
+
assert_equal "TEST", @ex.clean_value("test")
|
243
|
+
end
|
244
|
+
|
245
|
+
it "reports an error and returns the original value when an error is raised in a clean block" do
|
246
|
+
@ex.clean { |value| raise "kaboom" }
|
247
|
+
|
248
|
+
@ex.use { "control" }
|
249
|
+
@ex.try { "candidate" }
|
250
|
+
assert_equal "control", @ex.run
|
251
|
+
|
252
|
+
assert_equal "control", @ex.published_result.control.cleaned_value
|
253
|
+
|
254
|
+
op, exception = @ex.exceptions.pop
|
255
|
+
|
256
|
+
assert_equal :clean, op
|
257
|
+
assert_equal "kaboom", exception.message
|
258
|
+
end
|
259
|
+
|
260
|
+
describe "#run_if" do
|
261
|
+
it "does not run the experiment if the given block returns false" do
|
262
|
+
candidate_ran = false
|
263
|
+
run_check_ran = false
|
264
|
+
|
265
|
+
@ex.use { 1 }
|
266
|
+
@ex.try { candidate_ran = true; 1 }
|
267
|
+
|
268
|
+
@ex.run_if { run_check_ran = true; false }
|
269
|
+
|
270
|
+
@ex.run
|
271
|
+
|
272
|
+
assert run_check_ran
|
273
|
+
refute candidate_ran
|
274
|
+
end
|
275
|
+
|
276
|
+
it "runs the experiment if the given block returns true" do
|
277
|
+
candidate_ran = false
|
278
|
+
run_check_ran = false
|
279
|
+
|
280
|
+
@ex.use { true }
|
281
|
+
@ex.try { candidate_ran = true }
|
282
|
+
|
283
|
+
@ex.run_if { run_check_ran = true }
|
284
|
+
|
285
|
+
@ex.run
|
286
|
+
|
287
|
+
assert run_check_ran
|
288
|
+
assert candidate_ran
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "#ignore_mismatched_observation?" do
|
293
|
+
before do
|
294
|
+
@a = Scientist::Observation.new(@ex, "a") { 1 }
|
295
|
+
@b = Scientist::Observation.new(@ex, "b") { 2 }
|
296
|
+
end
|
297
|
+
|
298
|
+
it "does not ignore an observation if no ignores are configured" do
|
299
|
+
refute @ex.ignore_mismatched_observation?(@a, @b)
|
300
|
+
end
|
301
|
+
|
302
|
+
it "calls a configured ignore block with the given observed values" do
|
303
|
+
called = false
|
304
|
+
@ex.ignore do |a, b|
|
305
|
+
called = true
|
306
|
+
assert_equal @a.value, a
|
307
|
+
assert_equal @b.value, b
|
308
|
+
true
|
309
|
+
end
|
310
|
+
|
311
|
+
assert @ex.ignore_mismatched_observation?(@a, @b)
|
312
|
+
assert called
|
313
|
+
end
|
314
|
+
|
315
|
+
it "calls multiple ignore blocks to see if any match" do
|
316
|
+
called_one = called_two = called_three = false
|
317
|
+
@ex.ignore { |a, b| called_one = true; false }
|
318
|
+
@ex.ignore { |a, b| called_two = true; false }
|
319
|
+
@ex.ignore { |a, b| called_three = true; false }
|
320
|
+
refute @ex.ignore_mismatched_observation?(@a, @b)
|
321
|
+
assert called_one
|
322
|
+
assert called_two
|
323
|
+
assert called_three
|
324
|
+
end
|
325
|
+
|
326
|
+
it "only calls ignore blocks until one matches" do
|
327
|
+
called_one = called_two = called_three = false
|
328
|
+
@ex.ignore { |a, b| called_one = true; false }
|
329
|
+
@ex.ignore { |a, b| called_two = true; true }
|
330
|
+
@ex.ignore { |a, b| called_three = true; false }
|
331
|
+
assert @ex.ignore_mismatched_observation?(@a, @b)
|
332
|
+
assert called_one
|
333
|
+
assert called_two
|
334
|
+
refute called_three
|
335
|
+
end
|
336
|
+
|
337
|
+
it "reports exceptions raised in an ignore block and returns false" do
|
338
|
+
def @ex.exceptions
|
339
|
+
@exceptions ||= []
|
340
|
+
end
|
341
|
+
|
342
|
+
def @ex.raised(op, exception)
|
343
|
+
exceptions << [op, exception]
|
344
|
+
end
|
345
|
+
|
346
|
+
@ex.ignore { raise "kaboom" }
|
347
|
+
|
348
|
+
refute @ex.ignore_mismatched_observation?(@a, @b)
|
349
|
+
|
350
|
+
op, exception = @ex.exceptions.pop
|
351
|
+
assert_equal :ignore, op
|
352
|
+
assert_equal "kaboom", exception.message
|
353
|
+
end
|
354
|
+
|
355
|
+
it "skips ignore blocks that raise and tests any remaining blocks if an exception is swallowed" do
|
356
|
+
def @ex.exceptions
|
357
|
+
@exceptions ||= []
|
358
|
+
end
|
359
|
+
|
360
|
+
# this swallows the exception rather than re-raising
|
361
|
+
def @ex.raised(op, exception)
|
362
|
+
exceptions << [op, exception]
|
363
|
+
end
|
364
|
+
|
365
|
+
@ex.ignore { raise "kaboom" }
|
366
|
+
@ex.ignore { true }
|
367
|
+
|
368
|
+
assert @ex.ignore_mismatched_observation?(@a, @b)
|
369
|
+
assert_equal 1, @ex.exceptions.size
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
describe "raising on mismatches" do
|
374
|
+
before do
|
375
|
+
@old_raise_on_mismatches = Fake.raise_on_mismatches?
|
376
|
+
end
|
377
|
+
|
378
|
+
after do
|
379
|
+
Fake.raise_on_mismatches = @old_raise_on_mismatches
|
380
|
+
end
|
381
|
+
|
382
|
+
it "raises when there is a mismatch if raise on mismatches is enabled" do
|
383
|
+
Fake.raise_on_mismatches = true
|
384
|
+
@ex.use { "fine" }
|
385
|
+
@ex.try { "not fine" }
|
386
|
+
|
387
|
+
assert_raises(Scientist::Experiment::MismatchError) { @ex.run }
|
388
|
+
end
|
389
|
+
|
390
|
+
it "doesn't raise when there is a mismatch if raise on mismatches is disabled" do
|
391
|
+
Fake.raise_on_mismatches = false
|
392
|
+
@ex.use { "fine" }
|
393
|
+
@ex.try { "not fine" }
|
394
|
+
|
395
|
+
@ex.run
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
describe Scientist::Observation do
|
2
|
+
|
3
|
+
before do
|
4
|
+
@experiment = Scientist::Experiment.new "test"
|
5
|
+
end
|
6
|
+
|
7
|
+
it "observes and records the execution of a block" do
|
8
|
+
ob = Scientist::Observation.new("test", @experiment) do
|
9
|
+
sleep 0.1
|
10
|
+
"ret"
|
11
|
+
end
|
12
|
+
|
13
|
+
assert_equal "ret", ob.value
|
14
|
+
refute ob.raised?
|
15
|
+
assert_in_delta 0.1, ob.duration, 0.01
|
16
|
+
end
|
17
|
+
|
18
|
+
it "stashes exceptions" do
|
19
|
+
ob = Scientist::Observation.new("test", @experiment) do
|
20
|
+
raise "exception"
|
21
|
+
end
|
22
|
+
|
23
|
+
assert ob.raised?
|
24
|
+
assert_equal "exception", ob.exception.message
|
25
|
+
assert_nil ob.value
|
26
|
+
end
|
27
|
+
|
28
|
+
it "compares values" do
|
29
|
+
a = Scientist::Observation.new("test", @experiment) { 1 }
|
30
|
+
b = Scientist::Observation.new("test", @experiment) { 1 }
|
31
|
+
|
32
|
+
assert a.equivalent_to?(b)
|
33
|
+
|
34
|
+
x = Scientist::Observation.new("test", @experiment) { 1 }
|
35
|
+
y = Scientist::Observation.new("test", @experiment) { 2 }
|
36
|
+
|
37
|
+
refute x.equivalent_to?(y)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "compares exception messages" do
|
41
|
+
a = Scientist::Observation.new("test", @experiment) { raise "error" }
|
42
|
+
b = Scientist::Observation.new("test", @experiment) { raise "error" }
|
43
|
+
|
44
|
+
assert a.equivalent_to?(b)
|
45
|
+
|
46
|
+
x = Scientist::Observation.new("test", @experiment) { raise "error" }
|
47
|
+
y = Scientist::Observation.new("test", @experiment) { raise "ERROR" }
|
48
|
+
|
49
|
+
refute x.equivalent_to?(y)
|
50
|
+
end
|
51
|
+
|
52
|
+
FirstErrror = Class.new(StandardError)
|
53
|
+
SecondError = Class.new(StandardError)
|
54
|
+
|
55
|
+
it "compares exception classes" do
|
56
|
+
x = Scientist::Observation.new("test", @experiment) { raise FirstError, "error" }
|
57
|
+
y = Scientist::Observation.new("test", @experiment) { raise SecondError, "error" }
|
58
|
+
z = Scientist::Observation.new("test", @experiment) { raise FirstError, "error" }
|
59
|
+
|
60
|
+
assert x.equivalent_to?(z)
|
61
|
+
refute x.equivalent_to?(y)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "compares values using a comparator block" do
|
65
|
+
a = Scientist::Observation.new("test", @experiment) { 1 }
|
66
|
+
b = Scientist::Observation.new("test", @experiment) { "1" }
|
67
|
+
|
68
|
+
refute a.equivalent_to?(b)
|
69
|
+
assert a.equivalent_to?(b) { |x, y| x.to_s == y.to_s }
|
70
|
+
|
71
|
+
yielded = []
|
72
|
+
a.equivalent_to?(b) do |x, y|
|
73
|
+
yielded << x
|
74
|
+
yielded << y
|
75
|
+
true
|
76
|
+
end
|
77
|
+
assert_equal [a.value, b.value], yielded
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#cleaned_value" do
|
81
|
+
it "returns the observation's value by default" do
|
82
|
+
a = Scientist::Observation.new("test", @experiment) { 1 }
|
83
|
+
assert_equal 1, a.cleaned_value
|
84
|
+
end
|
85
|
+
|
86
|
+
it "uses the experiment's clean block to clean a value when configured" do
|
87
|
+
@experiment.clean { |value| value.upcase }
|
88
|
+
a = Scientist::Observation.new("test", @experiment) { "test" }
|
89
|
+
assert_equal "TEST", a.cleaned_value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
describe Scientist::Result do
|
2
|
+
before do
|
3
|
+
@experiment = Scientist::Experiment.new "experiment"
|
4
|
+
end
|
5
|
+
|
6
|
+
it "is immutable" do
|
7
|
+
control = Scientist::Observation.new("control", @experiment)
|
8
|
+
candidate = Scientist::Observation.new("candidate", @experiment)
|
9
|
+
|
10
|
+
result = Scientist::Result.new @experiment,
|
11
|
+
observations: [control, candidate], control: control
|
12
|
+
|
13
|
+
assert result.frozen?
|
14
|
+
end
|
15
|
+
|
16
|
+
it "evaluates its observations" do
|
17
|
+
a = Scientist::Observation.new("a", @experiment) { 1 }
|
18
|
+
b = Scientist::Observation.new("b", @experiment) { 1 }
|
19
|
+
|
20
|
+
assert a.equivalent_to?(b)
|
21
|
+
|
22
|
+
result = Scientist::Result.new @experiment, observations: [a, b], control: a
|
23
|
+
assert result.matched?
|
24
|
+
refute result.mismatched?
|
25
|
+
assert_equal [], result.mismatched
|
26
|
+
|
27
|
+
x = Scientist::Observation.new("x", @experiment) { 1 }
|
28
|
+
y = Scientist::Observation.new("y", @experiment) { 2 }
|
29
|
+
z = Scientist::Observation.new("z", @experiment) { 3 }
|
30
|
+
|
31
|
+
result = Scientist::Result.new @experiment, observations: [x, y, z], control: x
|
32
|
+
refute result.matched?
|
33
|
+
assert result.mismatched?
|
34
|
+
assert_equal [y, z], result.mismatched
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has no mismatches if there is only a control observation" do
|
38
|
+
a = Scientist::Observation.new("a", @experiment) { 1 }
|
39
|
+
result = Scientist::Result.new @experiment, observations: [a], control: a
|
40
|
+
assert result.matched?
|
41
|
+
end
|
42
|
+
|
43
|
+
it "evaluates observations using the experiment's compare block" do
|
44
|
+
a = Scientist::Observation.new("a", @experiment) { "1" }
|
45
|
+
b = Scientist::Observation.new("b", @experiment) { 1 }
|
46
|
+
|
47
|
+
@experiment.compare { |x, y| x == y.to_s }
|
48
|
+
|
49
|
+
result = Scientist::Result.new @experiment, observations: [a, b], control: a
|
50
|
+
|
51
|
+
assert result.matched?, result.mismatched
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not ignore any mismatches when nothing's ignored" do
|
55
|
+
x = Scientist::Observation.new("x", @experiment) { 1 }
|
56
|
+
y = Scientist::Observation.new("y", @experiment) { 2 }
|
57
|
+
|
58
|
+
result = Scientist::Result.new @experiment, observations: [x, y], control: x
|
59
|
+
|
60
|
+
assert result.mismatched?
|
61
|
+
refute result.ignored?
|
62
|
+
end
|
63
|
+
|
64
|
+
it "uses the experiment's ignore block to ignore mismatched observations" do
|
65
|
+
x = Scientist::Observation.new("x", @experiment) { 1 }
|
66
|
+
y = Scientist::Observation.new("y", @experiment) { 2 }
|
67
|
+
called = false
|
68
|
+
@experiment.ignore { called = true }
|
69
|
+
|
70
|
+
result = Scientist::Result.new @experiment, observations: [x, y], control: x
|
71
|
+
|
72
|
+
refute result.mismatched?
|
73
|
+
assert result.ignored?
|
74
|
+
assert_equal [], result.mismatched
|
75
|
+
assert_equal [y], result.ignored
|
76
|
+
assert called
|
77
|
+
end
|
78
|
+
|
79
|
+
it "partitions observations into mismatched and ignored when applicable" do
|
80
|
+
x = Scientist::Observation.new("x", @experiment) { :x }
|
81
|
+
y = Scientist::Observation.new("y", @experiment) { :y }
|
82
|
+
z = Scientist::Observation.new("z", @experiment) { :z }
|
83
|
+
|
84
|
+
@experiment.ignore { |control, candidate| candidate == :y }
|
85
|
+
|
86
|
+
result = Scientist::Result.new @experiment, observations: [x, y, z], control: x
|
87
|
+
|
88
|
+
assert result.mismatched?
|
89
|
+
assert result.ignored?
|
90
|
+
assert_equal [y], result.ignored
|
91
|
+
assert_equal [z], result.mismatched
|
92
|
+
end
|
93
|
+
|
94
|
+
it "knows the experiment's name" do
|
95
|
+
a = Scientist::Observation.new("a", @experiment) { 1 }
|
96
|
+
b = Scientist::Observation.new("b", @experiment) { 1 }
|
97
|
+
result = Scientist::Result.new @experiment, observations: [a, b], control: a
|
98
|
+
|
99
|
+
assert_equal @experiment.name, result.experiment_name
|
100
|
+
end
|
101
|
+
|
102
|
+
it "has the context from an experiment" do
|
103
|
+
@experiment.context :foo => :bar
|
104
|
+
a = Scientist::Observation.new("a", @experiment) { 1 }
|
105
|
+
b = Scientist::Observation.new("b", @experiment) { 1 }
|
106
|
+
result = Scientist::Result.new @experiment, observations: [a, b], control: a
|
107
|
+
|
108
|
+
assert_equal({:foo => :bar}, result.context)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/test/scientist_test.rb
CHANGED
@@ -1,7 +1,45 @@
|
|
1
|
-
require "minitest/autorun"
|
2
|
-
|
3
1
|
describe Scientist do
|
4
|
-
it "
|
5
|
-
assert Scientist
|
2
|
+
it "has a version or whatever" do
|
3
|
+
assert Scientist::VERSION
|
4
|
+
end
|
5
|
+
|
6
|
+
it "provides a helper to instantiate and run experiments" do
|
7
|
+
obj = Object.new
|
8
|
+
obj.extend(Scientist)
|
9
|
+
|
10
|
+
r = obj.science "test" do |e|
|
11
|
+
e.use { :control }
|
12
|
+
e.try { :candidate }
|
13
|
+
end
|
14
|
+
|
15
|
+
assert_equal :control, r
|
16
|
+
end
|
17
|
+
|
18
|
+
it "provides an empty default_scientist_context" do
|
19
|
+
obj = Object.new
|
20
|
+
obj.extend(Scientist)
|
21
|
+
|
22
|
+
assert_equal Hash.new, obj.default_scientist_context
|
23
|
+
end
|
24
|
+
|
25
|
+
it "respects default_scientist_context" do
|
26
|
+
obj = Object.new
|
27
|
+
obj.extend(Scientist)
|
28
|
+
|
29
|
+
def obj.default_scientist_context
|
30
|
+
{ :default => true }
|
31
|
+
end
|
32
|
+
|
33
|
+
experiment = nil
|
34
|
+
|
35
|
+
obj.science "test" do |e|
|
36
|
+
experiment = e
|
37
|
+
e.context :inline => true
|
38
|
+
e.use { }
|
39
|
+
end
|
40
|
+
|
41
|
+
refute_nil experiment
|
42
|
+
assert_equal true, experiment.context[:default]
|
43
|
+
assert_equal true, experiment.context[:inline]
|
6
44
|
end
|
7
45
|
end
|