Class: Minitest::Bisect

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

Constant Summary collapse

VERSION =
"1.2.1"
SHH =
ENV["MTB_VERBOSE"].to_i >= 2 ? nil : " &> /dev/null"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBisect

Returns a new instance of Bisect.



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

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.



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

def culprits
  @culprits
end

#failuresObject

Returns the value of attribute failures.



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

def failures
  @failures
end

#modeObject

Returns the value of attribute mode.



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

def mode
  @mode
end

#seen_badObject

Returns the value of attribute seen_bad.



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

def seen_bad
  @seen_bad
end

#taintedObject Also known as: tainted?

Returns the value of attribute tainted.



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

def tainted
  @tainted
end

Class Method Details

.run(files) ⇒ Object



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

def self.run files
  new.run files
end

Instance Method Details

#bisect_files(files) ⇒ Object



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

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." 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



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

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 "#"

  # culprits populated by initial reproduction via minitest/server
  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



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

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



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/minitest/bisect.rb', line 117

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

  if bad then
    re = []

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

    bbc.each do |klass, methods|
      methods = methods.map(&:last).flatten.uniq.map { |method|
        method.gsub(/([`'"!?&\[\]\(\)\|\+])/, '\\\\\1')
      }

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

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

    cmd += " -n \"/^(?:#{re})$/\"" if bad
  end

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

  cmd
end

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



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/minitest/bisect.rb', line 155

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

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

#minitest_startObject

Server Methods:



151
152
153
# File 'lib/minitest/bisect.rb', line 151

def minitest_start
  self.failures.clear
end

#resetObject



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

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

#run(files) ⇒ Object



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

def run files
  Minitest::Server.run self

  cmd = nil

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

    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.sub(/--server \d+/, "")
ensure
  Minitest::Server.stop
end