answer-factory 0.1.2 → 0.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,296 @@
1
+ require File.join(File.dirname(__FILE__), "./../spec_helper")
2
+ include Machines
3
+
4
+ FakeWeb.allow_net_connect = false
5
+
6
+
7
+ describe "Machines::TestCase" do
8
+ describe "inputs Array" do
9
+ before(:each) do
10
+ @tc = TestCase.new
11
+ end
12
+
13
+ describe "bindings" do
14
+ it "should have an attribute called #inputs" do
15
+ @tc.should respond_to(:inputs)
16
+ end
17
+
18
+ it "should accept a Hash as an initialization argument for #inputs" do
19
+ lambda{TestCase.new(inputs:{})}.should_not raise_error
20
+ TestCase.new(inputs:{"x1" => [:int, 12]}).inputs.should ==
21
+ {"x1" => [:int, 12]}
22
+ end
23
+
24
+ it "should default :inputs to an empty Hash" do
25
+ TestCase.new.inputs.should == {}
26
+ end
27
+
28
+ it "should have an attribute called #outputs" do
29
+ @tc.should respond_to(:outputs)
30
+ end
31
+
32
+ it "should accept a Hash as an initialization argument for #outputs" do
33
+ lambda{TestCase.new(outputs:{})}.should_not raise_error
34
+ TestCase.new(outputs:{"y1" => [:bool, false]}).outputs.should ==
35
+ {"y1" => [:bool, false]}
36
+ end
37
+
38
+ it "should default :inputs to an empty Hash" do
39
+ TestCase.new.outputs.should == {}
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ describe "EvaluateWithTestCases" do
47
+
48
+ before(:each) do
49
+ @canned = '{"total_rows":1,"offset":0,"rows":[{"id":"0f60c293ad736abfdb083d33f71ef9ab","key":"ws1","value":{"_id":"0f60c293ad736abfdb083d33f71ef9ab","_rev":"1-473467b6dc1a4cba3498dd6eeb8e3206","blueprint":"do bar","tags":[],"scores":{},"progress":12,"timestamp":"2010/04/14 17:09:14 +0000"}}]}'
50
+
51
+ FakeWeb.register_uri(:any, "http://127.0.0.1:5984/foo_training/_design/tester/_view/test_cases",
52
+ :body => @canned, :status => [200, "OK"])
53
+ @factory = Factory.new(name:"foo")
54
+ @tester = EvaluateWithTestCases.new(name: :tester)
55
+ end
56
+
57
+ describe "initialization" do
58
+
59
+ describe "name" do
60
+ it "should have a name" do
61
+ @tester.should respond_to(:name)
62
+ end
63
+ end
64
+
65
+ describe "data" do
66
+ it "should call #load_training_data! if" do
67
+ EvaluateWithTestCases.new(name: :tester)
68
+ end
69
+ end
70
+
71
+ describe "sensors" do
72
+ it "should have a #sensors attribute" do
73
+ EvaluateWithTestCases.new(name: :tester).should respond_to(:sensors)
74
+ end
75
+
76
+ it "should default to an empty Hash" do
77
+ EvaluateWithTestCases.new(name: :tester).sensors.should == {}
78
+ end
79
+
80
+ describe ":build_sensor" do
81
+ before(:each) do
82
+ @m1 = EvaluateWithTestCases.new(name: :tester)
83
+ end
84
+
85
+ it "should respond to :build_sensor" do
86
+ @m1.should respond_to(:build_sensor)
87
+ end
88
+
89
+ it "should use the name argument as the Hash key in #sensors" do
90
+ @m1.build_sensor("harbor_master_score")
91
+ @m1.sensors.keys.should include("harbor_master_score")
92
+ end
93
+
94
+ it "should take a block and store it as a Proc as the value in #sensors" do
95
+ block = lambda {|a| 9}
96
+ @m1.build_sensor("harbor_master_score", &block)
97
+ @m1.sensors["harbor_master_score"].should == block
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+
105
+ describe "#score method" do
106
+ it "should respond to :score" do
107
+ @tester.should respond_to(:score)
108
+ end
109
+
110
+ it "should only accept a Batch" do
111
+ lambda{@tester.score(99)}.should raise_error(ArgumentError)
112
+ lambda{@tester.score(Batch.new)}.should_not raise_error(ArgumentError)
113
+ end
114
+
115
+ describe "running Interpreter" do
116
+ before(:each) do
117
+ @tester.test_cases = [TestCase.new(inputs: {"x1:int" => 1}, outputs: {"y1" => 2})]
118
+ @batch = Batch.[](Answer.new("do a"))
119
+ end
120
+
121
+ it "should create one Interpreter for each TestCase of #data_in_hand" do
122
+ i = Interpreter.new("")
123
+ Interpreter.should_receive(:new).at_least(1).times.and_return(i)
124
+ @tester.score(@batch)
125
+ end
126
+
127
+ it "should register all the sensors" do
128
+ @tester.sensors = {"y1" => Proc.new{|interpreter| interpreter.peek_value(:int)} }
129
+ i = Interpreter.new
130
+ Interpreter.should_receive(:new).and_return(i)
131
+ i.should_receive(:register_sensor).exactly(1).times
132
+ @tester.score(@batch)
133
+ end
134
+
135
+ it "should set up Interpreters correctly" do
136
+ i = Interpreter.new
137
+ Interpreter.should_receive(:new).with(
138
+ "do a", {:name=>:tester, :target_size_in_points=>99}).and_return(i)
139
+ @tester.score(@batch,target_size_in_points:99)
140
+ end
141
+
142
+
143
+
144
+ end
145
+
146
+ describe "install_training_data_from_csv!" do
147
+ before(:each) do
148
+ FakeWeb.register_uri(:any,
149
+ "http://127.0.0.1:5984/dammit_training/_bulk_docs",
150
+ :body => @canned, :status => [200, "OK"])
151
+ FakeWeb.register_uri(:any,
152
+ "http://127.0.0.1:5984/dammit_training/_design/tester/_view/test_cases",
153
+ :body => @canned, :status => [200, "OK"])
154
+
155
+ @f1 = Factory.new(name: "dammit")
156
+ @my_csv = "./spec/fixtures/my_data_source.csv"
157
+ @m1 = EvaluateWithTestCases.new(name: :tester, training_data_csv: @my_csv)
158
+ @training_db = "http://127.0.0.1:5984/dammit_training"
159
+
160
+ end
161
+
162
+ it "should get the filename as an initialization parameter" do
163
+ EvaluateWithTestCases.new(name: :tester, training_data_csv: "foo.csv").
164
+ csv_filename.should == "foo.csv"
165
+ EvaluateWithTestCases.new(name: :tester).csv_filename.should == nil
166
+ end
167
+
168
+ it "should open a csv file" do
169
+ f = File.open(@my_csv)
170
+ File.should_receive(:open).and_return(f)
171
+ c = CSV.new(f, headers: true)
172
+ CSV.should_receive(:new).with(f, headers: true).and_return(c)
173
+ @m1.install_training_data_from_csv(@my_csv)
174
+ end
175
+
176
+ it "should be the training_data default db" do
177
+ db = CouchRest.database!(@training_db)
178
+ CouchRest.should_receive(:database!).with(@training_db).and_return(db)
179
+ @m1.install_training_data_from_csv(@my_csv)
180
+ end
181
+
182
+ it "makes one doc for every row" do
183
+ db = CouchRest.database!(@training_db)
184
+ CouchRest.should_receive(:database!).with(@training_db).and_return(db)
185
+ db.should_receive(:bulk_save_doc).exactly(3).times
186
+ @m1.install_training_data_from_csv(@my_csv)
187
+ end
188
+
189
+
190
+ it "should raise an error if every header doesn't contain a colon and a type string" do
191
+ lambda{@m1.header_prep("x1")}.should raise_error ArgumentError
192
+ lambda{@m1.header_prep("x1:")}.should raise_error ArgumentError
193
+ lambda{@m1.header_prep("x1:int")}.should_not raise_error ArgumentError
194
+ lambda{@m1.header_prep(":int")}.should raise_error ArgumentError
195
+ end
196
+
197
+ end
198
+
199
+ describe "load_training_data!" do
200
+
201
+ before(:each) do
202
+ @factoreee = Factory.new(name:"dammit")
203
+ @m1 = EvaluateWithTestCases.new(name: :tester)
204
+ @design_doc = "tester/test_cases" # we'll assume this has been set up!
205
+ @expected = {"total_rows"=>1, "offset"=>0, "rows"=>[{"id"=>"05d195b7bb436743ee36b4223008c4ce", "key"=>"05d195b7bb436743ee36b4223008c4ce", "value"=>{"_id"=>"05d195b7bb436743ee36b4223008c4ce", "_rev"=>"1-c9fae927001a1d4789d6396bcf0cd5a7", "inputs"=>{"x1:int"=>7}, "outputs"=>{"y1:grault"=>12}}}]}
206
+
207
+ end
208
+
209
+ it "should get the couch_db uri from configatron" do
210
+ @m1.training_data_view.should ==
211
+ "http://127.0.0.1:5984/dammit_training/_design/#{@m1.name}/_view/test_cases"
212
+ end
213
+
214
+ it "should respond to :load_training_data!" do
215
+ @m1.should respond_to(:load_training_data!)
216
+ end
217
+
218
+ it "should access the couch_db uri" do
219
+ FakeWeb.register_uri(:any,
220
+ "http://127.0.0.1:5984/dammit_training/_design/tester/_view/test_cases",
221
+ :body => @canned, :status => [200, "OK"])
222
+ db = CouchRest.database!(@m1.training_datasource)
223
+ CouchRest.should_receive(:database!).and_return(db)
224
+ db.should_receive(:view).with(@design_doc).and_return(@expected)
225
+ @m1.load_training_data!
226
+ end
227
+
228
+ it "should throw a useful error if the view isn't available"
229
+
230
+ it "should ask for the view document" do
231
+ db = CouchRest.database!(@m1.training_datasource)
232
+ CouchRest.should_receive(:database!).and_return(db)
233
+ db.should_receive(:view).with(@design_doc).and_return(@expected)
234
+ @m1.load_training_data!
235
+ end
236
+
237
+ it "should store Array of TestCases in @test_cases" do
238
+ db = CouchRest.database!(@m1.training_datasource)
239
+ CouchRest.should_receive(:database!).and_return(db)
240
+ db.should_receive(:view).with(@design_doc).and_return(@expected)
241
+
242
+ @m1.load_training_data!
243
+ @m1.test_cases.should be_a_kind_of(Array)
244
+ @m1.test_cases.length.should == 1
245
+ end
246
+ end
247
+
248
+ describe "scoring" do
249
+ before(:each) do
250
+ @m1 = EvaluateWithTestCases.new(name: :tester)
251
+ @m1.test_cases = (0...10).collect do |i|
252
+ TestCase.new(inputs: {"x1:int" => i}, outputs: {"y1" => 2*i, "y2" => 3*i})
253
+ end
254
+ @m1.build_sensor("y1") {|a| 777}
255
+ @m1.build_sensor("y2") {|a| 666}
256
+
257
+ @batch = Batch.[](Answer.new("do a"))
258
+ @i1 = Interpreter.new
259
+ end
260
+
261
+ it "should make an Interpreter for each row of training data" do
262
+ @m1.stub!(:load_training_data!)
263
+ Interpreter.should_receive(:new).exactly(10).times.and_return(@i1)
264
+ @m1.score(@batch)
265
+ end
266
+
267
+ it "should run all the Interpreters" do
268
+ @m1.stub!(:load_training_data!)
269
+ Interpreter.should_receive(:new).exactly(10).times.and_return(@i1)
270
+ @i1.should_receive(:run).at_least(1).times.and_return({})
271
+ @m1.score(@batch)
272
+ end
273
+
274
+ it "should register its sensors before each Interpreter run" do
275
+ Interpreter.stub!(:new).and_return(@i1)
276
+ @i1.should_receive(:register_sensor).at_least(1).times
277
+ @m1.score(@batch)
278
+ end
279
+
280
+ it "should have a score for each sensor" do
281
+ @m1.score(@batch)
282
+ @batch.first.scores["y1"].should_not == nil
283
+ @batch.first.scores["y2"].should_not == nil
284
+ end
285
+
286
+ it "should return sum of absolute errors" do
287
+ @m1.stub!(:load_training_data!)
288
+ @m1.score(@batch)
289
+ @batch[0].scores["y1"].should == 777+775+773+771+769+767+765+763+761+759
290
+ @batch[0].scores["y2"].should == 666+663+660+657+654+651+648+645+642+639
291
+ end
292
+ end
293
+
294
+
295
+ end
296
+ end
@@ -0,0 +1,134 @@
1
+ require File.join(File.dirname(__FILE__), "./../spec_helper")
2
+
3
+ describe "Machines::SelectBySummedRank" do
4
+ before(:each) do
5
+ @best = Machines::SelectBySummedRank.new
6
+ @highlander = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
7
+ @highlander[0].scores = {e1:15, e2:12} # 3,3 = 6
8
+ @highlander[1].scores = {e1:5, e2:5} # 2,2 = 4
9
+ @highlander[2].scores = {e1:1, e2:1} # 1,1 = 2
10
+
11
+ @lowlander = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
12
+ @lowlander[0].scores = {e1:1, e2:3} # 1,3 = 4
13
+ @lowlander[1].scores = {e1:2, e2:2} # 2,2 = 4
14
+ @lowlander[2].scores = {e1:3, e2:1} # 3,1 = 4
15
+
16
+ @allover = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
17
+ @allover[0].scores = {e1:1, e2:3} # 1,2,-
18
+ @allover[1].scores = {e1:2, e2:2, e3:2} # 2,1,1
19
+ @allover[2].scores = {e1:3} # 3,-,-
20
+
21
+ @separate = Batch.[](Answer.new("foo"), Answer.new("bar"))
22
+ @separate[0].scores = {e1:1}
23
+ @separate[1].scores = {e2:2, e3:2}
24
+ end
25
+
26
+
27
+ describe "#screen method" do
28
+ it "should respond to :screen" do
29
+ @best.should respond_to(:screen)
30
+ end
31
+
32
+ it "should only accept a Batch as its argument" do
33
+ lambda{@best.screen(129)}.should raise_error
34
+ lambda{@best.screen([Answer.new("foo")])}.should raise_error
35
+ lambda{@best.screen(@highlander, comparison_criteria:[:e1])}.should_not raise_error
36
+ end
37
+
38
+ it "should produce a Batch" do
39
+ @best.screen(@highlander, comparison_criteria:[:e2]).should be_a_kind_of(Batch)
40
+ end
41
+
42
+ it "should use the :comparison_criteria option as a template Array of score keys" do
43
+ ignore_most = @best.screen(@lowlander, comparison_criteria:[:e1])
44
+ ignore_most.length.should == 1
45
+ ignore_most.should include(@lowlander[0])
46
+
47
+ ignore_most = @best.screen(@lowlander, comparison_criteria:[:e2])
48
+ ignore_most.length.should == 1
49
+ ignore_most.should include(@lowlander[2])
50
+
51
+ ignore_most = @best.screen(@allover, comparison_criteria:[:e1])
52
+ ignore_most.length.should == 1
53
+ ignore_most.should include(@allover[0])
54
+ end
55
+
56
+ it "should use the intersection of all the score keys in the :batch as a default for criteria" do
57
+ @best.should_receive(:shared_goals).and_return([:e1])
58
+ @best.screen(@highlander)
59
+ end
60
+
61
+ it "should accept (and store) an initialization :comparison_criteria option" do
62
+ just_one = Machines::SelectBySummedRank.new(comparison_criteria:[:e2]).
63
+ screen(@lowlander)
64
+ just_one.length.should == 1
65
+ just_one.should include(@lowlander[2])
66
+ end
67
+
68
+ it "should include all Answers lacking a given score (since they can't be ranked)" do
69
+ dunno = @best.screen(@allover, comparison_criteria:[:e2])
70
+ dunno.length.should == 2
71
+ dunno.should include(@allover[1])
72
+ dunno.should include(@allover[2])
73
+ dunno.should_not include(@allover[0])
74
+ end
75
+
76
+ it "should return the entire batch if no scores are shared" do
77
+ @best.screen(@separate).length.should == 2
78
+ end
79
+
80
+ it "should override its initialization if given a #build option" do
81
+ overridden = Machines::SelectBySummedRank.new(comparison_criteria:[:e2]).
82
+ screen(@lowlander,comparison_criteria:[:e1])
83
+ overridden.length.should == 1
84
+ overridden[0].scores.should == {e1:1, e2:3}
85
+ end
86
+
87
+ it "should return the lowest-ranking subset of the argument Batch" do
88
+ @best.screen(@lowlander).length.should == 3
89
+ end
90
+
91
+ it "should return objects from the argument, not clones" do
92
+ (@highlander.collect {|a| a.object_id}).should include(@best.screen(@highlander)[0].object_id)
93
+ end
94
+
95
+ it "should return a new Batch object" do
96
+ @best.screen(@highlander).object_id.should_not == @highlander.object_id
97
+ end
98
+
99
+ it "should not change the :progress of the Answers" do
100
+ @best.screen(@highlander).each {|a| a.progress.should == 0}
101
+ end
102
+
103
+ end
104
+
105
+
106
+ it "should respond to :generate as an alias to :screen" do
107
+ Machines::SelectBySummedRank.new.should respond_to(:generate)
108
+ end
109
+
110
+
111
+ describe "all_goals" do
112
+ it "should return an Array of score keys" do
113
+ @best.all_goals(@highlander).should == [:e1,:e2]
114
+ end
115
+
116
+ it "should include every score key in the batch passed in" do
117
+ @best.all_goals(@allover).should == [:e1,:e2, :e3]
118
+ end
119
+
120
+ it "should have one copy of each score name" do
121
+ @best.all_goals(@highlander).find_all {|e| e == :e1}.length.should == 1
122
+ end
123
+ end
124
+
125
+
126
+ describe "shared_goals" do
127
+ it "should return an Array of only the shared score keys from the Batch passed in" do
128
+ @best.shared_goals(@highlander).should == [:e1,:e2]
129
+ @best.shared_goals(@allover).should == [:e1]
130
+ @best.shared_goals(@separate).should == []
131
+ end
132
+ end
133
+ end
134
+
@@ -4,6 +4,7 @@ describe "Machines::SelectNondominated" do
4
4
  describe "#screen method" do
5
5
  before(:each) do
6
6
  @best = Machines::SelectNondominated.new
7
+
7
8
  @highlander = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
8
9
  @highlander[0].scores = {e1:5, e2:15}
9
10
  @highlander[1].scores = {e1:15,e2:5}
@@ -18,6 +19,10 @@ describe "Machines::SelectNondominated" do
18
19
  @allover[0].scores = {e1:1, e2:3}
19
20
  @allover[1].scores = {e1:2, e2:2, e3:2}
20
21
  @allover[2].scores = {e1:3}
22
+
23
+ @separate = Batch.[](Answer.new("foo"), Answer.new("bar"))
24
+ @separate[0].scores = {e1:1}
25
+ @separate[1].scores = {e2:2, e3:2}
21
26
  end
22
27
 
23
28
  it "should respond to :screen" do
@@ -37,6 +42,7 @@ describe "Machines::SelectNondominated" do
37
42
  it "should accept a template Array of score keys" do
38
43
  @best.screen(@lowlander).length.should == 3
39
44
  @best.screen(@lowlander,comparison_criteria:[:e2]).length.should == 1
45
+ @best.screen(@lowlander,comparison_criteria:[:e2]).should include(@lowlander[2])
40
46
  end
41
47
 
42
48
  it "should use an initialization template as well" do
@@ -56,6 +62,8 @@ describe "Machines::SelectNondominated" do
56
62
  @best.screen(@allover, comparison_criteria:[:e1]).length.should == 1
57
63
  @best.screen(@allover, comparison_criteria:[:e2]).length.should == 2
58
64
  @best.screen(@allover, comparison_criteria:[:e3]).length.should == 3
65
+
66
+ @best.screen(@separate).should == @separate
59
67
  end
60
68
 
61
69
  it "should return the nondominated subset of the argument" do