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,587 @@
1
+ require 'thor/shell/basic'
2
+
3
+ module DuckTest
4
+ module FrameWork
5
+
6
+ autoload :FileManager, 'duck_test/frame_work/file_manager'
7
+ autoload :FilterSet, 'duck_test/frame_work/filter_set'
8
+ autoload :Map, 'duck_test/frame_work/map'
9
+ autoload :Queue, 'duck_test/frame_work/queue'
10
+ autoload :QueueEvent, 'duck_test/frame_work/queue_event'
11
+ autoload :RSpec, 'duck_test/frame_work/rspec/base'
12
+ autoload :Runner, 'duck_test/frame_work/runner'
13
+ autoload :TestUnit, 'duck_test/frame_work/test_unit/base'
14
+ autoload :WatchConfig, 'duck_test/frame_work/watch_config'
15
+ autoload :WatchEvent, 'duck_test/frame_work/watch_event'
16
+
17
+ ##################################################################################
18
+ # Base class for all FrameWork implementations testunit, minitest, rpsec, etc.
19
+ class Base
20
+ include DuckTest::ConfigHelper
21
+ include FileManager
22
+ include LoggerHelper
23
+ #include Thor::Actions
24
+
25
+ attr_accessor :name
26
+ attr_accessor :listener
27
+ attr_accessor :queue
28
+ attr_accessor :pre_load
29
+ attr_accessor :pre_run
30
+ attr_accessor :post_load
31
+ attr_accessor :post_run
32
+
33
+ ##################################################################################
34
+ # Initializes a testing framework object.
35
+ # @param [String, Symbol] name The name of the testing framwork: :testunit, :rspec, etc.
36
+ # @return [Base] Instance of {Base}
37
+ def initialize(name)
38
+ super()
39
+ self.name = name
40
+ end
41
+
42
+ ##################################################################################
43
+ # Clears all of the currently queue test suites. The intention is to allow a developer to override this method
44
+ # within a custom framework class to modify the behavior without having to alter {Base}
45
+ def clear_tests
46
+ ::Test::Unit::TestCase.reset
47
+ end
48
+
49
+ ##################################################################################
50
+ # Displays information about the current loaded testing framework.
51
+ # @return [String] The output message
52
+ def info
53
+ pad = 25
54
+ stats_black = self.list_stats(:black)
55
+ stats_white = self.list_stats(:white)
56
+
57
+ buffer = %(\r\n#{"DuckTest version:".rjust(pad)} #{DuckTest::VERSION})
58
+ buffer << %(\r\n#{"Ruby:".rjust(pad)} #{RUBY_VERSION})
59
+ buffer << %(\r\n#{"Rails:".rjust(pad)} #{Rails.version})
60
+ buffer << %(\r\n#{"Gem:".rjust(pad)} #{Gem::VERSION})
61
+ buffer << %(\r\n#{"Testing framework:".rjust(pad)} #{self.name})
62
+ buffer << %(\r\n#{"Autorun:".rjust(pad)} #{self.autorun ? "ON" : "OFF"})
63
+ buffer << %(\r\n#{"Blacklisted:".rjust(pad)} Directories: (#{stats_black[:dirs]}) Files: (#{stats_black[:files]}))
64
+ buffer << %(\r\n#{"Whitelisted:".rjust(pad)} Directories: (#{stats_white[:dirs]}) Files: (#{stats_white[:files]}))
65
+ buffer << %(\r\n#{"Listener Speed:".rjust(pad)} #{self.listener.speed})
66
+ buffer << %(\r\n#{"ActiveRecord Log Level:".rjust(pad)} #{DuckTest::Logger.to_severity(ActiveRecord::Base.logger.level)})
67
+ buffer << %(\r\n#{"Log Level:".rjust(pad)} #{DuckTest::Logger.to_severity(DuckTest::Logger.ducklog.level)})
68
+ buffer << %(\r\n#{"Queue Latency:".rjust(pad)} #{self.queue.latency})
69
+ buffer << %(\r\n#{"Queue Speed:".rjust(pad)} #{self.queue.speed})
70
+
71
+ return buffer
72
+ end
73
+
74
+ ##################################################################################
75
+ # ...
76
+ def listener_event(event)
77
+ # TODO add support for all of the events.
78
+
79
+ ducklog.system "listener_event: #{event.flag}"
80
+
81
+ begin
82
+ case event.flag
83
+ when :destroy
84
+ when :update
85
+ self.queue.push(event.file_spec)
86
+
87
+ when :create
88
+ file_object_parent = self.find_file_object_parent(:white, event.file_spec)
89
+ if file_object_parent
90
+ if self.watch_file_spec(event.file_spec, file_object_parent[:watch_config])
91
+ self.queue.push(event.file_spec)
92
+ else
93
+ self.add_to_list(:black, event.file_spec, file_object_parent[:watch_config])
94
+ end
95
+ else
96
+ self.add_to_list(:black, event.file_spec, file_object_parent[:watch_config])
97
+ end
98
+
99
+ when :moved
100
+ #self.queue.push(event.file_spec)
101
+
102
+ end
103
+
104
+ rescue Exception => e
105
+ ducklog.exception e
106
+ end
107
+
108
+ end
109
+
110
+ ##################################################################################
111
+ # Physically loads files from disk.
112
+ def load_files_from_disk(event)
113
+
114
+ ducklog.console "load_files_from_disk: #{event.files.length}"
115
+
116
+ event.files.each do |file|
117
+
118
+ begin
119
+
120
+ ducklog.console "==> #{File.basename(file)}"
121
+ load file
122
+
123
+ rescue Exception => e
124
+ ducklog.exception e
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ ##################################################################################
132
+ # Processes a list of file specifications and prepares them to be run
133
+ def queue_event(event)
134
+
135
+ non_runnable_files = []
136
+ runnable_files = []
137
+
138
+ begin
139
+
140
+ if self.autorun?
141
+
142
+ ducklog.console "\r\n==> preparing tests: #{event.files.length}"
143
+
144
+ event.files.each do |file_spec|
145
+
146
+ ducklog.system "\r\n ==> file_spec: #{file_spec}"
147
+
148
+ # is the file black listed?
149
+ if self.black_listed?(file_spec)
150
+ ducklog.system " black_listed? true"
151
+
152
+ else
153
+
154
+ file_object = self.white_listed?(file_spec)
155
+ ducklog.system " white_listed? #{!file_object.blank?}"
156
+
157
+ # is the file white listed and runnable?
158
+ if file_object && file_object[:watch_config].runnable?
159
+ if file_object[:is_dir]
160
+ ducklog.system " --> SHOULD HAVE RUN THE FILE, BUT, IT IS A DIRECTORY !!!! white_listed? true runnable? #{file_object[:watch_config].runnable?} is_dir: #{file_object[:is_dir]}"
161
+ else
162
+ ducklog.system " --> SHOULD RUN THE FILE !!!! white_listed? true runnable? #{file_object[:watch_config].runnable?} is_dir: #{file_object[:is_dir]}"
163
+ runnable_files.push(file_spec)
164
+ end
165
+
166
+ # is the file white listed and NON-runnable?
167
+ elsif file_object && !file_object[:watch_config].runnable?
168
+
169
+ ducklog.system " --> Need to resolve if file is associated with runnable files"
170
+ non_runnable_files.push(file_spec)
171
+ runnable_files.concat(find_runnable_files(file_spec, file_object[:watch_config]))
172
+
173
+ else
174
+
175
+ if self.white_listed?(file_spec)
176
+ ducklog.system "i don't know what to do"
177
+
178
+ else
179
+
180
+ # if we have a file that has changed and was not previously on the whitelist, then, the event must have been triggered due to some
181
+ # other action such as after being created or being moved to the containing directory.
182
+ file_object_parent = self.find_file_object_parent(:white, file_spec)
183
+
184
+ # is the file watchable?
185
+ if file_object_parent && self.watchable?(file_spec, file_object_parent[:watch_config])
186
+ ducklog.system " YES - file_object_parent true AND watchable - the file should be whitelisted"
187
+ ducklog.system " need to resolve if file is associated with runnable files"
188
+ non_runnable_files.push(file_spec)
189
+ runnable_files.concat(find_runnable_files(file_spec, file_object_parent[:watch_config]))
190
+ else
191
+ ducklog.system " NO - file_object_parent false - the file should be blacklisted"
192
+ end
193
+
194
+ end
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ ducklog.console " ==> running tests: #{runnable_files.length}"
201
+
202
+ run_fork(non_runnable_files, runnable_files, true)
203
+
204
+ else
205
+
206
+ ducklog.console self.autorun_status
207
+ self.queue.reset
208
+
209
+ end
210
+
211
+ rescue Exception => e
212
+ ducklog.exception e
213
+ end
214
+
215
+ return runnable_files
216
+ end
217
+
218
+ ##################################################################################
219
+ # Loads all runnable test files from disk and executes the run_tests method.
220
+ # @return [String] A message indicating the status of the run.
221
+ def run_all
222
+ msg = nil
223
+
224
+ non_runnable_files = []
225
+ runnable_files = []
226
+
227
+ self.white_list.each do |file_object|
228
+ unless file_object.last[:is_dir]
229
+ if file_object.last[:watch_config].runnable?
230
+ runnable_files.push(file_object.first)
231
+ else
232
+ non_runnable_files.push(file_object.first)
233
+ end
234
+ end
235
+ end
236
+
237
+ run_fork(non_runnable_files, runnable_files, true)
238
+
239
+ return msg
240
+ end
241
+
242
+ ##################################################################################
243
+ def loadable?(file_spec)
244
+ value = false
245
+
246
+ file_object = self.white_listed?(file_spec)
247
+ if file_object
248
+ value = !file_object[:watch_config].filter_set.non_loadable?(file_spec, nil)
249
+ end
250
+
251
+ return value
252
+ end
253
+
254
+ ##################################################################################
255
+ # Loads all of the runnable tests contained in the runnable_files argument and executes the run_tests method.
256
+ # @param [Array] non_runnable_files A array of non-runnable files to load.
257
+ # @param [Array] runnable_files A array of runnable files load and execute.
258
+ # @param [Boolean] force_run Forces the tests to run regardless of the current state of autorun.
259
+ # @return [NilClass]
260
+ def run_fork(non_runnable_files, runnable_files, force_run = false)
261
+
262
+ buffer = []
263
+ non_runnable_files.each do |file_spec|
264
+ if self.loadable?(file_spec)
265
+ buffer.push(file_spec)
266
+ else
267
+ self.non_loadable_history = self.non_loadable_history.concat([file_spec])
268
+ end
269
+ end
270
+
271
+ self.non_runnable_history = self.non_runnable_history.concat(buffer)
272
+ non_runnable_files = self.non_runnable_history
273
+
274
+ self.runnable_history = self.runnable_history.concat(runnable_files)
275
+
276
+ ducklog.console self.autorun_status
277
+
278
+ if runnable_files.length > 0
279
+
280
+ pid = fork do
281
+
282
+ if non_runnable_files.length > 0
283
+
284
+ clear_constants(non_runnable_files)
285
+
286
+ self.pre_load.call self, :non_runnable unless self.pre_load.blank?
287
+
288
+ load_files_from_disk(QueueEvent.new(self, non_runnable_files))
289
+
290
+ self.post_load.call self, :non_runnable unless self.post_load.blank?
291
+
292
+ end
293
+
294
+ self.pre_load.call self, :runnable unless self.pre_load.blank?
295
+
296
+ clear_tests
297
+
298
+ load_files_from_disk(QueueEvent.new(self, runnable_files))
299
+
300
+ self.post_load.call self, :runnable unless self.post_load.blank?
301
+
302
+ if self.autorun || force_run
303
+
304
+ self.pre_run.call self unless self.pre_run.blank?
305
+
306
+ run_tests
307
+
308
+ self.post_run.call self unless self.post_run.blank?
309
+
310
+ end
311
+
312
+ end
313
+
314
+ Process.wait pid
315
+
316
+ end
317
+
318
+ ActiveRecord::Base.connection_pool.verify_active_connections! if defined?(ActiveRecord::Base)
319
+
320
+ end
321
+
322
+ ##################################################################################
323
+ # Manually runs any tests pending in the queue.
324
+ # @return [String] A message indicating the status of the run.
325
+ def run_manually(expressions = nil)
326
+ msg = nil
327
+ list = []
328
+ expressions = expressions.kind_of?(Symbol) ? expressions.to_s : expressions
329
+ if expressions.kind_of?(String)
330
+ if expressions.include?(",")
331
+ expressions = expressions.split(",")
332
+
333
+ elsif expressions.include?(" ")
334
+ expressions = expressions.split(" ")
335
+
336
+ else
337
+ expressions = [expressions]
338
+
339
+ end
340
+ end
341
+ expressions = expressions.kind_of?(Regexp) ? [expressions] : expressions
342
+ expressions = expressions.kind_of?(Array) ? expressions : [expressions]
343
+
344
+ self.queue.reset
345
+
346
+ self.white_list.each do |file_object|
347
+
348
+ if !file_object.last[:is_dir] && file_object.last[:watch_config].runnable?
349
+
350
+ file_spec = file_object.first
351
+ file_name = File.basename(file_spec, ".rb")
352
+
353
+ expressions.each do |expression|
354
+
355
+ expression = expression.kind_of?(Symbol) ? expression.to_s : expression
356
+
357
+ if expression.kind_of?(String)
358
+
359
+ if file_name =~ /^#{expression}/
360
+ list.push(file_spec)
361
+ end
362
+
363
+ elsif expression.kind_of?(Regexp)
364
+
365
+ if file_name =~ expression
366
+ list.push(file_spec)
367
+ end
368
+
369
+ end
370
+
371
+ end
372
+ end
373
+
374
+ end
375
+
376
+ if list.length > 1
377
+ list.each_with_index {|item, index| STDOUT.puts "#{(index + 1).to_s.rjust(3)} - #{item.gsub(self.root, "")}"}
378
+ action = Thor::Shell::Basic.new.ask("Which file would you like to run (ENTER for all) ?")
379
+ unless action.blank?
380
+ buffer = []
381
+ index = action.to_i - 1
382
+ if (index >= 0 && index < list.length)
383
+ buffer.push(list[index])
384
+ list = buffer
385
+ else
386
+ list = []
387
+ end
388
+
389
+ end
390
+ end
391
+
392
+ if list.length > 0
393
+ self.run_fork([], list, true)
394
+ end
395
+
396
+ return msg
397
+ end
398
+
399
+ ##################################################################################
400
+ # See {Platforms::Listener#speed}
401
+ # @return [NilClass]
402
+ def set_listener_speed(value)
403
+ self.listener.speed = value
404
+ return nil
405
+ end
406
+
407
+ ##################################################################################
408
+ # See {Queue#speed}
409
+ # @return [NilClass]
410
+ def set_queue_speed(value)
411
+ self.queue.set_speed(value)
412
+ return nil
413
+ end
414
+
415
+ ##################################################################################
416
+ # See {Queue#latency}
417
+ # @return [NilClass]
418
+ def set_latency(value)
419
+ self.queue.set_latency(value)
420
+ return nil
421
+ end
422
+
423
+ ##################################################################################
424
+ # Configures and starts a testing framework.
425
+ # @param [Hash] config Hash containing all configuration values for the framework: paths, autorun, watch configurations, etc.
426
+ # @return [Base] Returns self.
427
+ def startup(config)
428
+
429
+ begin
430
+
431
+ ducklog.system "Starting framework: #{self.name}"
432
+
433
+ self.root = config[:root]
434
+ self.autorun = config[:autorun]
435
+
436
+ self.pre_load = config[:pre_load]
437
+ self.pre_run = config[:pre_run]
438
+ self.post_load = config[:post_load]
439
+ self.post_run = config[:post_run]
440
+
441
+ ducklog.console self.autorun_status
442
+
443
+ unless config[:watch_configs].blank?
444
+ config[:watch_configs].each do |watch_config|
445
+ self.watch_configs.push(watch_config)
446
+ end
447
+ end
448
+
449
+ self.build_watch_lists
450
+
451
+ self.start
452
+
453
+ rescue Exception => e
454
+ ducklog.exception e
455
+ end
456
+
457
+ return self
458
+ end
459
+
460
+ ##################################################################################
461
+ # TODO implement a shutdown. future version might implement the ability to shutdown a framework
462
+ # and switch to another on the fly. at this point, i haven't even tried any test code to verify each of the native
463
+ # notifiers will fully stop watching files. I would not want to switch framework and have an orphan notifier trigger
464
+ # an unwanted event.
465
+ def shutdown
466
+ end
467
+
468
+ ##################################################################################
469
+ # Starts a FileManager session. start will instantiate a file watcher / listener for the current platform and begin watching
470
+ # files based on configuration specified in config/environments. Also, an event queue is created to listen for and act upon
471
+ # changes to watched files.
472
+ # @return [NilClass]
473
+ def start
474
+ self.queue = Queue.new
475
+ self.queue.autorun = self.autorun
476
+ self.queue.queue_event {|event| queue_event(event)}
477
+ self.queue.start
478
+
479
+ if self.is_linux? && self.available?
480
+ self.listener = Platforms::Linux::Listener.new
481
+
482
+ elsif self.is_mac? && self.available?
483
+ self.listener = Platforms::Mac::Listener.new
484
+
485
+ elsif self.is_windows? && self.available?
486
+ self.listener = Platforms::Windows::Listener.new
487
+
488
+ else
489
+ self.listener = Platforms::Generic::Listener.new
490
+
491
+ end
492
+
493
+ unless self.listener.blank?
494
+
495
+ self.listener.listener_event {|event| listener_event(event)}
496
+
497
+ ducklog.console "Loading watchlist for: #{self.name}"
498
+ ducklog.system "========================================================="
499
+
500
+ self.white_list.each do |file|
501
+ file_spec = file.first
502
+ ducklog.system "watch: #{file_spec}"
503
+ self.listener.watch(file_spec)
504
+ end
505
+
506
+ if self.white_list.length > 0
507
+ stats = self.list_stats(:white)
508
+ ducklog.console "Watching (#{stats[:dirs]}) directories (#{stats[:files]}) files..."
509
+ else
510
+ ducklog.console "You are not watching any files. Add DuckTest.config block to config/environments/test.rb to watch files..."
511
+ end
512
+
513
+ self.listener.start
514
+
515
+ ducklog.console "For help, type: 'duck' at the command prompt"
516
+
517
+ end
518
+
519
+ return nil
520
+ end
521
+
522
+ ##################################################################################
523
+ # Stops the current instance of FileManager
524
+ # @return [NilClass]
525
+ def stop
526
+ # need to stop the queue as well.
527
+ self.queue.stop
528
+ self.listener.stop
529
+ return nil
530
+ end
531
+
532
+ ##################################################################################
533
+ # Toggles the current state of autorun for the current FrameWork instance including it's queue.
534
+ # @return [String] Returns a message indicating the current autorun status.
535
+ def toggle_autorun
536
+ self.autorun = self.autorun ? false : true
537
+ self.queue.autorun = self.autorun
538
+ return self.autorun_status
539
+ end
540
+
541
+ ##################################################################################
542
+ # Returns the current total number of directories and files for the black or white list.
543
+ # @return [Hash] Returns the stats for a list.
544
+ def list_stats(target)
545
+ stats = {dirs: 0, files: 0}
546
+ list = target.eql?(:black) ? self.black_list : self.white_list
547
+
548
+ if list.length > 0
549
+ list.each do |file_object|
550
+ if file_object.last[:is_dir]
551
+ stats[:dirs] += 1
552
+ else
553
+ stats[:files] += 1
554
+ end
555
+ end
556
+ end
557
+
558
+ return stats
559
+ end
560
+
561
+ ##################################################################################
562
+ # I'm pretty sure I will be able to deprecate this method since now I am running the tests
563
+ # within a fork. keeping it in case I need it later
564
+ def clear_constants(file_list)
565
+
566
+ #ActiveRecord::Base.s.verify_active_connections! if defined?(ActiveRecord::Base)
567
+
568
+ file_list.each do |file_spec|
569
+
570
+ begin
571
+
572
+ file_name = File.basename(file_spec, ".*")
573
+ ducklog.console "removing constant: #{file_name} ==> #{file_name.classify}"
574
+ Object.send(:remove_const, file_name.classify.to_sym)
575
+
576
+ rescue Exception => e
577
+ # for now, I have decided not to warn the developer about constants that probably won't be there most of the time
578
+ # ducklog.exception e
579
+ end
580
+
581
+ end
582
+
583
+ end
584
+
585
+ end
586
+ end
587
+ end