duck_test 0.1.4

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 (45) hide show
  1. data/bin/ducktest +29 -0
  2. data/lib/duck_test/autoload_config.rb +103 -0
  3. data/lib/duck_test/base.rb +41 -0
  4. data/lib/duck_test/commands.rb +208 -0
  5. data/lib/duck_test/config.rb +675 -0
  6. data/lib/duck_test/config_helper.rb +99 -0
  7. data/lib/duck_test/console.rb +18 -0
  8. data/lib/duck_test/default_config.rb +48 -0
  9. data/lib/duck_test/frame_work/base.rb +587 -0
  10. data/lib/duck_test/frame_work/file_manager.rb +511 -0
  11. data/lib/duck_test/frame_work/filter_set.rb +233 -0
  12. data/lib/duck_test/frame_work/map.rb +331 -0
  13. data/lib/duck_test/frame_work/queue.rb +221 -0
  14. data/lib/duck_test/frame_work/queue_event.rb +29 -0
  15. data/lib/duck_test/frame_work/rspec/base.rb +17 -0
  16. data/lib/duck_test/frame_work/rspec/frame_work.rb +30 -0
  17. data/lib/duck_test/frame_work/test_unit/base.rb +14 -0
  18. data/lib/duck_test/frame_work/test_unit/frame_work.rb +33 -0
  19. data/lib/duck_test/frame_work/watch_config.rb +69 -0
  20. data/lib/duck_test/gem/helper.rb +107 -0
  21. data/lib/duck_test/logger.rb +127 -0
  22. data/lib/duck_test/option_parser.rb +30 -0
  23. data/lib/duck_test/platforms/base.rb +18 -0
  24. data/lib/duck_test/platforms/dependencies.rb +18 -0
  25. data/lib/duck_test/platforms/generic/base.rb +15 -0
  26. data/lib/duck_test/platforms/generic/listener.rb +104 -0
  27. data/lib/duck_test/platforms/linux/base.rb +15 -0
  28. data/lib/duck_test/platforms/linux/listener.rb +76 -0
  29. data/lib/duck_test/platforms/listener.rb +303 -0
  30. data/lib/duck_test/platforms/mac/base.rb +15 -0
  31. data/lib/duck_test/platforms/mac/listener.rb +79 -0
  32. data/lib/duck_test/platforms/mac/listener.rb.orig +147 -0
  33. data/lib/duck_test/platforms/os_helper.rb +102 -0
  34. data/lib/duck_test/platforms/watch_event.rb +47 -0
  35. data/lib/duck_test/platforms/windows/base.rb +15 -0
  36. data/lib/duck_test/platforms/windows/listener.rb +123 -0
  37. data/lib/duck_test/railtie.rb +29 -0
  38. data/lib/duck_test/usage.rb +34 -0
  39. data/lib/duck_test/usage.yml +112 -0
  40. data/lib/duck_test/version.rb +3 -0
  41. data/lib/duck_test.rb +6 -0
  42. data/lib/notes.txt +215 -0
  43. data/lib/tasks/duck_tests.rake +35 -0
  44. data/lib/tasks/gem_tasks.rake +18 -0
  45. metadata +92 -0
@@ -0,0 +1,511 @@
1
+ module DuckTest
2
+ module FrameWork
3
+
4
+ # module containing data and methods for building list, resolving filters, managing all watched directories / files, etc.
5
+ # holds the common whitelist and blacklist, watch configurations, a reference to a file listener and a runnable test queue.
6
+ module FileManager
7
+ include DuckTest::ConfigHelper
8
+ include LoggerHelper
9
+ include DuckTest::Platforms::OSHelpers
10
+
11
+ ##################################################################################
12
+ # Adds a file object to either the black or white list. Both lists are actually Hashes, so,
13
+ # the file spec is used as the key and the value is a Hash containing {WatchConfig WatchConfig}
14
+ # and several other data elements and is stored in the target list. The Hash associated with the file spec
15
+ # is referred to as a "file object". add_to_list will verify that file_spec actually exists on disk prior to adding it to either list.
16
+ # Also, a boolean flag is set on the target Hash object indicating if file_spec is a directory.
17
+ #
18
+ # watch_config = WatchConfig.new
19
+ # add_to_list(:white, "duck_test/spec/testdir/spec/test01.rb", watch_config)
20
+ # puts self.white_list # => {"/alldata/rails/gems/duck_test/spec/testdir/spec/test01.rb"=>{:watch_config=>#<DuckTest::FrameWork::WatchConfig:0x00000003451ae8>, :is_dir=>false}}
21
+ #
22
+ # @param [Symbol] target The target list to add the file object.
23
+ # @param [String] file_spec A file name that adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
24
+ # @param [WatchConfig] config A valid {WatchConfig} object.
25
+ # @return [NilClass]
26
+ def add_to_list(target, file_spec, config)
27
+
28
+ # verify the file exists prior to adding it to a list.
29
+ if File.exist?(file_spec)
30
+ case target
31
+ when :black
32
+ self.black_list[file_spec] = {watch_config: config, is_dir: File.directory?(file_spec)}
33
+
34
+ when :white
35
+ self.white_list[file_spec] = {watch_config: config, is_dir: File.directory?(file_spec)}
36
+
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ ##################################################################################
43
+ # A list of file objects that have been blacklisted. Files that are blacklisted
44
+ # are not processed in any way.
45
+ # @return [Hash]
46
+ def black_list
47
+ @black_list ||= {}
48
+ return @black_list
49
+ end
50
+
51
+ ##################################################################################
52
+ # Determines if a file object has been blacklisted.
53
+ # @param [String] file_spec A file name that adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
54
+ # @return [Boolean] Returns true if the file has been blacklisted, otherwise, false.
55
+ def black_listed?(file_spec)
56
+ return self.black_list[file_spec]
57
+ end
58
+
59
+ ##################################################################################
60
+ # Builds full black and white lists of directories and files based on the current list of watch_configs.
61
+ def build_watch_lists
62
+
63
+ begin
64
+
65
+ potential_blacklist = {}
66
+
67
+ self.watch_configs.each do |watch_config|
68
+
69
+ ducklog.system "build_watch_lists => watch_config.pattern.blank?: #{watch_config.pattern.blank?} #{watch_config.pattern}"
70
+
71
+ unless watch_config.pattern.blank?
72
+
73
+ watch_config_root = watch_config.watch_basedir
74
+
75
+ # add the full path to all of the patterns
76
+ patterns = []
77
+ if watch_config.pattern.kind_of?(Array)
78
+ watch_config.pattern.each {|pattern| patterns.push(File.expand_path(File.join(self.root, watch_config_root, pattern)))}
79
+ else
80
+ patterns.push(File.expand_path(File.join(self.root, watch_config_root, watch_config.pattern)))
81
+ end
82
+
83
+ ducklog.system "searching for: #{patterns}"
84
+
85
+ # use the standard Dir.glob method to build a list of directories and files based on pattern
86
+ list = Dir.glob(patterns, File::FNM_DOTMATCH)
87
+
88
+ # i'm not sure how Dir.glob will return files on different operating systems, so, sort the array first to put the directories
89
+ # at the top of the list. may have to change this later to simply process and black list the directories before processing files.
90
+ # the reason for this is so that we can black list a file if it's directory is black_listed
91
+ list.sort!
92
+
93
+ #process_file_list watch_config, list, :directory
94
+ #verify_parent_directory_nodes
95
+ #process_file_list watch_config, list, :file
96
+
97
+ list.each do |file_spec|
98
+ if File.directory?(file_spec)
99
+ self.watch_file_spec(file_spec, watch_config)
100
+ end
101
+ end
102
+
103
+ list.each do |file_spec|
104
+ unless File.directory?(file_spec)
105
+ self.watch_file_spec(file_spec, watch_config)
106
+ end
107
+ end
108
+
109
+ list.each do |file_spec|
110
+ unless self.white_listed?(file_spec)
111
+ potential_blacklist[file_spec] = watch_config
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+
118
+ potential_blacklist.each do |file_object|
119
+ unless self.white_listed?(file_object.first)
120
+ self.add_to_list(:black, file_object.first, file_object.last)
121
+ end
122
+ end
123
+
124
+ rescue Exception => e
125
+ ducklog.exception e
126
+ end
127
+
128
+ end
129
+
130
+ ##################################################################################
131
+ # Returns the parent of a file object. File objects are stored as a file spec and an
132
+ # associated Hash when added using {FileManager#add_to_list}. The directory containing
133
+ # the actual file spec is considered to be the parent of a file object.
134
+ #
135
+ # # the following is a file spec considered to point to an actual file on disk.
136
+ # "duck_test/spec/testdir/spec/test01.rb"
137
+ #
138
+ # # the following is a file spec considered to be the parent of the above file object.
139
+ # "duck_test/spec/testdir/spec"
140
+ #
141
+ # find_file_object_parent will the use dirname of the file spec to find the parent file object
142
+ # and return the associated Hash.
143
+ # @param [Symbol] target The target list to add the file object.
144
+ # @param [String] file_spec A file name that adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
145
+ # @return [Hash] A file object is actually a Hash.
146
+ def find_file_object_parent(target, file_spec)
147
+ case target
148
+ when :black
149
+ return self.black_listed?(File.dirname(file_spec))
150
+
151
+ when :white
152
+ return self.white_listed?(File.dirname(file_spec))
153
+
154
+ end
155
+
156
+ return nil
157
+ end
158
+
159
+ ##################################################################################
160
+ # Searches for runnable files mapped to a single non-runnable file.
161
+ # See {file:MAPS.md} for details and examples.
162
+ # @param [String] file_spec A full file specification including path that also adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
163
+ # @param [WatchConfig] The watch configuration object that is associated with the file spec within {#white_list}.
164
+ # @return [Array] A list of runnable files.
165
+ def find_runnable_files(file_spec, watch_config)
166
+ list = []
167
+
168
+ source_parts = split_file_spec(file_spec)
169
+
170
+ watch_config.maps.each do |map|
171
+
172
+ if map.match?(source_parts)
173
+
174
+ self.white_list.each do |file_object|
175
+
176
+ unless file_object.last[:is_dir] || file_object.first.eql?(file_spec)
177
+
178
+ target_parts = split_file_spec(file_object.first)
179
+
180
+ map.maps.each do |target_map|
181
+
182
+ if target_map.match_target?(target_parts, source_parts)
183
+ ducklog.system "find_runnable_files added: #{file_object.first}"
184
+ list.push(file_object.first)
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+
195
+ end
196
+
197
+ return list.uniq
198
+ end
199
+
200
+ ##################################################################################
201
+ # A simple Array that holds a list of all the non-runnable files that have been loaded from disk after being changed.
202
+ # @return [Array]
203
+ def non_loadable_history
204
+ @non_loadable_history ||= []
205
+ return @non_loadable_history.uniq
206
+ end
207
+
208
+ ##################################################################################
209
+ # Assigns the non-runnable history array.
210
+ # @return [Array]
211
+ def non_loadable_history=(value)
212
+ @non_loadable_history = value
213
+ end
214
+
215
+ ##################################################################################
216
+ # A simple Array that holds a list of all the non-runnable files that have been loaded from disk after being changed.
217
+ # @return [Array]
218
+ def non_runnable_history
219
+ @non_runnable_history ||= []
220
+ return @non_runnable_history.uniq
221
+ end
222
+
223
+ ##################################################################################
224
+ # Assigns the non-runnable history array.
225
+ # @return [Array]
226
+ def non_runnable_history=(value)
227
+ @non_runnable_history = value
228
+ end
229
+
230
+ ##################################################################################
231
+ # ...
232
+ def process_file_list(watch_config, list, type)
233
+ list.each do |file_spec|
234
+
235
+ is_dir = File.directory?(file_spec)
236
+ if (is_dir && type.eql?(:directory)) || (!is_dir && type.eql?(:file))
237
+
238
+ if watchable?(file_spec, watch_config)
239
+ if self.white_listed?(file_spec)
240
+ ducklog.console " already watching: #{file_spec}"
241
+ else
242
+ self.add_to_list(:white, file_spec, watch_config)
243
+ end
244
+ else
245
+ # don't see the relevance of notifying the user about files that have already been blacklisted
246
+ # maybe change it later...
247
+ unless self.black_listed?(file_spec)
248
+ self.add_to_list(:black, file_spec, watch_config)
249
+ end
250
+ end
251
+
252
+ end
253
+
254
+ end
255
+ end
256
+
257
+ ##################################################################################
258
+ # A simple Array that holds a list of all the runnable files that have been loaded from disk after being changed.
259
+ # @return [Array]
260
+ def runnable_history
261
+ @runnable_history ||= []
262
+ return @runnable_history.uniq
263
+ end
264
+
265
+ ##################################################################################
266
+ # Assigns the runnable history array.
267
+ # @return [Array]
268
+ def runnable_history=(value)
269
+ @runnable_history = value
270
+ end
271
+
272
+ ##################################################################################
273
+ # Splits a full file specification into several parts: Rails.root, basedir, file name
274
+ #
275
+ # puts split_file_spec("/home/my_home/my_app/app/models/bike.rb") # => {:dir_spec=>"//home/my_home/my_app", :file_name=>"bike.rb", :sub_directory=>"app/models"}
276
+ #
277
+ # @param [String] file_spec A full file specification including path that also adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
278
+ # @return [Array]
279
+ def split_file_spec(file_spec)
280
+ values = {dir_spec: File.expand_path(self.root), file_spec: file_spec}
281
+ buffer = File.split(file_spec.gsub("#{values[:dir_spec]}#{File::SEPARATOR}", ""))
282
+ values[:file_name] = buffer.pop
283
+ values[:sub_directory] = File.join(buffer)
284
+ return values
285
+ end
286
+
287
+ ##################################################################################
288
+ def watch_file_spec(file_spec, watch_config)
289
+ success = false
290
+ root_dir = File.join(self.root, watch_config.watch_basedir, File::SEPARATOR)
291
+ parts = file_spec.gsub(root_dir, "").split(File::SEPARATOR)
292
+
293
+ non_blacklisted_paths = []
294
+ buffer = root_dir
295
+ parts.each do |part|
296
+ buffer = File.join(buffer, part)
297
+ if self.black_listed?(buffer)
298
+ non_blacklisted_paths = []
299
+ break
300
+ else
301
+ non_blacklisted_paths.push buffer
302
+ end
303
+ end
304
+
305
+ clear_paths = {}
306
+ non_blacklisted_paths.each do |path|
307
+ path_watch_config = nil
308
+ file_object = self.white_listed?(path)
309
+ if file_object
310
+ path_watch_config = file_object[:watch_config]
311
+ else
312
+ parent_file_object = self.find_file_object_parent(:white, path)
313
+ if parent_file_object
314
+ path_watch_config = parent_file_object[:watch_config]
315
+ else
316
+ path_watch_config = watch_config
317
+ end
318
+ end
319
+
320
+ if path_watch_config.blank?
321
+ clear_paths = {}
322
+ break
323
+ else
324
+ if self.watchable?(path, path_watch_config)
325
+ clear_paths[path] = path_watch_config
326
+ else
327
+ clear_paths = {}
328
+ break
329
+ end
330
+ end
331
+
332
+ end
333
+
334
+ #if clear_paths.blank?
335
+ #self.add_to_list(:black, file_spec, watch_config)
336
+ #else
337
+ unless clear_paths.blank?
338
+ success = true
339
+ clear_paths.each do |file_object|
340
+ unless self.white_listed?(file_object.first)
341
+ self.add_to_list(:white, file_object.first, file_object.last)
342
+ end
343
+ end
344
+ end
345
+
346
+ return success
347
+ end
348
+
349
+ ##################################################################################
350
+ # ...
351
+ def verify_parent_directory_nodes
352
+ prisoners = []
353
+
354
+ self.white_list.each do |file_object|
355
+ root_dir = File.join(self.root, file_object.last[:watch_config].watch_basedir, File::SEPARATOR)
356
+ sub_directory = file_object.first.gsub(root_dir, "").split(File::SEPARATOR)
357
+ sub_directory.pop # get rid of the current directory
358
+
359
+ sub_directory.length.times do
360
+ buffer = File.join(self.root, file_object.last[:watch_config].watch_basedir, sub_directory)
361
+
362
+ if self.black_listed?(buffer) && !prisoners.include?(buffer)
363
+ prisoners.push(buffer)
364
+ end
365
+ sub_directory.pop
366
+ end
367
+ end
368
+
369
+ prisoners.each do |file_spec|
370
+ self.white_list[file_spec] = self.black_list[file_spec]
371
+ self.black_list.delete(file_spec)
372
+ end
373
+
374
+ end
375
+
376
+ ##################################################################################
377
+ # Determines if a directory or file is watchable based on a valid {WatchConfig} object. watchable? evaluates file_spec against the included / excluded values
378
+ # of {WatchConfig#filter_set WatchConfig#filter_set}. Directories / files that pass all of the criteria are considered watchable and watchable?
379
+ # will return true.
380
+ #
381
+ # {include:file:FILTERS.md}
382
+ #
383
+ # @param [String] file_spec A file name that adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
384
+ # @param [WatchConfig] config A valid {WatchConfig} object.
385
+ # @return [Boolean] Returns true if it is watchable, otherwise, it returns false.
386
+ def watchable?(file_spec, config)
387
+ value = false
388
+
389
+ # for directories / files the approach is pretty simple
390
+ # - if excluded has been configured
391
+ # return true if NOT excluded via filter_set
392
+ #
393
+ # - else if included has been configured
394
+ # return true if included via filter_set
395
+ #
396
+ # - otherwise
397
+ # return true
398
+ begin
399
+
400
+ unless config.blank? || self.black_listed?(file_spec)
401
+
402
+ ducklog.system "watchable? enter: #{file_spec}"
403
+ if File.directory?(file_spec)
404
+
405
+ # something to keep in mind here is i am factoring in the value of config.watch_basedir
406
+ # it can make the difference of included_dirs / excluded_dirs passing or failing
407
+ # depending on configuration.
408
+ sub_directory = file_spec.gsub(File.expand_path(File.join(self.root, config.watch_basedir)), "")
409
+ sub_directory = sub_directory =~ /^#{File::SEPARATOR}/ ? sub_directory.slice(1, sub_directory.length) : sub_directory
410
+ base_name = File.basename(file_spec)
411
+
412
+ unless base_name.eql?(".") || File.basename(file_spec).eql?("..")
413
+ explicit_exclude = config.filter_set.has_excluded_dirs? ? config.filter_set.excluded_dirs?(file_spec, sub_directory) : false
414
+ ducklog.system " config.filter_set.has_excluded_dirs? #{config.filter_set.has_excluded_dirs?} explicit_exclude? #{explicit_exclude}"
415
+
416
+ unless explicit_exclude
417
+ if config.filter_set.has_included_dirs?
418
+ value = config.filter_set.included_dirs?(file_spec, sub_directory)
419
+ ducklog.system " config.filter_set.has_included_dirs? #{config.filter_set.has_included_dirs?} explicit_include? #{value}"
420
+
421
+ else
422
+ value = true
423
+ ducklog.system " directory is ok to be included: #{value}"
424
+
425
+ end
426
+ end
427
+
428
+ end
429
+
430
+ else
431
+
432
+ # check if the current directory or any of the parent directories have been black listed.
433
+ # if so, then, do not white list the file.
434
+ dir_black_listed = false
435
+ dir_name = File.dirname(file_spec).gsub(self.root, "")
436
+ dir_name = dir_name.slice(1, dir_name.length) if dir_name =~ /^#{File::SEPARATOR}/
437
+ dir_name = dir_name.split(File::SEPARATOR)
438
+
439
+ dir_name.length.times do
440
+ buffer = File.join(self.root, dir_name)
441
+ if self.black_listed?(buffer)
442
+ ducklog.system " dir_black_listed ==> #{buffer}"
443
+ dir_black_listed = true
444
+ break
445
+ end
446
+ dir_name.pop
447
+ end
448
+
449
+ unless dir_black_listed
450
+
451
+ explicit_exclude = config.filter_set.has_excluded? ? config.filter_set.excluded?(file_spec) : false
452
+ ducklog.system " config.filter_set.has_excluded? #{config.filter_set.has_excluded?} explicit_exclude? #{explicit_exclude}"
453
+
454
+ unless explicit_exclude
455
+ if config.filter_set.has_included?
456
+ value = config.filter_set.included?(file_spec)
457
+ ducklog.system " config.filter_set.included? #{config.filter_set.has_included?} explicit_include? #{value}"
458
+
459
+ else
460
+ value = true
461
+ ducklog.system " file_spec is ok to be included: #{value}"
462
+
463
+ end
464
+ end
465
+
466
+ end
467
+
468
+ end
469
+
470
+ else
471
+ ducklog.console "config is empty or file already blacklisted: #{file_spec}"
472
+ end
473
+
474
+ rescue Exception => e
475
+ ducklog.exception e
476
+ end
477
+
478
+ ducklog.system " watchable? return: #{value}"
479
+
480
+ return value
481
+ end
482
+
483
+ ##################################################################################
484
+ # A simple Array of {WatchConfig WatchConfig} objects.
485
+ # @return [Array]
486
+ def watch_configs
487
+ @watch_configs ||= []
488
+ return @watch_configs
489
+ end
490
+
491
+ ##################################################################################
492
+ # A list of file objects that have been whitelisted. Files that are whitelisted
493
+ # are considered to be a valid file and actionable. A runnable test file is an
494
+ # example of a file that is actionable.
495
+ # @return [Hash]
496
+ def white_list
497
+ @white_list ||= {}
498
+ return @white_list
499
+ end
500
+
501
+ ##################################################################################
502
+ # Determines if a file object has been whitelisted.
503
+ # @param [String] file_spec A file name that adheres to {http://ruby-doc.org/core-1.9.3/File.html#method-c-basename File.basename}.
504
+ # @return [Boolean] Returns true if the file has been whitelisted, otherwise, false.
505
+ def white_listed?(file_spec)
506
+ return self.white_list[file_spec]
507
+ end
508
+
509
+ end
510
+ end
511
+ end