duck_test 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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