Class: UnitDiff

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

Overview

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([

Usage

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
  -v display version

Constant Summary collapse

WINDOZE =
/win32/ =~ RUBY_PLATFORM
DIFF =
if WINDOZE
  'diff.exe'
else
  if system("gdiff", __FILE__, __FILE__)
    'gdiff' # solaris and kin suck
  else
    'diff'
  end
end

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.unit_diffObject

Handy wrapper for UnitDiff#unit_diff.



55
56
57
58
# File 'lib/unit_diff.rb', line 55

def self.unit_diff
  trap 'INT' do exit 1 end
  puts UnitDiff.new.unit_diff
end

Instance Method Details

#diff(expect, butwas) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/unit_diff.rb', line 239

def diff expect, butwas
  output = nil

  Tempfile.open("expect") do |a|
    a.write(massage(expect))
    a.rewind
    Tempfile.open("butwas") do |b|
      b.write(massage(butwas))
      b.rewind

      diff_flags = $u ? "-u" : $c ? "-c" : ""
      diff_flags += " -b" if $b

      result = `#{DIFF} #{diff_flags} #{a.path} #{b.path}`
      output = if result.empty? then
                 "[no difference--suspect ==]"
               else
                 result.split(/\n/)
               end

      if $k then
        warn "moving #{a.path} to #{a.path}.keep"
        File.rename a.path, a.path + ".keep"
        warn "moving #{b.path} to #{b.path}.keep"
        File.rename b.path, b.path + ".keep"
      end
    end
  end

  output
end

#massage(data) ⇒ Object



271
272
273
274
275
276
277
278
# File 'lib/unit_diff.rb', line 271

def massage(data)
  count = 0
  # unescape newlines, strip <> from entire string
  data = data.join
  data = data.gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX') + "\n"
  data += "\n" unless data[-1] == ?\n
  data
end

#parse_diff(result) ⇒ Object

Parses a single diff recording the header and what was expected, and what was actually obtained.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/unit_diff.rb', line 125

def parse_diff(result)
  header = []
  expect = []
  butwas = []
  footer = []
  found = false
  state = :header

  until result.empty? do
    case state
    when :header then
      header << result.shift
      state = :expect if result.first =~ /^<|^Expected/
    when :expect then
      case result.first
      when /^Expected (.*?) to equal (.*?):$/ then
        expect << $1
        butwas << $2
        state = :footer
        result.shift
      when /^Expected (.*?), not (.*)$/m then
        expect << $1
        butwas << $2
        state = :footer
        result.shift
      when /^Expected (.*?)$/ then
        expect << "#{$1}\n"
        result.shift
      when /^to equal / then
        state = :spec_butwas
        bw = result.shift.sub(/^to equal (.*):?$/, '\1')
        butwas << bw
      else
        state = :butwas if result.first.sub!(/ expected( but was|, not)/, '')
        expect << result.shift
      end
    when :butwas then
      butwas = result[0..-1]
      result.clear
    when :spec_butwas then
      if result.first =~ /^\s+\S+ at |^:\s*$/
        state = :footer
      else
        butwas << result.shift
      end
    when :footer then
      butwas.last.sub!(/:$/, '')
      footer = result.map {|l| l.chomp }
      result.clear
    else
      raise "unknown state #{state}"
    end
  end

  return header, expect, nil, footer if butwas.empty?

  expect.last.chomp!
  expect.first.sub!(/^<\"/, '')
  expect.last.sub!(/\">$/, '')

  butwas.last.chomp!
  butwas.last.chop! if butwas.last =~ /\.$/
  butwas.first.sub!( /^<\"/, '')
  butwas.last.sub!(/\">$/, '')

  return header, expect, butwas, footer
end

#parse_input(input, output) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/unit_diff.rb', line 60

def parse_input(input, output)
  current = []
  data = []
  data << current
  print_lines = true

  term = "\nFinished".split(//).map { |c| c[0] }
  term_length = term.size

  old_sync = output.sync
  output.sync = true
  while line = input.gets
    case line
    when /^(Loaded suite|Started)/ then
      print_lines = true
      output.puts line
      chars = []
      while c = input.getc do
        begin
          output.putc c
        rescue Errno::EINVAL
          # weird bug that only happens on some windows machines
          # <-> using print does not show color on windows
          output.print c
        end
        chars << c
        tail = chars[-term_length..-1]
        break if chars.size >= term_length and tail == term
      end
      output.puts input.gets # the rest of "Finished in..."
      output.puts
      next
    when /^\s*$/, /^\(?\s*\d+\) (Failure|Error):/, /^\d+\)/ then
      print_lines = false
      current = []
      data << current
    when /^Finished in \d/ then
      print_lines = false
    end
    output.puts line if print_lines
    current << line
  end
  output.sync = old_sync
  data = data.reject { |o| o == ["\n"] or o.empty? }
  footer = data.pop

  data.map do |result|
    break if result.any? { |l| l =~ / expected( but was|, not)/ }

    header = result.find do |l|
      l =~ /^\(?\s*\d+\) (Failure|Error):/
    end

    break unless header

    message_index = result.index(header) + 2

    result[message_index..-1] = result[message_index..-1].join
  end

  return data, footer
end

#unit_diff(input = ARGF, output = $stdout) ⇒ Object

Scans Test::Unit output input looking for comparison failures and makes them easily readable by passing them through diff.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/unit_diff.rb', line 197

def unit_diff(input=ARGF, output=$stdout)
  $b = false unless defined? $b
  $c = false unless defined? $c
  $k = false unless defined? $k
  $u = false unless defined? $u

  data, footer = self.parse_input(input, output)

  output = []

  # Output
  data.each do |result|
    first = []
    second = []

    if result.first =~ /Error/ then
      output.push result.join('')
      next
    end

    prefix, expect, butwas, result_footer = parse_diff(result)

    output.push prefix.compact.map {|line| line.strip}.join("\n")

    if butwas then
      output.push self.diff(expect, butwas)

      output.push result_footer
      output.push ''
    else
      output.push expect.join('')
    end
  end

  if footer then
    footer.shift if footer.first.strip.empty?# unless footer.first.nil?
    output.push footer.compact.map {|line| line.strip}.join("\n")
  end

  return output.flatten.join("\n")
end