diy 1.0.0

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.
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