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