Class: Minitest::Bisect

Inherits:
Object
  • Object
show all
Defined in:
lib/minitest/bisect.rb

Constant Summary collapse

VERSION =
"1.0.0"
SHH =
" &> /dev/null"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBisect

Returns a new instance of Bisect.



16
17
18
19
# File 'lib/minitest/bisect.rb', line 16

def initialize
  self.culprits = []
  self.failures = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
end

Instance Attribute Details

#culpritsObject

Returns the value of attribute culprits.



9
10
11
# File 'lib/minitest/bisect.rb', line 9

def culprits
  @culprits
end

#failuresObject

Returns the value of attribute failures.



9
10
11
# File 'lib/minitest/bisect.rb', line 9

def failures
  @failures
end

#modeObject

Returns the value of attribute mode.



9
10
11
# File 'lib/minitest/bisect.rb', line 9

def mode
  @mode
end

#seen_badObject

Returns the value of attribute seen_bad.



9
10
11
# File 'lib/minitest/bisect.rb', line 9

def seen_bad
  @seen_bad
end

#taintedObject Also known as: tainted?

Returns the value of attribute tainted.



9
10
11
# File 'lib/minitest/bisect.rb', line 9

def tainted
  @tainted
end

Class Method Details

.run(files) ⇒ Object



12
13
14
# File 'lib/minitest/bisect.rb', line 12

def self.run files
  new.run files
end

Instance Method Details

#bisect_files(files) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/minitest/bisect.rb', line 51

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 += ["-s", $$]

  puts "reproducing..."
  system "#{build_files_cmd files, rb_flags, mt_flags} #{SHH}"
  abort "Reproduction run passed? Aborting." 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) ⇒ Object



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
# File 'lib/minitest/bisect.rb', line 79

def bisect_methods cmd
  self.mode = :methods

  puts "reproducing..."
  system "#{build_methods_cmd cmd} #{SHH}"
  abort "Reproduction run passed? Aborting." unless tainted?
  puts "reproduced"

  # from: {"file.rb"=>{"Class"=>["test_method"]}} to: "Class#test_method"
  bad = failures.values.first.to_a.join "#"

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

    system "#{build_methods_cmd cmd, test, bad} #{SHH}"

    self.tainted?
  end

  puts
  puts "Minimal methods found in #{count} steps:"
  puts
  cmd = build_methods_cmd cmd, found, bad
  puts cmd
  puts
  cmd
end

#build_files_cmd(culprits, rb, mt) ⇒ Object



107
108
109
110
111
112
113
# File 'lib/minitest/bisect.rb', line 107

def build_files_cmd culprits, rb, mt
  reset

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

  %(ruby #{rb.shelljoin} -e '#{tests}' -- #{mt.shelljoin})
end

#build_methods_cmd(cmd, culprits = [], bad = nil) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/minitest/bisect.rb', line 115

def build_methods_cmd cmd, culprits = [], bad = nil
  reset

  if bad then
    re = []

    bbc = (culprits + [bad]).map { |s| s.split(/#/) }.group_by(&:first)
    bbc.each do |klass, methods|
      methods = methods.map(&:last).flatten

      re << /#{klass}##{Regexp.union(methods)}/
    end

    re = Regexp.union(re).to_s.gsub(/-mix/, "")

    cmd += " -n '/^#{re}$/'" if bad
  end

  cmd
end

#resetObject



21
22
23
24
25
26
# File 'lib/minitest/bisect.rb', line 21

def reset
  self.seen_bad = false
  self.tainted  = false
  failures.clear
  # not clearing culprits on purpose
end

#result(file, klass, method, fails, assertions, time) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/minitest/bisect.rb', line 136

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

  unless fails.empty?
    self.tainted = true
    self.failures[file][klass] << method
  end
end

#run(files) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/minitest/bisect.rb', line 28

def run files
  Minitest::Server.run self

  cmd = nil

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

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

  puts "Final reproduction:"
  puts

  system cmd
ensure
  Minitest::Server.stop
end