Class: DuckTest::FrameWork::Base

Inherits:
Object
  • Object
show all
Includes:
ConfigHelper, FileManager, LoggerHelper
Defined in:
lib/duck_test/frame_work/base.rb

Overview

Base class for all FrameWork implementations testunit, minitest, rpsec, etc.

Direct Known Subclasses

RSpec::FrameWork, TestUnit::FrameWork

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods included from LoggerHelper

#ducklog

Methods included from FileManager

#add_to_list, #black_list, #black_listed?, #build_watch_lists, #find_file_object_parent, #find_runnable_files, #non_loadable_history, #non_loadable_history=, #non_runnable_history, #non_runnable_history=, #process_file_list, #runnable_history, #runnable_history=, #split_file_spec, #verify_parent_directory_nodes, #watch_configs, #watch_file_spec, #watchable?, #white_list, #white_listed?

Methods included from Platforms::OSHelpers

#available?, #current_os, #is_linux?, #is_mac?, #is_windows?

Methods included from ConfigHelper

#autorun, #autorun=, #autorun?, #autorun_status, #root, #root=, #runnable_basedir, #runnable_basedir=, #watch_basedir, #watch_basedir=

Constructor Details

- (Base) initialize(name)

Initializes a testing framework object.

Parameters:

  • name (String, Symbol)

    The name of the testing framwork: :testunit, :rspec, etc.



37
38
39
40
# File 'lib/duck_test/frame_work/base.rb', line 37

def initialize(name)
  super()
  self.name = name
end

Instance Attribute Details

- (Object) listener

Returns the value of attribute listener



26
27
28
# File 'lib/duck_test/frame_work/base.rb', line 26

def listener
  @listener
end

- (Object) name

include Thor::Actions



25
26
27
# File 'lib/duck_test/frame_work/base.rb', line 25

def name
  @name
end

- (Object) post_load

Returns the value of attribute post_load



30
31
32
# File 'lib/duck_test/frame_work/base.rb', line 30

def post_load
  @post_load
end

- (Object) post_run

Returns the value of attribute post_run



31
32
33
# File 'lib/duck_test/frame_work/base.rb', line 31

def post_run
  @post_run
end

- (Object) pre_load

Returns the value of attribute pre_load



28
29
30
# File 'lib/duck_test/frame_work/base.rb', line 28

def pre_load
  @pre_load
end

- (Object) pre_run

Returns the value of attribute pre_run



29
30
31
# File 'lib/duck_test/frame_work/base.rb', line 29

def pre_run
  @pre_run
end

- (Object) queue

Returns the value of attribute queue



27
28
29
# File 'lib/duck_test/frame_work/base.rb', line 27

def queue
  @queue
end

Class Method Details

+ (Boolean) ok_to_run

Global flag used to prevent Test::Unit::Runner from running automatically after exiting the console

Returns:

  • (Boolean)


134
135
136
137
# File 'lib/duck_test/frame_work/base.rb', line 134

def self.ok_to_run
  @@ok_to_run = false unless defined?(@@ok_to_run)
  return @@ok_to_run
end

+ (Object) ok_to_run=(value)



139
140
141
# File 'lib/duck_test/frame_work/base.rb', line 139

def self.ok_to_run=(value)
  @@ok_to_run = value
end

Instance Method Details

- (Object) clear_constants(file_list)

I'm pretty sure I will be able to deprecate this method since now I am running the tests within a fork. keeping it in case I need it later



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/duck_test/frame_work/base.rb', line 607

def clear_constants(file_list)

  #ActiveRecord::Base.s.verify_active_connections! if defined?(ActiveRecord::Base)

  file_list.each do |file_spec|

    begin

      file_name = File.basename(file_spec, ".*")
      ducklog.console "removing constant: #{file_name} ==> #{file_name.classify}"
      Object.send(:remove_const, file_name.classify.to_sym)

    rescue Exception => e
    # for now, I have decided not to warn the developer about constants that probably won't be there most of the time
    # ducklog.exception e
    end

  end

end

- (Object) clear_tests

Clears all of the currently queue test suites. The intention is to allow a developer to override this method within a custom framework class to modify the behavior without having to alter DuckTest::FrameWork::Base



45
46
47
# File 'lib/duck_test/frame_work/base.rb', line 45

def clear_tests
  ::Test::Unit::TestCase.reset
end

- (String) info

Displays information about the current loaded testing framework.

Returns:

  • (String)

    The output message



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/duck_test/frame_work/base.rb', line 52

def info
  pad = 25
  stats_black = self.list_stats(:black)
  stats_white = self.list_stats(:white)

  buffer = %(\r\n#{"DuckTest version:".rjust(pad)} #{DuckTest::VERSION})
  buffer << %(\r\n#{"Ruby:".rjust(pad)} #{RUBY_VERSION})
  buffer << %(\r\n#{"Rails:".rjust(pad)} #{Rails.version})
  buffer << %(\r\n#{"Gem:".rjust(pad)} #{Gem::VERSION})
  buffer << %(\r\n#{"Testing framework:".rjust(pad)} #{self.name})
  buffer << %(\r\n#{"Autorun:".rjust(pad)} #{self.autorun ? "ON" : "OFF"})
  buffer << %(\r\n#{"Blacklisted:".rjust(pad)} Directories: (#{stats_black[:dirs]})  Files: (#{stats_black[:files]}))
  buffer << %(\r\n#{"Whitelisted:".rjust(pad)} Directories: (#{stats_white[:dirs]})  Files: (#{stats_white[:files]}))
  buffer << %(\r\n#{"Listener Speed:".rjust(pad)} #{self.listener.speed})
  buffer << %(\r\n#{"ActiveRecord Log Level:".rjust(pad)} #{DuckTest::Logger.to_severity(ActiveRecord::Base.logger.level)})
  buffer << %(\r\n#{"Log Level:".rjust(pad)} #{DuckTest::Logger.to_severity(DuckTest::Logger.ducklog.level)})
  buffer << %(\r\n#{"Queue Latency:".rjust(pad)} #{self.queue.latency})
  buffer << %(\r\n#{"Queue Speed:".rjust(pad)} #{self.queue.speed})

  return buffer
end

- (Hash) list_stats(target)

Returns the current total number of directories and files for the black or white list.

Returns:

  • (Hash)

    Returns the stats for a list.



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/duck_test/frame_work/base.rb', line 587

def list_stats(target)
  stats = {dirs: 0, files: 0}
  list = target.eql?(:black) ? self.black_list : self.white_list
  
  if list.length > 0
    list.each do |file_object|
      if file_object.last[:is_dir]
        stats[:dirs] += 1
      else
        stats[:files] += 1
      end
    end
  end
  
  return stats
end

- (Object) listener_event(event)



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/duck_test/frame_work/base.rb', line 76

def listener_event(event)
  # TODO add support for all of the events.

  ducklog.system "listener_event: #{event.flag}"

  begin
    case event.flag
    when :destroy
    when :update
      self.queue.push(event.file_spec)

    when :create
      file_object_parent = self.find_file_object_parent(:white, event.file_spec)
      if file_object_parent
        if self.watch_file_spec(event.file_spec, file_object_parent[:watch_config])
          self.queue.push(event.file_spec)
        else
          self.add_to_list(:black, event.file_spec, file_object_parent[:watch_config])
        end
      else
        self.add_to_list(:black, event.file_spec, file_object_parent[:watch_config])
      end

    when :moved
      #self.queue.push(event.file_spec)

    end

  rescue Exception => e
    ducklog.exception e
  end

end

- (Object) load_files_from_disk(event)

Physically loads files from disk.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/duck_test/frame_work/base.rb', line 112

def load_files_from_disk(event)

  ducklog.console "load_files_from_disk: #{event.files.length}"

  event.files.each do |file|

    begin

      ducklog.console "==> #{File.basename(file)}"
      load file

    rescue Exception => e
      ducklog.exception e
    end

  end

end

- (Boolean) loadable?(file_spec)

Returns:

  • (Boolean)


255
256
257
258
259
260
261
262
263
264
# File 'lib/duck_test/frame_work/base.rb', line 255

def loadable?(file_spec)
  value = false

  file_object = self.white_listed?(file_spec)
  if file_object
     value = !file_object[:watch_config].filter_set.non_loadable?(file_spec, nil)
  end

  return value
end

- (Object) queue_event(event)

Processes a list of file specifications and prepares them to be run



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/duck_test/frame_work/base.rb', line 145

def queue_event(event)

  non_runnable_files = []
  runnable_files = []

  begin

    if self.autorun?

      ducklog.console "\r\n==> preparing tests: #{event.files.length}"

      event.files.each do |file_spec|

        ducklog.system "\r\n  ==> file_spec: #{file_spec}"

        # is the file black listed?
        if self.black_listed?(file_spec)
          ducklog.system "    black_listed? true"

        else

          file_object = self.white_listed?(file_spec)
          ducklog.system "    white_listed? #{!file_object.blank?}"

          # is the file white listed and runnable?
          if file_object && file_object[:watch_config].runnable?
            if file_object[:is_dir]
              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]}"
            else
              ducklog.system "    --> SHOULD RUN THE FILE !!!! white_listed? true   runnable? #{file_object[:watch_config].runnable?} is_dir: #{file_object[:is_dir]}"
              runnable_files.push(file_spec)
            end

          # is the file white listed and NON-runnable?
          elsif file_object && !file_object[:watch_config].runnable?

            ducklog.system "    --> Need to resolve if file is associated with runnable files"
            non_runnable_files.push(file_spec)
            runnable_files.concat(find_runnable_files(file_spec, file_object[:watch_config]))

          else

            if self.white_listed?(file_spec)
              ducklog.system "i don't know what to do"

            else

              # 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
              # other action such as after being created or being moved to the containing directory.
              file_object_parent = self.find_file_object_parent(:white, file_spec)

              # is the file watchable?
              if file_object_parent && self.watchable?(file_spec, file_object_parent[:watch_config])
                ducklog.system "      YES - file_object_parent true AND watchable  - the file should be whitelisted"
                ducklog.system "        need to resolve if file is associated with runnable files"
                non_runnable_files.push(file_spec)
                runnable_files.concat(find_runnable_files(file_spec, file_object_parent[:watch_config]))
              else
                ducklog.system "      NO - file_object_parent false - the file should be blacklisted"
              end

            end
          end
        end

      end

      ducklog.console "  ==> running tests: #{runnable_files.length}"

      run_fork(non_runnable_files, runnable_files, true)

    else

      ducklog.console self.autorun_status
      self.queue.reset

    end

  rescue Exception => e
    ducklog.exception e
  end

  return runnable_files
end

- (String) run_all

Loads all runnable test files from disk and executes the run_tests method.

Returns:

  • (String)

    A message indicating the status of the run.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/duck_test/frame_work/base.rb', line 233

def run_all
  msg = nil

  non_runnable_files = []
  runnable_files = []

  self.white_list.each do |file_object|
    unless file_object.last[:is_dir]
      if file_object.last[:watch_config].runnable?
        runnable_files.push(file_object.first)
      else
        non_runnable_files.push(file_object.first)
      end
    end
  end

  run_fork(non_runnable_files, runnable_files, true)

  return msg
end

- (NilClass) run_fork(non_runnable_files, runnable_files, force_run = false)

Loads all of the runnable tests contained in the runnable_files argument and executes the run_tests method.

Parameters:

  • non_runnable_files (Array)

    A array of non-runnable files to load.

  • runnable_files (Array)

    A array of runnable files load and execute.

  • force_run (Boolean) (defaults to: false)

    Forces the tests to run regardless of the current state of autorun.

Returns:

  • (NilClass)


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/duck_test/frame_work/base.rb', line 272

def run_fork(non_runnable_files, runnable_files, force_run = false)

  buffer = []
  non_runnable_files.each do |file_spec|
    if self.loadable?(file_spec)
      buffer.push(file_spec)
    else
      self.non_loadable_history = self.non_loadable_history.concat([file_spec])
    end
  end

  self.non_runnable_history = self.non_runnable_history.concat(buffer)
  non_runnable_files = self.non_runnable_history

  self.runnable_history = self.runnable_history.concat(runnable_files)

  ducklog.console self.autorun_status

  if runnable_files.length > 0

    pid = fork do

      if non_runnable_files.length > 0

        clear_constants(non_runnable_files)

        self.pre_load.call self, :non_runnable unless self.pre_load.blank?

        load_files_from_disk(QueueEvent.new(self, non_runnable_files))

        self.post_load.call self, :non_runnable unless self.post_load.blank?

      end

      self.pre_load.call self, :runnable unless self.pre_load.blank?

      clear_tests

      load_files_from_disk(QueueEvent.new(self, runnable_files))

      self.post_load.call self, :runnable unless self.post_load.blank?

      if self.autorun || force_run

        self.pre_run.call self unless self.pre_run.blank?

        self.class.ok_to_run = true

        run_tests

        # prevents tests from autorunning when the console exits.
        self.class.ok_to_run = false

        self.post_run.call self unless self.post_run.blank?

      end

    end

    Process.wait pid

  end
  
  if defined?(ActiveRecord::Base)
    ::ActiveRecord::Base.clear_active_connections!
    ::ActiveRecord::Base.establish_connection
  end

end

- (String) run_manually(expressions = nil)

Manually runs any tests pending in the queue.

Returns:

  • (String)

    A message indicating the status of the run.



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/duck_test/frame_work/base.rb', line 345

def run_manually(expressions = nil)
  msg = nil
  list = []
  expressions = expressions.kind_of?(Symbol) ? expressions.to_s : expressions
  if expressions.kind_of?(String)
    if expressions.include?(",")
      expressions = expressions.split(",")

    elsif expressions.include?(" ")
      expressions = expressions.split(" ")

    else
      expressions = [expressions]

    end
  end
  expressions = expressions.kind_of?(Regexp) ? [expressions] : expressions
  expressions = expressions.kind_of?(Array) ? expressions : [expressions]

  self.queue.reset
  
  self.white_list.each do |file_object|

    if !file_object.last[:is_dir] && file_object.last[:watch_config].runnable?

      file_spec = file_object.first
      file_name = File.basename(file_spec, ".rb")

      expressions.each do |expression|

        expression = expression.kind_of?(Symbol) ? expression.to_s : expression

        if expression.kind_of?(String)

          if file_name =~ /^#{expression}/
            list.push(file_spec)
          end

        elsif expression.kind_of?(Regexp)

          if file_name =~ expression
            list.push(file_spec)
          end

        end

      end
    end

  end

  if list.length > 1
    list.each_with_index {|item, index| STDOUT.puts "#{(index + 1).to_s.rjust(3)} - #{item.gsub(self.root, "")}"}
    action = Thor::Shell::Basic.new.ask("Which file would you like to run (ENTER for all) ?")
    unless action.blank?
      buffer = []
      index = action.to_i - 1
      if (index >= 0 && index < list.length)
        buffer.push(list[index])
        list = buffer
      else
        list = []
      end
      
    end
  end
  
  if list.length > 0
    self.run_fork([], list, true)
  end

  return msg
end

- (NilClass) set_latency(value)

Returns:

  • (NilClass)


444
445
446
447
448
449
450
# File 'lib/duck_test/frame_work/base.rb', line 444

def set_latency(value)
  begin
    self.queue.set_latency(value.to_f)
  rescue
  end
  return nil
end

- (NilClass) set_listener_speed(value)

Returns:

  • (NilClass)


422
423
424
425
426
427
428
# File 'lib/duck_test/frame_work/base.rb', line 422

def set_listener_speed(value)
  begin
    self.listener.speed = value.to_f
  rescue
  end
  return nil
end

- (NilClass) set_queue_speed(value)

Returns:

  • (NilClass)


433
434
435
436
437
438
439
# File 'lib/duck_test/frame_work/base.rb', line 433

def set_queue_speed(value)
  begin
    self.queue.set_speed(value.to_f)
  rescue
  end
  return nil
end

- (Object) shutdown

TODO implement a shutdown. future version might implement the ability to shutdown a framework and switch to another on the fly. at this point, i haven't even tried any test code to verify each of the native notifiers will fully stop watching files. I would not want to switch framework and have an orphan notifier trigger an unwanted event.



508
509
# File 'lib/duck_test/frame_work/base.rb', line 508

def shutdown
end

- (NilClass) start

Starts a FileManager session. start will instantiate a file watcher / listener for the current platform and begin watching files based on configuration specified in config/environments. Also, an event queue is created to listen for and act upon changes to watched files.

Returns:

  • (NilClass)


516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/duck_test/frame_work/base.rb', line 516

def start
  self.queue = Queue.new
  self.queue.autorun = self.autorun
  self.queue.queue_event {|event| queue_event(event)}
  self.queue.start

  if self.is_linux? && self.available?
    self.listener = Platforms::Linux::Listener.new

  elsif self.is_mac? && self.available?
    self.listener = Platforms::Mac::Listener.new

  elsif self.is_windows? && self.available?
    self.listener = Platforms::Windows::Listener.new

  else
    self.listener = Platforms::Generic::Listener.new

  end

  unless self.listener.blank?

    self.listener.listener_event {|event| listener_event(event)}

    ducklog.console "Loading watchlist for: #{self.name}"
    ducklog.system "========================================================="

    self.white_list.each do |file|
      file_spec = file.first
      ducklog.system "watch: #{file_spec}"
      self.listener.watch(file_spec)
    end

    if self.white_list.length > 0
      stats = self.list_stats(:white)
      ducklog.console "Watching (#{stats[:dirs]}) directories (#{stats[:files]}) files..."
    else
      ducklog.console "You are not watching any files.  Add DuckTest.config block to config/environments/test.rb to watch files..."
    end

    self.listener.start
    
    ducklog.console "For help, type: 'duck' at the command prompt"

  end

  return nil
end

- (Base) startup(config)

Configures and starts a testing framework.

Parameters:

  • config (Hash)

    Hash containing all configuration values for the framework: paths, autorun, watch configurations, etc.

Returns:

  • (Base)

    Returns self.



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/duck_test/frame_work/base.rb', line 456

def startup(config)

  begin

    ducklog.system "Starting framework: #{self.name}"

    self.root = config[:root]
    self.autorun = config[:autorun]

    self.pre_load = config[:pre_load]
    self.pre_run = config[:pre_run]
    self.post_load = config[:post_load]
    self.post_run = config[:post_run]

    ducklog.console self.autorun_status

    unless config[:watch_configs].blank?
      config[:watch_configs].each do |watch_config|
        self.watch_configs.push(watch_config)
      end
    end

    self.build_watch_lists

    self.start

    RunCommands.load

    self.set_latency(RunCommands.config[:latency]) unless RunCommands.config[:latency].blank?
    self.set_listen_speed(RunCommands.config[:listen_speed]) unless RunCommands.config[:listen_speed].blank?
    self.set_queue_speed(RunCommands.config[:speed]) unless RunCommands.config[:speed].blank?

    unless RunCommands.config[:ar].blank?
      ActiveRecord::Base.logger.level = Logger.to_severity(RunCommands.config[:ar].to_sym)
    end

    unless RunCommands.config[:ll].blank?
      Logger.log_level = RunCommands.config[:ll]
    end

  rescue Exception => e
    ducklog.exception e
  end

  return self
end

- (NilClass) stop

Stops the current instance of FileManager

Returns:

  • (NilClass)


568
569
570
571
572
573
# File 'lib/duck_test/frame_work/base.rb', line 568

def stop
  # need to stop the queue as well.
  self.queue.stop
  self.listener.stop
  return nil
end

- (String) toggle_autorun

Toggles the current state of autorun for the current FrameWork instance including it's queue.

Returns:

  • (String)

    Returns a message indicating the current autorun status.



578
579
580
581
582
# File 'lib/duck_test/frame_work/base.rb', line 578

def toggle_autorun
  self.autorun = self.autorun ? false : true
  self.queue.autorun = self.autorun
  return self.autorun_status
end