answer-factory 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.
- data/LICENSE.txt +21 -0
- data/Rakefile +29 -0
- data/Thorfile +79 -0
- data/VERSION +1 -0
- data/_spikes/old_vs_new_dominated_by?.rb +45 -0
- data/config/database.yml +9 -0
- data/lib/answer-factory.rb +14 -0
- data/lib/answers/answer.rb +126 -0
- data/lib/answers/batch.rb +49 -0
- data/lib/factories/factory.rb +53 -0
- data/lib/factories/workstation.rb +33 -0
- data/lib/operators/basic_operators.rb +240 -0
- data/lib/operators/evaluators.rb +113 -0
- data/lib/operators/samplers_and_selectors.rb +131 -0
- data/pkg/nudgegp-0.0.1.gem +0 -0
- data/readme.md +29 -0
- data/spec/answer_spec.rb +412 -0
- data/spec/batch_spec.rb +98 -0
- data/spec/config_spec.rb +94 -0
- data/spec/factories/factory_spec.rb +86 -0
- data/spec/factories/workstation_spec.rb +139 -0
- data/spec/operators/any_one_sampler_spec.rb +39 -0
- data/spec/operators/dominated_quantile_spec.rb +111 -0
- data/spec/operators/duplicate_genomes_spec.rb +35 -0
- data/spec/operators/evaluators/program_point_evaluator_spec.rb +43 -0
- data/spec/operators/evaluators/test_case_evaluator_spec.rb +129 -0
- data/spec/operators/infrastructure_spec.rb +45 -0
- data/spec/operators/most_dominated_subset_spec.rb +47 -0
- data/spec/operators/nondominated_subset_spec.rb +103 -0
- data/spec/operators/pointCrossover_spec.rb +60 -0
- data/spec/operators/pointDeletion_spec.rb +62 -0
- data/spec/operators/pointMutation_spec.rb +77 -0
- data/spec/operators/random_guess_spec.rb +77 -0
- data/spec/operators/resample_and_clone_spec.rb +60 -0
- data/spec/operators/resample_values_spec.rb +135 -0
- data/spec/operators/uniformBackboneCrossover_spec.rb +67 -0
- data/spec/spec_helper.rb +14 -0
- metadata +201 -0
Binary file
|
data/readme.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Pragmatic Genetic Programming with Nudge
|
2
|
+
|
3
|
+
## Caveat
|
4
|
+
|
5
|
+
This particular project is undergoing a lot of serious surgery right now.
|
6
|
+
|
7
|
+
## What it does
|
8
|
+
|
9
|
+
Well, it's complicated, and in a state of dynamic change right this second.
|
10
|
+
|
11
|
+
Let's just say it helps _regular people_ build, run and manage technical experiments that use genetic programming. There's more... but that's the bottom line.
|
12
|
+
|
13
|
+
## Getting started
|
14
|
+
|
15
|
+
You'll need a working (and running) installation of CouchDB.
|
16
|
+
|
17
|
+
Then:
|
18
|
+
|
19
|
+
gem install nudge
|
20
|
+
gem install thor
|
21
|
+
gem install couch-rest
|
22
|
+
gem install sinatra
|
23
|
+
|
24
|
+
and for running specs (which you _should be doing_):
|
25
|
+
|
26
|
+
gem install rspec
|
27
|
+
gem install sinatra
|
28
|
+
gem install fakeweb
|
29
|
+
|
data/spec/answer_spec.rb
ADDED
@@ -0,0 +1,412 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
3
|
+
|
4
|
+
|
5
|
+
describe "Answer" do
|
6
|
+
|
7
|
+
describe "initialization" do
|
8
|
+
it "should accept a string OR a NudgeProgram as an initialization parameter" do
|
9
|
+
lambda{Answer.new("any string")}.should_not raise_error
|
10
|
+
lambda{Answer.new(NudgeProgram.new("block {}"))}.should_not raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should validate the initial parameter and make sure it's only one of the accepted classes" do
|
14
|
+
lambda{Answer.new(8812.9)}.should raise_error(ArgumentError)
|
15
|
+
lambda{Answer.new(Time.now)}.should raise_error(ArgumentError)
|
16
|
+
lambda{Answer.new}.should raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set the #draft_blueprint attribute from the string, if present" do
|
20
|
+
Answer.new("do x").draft_blueprint.should == "do x"
|
21
|
+
Answer.new("some «random»\njunk").draft_blueprint.should == "some «random»\njunk"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should set the #draft_blueprint attribute to the blueprint of the NudgeProgram, if present" do
|
25
|
+
Answer.new(NudgeProgram.new("value «foo»\n«foo» bar")).draft_blueprint.should ==
|
26
|
+
"value «foo» \n«foo» bar"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should set the #program attribute to a new NudgeProgram based on init'n string, if present" do
|
30
|
+
np = Answer.new("do x").program
|
31
|
+
np.should be_a_kind_of(NudgeProgram)
|
32
|
+
np.blueprint.should == "do x"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should set the #program attribute even if the init'n string is unparseable" do
|
36
|
+
np = Answer.new("not a program")
|
37
|
+
np.program.should be_a_kind_of(NudgeProgram)
|
38
|
+
np.draft_blueprint.should == "not a program"
|
39
|
+
np.program.linked_code.should be_a_kind_of(NilPoint)
|
40
|
+
np.program.blueprint.should == ""
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should set the #program attribute to the init'n NudgeProgram, if present" do
|
44
|
+
prog = NudgeProgram.new("block {ref a ref b}")
|
45
|
+
na = Answer.new(prog)
|
46
|
+
na.program.should == prog
|
47
|
+
na.draft_blueprint.should == prog.blueprint
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should respond to #blueprint by returning self.program.blueprint" do
|
51
|
+
prog = NudgeProgram.new("block {ref a do x}")
|
52
|
+
na = Answer.new(prog)
|
53
|
+
prog.should_receive(:blueprint).and_return("hi there")
|
54
|
+
na.blueprint.should == "hi there"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have a #scores hash, which is empty" do
|
58
|
+
Answer.new("foo").scores.should == {}
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should have a #timestamp, which is when (wall clock time) it was made" do
|
62
|
+
tn = Time.now
|
63
|
+
Time.should_receive(:now).and_return(tn)
|
64
|
+
Answer.new("bar").timestamp.should == tn
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should have a [named] #progress attribute, defaulting to zero" do
|
68
|
+
Answer.new("baz").progress.should == 0
|
69
|
+
Answer.new("baz", progress:12).progress.should == 12
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should have an #ancestors Array, defaulting to []" do
|
73
|
+
Answer.new("quux").ancestors.should == []
|
74
|
+
Answer.new("quux", ancestors:[1,2,3]).ancestors.should == [1,2,3]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
describe "serialization" do
|
80
|
+
describe "writing" do
|
81
|
+
before(:each) do
|
82
|
+
@a1 = Answer.new("block {do a}")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should contain the blueprint" do
|
86
|
+
@a1.data['blueprint'].should == @a1.blueprint
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should contain the tags" do
|
90
|
+
@a1.data['tags'].should == @a1.tags
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
it "should contain the scores" do
|
95
|
+
@a1.data['scores'].should == @a1.scores
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should contain the timestamp" do
|
99
|
+
@a1.data['timestamp'].should == @a1.timestamp
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "reading" do
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
describe "#parses?" do
|
110
|
+
it "should respond to #parses? with an answer from its NudgeProgram" do
|
111
|
+
Answer.new("not good word").parses?.should == false
|
112
|
+
Answer.new("block {do nice_nudge}").parses?.should == true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
describe "scores" do
|
118
|
+
before(:each) do
|
119
|
+
@answer1 = Answer.new("do a")
|
120
|
+
@answer1.scores[:error] = 81.9
|
121
|
+
@answer1.scores[:a_1] = 2200
|
122
|
+
|
123
|
+
@answer2 = Answer.new("some other crap")
|
124
|
+
@answer2.scores[:error] = 7
|
125
|
+
@answer2.scores[:a_1] = 1200
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should require symbols as keys" do
|
129
|
+
lambda{@answer2.scores["foo"]}.should raise_error(ArgumentError)
|
130
|
+
lambda{@answer2.scores[:bar]}.should_not raise_error(ArgumentError)
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#known_criteria" do
|
134
|
+
it "#known_criteria should return a sorted list of the keys of the scores hash" do
|
135
|
+
@answer1.known_criteria.should == [:a_1, :error]
|
136
|
+
Answer.new("x").known_criteria.should == []
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "#score_vector" do
|
141
|
+
it "#score_vector should return an Array of #scores" do
|
142
|
+
a1s = @answer1.score_vector
|
143
|
+
a1s.should be_a_kind_of(Array)
|
144
|
+
a1s.sort.should == [2200, 81.9].sort # ignoring returned orderuntil next spec
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should use the argument to order the scores in the result" do
|
148
|
+
@answer1.score_vector([:a_1]).should == [2200]
|
149
|
+
@answer1.score_vector([:error, :a_1]).should == [81.9, 2200]
|
150
|
+
end
|
151
|
+
|
152
|
+
it "the scores should be by default in #known_criteria (alphabetical) order" do
|
153
|
+
@answer1.scores[:bobby] = -121.1
|
154
|
+
a1s = @answer1.score_vector
|
155
|
+
a1s[0].should == @answer1.scores[:a_1]
|
156
|
+
a1s[1].should == @answer1.scores[:bobby]
|
157
|
+
a1s[2].should == @answer1.scores[:error]
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should return nil for any nonexistent score item" do
|
161
|
+
@answer1.score_vector([:nonexisto, :a_1]).should == [nil, 2200]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "#dominated_by?" do
|
166
|
+
|
167
|
+
it "should return false when the compared Answer doesn't have the same scoring_criteria" do
|
168
|
+
@answer1.scores = {a:10}
|
169
|
+
@answer2.scores = { b:99}
|
170
|
+
@answer1.dominated_by?(@answer2).should == false
|
171
|
+
|
172
|
+
@answer1.scores = {a:10, b:10}
|
173
|
+
@answer2.scores = { b:99, c:10}
|
174
|
+
@answer1.dominated_by?(@answer2).should == false
|
175
|
+
|
176
|
+
@answer1.scores = {a:10, b:10, c:10, d:10}
|
177
|
+
@answer2.scores = { b:99, c:10, d:1}
|
178
|
+
@answer1.dominated_by?(@answer2).should == false
|
179
|
+
|
180
|
+
@answer1.scores = {x1:1, x2:2, x3:3}
|
181
|
+
@answer2.scores = { x2:0, x4:12}
|
182
|
+
@answer1.dominated_by?(@answer2).should == false
|
183
|
+
@answer2.dominated_by?(@answer1).should == false
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should use the comparison template to check whether the scoring criteria are the same" do
|
187
|
+
@answer1.scores = {a:10, b:10, c:10, d:10}
|
188
|
+
@answer2.scores = { b:99, c:10, d:1}
|
189
|
+
@answer1.dominated_by?(@answer2).should == false
|
190
|
+
|
191
|
+
@answer1.scores = {a:10, b:10, c:10, d:10}
|
192
|
+
@answer2.scores = { b:99, c:10, d:1}
|
193
|
+
@answer1.dominated_by?(@answer2, [:d]).should == true
|
194
|
+
@answer2.dominated_by?(@answer1, [:b,:c]).should == true
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should return true if another Answer has one score better and all the rest the same" do
|
198
|
+
@answer1.scores = {:a => 10, :b =>10}
|
199
|
+
@answer2.scores = {:a => 10, :b => 9}
|
200
|
+
@answer1.dominated_by?(@answer2).should == true
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should return false if another Answer has all scores worse" do
|
204
|
+
@answer1.scores = {:a => 10, :b =>10}
|
205
|
+
@answer2.scores = {:a=> 1000, :b => 1000}
|
206
|
+
@answer1.dominated_by?(@answer2).should == false
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should return false if another Answer has all scores the same and one worse" do
|
210
|
+
@answer1.scores = {:a => 10, :b =>10}
|
211
|
+
@answer2.scores = {:a=> 10, :b => 1000}
|
212
|
+
@answer1.dominated_by?(@answer2).should == false
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should return false if another Answer has some scores better and some worse" do
|
216
|
+
@answer1.scores = {:a => 10, :b =>10}
|
217
|
+
@answer2.scores = {:a=> 1000, :b => 1}
|
218
|
+
@answer1.dominated_by?(@answer2).should == false
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should return true if another Answer has all scores better" do
|
222
|
+
@answer1.scores = {:a => 10, :b =>10}
|
223
|
+
@answer2.scores = {:a=> 1, :b => 1}
|
224
|
+
@answer1.dominated_by?(@answer2).should == true
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should return false if another Answer has all scores identical" do
|
228
|
+
@answer1.scores = {:a => 10, :b =>10}
|
229
|
+
@answer2.scores = {:a=> 10, :b => 10}
|
230
|
+
@answer1.dominated_by?(@answer2).should == false
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should use known_criteria if no comparison template is passed in" do
|
234
|
+
@answer1.scores = {:a => 10, :b =>10, :c => 100}
|
235
|
+
@answer2.scores = {:a=> 1, :b => 1, :c => 99}
|
236
|
+
@answer1.should_receive(:known_criteria).at_least(1).times.and_return([:a,:b,:c])
|
237
|
+
@answer1.dominated_by?(@answer2).should == true
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should use a comparison template if one is passed in" do
|
241
|
+
@answer1.scores = {:a => 10, :b =>10, :c => 10}
|
242
|
+
@answer2.scores = {:a=> 1, :b => 200, :c => 10}
|
243
|
+
@answer1.dominated_by?(@answer2, [:a]).should == true
|
244
|
+
@answer1.dominated_by?(@answer2, [:b]).should == false
|
245
|
+
@answer1.dominated_by?(@answer2, [:c]).should == false
|
246
|
+
|
247
|
+
@answer2.dominated_by?(@answer1, [:a]).should == false
|
248
|
+
@answer2.dominated_by?(@answer1, [:b]).should == true
|
249
|
+
@answer2.dominated_by?(@answer1, [:c]).should == false
|
250
|
+
|
251
|
+
@answer1.dominated_by?(@answer2, [:a, :c]).should == true
|
252
|
+
@answer2.dominated_by?(@answer1, [:b, :c]).should == true
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
it "should be conservative when comparing against a missing score" do
|
257
|
+
@answer1.scores = {:a => 10, :b =>nil, :c => 100}
|
258
|
+
@answer2.scores = {:a=> 1, :b => 12, :c => 99}
|
259
|
+
lambda{@answer1.dominated_by?(@answer2)}.should_not raise_error(NoMethodError)
|
260
|
+
@answer1.dominated_by?(@answer2).should == false
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
describe "#points method" do
|
267
|
+
it "should return self.program.points" do
|
268
|
+
Answer.new("").points.should == 0
|
269
|
+
Answer.new("block {do a do b}").points.should == 3
|
270
|
+
Answer.new("block {value «foo» block {value «foo»}}").points.should == 4
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
describe "replace_point_or_clone" do
|
276
|
+
before(:each) do
|
277
|
+
@simple = Answer.new("block { do a do b do c do d do e}")
|
278
|
+
@complicated = Answer.new("block { block {do a} do b block { block {do c} block {do d}} do e}")
|
279
|
+
@str_insert = "ref x"
|
280
|
+
@pt_insert = ReferencePoint.new("y")
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should return a NudgeProgram (not an Answer)" do
|
284
|
+
@simple.replace_point_or_clone(2,@str_insert).should be_a_kind_of(NudgeProgram)
|
285
|
+
end
|
286
|
+
|
287
|
+
it "should return a clone of the original NudgeProgram if the position param is out of bounds" do
|
288
|
+
new_guy = @simple.replace_point_or_clone(-11,"do x")
|
289
|
+
new_guy.blueprint.should == @simple.blueprint
|
290
|
+
new_guy.object_id.should_not == @simple.program.object_id
|
291
|
+
|
292
|
+
new_guy = @simple.replace_point_or_clone(1911,"do x")
|
293
|
+
new_guy.blueprint.should == @simple.blueprint
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should raise an ArgumentError if passed an unparseable replacement" do
|
297
|
+
lambda{@simple.replace_point_or_clone(3,"some crap")}.should raise_error(ArgumentError)
|
298
|
+
lambda{@simple.replace_point_or_clone(3,"block {}")}.should_not raise_error
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should produce a new parsable NudgeProgram with a new blueprint (as expected)" do
|
302
|
+
@simple.replace_point_or_clone(2,@str_insert).blueprint.should ==
|
303
|
+
"block {\n ref x\n do b\n do c\n do d\n do e}"
|
304
|
+
@complicated.replace_point_or_clone(2,@str_insert).blueprint.should ==
|
305
|
+
"block {\n ref x\n do b\n block {\n block {\n do c}\n block {\n do d}}\n do e}"
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should replace the expected specific point (and any subpoints and footnotes it has)" do
|
309
|
+
a1 = Answer.new("block {value «code» value «int»}\n«code» value «float»\n«float» 1.1\n«int» 12")
|
310
|
+
a1.replace_point_or_clone(2,"do x").blueprint.should ==
|
311
|
+
"block {\n do x\n value «int»} \n«int» 12"
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should insert the new footnotes it needs in the right place in the footnote_section" do
|
315
|
+
a1 = Answer.new("block {value «code» value «int»}\n«code» value «float»\n«float» 1.1\n«int» 12")
|
316
|
+
a1.replace_point_or_clone(3,"value «foo»\n«foo» bar").blueprint.should ==
|
317
|
+
"block {\n value «code»\n value «foo»} \n«code» value «float»\n«float» 1.1\n«foo» bar"
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should not affect extra (unused) footnotes" do
|
321
|
+
a1 = Answer.new("block {value «int»}\n«float» 1.1\n«int» 12")
|
322
|
+
a1.replace_point_or_clone(2, "do x").blueprint.should ==
|
323
|
+
"block {\n do x} \n«float» 1.1"
|
324
|
+
end
|
325
|
+
|
326
|
+
it "should work when putting in something that lacks a footnote" do
|
327
|
+
@simple.replace_point_or_clone(3, "value «quux»").blueprint.should ==
|
328
|
+
"block {\n do a\n value «quux»\n do c\n do d\n do e} \n«quux»"
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should act as expected when replacing the root" do
|
332
|
+
@simple.replace_point_or_clone(1, "value «quux»").blueprint.should ==
|
333
|
+
"value «quux» \n«quux»"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
describe "delete_point_or_clone" do
|
340
|
+
before(:each) do
|
341
|
+
@simple = Answer.new("block { do a do b do c do d do e}")
|
342
|
+
@complicated = Answer.new("block { block {do a} do b block { block {do c} block {do d}} do e}")
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
it "should return a NudgeProgram (not an Answer)" do
|
347
|
+
@simple.delete_point_or_clone(2).should be_a_kind_of(NudgeProgram)
|
348
|
+
end
|
349
|
+
|
350
|
+
it "should return a clone of the calling Answer's program if the point is out of bounds" do
|
351
|
+
new_guy = @simple.delete_point_or_clone(-11)
|
352
|
+
new_guy.blueprint.should == @simple.blueprint
|
353
|
+
new_guy.object_id.should_not == @simple.program.object_id
|
354
|
+
|
355
|
+
new_guy = @simple.delete_point_or_clone(1911)
|
356
|
+
new_guy.blueprint.should == @simple.blueprint
|
357
|
+
end
|
358
|
+
|
359
|
+
it "should return a NudgeProgram with at least one fewer program points (if the range is OK)" do
|
360
|
+
@complicated.delete_point_or_clone(2).points.should < @complicated.points
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should delete the expected specific point (and any subpoints it has)" do
|
364
|
+
@complicated.delete_point_or_clone(5).blueprint.should ==
|
365
|
+
"block {\n block {\n do a}\n do b\n do e}"
|
366
|
+
end
|
367
|
+
|
368
|
+
it "should return NudgeProgram.new('block {}') when the entire program is deleted" do
|
369
|
+
@complicated.delete_point_or_clone(1).blueprint.should == "block {}"
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should delete the footnotes associated with a point it deletes" do
|
373
|
+
a1 = Answer.new("block {value «a» value «b» value «c»}\n«a» 1\n«b» 2\n«c» 3")
|
374
|
+
a1.delete_point_or_clone(3).blueprint.should ==
|
375
|
+
"block {\n value «a»\n value «c»} \n«a» 1\n«c» 3"
|
376
|
+
end
|
377
|
+
|
378
|
+
it "should not delete extra footnotes" do
|
379
|
+
a1 = Answer.new("block {value «a»}\n«a» 1\n«b» 2\n«c» 3")
|
380
|
+
a1.delete_point_or_clone(2).blueprint.should ==
|
381
|
+
"block {} \n«b» 2\n«c» 3"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
|
386
|
+
describe "tags" do
|
387
|
+
it "should have a tags array" do
|
388
|
+
Answer.new("").tags.should be_a_kind_of(Set)
|
389
|
+
end
|
390
|
+
|
391
|
+
it "should be possible to set the tags upon initialization" do
|
392
|
+
Answer.new("do a", tags:[:here, :now]).tags.should == Set.new([:here, :now])
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should have an #add_tag method" do
|
396
|
+
a1 = Answer.new("")
|
397
|
+
a1.add_tag(:foo)
|
398
|
+
a1.tags.should include(:foo)
|
399
|
+
end
|
400
|
+
|
401
|
+
it "should validate that tags being added are Symbols" do
|
402
|
+
lambda{Answer.new("").add_tag("foo")}.should raise_error(ArgumentError)
|
403
|
+
end
|
404
|
+
|
405
|
+
it "should have an #remove_tag method" do
|
406
|
+
a1 = Answer.new("", tags:[:a, :b])
|
407
|
+
a1.tags.should include(:a)
|
408
|
+
a1.remove_tag(:a)
|
409
|
+
a1.tags.should_not include(:a)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
data/spec/batch_spec.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "./spec_helper")
|
2
|
+
|
3
|
+
describe "Batches" do
|
4
|
+
it "should be a kind of Array" do
|
5
|
+
Batch.new.should be_a_kind_of(Array)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should raise an exception if a non-Answer is added after initialization" do
|
9
|
+
careful = Batch.[](Answer.new("block {}"), Answer.new("block {}"))
|
10
|
+
careful.length.should == 2
|
11
|
+
|
12
|
+
lambda{careful = Batch.[](12)}.should raise_error(ArgumentError)
|
13
|
+
lambda{careful = Batch.[](Answer.new("do int_add"))}.should_not raise_error(ArgumentError)
|
14
|
+
|
15
|
+
lambda{careful = Batch.[](Answer.new("do int_add"),12)}.should raise_error(ArgumentError)
|
16
|
+
lambda{careful = Batch.[](Answer.new("do int_add"),
|
17
|
+
Answer.new("do int_add"))}.should_not raise_error(ArgumentError)
|
18
|
+
|
19
|
+
lambda{careful = Batch.new(12)}.should raise_error(ArgumentError)
|
20
|
+
lambda{careful = Batch.new(Answer.new("do int_add"))}.should_not raise_error(ArgumentError)
|
21
|
+
|
22
|
+
lambda{careful[1] = 991}.should raise_error(ArgumentError)
|
23
|
+
lambda{careful[1] = Answer.new("do int_add")}.should_not raise_error(ArgumentError)
|
24
|
+
|
25
|
+
lambda{careful << false}.should raise_error(ArgumentError)
|
26
|
+
lambda{careful << Answer.new("do int_add")}.should_not raise_error(ArgumentError)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
describe "database persistence" do
|
31
|
+
before(:each) do
|
32
|
+
FakeWeb.allow_net_connect = false
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "bulk_save!" do
|
36
|
+
it "should have a #bulk_save method" do
|
37
|
+
Batch.new.should respond_to(:bulk_save!)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should validate a String as its argument" do
|
41
|
+
lambda{Batch.new.bulk_save!("some string")}.should_not raise_error(ArgumentError)
|
42
|
+
lambda{Batch.new.bulk_save!(8812)}.should raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
describe "writing" do
|
47
|
+
before(:each) do
|
48
|
+
@uri = "http://mycouch.db/boo"
|
49
|
+
@b1 = Batch.new
|
50
|
+
@a1 = Answer.new("do a")
|
51
|
+
@b1 << @a1
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should create the database if it doesn't exist" do
|
55
|
+
FakeWeb.register_uri(:any, @uri, :body => "We are here!", :status => [200, "OK"])
|
56
|
+
CouchRest.stub(:database!).and_return(the_db = Object.new)
|
57
|
+
the_db.stub!(:bulk_save)
|
58
|
+
CouchRest.should_receive(:database!)
|
59
|
+
Batch.new.bulk_save!(@uri)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should bulk_save the Answers" do
|
63
|
+
CouchRest.stub(:database!).and_return(the_db = Object.new)
|
64
|
+
the_db.should_receive(:bulk_save)
|
65
|
+
@b1.bulk_save!(@uri)
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "data" do
|
69
|
+
it "should return an array of its contents' #data" do
|
70
|
+
@b1.data.should == [ @a1.data ]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "reading" do
|
76
|
+
before(:each) do
|
77
|
+
@uri = "http://127.0.0.1/baz:5984"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "create a new Batch" do
|
81
|
+
Batch.load_tagged_answers(@uri,"foo").should be_a_kind_of(Batch)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should access the database" do
|
85
|
+
CouchRest.should_receive(:database).with(@uri)
|
86
|
+
Batch.load_tagged_answers(@uri, "workstation_1")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should work"
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
it "should have a Batch.load_from_couch method that reads a bunch of Answers from the db"
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|