class Minitest::Bisect

Constants

RUBY

Borrowed from rake

SHH
VERSION

Attributes

culprits[RW]
failures[RW]
mode[RW]
seen_bad[RW]
tainted[RW]
tainted?[RW]

Public Class Methods

new() click to toggle source
# File lib/minitest/bisect.rb, line 64
def initialize
  self.culprits = []
  self.failures = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }
end
run(files) click to toggle source
# File lib/minitest/bisect.rb, line 57
def self.run files
  new.run files
rescue => e
  warn e.message
  exit 1
end

Public Instance Methods

bisect_files(files) click to toggle source
# File lib/minitest/bisect.rb, line 102
def bisect_files files
  self.mode = :files

  files, flags = files.partition { |arg| File.file? arg }
  rb_flags, mt_flags = flags.partition { |arg| arg =~ /^-I/ }
  mt_flags += ["--server", $$]

  puts "reproducing..."
  system "#{build_files_cmd files, rb_flags, mt_flags} #{SHH}"
  abort "Reproduction run passed? Aborting. Try running with MTB_VERBOSE=2 to verify." unless tainted?
  puts "reproduced"

  found, count = files.find_minimal_combination_and_count do |test|
    puts "# of culprit files: #{test.size}"

    system "#{build_files_cmd test, rb_flags, mt_flags} #{SHH}"

    self.tainted?
  end

  puts
  puts "Minimal files found in #{count} steps:"
  puts
  cmd = build_files_cmd found, rb_flags, mt_flags
  puts cmd
  cmd
end
bisect_methods(cmd) click to toggle source
# File lib/minitest/bisect.rb, line 130
def bisect_methods cmd
  self.mode = :methods

  time_it "reproducing...", build_methods_cmd(cmd)

  unless tainted? then
    $stderr.puts "Reproduction run passed? Aborting."
    abort "Try running with MTB_VERBOSE=2 to verify."
  end

  bad = map_failures

  raise "Nothing to verify against because every test failed. Aborting." if
    culprits.empty? && seen_bad

  time_it "verifying...", build_methods_cmd(cmd, [], bad)

  new_bad = map_failures

  if bad == new_bad then
    warn "Tests fail by themselves. This may not be an ordering issue."
  end

  # culprits populated by initial reproduction via minitest/server
  found, count = culprits.find_minimal_combination_and_count do |test|
    prompt = "# of culprit methods: #{test.size}"

    time_it prompt, build_methods_cmd(cmd, test, bad)

    self.tainted?
  end

  puts
  puts "Minimal methods found in #{count} steps:"
  puts
  puts "Culprit methods: %p" % [found]
  puts
  cmd = build_methods_cmd cmd, found, bad
  puts cmd.sub(/--server \d+/, "")
  puts
  cmd
end
build_files_cmd(culprits, rb, mt) click to toggle source
# File lib/minitest/bisect.rb, line 188
def build_files_cmd culprits, rb, mt
  reset

  tests = culprits.flatten.compact.map { |f| %(require "./#{f}") }.join " ; "

  %(#{RUBY} #{rb.shelljoin} -e '#{tests}' -- #{mt.map(&:to_s).shelljoin})
end
build_methods_cmd(cmd, culprits = [], bad = nil) click to toggle source
# File lib/minitest/bisect.rb, line 220
def build_methods_cmd cmd, culprits = [], bad = nil
  reset

  if bad then
    re = build_re culprits + bad

    cmd += " -n \"#{re}\"" if bad
  end

  if ENV["MTB_VERBOSE"].to_i >= 1 then
    puts
    puts cmd
    puts
  end

  cmd
end
build_re(bad) click to toggle source
# File lib/minitest/bisect.rb, line 196
def build_re bad
  re = []

  # bad by class, you perv
  bbc = bad.map { |s| s.split(/#/, 2) }.group_by(&:first)

  bbc.each do |klass, methods|
    methods = methods.map(&:last).flatten.uniq.map { |method|
      re_escape method
    }

    methods = methods.join "|"
    re << /#{re_escape klass}#(?:#{methods})/.to_s[7..-2] # (?-mix:...)
  end

  re = re.join("|").to_s.gsub(/-mix/, "")

  "/^(?:#{re})$/"
end
map_failures() click to toggle source
# File lib/minitest/bisect.rb, line 180
def map_failures
  # from: {"file.rb"=>{"Class"=>["test_method1", "test_method2"]}}
  #   to: ["Class#test_method1", "Class#test_method2"]
  failures.values.map { |h|
    h.map { |k,vs| vs.map { |v| "#{k}##{v}" } }
  }.flatten.sort
end
minitest_result(file, klass, method, fails, assertions, time) click to toggle source
# File lib/minitest/bisect.rb, line 245
def minitest_result file, klass, method, fails, assertions, time
  fails.reject! { |fail| Minitest::Skip === fail }

  if mode == :methods then
    if fails.empty? then
      culprits << "#{klass}##{method}" unless seen_bad # UGH
    else
      self.seen_bad = true
    end
  end

  return if fails.empty?

  self.tainted = true
  self.failures[file][klass] << method
end
minitest_start() click to toggle source

Server Methods:

# File lib/minitest/bisect.rb, line 241
def minitest_start
  self.failures.clear
end
re_escape(str) click to toggle source
# File lib/minitest/bisect.rb, line 216
def re_escape str
  str.gsub(/([`'"!?&\[\]\(\)\{\}\|\+])/, '\\\\\1')
end
reset() click to toggle source
# File lib/minitest/bisect.rb, line 69
def reset
  self.seen_bad = false
  self.tainted  = false
  failures.clear
  # not clearing culprits on purpose
end
run(args) click to toggle source
# File lib/minitest/bisect.rb, line 76
def run args
  Minitest::Server.run self

  cmd = nil

  if :until_I_have_negative_filtering_in_minitest != 0 then
    mt_flags = args.dup
    expander = Minitest::Bisect::PathExpander.new mt_flags

    files = expander.process
    rb_flags = expander.rb_flags
    mt_flags += ["--server", $$]

    cmd = bisect_methods build_files_cmd(files, rb_flags, mt_flags)
  else
    cmd = bisect_methods bisect_files args
  end

  puts "Final reproduction:"
  puts

  system cmd.sub(/--server \d+/, "")
ensure
  Minitest::Server.stop
end
time_it(prompt, cmd) click to toggle source
# File lib/minitest/bisect.rb, line 173
def time_it prompt, cmd
  print prompt
  t0 = Time.now
  system "#{cmd} #{SHH}"
  puts " in %.2f sec" % (Time.now - t0)
end