micron 0.5.0
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.
- checksums.yaml +7 -0
 - data/Gemfile +19 -0
 - data/Gemfile.lock +88 -0
 - data/Rakefile +40 -0
 - data/VERSION +1 -0
 - data/bin/micron +4 -0
 - data/lib/micron.rb +29 -0
 - data/lib/micron/app.rb +127 -0
 - data/lib/micron/app/options.rb +73 -0
 - data/lib/micron/assertion.rb +10 -0
 - data/lib/micron/fork_runner.rb +55 -0
 - data/lib/micron/minitest.rb +45 -0
 - data/lib/micron/proc_runner.rb +114 -0
 - data/lib/micron/rake.rb +29 -0
 - data/lib/micron/reporter.rb +30 -0
 - data/lib/micron/reporter/console.rb +146 -0
 - data/lib/micron/reporter/coverage.rb +37 -0
 - data/lib/micron/runner.rb +95 -0
 - data/lib/micron/runner/backtrace_filter.rb +39 -0
 - data/lib/micron/runner/clazz.rb +45 -0
 - data/lib/micron/runner/clazz19.rb +24 -0
 - data/lib/micron/runner/debug.rb +22 -0
 - data/lib/micron/runner/exception_info.rb +16 -0
 - data/lib/micron/runner/fork_worker.rb +185 -0
 - data/lib/micron/runner/forking_clazz.rb +40 -0
 - data/lib/micron/runner/liveness_checker.rb +40 -0
 - data/lib/micron/runner/liveness_checker/ping.rb +65 -0
 - data/lib/micron/runner/liveness_checker/pong.rb +36 -0
 - data/lib/micron/runner/method.rb +124 -0
 - data/lib/micron/runner/parallel_clazz.rb +135 -0
 - data/lib/micron/runner/proc_clazz.rb +48 -0
 - data/lib/micron/runner/process_reaper.rb +98 -0
 - data/lib/micron/runner/shim.rb +68 -0
 - data/lib/micron/runner/test_file.rb +79 -0
 - data/lib/micron/test_case.rb +36 -0
 - data/lib/micron/test_case/assertions.rb +701 -0
 - data/lib/micron/test_case/lifecycle_hooks.rb +74 -0
 - data/lib/micron/test_case/redir_logging.rb +85 -0
 - data/lib/micron/test_case/teardown_coverage.rb +13 -0
 - data/lib/micron/util/ex.rb +23 -0
 - data/lib/micron/util/io.rb +54 -0
 - data/lib/micron/util/thread_dump.rb +29 -0
 - data/micron.gemspec +97 -0
 - metadata +184 -0
 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Micron
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 3 
     | 
    
         
            +
                class LivenessChecker
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  class Pong
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    include Debug
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    attr_reader :thread
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    def initialize(reader, writer)
         
     | 
| 
      
 12 
     | 
    
         
            +
                      @thread = Thread.new(reader, writer) { |reader, writer|
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                        Thread.current[:name] = "ponger"
         
     | 
| 
      
 15 
     | 
    
         
            +
                        debug "thread started"
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 18 
     | 
    
         
            +
                          writer.sync = true
         
     | 
| 
      
 19 
     | 
    
         
            +
                          while line = reader.readline
         
     | 
| 
      
 20 
     | 
    
         
            +
                            debug "got ping request, replying"
         
     | 
| 
      
 21 
     | 
    
         
            +
                            writer.puts "pong"
         
     | 
| 
      
 22 
     | 
    
         
            +
                          end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                        rescue Exception => ex
         
     | 
| 
      
 25 
     | 
    
         
            +
                          debug "caught error: #{Micron.dump_ex(ex)}"
         
     | 
| 
      
 26 
     | 
    
         
            +
                        end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                        debug "thread exiting from #{$$}"
         
     | 
| 
      
 29 
     | 
    
         
            +
                      }
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  end # Pong
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,124 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            require 'hitimes'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Micron
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Method
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_reader :clazz, :name, :durations
         
     | 
| 
      
 9 
     | 
    
         
            +
                  attr_accessor :passed, :ex, :assertions
         
     | 
| 
      
 10 
     | 
    
         
            +
                  attr_reader :stdout, :stderr
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(clazz, name)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @passed    = false
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @durations = {}
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @assertions = 0
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    @clazz     = clazz
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @name      = name
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def run
         
     | 
| 
      
 22 
     | 
    
         
            +
                    out, err = Micron.capture_io {
         
     | 
| 
      
 23 
     | 
    
         
            +
                      run_test()
         
     | 
| 
      
 24 
     | 
    
         
            +
                    }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @stdout = out
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @stderr = err
         
     | 
| 
      
 27 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  # Execute the actual test
         
     | 
| 
      
 31 
     | 
    
         
            +
                  def run_test
         
     | 
| 
      
 32 
     | 
    
         
            +
                    t = nil
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 35 
     | 
    
         
            +
                      t = clazz.create
         
     | 
| 
      
 36 
     | 
    
         
            +
                      if t.respond_to? :micron_method= then
         
     | 
| 
      
 37 
     | 
    
         
            +
                        t.micron_method = self # minitest compat shim
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                      time(:setup)   { setup(t) }
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                      time(:runtime) { t.send(name) }
         
     | 
| 
      
 43 
     | 
    
         
            +
                      self.passed = true
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    rescue *PASSTHROUGH_EXCEPTIONS
         
     | 
| 
      
 46 
     | 
    
         
            +
                      raise
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    rescue Exception => e
         
     | 
| 
      
 49 
     | 
    
         
            +
                      self.passed = false
         
     | 
| 
      
 50 
     | 
    
         
            +
                      self.ex     = ExceptionInfo.new(e)
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 53 
     | 
    
         
            +
                      self.assertions += t._assertions if not t.nil?
         
     | 
| 
      
 54 
     | 
    
         
            +
                      time(:teardown) {
         
     | 
| 
      
 55 
     | 
    
         
            +
                        teardown(t) if not t.nil?
         
     | 
| 
      
 56 
     | 
    
         
            +
                      }
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def passed?
         
     | 
| 
      
 61 
     | 
    
         
            +
                    passed
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def skipped?
         
     | 
| 
      
 65 
     | 
    
         
            +
                    ex.kind_of?(Skip)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def failed?
         
     | 
| 
      
 69 
     | 
    
         
            +
                    !passed
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def status
         
     | 
| 
      
 73 
     | 
    
         
            +
                    if skipped? then
         
     | 
| 
      
 74 
     | 
    
         
            +
                      "skip"
         
     | 
| 
      
 75 
     | 
    
         
            +
                    elsif passed? then
         
     | 
| 
      
 76 
     | 
    
         
            +
                      "pass"
         
     | 
| 
      
 77 
     | 
    
         
            +
                    else
         
     | 
| 
      
 78 
     | 
    
         
            +
                      "fail"
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  # Get the total duration of this method's run (setup + runtime + teardown)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  def total_duration
         
     | 
| 
      
 84 
     | 
    
         
            +
                    n = 0.0
         
     | 
| 
      
 85 
     | 
    
         
            +
                    @durations.values.each{ |d| n += d }
         
     | 
| 
      
 86 
     | 
    
         
            +
                    n
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  private
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  # Time the given block of code and enter it into the log
         
     | 
| 
      
 93 
     | 
    
         
            +
                  def time(name, &block)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    @durations[name] = Hitimes::Interval.measure(&block)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  # Call setup methods
         
     | 
| 
      
 98 
     | 
    
         
            +
                  def setup(t)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    t.before_setup
         
     | 
| 
      
 100 
     | 
    
         
            +
                    t.setup
         
     | 
| 
      
 101 
     | 
    
         
            +
                    t.after_setup
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                  # Call teardown methods
         
     | 
| 
      
 105 
     | 
    
         
            +
                  def teardown(t)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    %w{before_teardown teardown after_teardown}.each do |hook|
         
     | 
| 
      
 107 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 108 
     | 
    
         
            +
                        t.send(hook)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      rescue *PASSTHROUGH_EXCEPTIONS
         
     | 
| 
      
 110 
     | 
    
         
            +
                        raise
         
     | 
| 
      
 111 
     | 
    
         
            +
                      rescue Exception => e
         
     | 
| 
      
 112 
     | 
    
         
            +
                        self.passed = false
         
     | 
| 
      
 113 
     | 
    
         
            +
                        if self.ex.nil? then
         
     | 
| 
      
 114 
     | 
    
         
            +
                          self.ex = e
         
     | 
| 
      
 115 
     | 
    
         
            +
                        else
         
     | 
| 
      
 116 
     | 
    
         
            +
                          self.ex = [ self.ex, ExceptionInfo.new(e) ].flatten!
         
     | 
| 
      
 117 
     | 
    
         
            +
                        end
         
     | 
| 
      
 118 
     | 
    
         
            +
                      end
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,135 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            require "thwait"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require "micron/runner/process_reaper"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Micron
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                # Base class for parallel Clazz implementations
         
     | 
| 
      
 10 
     | 
    
         
            +
                class ParallelClazz < Clazz
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def run
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # spawn tests in separate processes
         
     | 
| 
      
 14 
     | 
    
         
            +
                    tests = []
         
     | 
| 
      
 15 
     | 
    
         
            +
                    debug "spawning #{methods.size} methods"
         
     | 
| 
      
 16 
     | 
    
         
            +
                    methods.each do |method|
         
     | 
| 
      
 17 
     | 
    
         
            +
                      tests << spawn_test(method)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    # wait for all test methods to return
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @methods = wait_for_tests(tests)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    # collect results
         
     | 
| 
      
 24 
     | 
    
         
            +
                    # @methods = collect_results(finished)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    debug "collected #{@methods.size} methods"
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  private
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  # Wait for all test processes to complete, rerunning failures if needed
         
     | 
| 
      
 32 
     | 
    
         
            +
                  #
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # @param [ForkWorker] tests
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @return [Array<Method>]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def wait_for_tests(tests)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    # OUT.puts "waiting for tests"
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                    results = []
         
     | 
| 
      
 41 
     | 
    
         
            +
                    watchers = []
         
     | 
| 
      
 42 
     | 
    
         
            +
                    hang_watchers = []
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    test_queue = Queue.new
         
     | 
| 
      
 45 
     | 
    
         
            +
                    tests.each { |t| test_queue.push(t) }
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    meta_watcher = Thread.new {
         
     | 
| 
      
 48 
     | 
    
         
            +
                      # thread which will make sure we're watching all tests, including
         
     | 
| 
      
 49 
     | 
    
         
            +
                      # any that get respawned
         
     | 
| 
      
 50 
     | 
    
         
            +
                      Thread.current[:name] = "meta_watcher"
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                      while true
         
     | 
| 
      
 53 
     | 
    
         
            +
                        test = test_queue.pop # blocking
         
     | 
| 
      
 54 
     | 
    
         
            +
                        debug "creating watcher for #{test.pid}"
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                        watchers << Thread.new(test) { |test|
         
     | 
| 
      
 57 
     | 
    
         
            +
                          Thread.current[:name] = "watcher-#{test.pid}"
         
     | 
| 
      
 58 
     | 
    
         
            +
                          debug "start"
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                          while true
         
     | 
| 
      
 61 
     | 
    
         
            +
                            begin
         
     | 
| 
      
 62 
     | 
    
         
            +
                              status = test.wait2.to_i
         
     | 
| 
      
 63 
     | 
    
         
            +
                              # puts "process #{test.pid} exited with status #{status}"
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                              if status == 0 then
         
     | 
| 
      
 66 
     | 
    
         
            +
                                method = collect_result(test)
         
     | 
| 
      
 67 
     | 
    
         
            +
                                Micron.runner.report(:end_method, method)
         
     | 
| 
      
 68 
     | 
    
         
            +
                                results << method
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                              elsif status == 6 || status == 4 || status == 9 then
         
     | 
| 
      
 71 
     | 
    
         
            +
                                # segfault/coredump due to coverage
         
     | 
| 
      
 72 
     | 
    
         
            +
                                # puts "process #{test.pid} returned error"
         
     | 
| 
      
 73 
     | 
    
         
            +
                                method = test.context
         
     | 
| 
      
 74 
     | 
    
         
            +
                                test_queue << spawn_test(method) # new watcher thread will be spawned
         
     | 
| 
      
 75 
     | 
    
         
            +
                                debug "respawned failed test: #{method.clazz.name}##{method.name}"
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                              else
         
     | 
| 
      
 78 
     | 
    
         
            +
                                debug
         
     | 
| 
      
 79 
     | 
    
         
            +
                                debug "== UNKNOWN ERROR! =="
         
     | 
| 
      
 80 
     | 
    
         
            +
                                debug "STATUS: #{status}"
         
     | 
| 
      
 81 
     | 
    
         
            +
                                if !test.stdout.empty? then
         
     | 
| 
      
 82 
     | 
    
         
            +
                                  debug "STDOUT:"
         
     | 
| 
      
 83 
     | 
    
         
            +
                                  debug test.stdout
         
     | 
| 
      
 84 
     | 
    
         
            +
                                else
         
     | 
| 
      
 85 
     | 
    
         
            +
                                  debug "NO STDOUT"
         
     | 
| 
      
 86 
     | 
    
         
            +
                                end
         
     | 
| 
      
 87 
     | 
    
         
            +
                                if !test.stderr.empty? then
         
     | 
| 
      
 88 
     | 
    
         
            +
                                  debug "STDERR:"
         
     | 
| 
      
 89 
     | 
    
         
            +
                                  debug test.stderr
         
     | 
| 
      
 90 
     | 
    
         
            +
                                else
         
     | 
| 
      
 91 
     | 
    
         
            +
                                  debug "NO STDERR"
         
     | 
| 
      
 92 
     | 
    
         
            +
                                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                            rescue Errno::ECHILD
         
     | 
| 
      
 97 
     | 
    
         
            +
                              debug "retrying wait2"
         
     | 
| 
      
 98 
     | 
    
         
            +
                              next # retry - should get cached status
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                            rescue Exception => ex
         
     | 
| 
      
 101 
     | 
    
         
            +
                              debug "caught: #{Micron.dump_ex(ex)}"
         
     | 
| 
      
 102 
     | 
    
         
            +
                            end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                            break # break loop by default
         
     | 
| 
      
 105 
     | 
    
         
            +
                          end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                          debug "exit thread"
         
     | 
| 
      
 109 
     | 
    
         
            +
                          watchers.delete(Thread.current)
         
     | 
| 
      
 110 
     | 
    
         
            +
                        }
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                        # create another thread to make sure the process didn't hang after
         
     | 
| 
      
 113 
     | 
    
         
            +
                        # throwing an error on stderr
         
     | 
| 
      
 114 
     | 
    
         
            +
                        hang_watchers << ProcessReaper.create(test)
         
     | 
| 
      
 115 
     | 
    
         
            +
                      end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                      debug "exiting"
         
     | 
| 
      
 118 
     | 
    
         
            +
                    }
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    while !watchers.empty? || !test_queue.empty?
         
     | 
| 
      
 121 
     | 
    
         
            +
                      watchers.reject!{ |w| !w.alive? } # prune dead threads
         
     | 
| 
      
 122 
     | 
    
         
            +
                      ThreadsWait.all_waits(*watchers)
         
     | 
| 
      
 123 
     | 
    
         
            +
                    end
         
     | 
| 
      
 124 
     | 
    
         
            +
                    debug "all watcher threads finished for #{self.name}"
         
     | 
| 
      
 125 
     | 
    
         
            +
                    meta_watcher.kill
         
     | 
| 
      
 126 
     | 
    
         
            +
                    hang_watchers.each{ |t| t.kill }
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                    return results
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
              end
         
     | 
| 
      
 134 
     | 
    
         
            +
            end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,48 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            module Micron
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                # A Clazz implementation which will fork/exec before running each test method
         
     | 
| 
      
 6 
     | 
    
         
            +
                class ProcClazz < ParallelClazz
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  private
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  # Spawn a process for the given method
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # @param [Method] method
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # @param [Boolean] dispose_output       If true, throw away stdout/stderr (default: true)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # @return [Hash]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def spawn_test(method, dispose_output=true)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    # fork/exec once per method, synchronously
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ENV["MICRON_TEST_CLASS"] = method.clazz.name
         
     | 
| 
      
 19 
     | 
    
         
            +
                    ENV["MICRON_TEST_METHOD"] = method.name.to_s
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    ForkWorker.new(method) {
         
     | 
| 
      
 22 
     | 
    
         
            +
                      exec("bundle exec micron --runmethod")
         
     | 
| 
      
 23 
     | 
    
         
            +
                    }.run
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  # Because we fork exec, we can't just read the back from a pipe. Instead,
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # the child process dumps it to a file and we load it from there.
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # @param [ForkWorker] test
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # @return [Method]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  def collect_result(test)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    results = []
         
     | 
| 
      
 34 
     | 
    
         
            +
                    data_file = File.join(ENV["MICRON_PATH"], "#{test.pid}.data")
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    File.open(data_file) do |f|
         
     | 
| 
      
 37 
     | 
    
         
            +
                      while !f.eof
         
     | 
| 
      
 38 
     | 
    
         
            +
                        results << Marshal.load(f) # read Method from child via file
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                    File.delete(data_file)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    return results.first # should always be just one
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            module Micron
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 4 
     | 
    
         
            +
                class ProcessReaper
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  extend Debug
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def self.create(test)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    Thread.new(test) { |test|
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                      Thread.current[:name] = "reaper-#{test.pid}"
         
     | 
| 
      
 12 
     | 
    
         
            +
                      debug "started"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                      err = 0
         
     | 
| 
      
 15 
     | 
    
         
            +
                      sel = 0
         
     | 
| 
      
 16 
     | 
    
         
            +
                      open = false
         
     | 
| 
      
 17 
     | 
    
         
            +
                      while true
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                        if test.wait_nonblock then
         
     | 
| 
      
 20 
     | 
    
         
            +
                          # process exited!
         
     | 
| 
      
 21 
     | 
    
         
            +
                          break
         
     | 
| 
      
 22 
     | 
    
         
            +
                        end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                          if err > 10 then # should wait about 3 sec for the proc to exit
         
     | 
| 
      
 27 
     | 
    
         
            +
                            debug "Unleash the reaper!!"
         
     | 
| 
      
 28 
     | 
    
         
            +
                            Process.kill(9, test.pid)
         
     | 
| 
      
 29 
     | 
    
         
            +
                            break
         
     | 
| 
      
 30 
     | 
    
         
            +
                          end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                          if !open then
         
     | 
| 
      
 33 
     | 
    
         
            +
                            if IO.select([test.err], nil, nil, 1).nil? then
         
     | 
| 
      
 34 
     | 
    
         
            +
                              sel += 1
         
     | 
| 
      
 35 
     | 
    
         
            +
                              debug "select = #{sel}"
         
     | 
| 
      
 36 
     | 
    
         
            +
                              # if sel > 3 then
         
     | 
| 
      
 37 
     | 
    
         
            +
                              #   # thread dead??
         
     | 
| 
      
 38 
     | 
    
         
            +
                              #   debug "not ready yet?! Unleash the reaper!! #{test.pid}"
         
     | 
| 
      
 39 
     | 
    
         
            +
                              #   Process.kill(9, test.pid)
         
     | 
| 
      
 40 
     | 
    
         
            +
                              #   break
         
     | 
| 
      
 41 
     | 
    
         
            +
                              # end
         
     | 
| 
      
 42 
     | 
    
         
            +
                              err += 1 if err > 0
         
     | 
| 
      
 43 
     | 
    
         
            +
                              debug "err = #{err}"
         
     | 
| 
      
 44 
     | 
    
         
            +
                              Thread.pass
         
     | 
| 
      
 45 
     | 
    
         
            +
                              next
         
     | 
| 
      
 46 
     | 
    
         
            +
                            end
         
     | 
| 
      
 47 
     | 
    
         
            +
                            debug "opened err io"
         
     | 
| 
      
 48 
     | 
    
         
            +
                            open = true
         
     | 
| 
      
 49 
     | 
    
         
            +
                          end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                          str = test.err.read_nonblock(1024*16)
         
     | 
| 
      
 52 
     | 
    
         
            +
                          debug str if !str.nil?
         
     | 
| 
      
 53 
     | 
    
         
            +
                          if !str.nil? &&
         
     | 
| 
      
 54 
     | 
    
         
            +
                            (str.include?("malloc: *** error for object") ||
         
     | 
| 
      
 55 
     | 
    
         
            +
                             str.include?("Segmentation fault")) then
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                            debug "looks like we got an error"
         
     | 
| 
      
 58 
     | 
    
         
            +
                            err = 1
         
     | 
| 
      
 59 
     | 
    
         
            +
                          end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                        rescue EOFError
         
     | 
| 
      
 62 
     | 
    
         
            +
                          debug "caught EOFError"
         
     | 
| 
      
 63 
     | 
    
         
            +
                          err = 1
         
     | 
| 
      
 64 
     | 
    
         
            +
                          # see if it exited
         
     | 
| 
      
 65 
     | 
    
         
            +
                          if test.wait_nonblock then
         
     | 
| 
      
 66 
     | 
    
         
            +
                            # exited, we're all good..
         
     | 
| 
      
 67 
     | 
    
         
            +
                            debug "hang watcher exiting since it looks like process exited also"
         
     | 
| 
      
 68 
     | 
    
         
            +
                            break
         
     | 
| 
      
 69 
     | 
    
         
            +
                          end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                        rescue Errno::EWOULDBLOCK
         
     | 
| 
      
 72 
     | 
    
         
            +
                          # debug "would block?!"
         
     | 
| 
      
 73 
     | 
    
         
            +
                          open = false
         
     | 
| 
      
 74 
     | 
    
         
            +
                          next
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                        rescue Exception => ex
         
     | 
| 
      
 77 
     | 
    
         
            +
                          debug "caught another ex?!"
         
     | 
| 
      
 78 
     | 
    
         
            +
                          debug Micron.dump_ex(ex, true)
         
     | 
| 
      
 79 
     | 
    
         
            +
                          err = 1
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                        end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                        if err > 0 then
         
     | 
| 
      
 84 
     | 
    
         
            +
                          err += 1
         
     | 
| 
      
 85 
     | 
    
         
            +
                          sleep 0.1
         
     | 
| 
      
 86 
     | 
    
         
            +
                        end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                      end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                      reapers.delete(Thread.current)
         
     | 
| 
      
 91 
     | 
    
         
            +
                      debug "thread exiting"
         
     | 
| 
      
 92 
     | 
    
         
            +
                    }
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,68 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            require "tempfile"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Micron
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Runner
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Shim
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  # Create a temp shim path
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def self.setup
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    ruby_path = `which ruby`.strip
         
     | 
| 
      
 12 
     | 
    
         
            +
                    shim = <<-EOF
         
     | 
| 
      
 13 
     | 
    
         
            +
            #!#{ruby_path}
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            if ENV["BUNDLE_GEMFILE"] then
         
     | 
| 
      
 16 
     | 
    
         
            +
              require "bundler"
         
     | 
| 
      
 17 
     | 
    
         
            +
              Bundler.setup(:default, :development)
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            require "easycov"
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            EasyCov.path = ENV["EASYCOV_PATH"]
         
     | 
| 
      
 23 
     | 
    
         
            +
            EasyCov.filters << EasyCov::IGNORE_GEMS << EasyCov::IGNORE_STDLIB
         
     | 
| 
      
 24 
     | 
    
         
            +
            EasyCov.start
         
     | 
| 
      
 25 
     | 
    
         
            +
            EasyCov.install_exit_hook()
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            script = ARGV.shift
         
     | 
| 
      
 28 
     | 
    
         
            +
            $0 = script
         
     | 
| 
      
 29 
     | 
    
         
            +
            require script
         
     | 
| 
      
 30 
     | 
    
         
            +
            EOF
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    @shim_dir = Dir.mktmpdir("micron-shim-")
         
     | 
| 
      
 33 
     | 
    
         
            +
                    file = File.join(@shim_dir, "ruby")
         
     | 
| 
      
 34 
     | 
    
         
            +
                    File.open(file, 'w') do |f|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      f.write(shim)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                    File.chmod(0777, file)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    EasyCov::Filters.stdlib_paths # make sure this gets cached in env
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  end # setup
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  # Clean up any existing shim dirs. This should be called only when the
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # master process exists (i.e. Micron::App)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  def self.cleanup!
         
     | 
| 
      
 45 
     | 
    
         
            +
                    # return
         
     | 
| 
      
 46 
     | 
    
         
            +
                    Dir.glob(File.join(Dir.tmpdir, "micron-shim-*")).each do |d|
         
     | 
| 
      
 47 
     | 
    
         
            +
                      FileUtils.rm_rf(d)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  # Wrap the given call with our shim PATH. Any calls to ruby will be
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # redirected to our script to enable coverage collection.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def self.wrap(&block)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    # enable shim
         
     | 
| 
      
 55 
     | 
    
         
            +
                    ENV["EASYCOV_PATH"] = EasyCov.path
         
     | 
| 
      
 56 
     | 
    
         
            +
                    old_path = ENV["PATH"]
         
     | 
| 
      
 57 
     | 
    
         
            +
                    ENV["PATH"] = "#{@shim_dir}:#{old_path}"
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    # call orig method
         
     | 
| 
      
 60 
     | 
    
         
            +
                    block.call()
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    # disable shim
         
     | 
| 
      
 63 
     | 
    
         
            +
                    ENV["PATH"] = old_path
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
            end
         
     |