Object
UnitDiff makes reading Test::Unit output easy and fun. Instead of a confusing jumble of text with nearly unnoticable changes like this:
1) Failure: test_to_gpoints(RouteTest) [test/unit/route_test.rb:29]: <"new GPolyline([\n new GPoint( 47.00000, -122.00000),\n new GPoint( 46.5000 0, -122.50000),\n new GPoint( 46.75000, -122.75000),\n new GPoint( 46.00000, -123.00000)])"> expected but was <"new Gpolyline([\n new GPoint( 47.00000, -122.00000),\n new GPoint( 46.5000 0, -122.50000),\n new GPoint( 46.75000, -122.75000),\n new GPoint( 46.00000, -123.00000)])">.
You get an easy-to-read diff output like this:
1) Failure: test_to_gpoints(RouteTest) [test/unit/route_test.rb:29]: 1c1 < new GPolyline([ --- > new Gpolyline([
test.rb | unit_diff [options] options: -b ignore whitespace differences -c contextual diff -h show usage -k keep temp diff files around -l prefix line numbers on the diffs -u unified diff [default] -p plain diff -v display version
Handy wrapper for UnitDiff#unit_diff.
# File lib/unit_diff.rb, line 57 57: def self.unit_diff 58: trap 'INT' do exit 1 end 59: puts UnitDiff.new.unit_diff 60: end
# File lib/unit_diff.rb, line 232 232: def diff expect, butwas 233: output = nil 234: 235: Tempfile.open("expect") do |a| 236: a.write(massage(expect)) 237: a.rewind 238: Tempfile.open("butwas") do |b| 239: b.write(massage(butwas)) 240: b.rewind 241: 242: diff_flags = $p ? "" : $c ? "-c" : "-u" 243: diff_flags += " -b" if $b 244: 245: result = `#{DIFF} #{diff_flags} #{a.path} #{b.path}` 246: result.sub!(/^\-\-\- .+/, "--- expected") 247: result.sub!(/^\+\+\+ .+/, "+++ actual") 248: 249: output = if result.empty? then 250: "[no difference--suspect ==]" 251: else 252: result.split(/\n/) 253: end 254: 255: if $k then 256: warn "moving #{a.path} to #{a.path}.keep" 257: File.rename a.path, a.path + ".keep" 258: warn "moving #{b.path} to #{b.path}.keep" 259: File.rename b.path, b.path + ".keep" 260: end 261: end 262: end 263: 264: output 265: end
# File lib/unit_diff.rb, line 267 267: def massage(data) 268: # unescape newlines, strip <> from entire string 269: data = data.join 270: data = data.gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/, '0xXXXXXX') + "\n" 271: data += "\n" unless data[1] == \n\ 272: data 273: end
Parses a single diff recording the header and what was expected, and what was actually obtained.
# File lib/unit_diff.rb, line 121 121: def parse_diff(result) 122: header = [] 123: expect = [] 124: butwas = [] 125: footer = [] 126: state = :header 127: 128: until result.empty? do 129: case state 130: when :header then 131: header << result.shift 132: state = :expect if result.first =~ /^<|^Expected/ 133: when :expect then 134: case result.first 135: when /^Expected (.*?) to equal (.*?):$/ then 136: expect << $1 137: butwas << $2 138: state = :footer 139: result.shift 140: when /^Expected (.*?), not (.*)$/ then 141: expect << $1 142: butwas << $2 143: state = :footer 144: result.shift 145: when /^Expected (.*?)$/ then 146: expect << "#{$1}\n" 147: result.shift 148: when /^to equal / then 149: state = :spec_butwas 150: bw = result.shift.sub(/^to equal (.*):?$/, '\1') 151: butwas << bw 152: else 153: state = :butwas if result.first.sub!(/ expected( but was|, not)/, '') 154: expect << result.shift 155: end 156: when :butwas then 157: butwas = result[0..1] 158: result.clear 159: when :spec_butwas then 160: if result.first =~ /^\s+\S+ at |^:\s*$/ 161: state = :footer 162: else 163: butwas << result.shift 164: end 165: when :footer then 166: butwas.last.sub!(/:$/, '') 167: footer = result.map {|l| l.chomp } 168: result.clear 169: else 170: raise "unknown state #{state}" 171: end 172: end 173: 174: return header, expect, nil, footer if butwas.empty? 175: 176: expect.last.chomp! 177: expect.first.sub!(/^<\"/, '') 178: expect.last.sub!(/\">$/, '') 179: 180: butwas.last.chomp! 181: butwas.last.chop! if butwas.last =~ /\.$/ 182: butwas.first.sub!( /^<\"/, '') 183: butwas.last.sub!(/\">$/, '') 184: 185: return header, expect, butwas, footer 186: end
# File lib/unit_diff.rb, line 62 62: def parse_input(input, output) 63: current = [] 64: data = [] 65: data << current 66: print_lines = true 67: 68: term = "\nFinished".split(//).map { |c| c[0] } 69: term_length = term.size 70: 71: old_sync = output.sync 72: output.sync = true 73: while line = input.gets 74: case line 75: when /^(Loaded suite|Started|# Running tests:)/ then 76: print_lines = true 77: output.puts line 78: chars = [] 79: while c = input.getc do 80: output.putc c 81: chars << c 82: tail = chars[-term_length..1] 83: break if chars.size >= term_length and tail == term 84: end 85: output.puts input.gets # the rest of "Finished in..." 86: output.puts 87: next 88: when /^\s*$/, /^\(?\s*\d+\) (Failure|Error):/, /^\d+\)/ then 89: print_lines = false 90: current = [] 91: data << current 92: when /^Finished in \d/ then 93: print_lines = false 94: end 95: output.puts line if print_lines 96: current << line 97: end 98: output.sync = old_sync 99: data = data.reject { |o| o == ["\n"] or o.empty? } 100: footer = data.pop 101: 102: data.map do |result| 103: break if result.any? { |l| l =~ / expected( but was|, not)/ } 104: 105: header = result.find do |l| 106: l =~ /^\(?\s*\d+\) (Failure|Error):/ 107: end 108: 109: break unless header 110: 111: message_index = result.index(header) + 2 112: 113: result[message_index..1] = result[message_index..1].join 114: end 115: 116: return data, footer 117: end
Scans Test::Unit output input looking for comparison failures and makes them easily readable by passing them through diff.
# File lib/unit_diff.rb, line 192 192: def unit_diff(input=ARGF, output=$stdout) 193: $b = false unless defined? $b 194: $c = false unless defined? $c 195: $k = false unless defined? $k 196: $u = true unless defined? $u 197: $p = false unless defined? $p 198: 199: data, footer = self.parse_input(input, output) 200: 201: output = [] 202: 203: # Output 204: data.each do |result| 205: if result.first =~ /Error/ then 206: output.push result.join('') 207: next 208: end 209: 210: prefix, expect, butwas, result_footer = parse_diff(result) 211: 212: output.push prefix.compact.map {|line| line.strip}.join("\n") 213: 214: if butwas then 215: output.push self.diff(expect, butwas) 216: 217: output.push result_footer 218: output.push '' 219: else 220: output.push expect.join('') 221: end 222: end 223: 224: if footer then 225: footer.shift if footer.first.strip.empty? 226: output.push footer.compact.map {|line| line.strip}.join("\n") 227: end 228: 229: return output.flatten.join("\n") 230: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.