diy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/History.txt +3 -0
  2. data/Manifest.txt +53 -0
  3. data/README.txt +148 -0
  4. data/Rakefile +17 -0
  5. data/lib/diy.rb +249 -0
  6. data/test/diy_test.rb +482 -0
  7. data/test/files/broken_construction.yml +7 -0
  8. data/test/files/cat/cat.rb +4 -0
  9. data/test/files/cat/extra_conflict.yml +5 -0
  10. data/test/files/cat/heritage.rb +2 -0
  11. data/test/files/cat/needs_input.yml +3 -0
  12. data/test/files/cat/the_cat_lineage.rb +1 -0
  13. data/test/files/dog/dog_model.rb +4 -0
  14. data/test/files/dog/dog_presenter.rb +4 -0
  15. data/test/files/dog/dog_view.rb +2 -0
  16. data/test/files/dog/file_resolver.rb +2 -0
  17. data/test/files/dog/other_thing.rb +2 -0
  18. data/test/files/dog/simple.yml +11 -0
  19. data/test/files/donkey/foo.rb +8 -0
  20. data/test/files/donkey/foo/bar/qux.rb +7 -0
  21. data/test/files/fud/objects.yml +13 -0
  22. data/test/files/fud/toy.rb +15 -0
  23. data/test/files/gnu/objects.yml +14 -0
  24. data/test/files/gnu/thinger.rb +8 -0
  25. data/test/files/goat/base.rb +8 -0
  26. data/test/files/goat/can.rb +6 -0
  27. data/test/files/goat/goat.rb +6 -0
  28. data/test/files/goat/objects.yml +12 -0
  29. data/test/files/goat/paper.rb +6 -0
  30. data/test/files/goat/plane.rb +8 -0
  31. data/test/files/goat/shirt.rb +6 -0
  32. data/test/files/goat/wings.rb +8 -0
  33. data/test/files/horse/holder_thing.rb +4 -0
  34. data/test/files/horse/objects.yml +7 -0
  35. data/test/files/non_singleton/air.rb +2 -0
  36. data/test/files/non_singleton/fat_cat.rb +4 -0
  37. data/test/files/non_singleton/objects.yml +19 -0
  38. data/test/files/non_singleton/pig.rb +4 -0
  39. data/test/files/non_singleton/thread_spinner.rb +4 -0
  40. data/test/files/non_singleton/tick.rb +4 -0
  41. data/test/files/non_singleton/yard.rb +2 -0
  42. data/test/files/yak/core_model.rb +4 -0
  43. data/test/files/yak/core_presenter.rb +4 -0
  44. data/test/files/yak/core_view.rb +1 -0
  45. data/test/files/yak/data_source.rb +1 -0
  46. data/test/files/yak/fringe_model.rb +4 -0
  47. data/test/files/yak/fringe_presenter.rb +4 -0
  48. data/test/files/yak/fringe_view.rb +1 -0
  49. data/test/files/yak/giant_squid.rb +4 -0
  50. data/test/files/yak/krill.rb +2 -0
  51. data/test/files/yak/my_objects.yml +21 -0
  52. data/test/files/yak/sub_sub_context_test.yml +27 -0
  53. data/test/test_helper.rb +38 -0
  54. metadata +118 -0
@@ -0,0 +1,482 @@
1
+ require File.dirname(__FILE__) + "/test_helper"
2
+ require 'diy'
3
+ require 'fileutils'
4
+ include FileUtils
5
+
6
+ class DIYTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ # Add load paths:
10
+ %w|gnu dog cat yak donkey goat horse fud non_singleton|.each do |p|
11
+ libdir = path_to_test_file(p)
12
+ $: << libdir unless $:.member?(libdir)
13
+ end
14
+ end
15
+
16
+ #
17
+ # HELPERS
18
+ #
19
+ def path_to_test_file(fname)
20
+ path_to("/files/#{fname}")
21
+ end
22
+
23
+ def load_context(file_name)
24
+ hash = YAML.load(File.read(path_to_test_file(file_name)))
25
+ load_hash(hash)
26
+ end
27
+
28
+ def load_hash(hash)
29
+ @diy = DIY::Context.new(hash)
30
+ end
31
+
32
+ def check_dog_objects(context)
33
+ assert_not_nil context, "nil context"
34
+ names = %w|dog_presenter dog_model dog_view file_resolver|
35
+ names.each do |n|
36
+ assert context.contains_object(n), "Context had no object '#{n}'"
37
+ end
38
+ end
39
+
40
+ #
41
+ # TESTS
42
+ #
43
+
44
+ def test_essential_use_case
45
+ load_context "dog/simple.yml"
46
+
47
+ # Check object defs
48
+ check_dog_objects @diy
49
+
50
+ # Tweak the load-path
51
+ $: << path_to_test_file("dog")
52
+
53
+ # Get the objects, use reference comparison to check composition
54
+ presenter = @diy.get_object('dog_presenter')
55
+ assert_not_nil presenter, 'nil dog_presenter'
56
+
57
+ model = @diy.get_object('dog_model')
58
+ assert_not_nil model, 'nil dog_model'
59
+ assert_same presenter.model, model, "Different model came from context than found in presenter"
60
+
61
+ view = @diy.get_object('dog_view')
62
+ assert_not_nil view, 'nil dog_view'
63
+ assert_same presenter.view, view, "Different view came from context than found in presenter"
64
+
65
+ resolver = @diy.get_object('file_resolver')
66
+ assert_not_nil resolver, 'nil file_resolver'
67
+ assert_same model.file_resolver, resolver, "File resolver in model is different than one in context"
68
+
69
+ # Check repeat access:
70
+ assert_same model, @diy.get_object('dog_model'), "Second access of model yielded different result"
71
+ assert_same view, @diy.get_object('dog_view'), "Second access of view yielded different result"
72
+ assert_same presenter, @diy.get_object('dog_presenter'), "Second access of presenter got difrnt result"
73
+ end
74
+
75
+ def test_classname_inside_a_module
76
+ load_hash 'thinger' => {'class' => "DiyTesting::Bar::Foo", 'lib' => 'foo'}
77
+ @diy.build_everything
78
+ assert_not_nil @diy['thinger'], "Should have got my thinger (which is hiding in a couple modules)"
79
+ end
80
+
81
+ def test_classname_inside_a_module_loads_from_directories_named_after_the_underscored_module_names
82
+ load_hash 'thinger' => {'class' => "Foo::Bar::Qux"}
83
+ # expect it to be loaded from: foo/bar/qux.rb
84
+ @diy.build_everything
85
+ assert_not_nil @diy['thinger'], "Should have got my thinger (which is hiding in a couple modules)"
86
+ end
87
+
88
+ def test_classname_inside_a_module_derives_the_namespaced_classname_from_the_underscored_object_def_key
89
+ load_hash 'foo/bar/qux' => nil
90
+ @diy.build_everything
91
+ assert_not_nil @diy['foo/bar/qux'], "Should have got my qux (which is hiding in a couple modules)"
92
+ end
93
+
94
+ def test_keys
95
+ load_context "dog/simple.yml"
96
+ assert_equal %w|dog_model dog_presenter dog_view file_resolver other_thing|, @diy.keys.sort
97
+ end
98
+
99
+ def test_subcontext_keys_should_include_parent_context_keys
100
+ load_context 'yak/sub_sub_context_test.yml'
101
+ main_keys = %w|core_presenter core_model core_view data_source|.sort
102
+ assert_equal main_keys, @diy.keys.sort, "Wrong keys in main context"
103
+ @diy.within :fringe_context do |fcontext|
104
+ fringe_keys = [main_keys, %w|fringe_model fringe_view fringe_presenter|].flatten.sort
105
+ assert_equal fringe_keys, fcontext.keys.sort, "Wrong keys in fringe context"
106
+ fcontext.within :deep_context do |dcontext|
107
+ deep_keys = [fringe_keys, %w|krill giant_squid|].flatten.sort
108
+ assert_equal deep_keys, dcontext.keys.sort
109
+ end
110
+ end
111
+ end
112
+
113
+ def test_constructor_no_hash
114
+ assert_raise RuntimeError do DIY::Context.new(nil) end
115
+ end
116
+
117
+ def test_constructor_bad_extra_inputs
118
+ err = assert_raise RuntimeError do
119
+ DIY::Context.new({}, Object.new)
120
+ end
121
+ assert_match(/extra inputs/i, err.message)
122
+ end
123
+
124
+ def test_from_yaml
125
+ text = File.read(path_to_test_file("dog/simple.yml"))
126
+ diy = DIY::Context.from_yaml(text)
127
+ check_dog_objects diy
128
+ end
129
+
130
+ def test_from_yaml_extra_inputs
131
+ extra = { 'the_cat_lineage' => 'siamese', :some_meat => 'horse' }
132
+ diy = DIY::Context.from_yaml(File.read(path_to_test_file('cat/needs_input.yml')), extra)
133
+ cat = diy['cat']
134
+ assert_equal 'siamese', cat.heritage
135
+ assert_equal 'horse', cat.food
136
+ end
137
+
138
+ def test_from_file
139
+ diy = DIY::Context.from_file(path_to_test_file("dog/simple.yml"))
140
+ check_dog_objects diy
141
+ end
142
+
143
+ def test_from_file_bad
144
+ assert_raise RuntimeError do
145
+ DIY::Context.from_file(nil)
146
+ end
147
+ assert_raise Errno::ENOENT do
148
+ DIY::Context.from_file("bad file name")
149
+ end
150
+ end
151
+
152
+ def test_from_file_extra_inputs
153
+ extra = { 'the_cat_lineage' => 'siamese', :some_meat => 'horse' }
154
+ diy = DIY::Context.from_file(path_to_test_file('cat/needs_input.yml'), extra)
155
+ cat = diy['cat']
156
+ assert_equal 'siamese', cat.heritage
157
+ assert_equal 'horse', cat.food
158
+ end
159
+
160
+ def test_contains_object
161
+ load_context "dog/simple.yml"
162
+ assert @diy.contains_object('dog_presenter'), "Should be true for dog_presenter"
163
+ assert !@diy.contains_object('woops'), "Should return false for 'woops'"
164
+ err = assert_raise ArgumentError do
165
+ @diy.contains_object(nil)
166
+ end
167
+ end
168
+
169
+ def test_contains_object_extra_inputs
170
+ extra = { 'the_cat_lineage' => 'siamese', :some_meat => 'horse' }
171
+ main = YAML.load(File.read(path_to_test_file('cat/needs_input.yml')))
172
+ diy = DIY::Context.new(main, extra)
173
+
174
+ assert diy.contains_object('cat')
175
+ assert diy.contains_object('the_cat_lineage')
176
+ assert diy.contains_object('some_meat')
177
+ end
178
+
179
+ def test_get_object
180
+ load_context "dog/simple.yml"
181
+ assert_not_nil @diy.get_object('file_resolver'), "nil resolver?"
182
+ assert_raise ArgumentError do
183
+ @diy.get_object(nil)
184
+ end
185
+ assert_raise DIY::ConstructionError do
186
+ @diy.get_object("no such object")
187
+ end
188
+ end
189
+
190
+ def test_hash_style_access
191
+ load_context "dog/simple.yml"
192
+ assert_not_nil @diy['file_resolver'], "nil resolver?"
193
+ assert_raise ArgumentError do
194
+ @diy[nil]
195
+ end
196
+ assert_raise DIY::ConstructionError do
197
+ @diy["no such object"]
198
+ end
199
+ end
200
+
201
+ def test_get_object_construction_error
202
+ load_context "broken_construction.yml"
203
+ err = assert_raise DIY::ConstructionError do
204
+ @diy.get_object 'dog_presenter'
205
+ end
206
+ assert_match(/dog_presenter/, err.message)
207
+ end
208
+
209
+ def test_context_with_extra_inputs
210
+ extra = { 'the_cat_lineage' => 'siamese', :some_meat => 'horse' }
211
+ main = YAML.load(File.read(path_to_test_file('cat/needs_input.yml')))
212
+ diy = DIY::Context.new(main, extra)
213
+ cat = diy['cat']
214
+ assert_equal 'siamese', cat.heritage
215
+ assert_equal 'horse', cat.food
216
+ end
217
+
218
+ def test_conflicting_extra_inputs
219
+ extra = { 'the_cat_lineage' => 'siamese', :some_meat => 'horse' }
220
+ main = YAML.load(File.read(path_to_test_file('cat/extra_conflict.yml')))
221
+
222
+ DIY::Context.new(main,extra)
223
+ flunk "Should have raised err"
224
+ rescue Exception => err
225
+ assert_match(/conflict/i, err.message)
226
+ end
227
+
228
+ def test_sub_context
229
+ load_context 'yak/my_objects.yml'
230
+
231
+ core_model = @diy['core_model']
232
+ assert_not_nil core_model, "no core model in main context?"
233
+
234
+ fmodel1 = nil
235
+ fview1 = nil
236
+ @diy.within('fringe_context') do |fc|
237
+ assert_not_nil fc["fringe_presenter"], "no fringe presenter"
238
+ fmodel1 = fc["fringe_model"]
239
+ fmodel1a = fc["fringe_model"]
240
+ assert_same fmodel1, fmodel1a, "Second fring model in fringe_context came out different"
241
+ assert_not_nil fmodel1, "no fringe_model"
242
+ fview1 = fc["fringe_view"]
243
+ assert_not_nil fview1, "no fringe_view"
244
+ assert_same core_model, fmodel1.connected
245
+ end
246
+
247
+ fmodel2 = nil
248
+ fview2 = nil
249
+ @diy.within('fringe_context') do |fc|
250
+ assert_not_nil fc["fringe_presenter"], "2: no fringe presenter"
251
+ fmodel2 = fc["fringe_model"]
252
+ fmodel2a = fc["fringe_model"]
253
+ assert_same fmodel2, fmodel2a, "Second fringe model in fringe_context came out different"
254
+ assert_not_nil fmodel2, "2: no fringe_model"
255
+ fview2 = fc["fringe_view"]
256
+ assert_not_nil fview2, "2: no fringe_view"
257
+ assert_same core_model, fmodel2.connected
258
+
259
+ assert fmodel1.object_id != fmodel2.object_id, "fringe models 1 and 2 are same!"
260
+ assert fview1.object_id != fview2.object_id, "fringe views 1 and 2 are same!"
261
+ end
262
+ end
263
+
264
+ def test_sub_sub_context
265
+ load_context 'yak/sub_sub_context_test.yml'
266
+
267
+ core_model = @diy['core_model']
268
+ assert_not_nil core_model, "no core model in main context?"
269
+
270
+ fmodel1 = nil
271
+ fview1 = nil
272
+ @diy.within('fringe_context') do |fc|
273
+ assert_not_nil fc["fringe_presenter"], "no fringe presenter"
274
+ fmodel1 = fc["fringe_model"]
275
+ fmodel1a = fc["fringe_model"]
276
+ assert_same fmodel1, fmodel1a, "Second fring model in fringe_context came out different"
277
+ assert_not_nil fmodel1, "no fringe_model"
278
+ fview1 = fc["fringe_view"]
279
+ assert_not_nil fview1, "no fringe_view"
280
+ assert_same core_model, fmodel1.connected
281
+
282
+ fc.within :deep_context do |dc|
283
+ krill = dc['krill']
284
+ assert_not_nil krill, "nil krill"
285
+ assert_same krill, dc['krill'], "krill was different second time"
286
+ giant_squid = dc['giant_squid']
287
+ assert_same fview1, giant_squid.fringe_view, "wrong view in squid"
288
+ assert_same core_model, giant_squid.core_model, "wrong model in squid"
289
+ assert_same krill, giant_squid.krill, "wrong krill in squid"
290
+ end
291
+ end
292
+
293
+ end
294
+
295
+ def test_build_everything
296
+ # Singletons in the goat context will generate test output in their constructors.
297
+ # We just gotta tell em where:
298
+ ofile = path_to_test_file('goat/output.tmp')
299
+ $goat_test_output_file = ofile
300
+
301
+ # Reusable setup for this test
302
+ prep_output = proc do
303
+ remove ofile if File.exist?(ofile)
304
+ end
305
+
306
+ # Reusable assertion set and cleanup
307
+ examine_output = proc do
308
+ # Examine output file for expected construction
309
+ assert File.exist?(ofile), "no goat output created"
310
+ lines = File.readlines(ofile).map { |x| x.strip }
311
+ %w|can paper shirt goat|.each do |object|
312
+ assert lines.member?("#{object} built"), "Didn't see constructor output for #{object}"
313
+ end
314
+ assert_equal 4, lines.size, "wrong number of entries in output file"
315
+
316
+ # Make sure the subcontext was not built
317
+ assert !lines.member?("plane built"), "plane should not have been built -- it's in the subcontext"
318
+ assert !lines.member?("wings built"), "wings should not have been built -- it's in the subcontext"
319
+
320
+ # Check the objects in the context
321
+ %w|can paper shirt goat|.each do |object|
322
+ assert_same @diy[object], @diy[object], "Multiple accesses on #{object} yielded different refs"
323
+ end
324
+
325
+ # Try the subcontext
326
+ @diy.within('the_sub_context') do |tsc|
327
+ %w|plane wings|.each do |object|
328
+ assert_same tsc[object], tsc[object], "Multiple accesses on #{object} (in subcontext) yielded different refs"
329
+ end
330
+ end
331
+ # cleanup
332
+ remove ofile if File.exist?(ofile)
333
+ end
334
+
335
+ # Test all three methods
336
+ [:build_everything, :build_all, :preinstantiate_singletons].each do |method_name|
337
+ prep_output.call
338
+ load_context 'goat/objects.yml'
339
+ # go
340
+ @diy.send method_name
341
+ examine_output.call
342
+ end
343
+ ensure
344
+ # cleanup
345
+ remove ofile if File.exist?(ofile)
346
+ end
347
+
348
+ # See that the current object factory context can be referenced within the yaml
349
+ def test_this_context
350
+ load_context 'horse/objects.yml'
351
+
352
+ assert_same @diy, @diy['this_context'], "basic self-reference failed"
353
+ assert_same @diy, @diy['holder_thing'].thing_held, "composition self-reference failed"
354
+ end
355
+
356
+ def test_this_context_works_for_subcontexts
357
+ load_context 'horse/objects.yml'
358
+
359
+ @diy.within('repeater') do |ctx|
360
+ assert_same ctx, ctx['this_context'], "self-ref inside a subcontext doesn't work"
361
+ end
362
+ end
363
+
364
+ def test_multiple_classes_in_one_file
365
+ load_context 'fud/objects.yml'
366
+
367
+ toy = @diy['toy']
368
+ widget = @diy['widget']
369
+ thing = @diy['thing_ama_jack']
370
+ trinket = @diy['trinket']
371
+
372
+ assert_same widget, toy.widget, "wrong widget in toy"
373
+ assert_same trinket, toy.trinket, "wrong trinket in toy"
374
+ assert_same thing, trinket.thing_ama_jack, "wrong thing_ama_jack in trinket"
375
+ end
376
+
377
+ def test_objects_can_be_set_in_a_context_and_diy_will_not_attempt_to_build_it_as_a_dependency
378
+ load_context 'gnu/objects.yml'
379
+
380
+ injected = 'boom'
381
+ @diy[:injected] = injected
382
+ thinger = @diy[:thinger]
383
+ assert_not_nil thinger
384
+ assert_same injected, thinger.injected
385
+ assert_same injected, @diy[:injected]
386
+
387
+ inner_injected = 'slam'
388
+ @diy.within :inny do |sub|
389
+ sub.set_object :inner_injected, inner_injected
390
+ inner_thinger = sub[:inner_thinger]
391
+ assert_not_nil inner_thinger
392
+ assert_same inner_injected, inner_thinger.injected
393
+ assert_same inner_injected, sub[:inner_injected]
394
+ end
395
+ end
396
+
397
+ def test_should_not_allow_setting_of_an_object_which_has_already_been_loaded
398
+ load_context 'gnu/objects.yml'
399
+
400
+ injected = 'boom'
401
+ @diy[:injected] = injected
402
+ err = assert_raise RuntimeError do
403
+ @diy[:injected] = injected
404
+ end
405
+ assert_match(/object 'injected' already exists/i, err.message)
406
+ assert_same injected, @diy[:injected]
407
+
408
+ thinger = @diy[:thinger]
409
+ err = assert_raise RuntimeError do
410
+ @diy[:thinger] = 'sdf'
411
+ end
412
+ assert_match(/object 'thinger' already exists/i, err.message)
413
+ assert_same thinger, @diy[:thinger]
414
+ end
415
+
416
+ def test_should_cause_non_singletons_to_be_rebuilt_every_time_they_are_accessed
417
+ load_context 'non_singleton/objects.yml'
418
+
419
+ air = @diy['air']
420
+ assert_not_nil air, "No air"
421
+ assert_same air, @diy['air'], "Air should be a singleton"
422
+
423
+ yard = @diy['yard']
424
+ assert_not_nil yard, "No yard"
425
+ assert_same yard, @diy['yard'], "yard should be a singleton"
426
+
427
+ pig = @diy['pig']
428
+ assert_not_nil pig, "No pig"
429
+ assert_same pig, @diy['pig'], "Pig should be a singleton"
430
+
431
+ thread_spinner1 = @diy['thread_spinner']
432
+ assert_not_nil thread_spinner1, "Couldn't get thread spinner"
433
+ thread_spinner2 = @diy['thread_spinner']
434
+ assert_not_nil thread_spinner2, "Couldn't get second thread spinner"
435
+ assert thread_spinner1.object_id != thread_spinner2.object_id, "Thread spinners should be different instances"
436
+ thread_spinner3 = pig.thread_spinner
437
+ assert_not_nil thread_spinner3, "Didn't get a spinner from the pig"
438
+ assert thread_spinner2.object_id != thread_spinner3.object_id, "Thread spinner from pig should be different instance than the others"
439
+ assert thread_spinner1.object_id != thread_spinner3.object_id, "Thread spinner from pig should be different instance than the others"
440
+
441
+ assert_same air, thread_spinner1.air, "spinner 1 air should be singleton reference"
442
+ assert_same air, thread_spinner2.air, "spinner 2 air should be singleton reference"
443
+ assert_same air, thread_spinner3.air, "spinner 3 air should be singleton reference"
444
+ end
445
+
446
+ def test_should_handle_nonsingletons_in_sub_contexts
447
+ load_context 'non_singleton/objects.yml'
448
+
449
+ yard = @diy['yard']
450
+ assert_not_nil yard, "No yard"
451
+ assert_same yard, @diy['yard'], "yard should be a singleton"
452
+
453
+ thread_spinner1 = @diy['thread_spinner']
454
+ assert_not_nil thread_spinner1, "Couldn't get thread spinner"
455
+
456
+ air = @diy['air']
457
+ assert_not_nil air, "No air"
458
+ assert_same air, @diy['air'], "Air should be a singleton"
459
+
460
+ @diy.within :inner_sanctum do |sanct|
461
+ tick1 = sanct['tick']
462
+ assert_not_nil tick1, "Couldn't get tick1 from inner sanctum"
463
+ tick2 = sanct['tick']
464
+ assert_not_nil tick2, "Couldn't get tick2 from inner sanctum"
465
+ assert tick1.object_id != tick2.object_id, "Tick should not be a singleton"
466
+
467
+ cat = sanct['fat_cat']
468
+ assert_not_nil cat, "Couldn't get cat from sanctum"
469
+ assert_same cat, sanct['fat_cat'], "Cat SHOULD be singleton"
470
+
471
+ tick3 = cat.tick
472
+ assert_not_nil tick3, "Couldn't get tick from cat"
473
+ assert tick1.object_id != tick3.object_id, "tick from cat matched an earlier tick; should not be so"
474
+
475
+ assert_same yard, cat.yard, "Cat's yard should be same as other yard"
476
+ assert_not_nil cat.thread_spinner, "No thread spinner in cat?"
477
+
478
+ assert_same air, cat.thread_spinner.air, "spinner 1 air should be singleton reference"
479
+ assert thread_spinner1.object_id != cat.thread_spinner.object_id, "cat's thread spinner matched the other spinner; should not be so"
480
+ end
481
+ end
482
+ end