grosser-autotest 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +574 -0
- data/README.markdown +74 -0
- data/VERSION.yml +4 -0
- data/bin/autotest +52 -0
- data/bin/unit_diff +37 -0
- data/lib/autotest.rb +661 -0
- data/lib/autotest/autoupdate.rb +26 -0
- data/lib/autotest/camping.rb +37 -0
- data/lib/autotest/cctray.rb +57 -0
- data/lib/autotest/discover.rb +6 -0
- data/lib/autotest/emacs.rb +36 -0
- data/lib/autotest/email_notify.rb +66 -0
- data/lib/autotest/fixtures.rb +12 -0
- data/lib/autotest/growl.rb +29 -0
- data/lib/autotest/heckle.rb +14 -0
- data/lib/autotest/html_report.rb +31 -0
- data/lib/autotest/jabber_notify.rb +111 -0
- data/lib/autotest/kdenotify.rb +14 -0
- data/lib/autotest/menu.rb +51 -0
- data/lib/autotest/migrate.rb +7 -0
- data/lib/autotest/notify.rb +34 -0
- data/lib/autotest/once.rb +9 -0
- data/lib/autotest/pretty.rb +83 -0
- data/lib/autotest/rails.rb +81 -0
- data/lib/autotest/rcov.rb +22 -0
- data/lib/autotest/redgreen.rb +21 -0
- data/lib/autotest/restart.rb +11 -0
- data/lib/autotest/shame.rb +45 -0
- data/lib/autotest/snarl.rb +51 -0
- data/lib/autotest/timestamp.rb +9 -0
- data/lib/unit_diff.rb +258 -0
- data/test/helper.rb +6 -0
- data/test/test_autotest.rb +454 -0
- data/test/test_unit_diff.rb +313 -0
- metadata +89 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
module Autotest::Menu
|
4
|
+
WINDOZE = /win32/ =~ RUBY_PLATFORM unless defined? WINDOZE
|
5
|
+
|
6
|
+
if WINDOZE then
|
7
|
+
require "Win32API"
|
8
|
+
def self.getchar
|
9
|
+
Win32API.new("crtdll", "_getch", [], "L").Call
|
10
|
+
end
|
11
|
+
else
|
12
|
+
STTY_SAVE_STATE=`stty -g`
|
13
|
+
def self.getchar
|
14
|
+
system 'stty raw echo'
|
15
|
+
STDIN.getc
|
16
|
+
ensure
|
17
|
+
system "stty '#{STTY_SAVE_STATE}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.menu(choices)
|
22
|
+
result = nil
|
23
|
+
choices.sort.each do |c, desc|
|
24
|
+
puts "#{c.chr}: #{desc}"
|
25
|
+
end
|
26
|
+
until choices[result]
|
27
|
+
print "menu> "
|
28
|
+
result = getchar
|
29
|
+
print " invalid input" unless choices[result]
|
30
|
+
puts
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
Autotest.add_hook(:interrupt) do |at|
|
36
|
+
$stderr.puts "menu"
|
37
|
+
case menu ?q => "quit", ?c => "continue", ?r => "restart"
|
38
|
+
when ?c
|
39
|
+
true
|
40
|
+
when ?r
|
41
|
+
at.reset
|
42
|
+
true
|
43
|
+
when ?q
|
44
|
+
at.wants_to_quit = true
|
45
|
+
true
|
46
|
+
else
|
47
|
+
false
|
48
|
+
end
|
49
|
+
# puts "you chose #{c.chr}"
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Autotest::Notify
|
2
|
+
def self.notify(title, message, priority='critical')
|
3
|
+
icon = if priority == 'critical'
|
4
|
+
'dialog-error'
|
5
|
+
else
|
6
|
+
'dialog-information'
|
7
|
+
end
|
8
|
+
system "notify-send -u #{priority} -t 10000 -i #{icon} '#{title}' '#{message.inspect}'"
|
9
|
+
end
|
10
|
+
|
11
|
+
Autotest.add_hook :red do |at|
|
12
|
+
tests = 0
|
13
|
+
assertions = 0
|
14
|
+
failures = 0
|
15
|
+
errors = 0
|
16
|
+
at.results.scan(/(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/) do |t, a, f, e|
|
17
|
+
tests += t.to_i
|
18
|
+
assertions += a.to_i
|
19
|
+
failures += f.to_i
|
20
|
+
errors += e.to_i
|
21
|
+
end
|
22
|
+
message = "%d tests, %d assertions, %d failures, %d errors" %
|
23
|
+
[tests, assertions, failures, errors]
|
24
|
+
notify("Tests Failed", message)
|
25
|
+
end
|
26
|
+
|
27
|
+
Autotest.add_hook :green do |at|
|
28
|
+
notify("Tests Passed", "Outstanding tests passed", 'low') if at.tainted
|
29
|
+
end
|
30
|
+
|
31
|
+
Autotest.add_hook :all do |at|_hook
|
32
|
+
notify("autotest", "Tests have fully passed", 'low')
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'osx/cocoa'
|
4
|
+
include Math
|
5
|
+
include OSX
|
6
|
+
|
7
|
+
OSX::NSBundle.bundleWithPath(File.expand_path("~/Library/Frameworks/Aquaterm.framework")).load
|
8
|
+
OSX.ns_import :AQTAdapter
|
9
|
+
|
10
|
+
class Autotest::Pretty
|
11
|
+
BLACK = 0
|
12
|
+
WHITE = 1
|
13
|
+
RED = 2
|
14
|
+
GREEN = 3
|
15
|
+
GRAY = 4
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@past = []
|
19
|
+
|
20
|
+
@adapter = AQTAdapter.alloc.init
|
21
|
+
@adapter.openPlotWithIndex 1
|
22
|
+
@adapter.setPlotSize([122,122])
|
23
|
+
@adapter.setPlotTitle("Autotest Status")
|
24
|
+
|
25
|
+
@adapter.setColormapEntry_red_green_blue(0, 0.0, 0.0, 0.0) # black
|
26
|
+
@adapter.setColormapEntry_red_green_blue(1, 1.0, 1.0, 1.0) # white
|
27
|
+
@adapter.setColormapEntry_red_green_blue(2, 1.0, 0.0, 0.0) # red
|
28
|
+
@adapter.setColormapEntry_red_green_blue(3, 0.0, 1.0, 0.0) # green
|
29
|
+
@adapter.setColormapEntry_red_green_blue(4, 0.7, 0.7, 0.7) # gray
|
30
|
+
|
31
|
+
draw
|
32
|
+
end
|
33
|
+
|
34
|
+
def draw
|
35
|
+
@past.shift if @past.size > 100
|
36
|
+
|
37
|
+
@adapter.takeColorFromColormapEntry(@past.last ? GREEN : RED)
|
38
|
+
@adapter.addFilledRect([0, 0, 122, 122])
|
39
|
+
|
40
|
+
@adapter.takeColorFromColormapEntry(BLACK)
|
41
|
+
@adapter.addFilledRect([10, 10, 102, 102])
|
42
|
+
|
43
|
+
@adapter.takeColorFromColormapEntry(GRAY)
|
44
|
+
@adapter.addFilledRect([11, 11, 100, 100])
|
45
|
+
|
46
|
+
@adapter.takeColorFromColormapEntry(0)
|
47
|
+
|
48
|
+
@past.each_with_index do |passed,i|
|
49
|
+
x = i % 10
|
50
|
+
y = i / 10
|
51
|
+
|
52
|
+
@adapter.takeColorFromColormapEntry(passed ? GREEN : RED)
|
53
|
+
@adapter.addFilledRect([x*10+11, y*10+11, 10, 10])
|
54
|
+
end
|
55
|
+
@adapter.renderPlot
|
56
|
+
end
|
57
|
+
|
58
|
+
def pass
|
59
|
+
@past.push true
|
60
|
+
draw
|
61
|
+
end
|
62
|
+
|
63
|
+
def fail
|
64
|
+
@past.push false
|
65
|
+
draw
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
@adapter.closePlot
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
unless $TESTING then
|
74
|
+
board = Autotest::Pretty.new
|
75
|
+
|
76
|
+
Autotest.add_hook :red do |at|
|
77
|
+
board.fail unless $TESTING
|
78
|
+
end
|
79
|
+
|
80
|
+
Autotest.add_hook :green do |at|
|
81
|
+
board.pass unless $TESTING
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'autotest'
|
2
|
+
|
3
|
+
class Autotest::Rails < Autotest
|
4
|
+
|
5
|
+
def initialize # :nodoc:
|
6
|
+
super
|
7
|
+
|
8
|
+
add_exception %r%^\./(?:db|doc|log|public|script|tmp|vendor)%
|
9
|
+
|
10
|
+
clear_mappings
|
11
|
+
|
12
|
+
self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
|
13
|
+
impl = File.basename(filename, '.rb')
|
14
|
+
files_matching %r%^test/unit/#{impl}_test.rb$%
|
15
|
+
# TODO: (unit|functional|integration) maybe?
|
16
|
+
end
|
17
|
+
|
18
|
+
add_mapping %r%^test/fixtures/(.*)s.yml% do |_, m|
|
19
|
+
["test/unit/#{m[1]}_test.rb",
|
20
|
+
"test/controllers/#{m[1]}_controller_test.rb",
|
21
|
+
"test/views/#{m[1]}_view_test.rb",
|
22
|
+
"test/functional/#{m[1]}_controller_test.rb"]
|
23
|
+
end
|
24
|
+
|
25
|
+
add_mapping %r%^test/(unit|integration|controllers|views|functional)/.*rb$% do |filename, _|
|
26
|
+
filename
|
27
|
+
end
|
28
|
+
|
29
|
+
add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
|
30
|
+
"test/unit/#{m[1]}_test.rb"
|
31
|
+
end
|
32
|
+
|
33
|
+
add_mapping %r%^app/helpers/application_helper.rb% do
|
34
|
+
files_matching %r%^test/(views|functional)/.*_test\.rb$%
|
35
|
+
end
|
36
|
+
|
37
|
+
add_mapping %r%^app/helpers/(.*)_helper.rb% do |_, m|
|
38
|
+
if m[1] == "application" then
|
39
|
+
files_matching %r%^test/(views|functional)/.*_test\.rb$%
|
40
|
+
else
|
41
|
+
["test/views/#{m[1]}_view_test.rb",
|
42
|
+
"test/functional/#{m[1]}_controller_test.rb"]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
add_mapping %r%^app/views/(.*)/% do |_, m|
|
47
|
+
["test/views/#{m[1]}_view_test.rb",
|
48
|
+
"test/functional/#{m[1]}_controller_test.rb"]
|
49
|
+
end
|
50
|
+
|
51
|
+
add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
|
52
|
+
if m[1] == "application" then
|
53
|
+
files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$%
|
54
|
+
else
|
55
|
+
["test/controllers/#{m[1]}_test.rb",
|
56
|
+
"test/functional/#{m[1]}_test.rb"]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
add_mapping %r%^app/views/layouts/% do
|
61
|
+
"test/views/layouts_view_test.rb"
|
62
|
+
end
|
63
|
+
|
64
|
+
add_mapping %r%^config/routes.rb$% do # FIX:
|
65
|
+
files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$%
|
66
|
+
end
|
67
|
+
|
68
|
+
add_mapping %r%^test/test_helper.rb|config/((boot|environment(s/test)?).rb|database.yml)% do
|
69
|
+
files_matching %r%^test/(unit|controllers|views|functional)/.*_test\.rb$%
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convert the pathname s to the name of class.
|
74
|
+
def path_to_classname(s)
|
75
|
+
sep = File::SEPARATOR
|
76
|
+
f = s.sub(/^test#{sep}((unit|functional|integration|views|controllers|helpers)#{sep})?/, '').sub(/\.rb$/, '').split(sep)
|
77
|
+
f = f.map { |path| path.split(/_/).map { |seg| seg.capitalize }.join }
|
78
|
+
f = f.map { |path| path =~ /Test$/ ? path : "#{path}Test" }
|
79
|
+
f.join('::')
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Autotest::RCov
|
2
|
+
@@command, @@pattern = "rcov", "test/*.rb"
|
3
|
+
|
4
|
+
def self.command= o
|
5
|
+
@@command = o
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.pattern= o
|
9
|
+
@@pattern = o
|
10
|
+
end
|
11
|
+
|
12
|
+
Autotest.add_hook :all_good do |at|
|
13
|
+
system "rake #{@@command} PATTERN=#{@@pattern}"
|
14
|
+
end
|
15
|
+
|
16
|
+
Autotest.add_hook :initialize do |at|
|
17
|
+
at.add_exception 'coverage'
|
18
|
+
at.add_exception 'coverage.info'
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
# special thanks to Pat Eyler, Sean Carley, and Rob Sanheim
|
4
|
+
# and to Peter Havens for rspec patches
|
5
|
+
module Autotest::RedGreen
|
6
|
+
BAR = "=" * 78
|
7
|
+
REDCODE = 31
|
8
|
+
GREENCODE = 32
|
9
|
+
|
10
|
+
Autotest.add_hook :ran_command do |at|
|
11
|
+
green = case at.results.last
|
12
|
+
when /^.* (\d+) failures, (\d+) errors$/ # Test::Unit
|
13
|
+
($1 == "0" and $2 == "0")
|
14
|
+
when /^\d+\s+examples?,\s+(\d+)\s+failure/ # RSpec
|
15
|
+
($1 == "0")
|
16
|
+
end
|
17
|
+
|
18
|
+
code = green ? GREENCODE : REDCODE
|
19
|
+
puts "\e[#{ code }m#{ BAR }\e[0m\n\n" unless green.nil?
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'code_statistics'
|
2
|
+
require 'rbosa'
|
3
|
+
|
4
|
+
module Autotest::Shame
|
5
|
+
@@chat_app = :adium
|
6
|
+
|
7
|
+
def self.chat_app= o
|
8
|
+
@@chat_app = o
|
9
|
+
end
|
10
|
+
|
11
|
+
# Until the rails team learns how to write modular code... I must steal :/
|
12
|
+
STATS_DIRECTORIES = [
|
13
|
+
%w(Controllers app/controllers),
|
14
|
+
%w(Helpers app/helpers),
|
15
|
+
%w(Models app/models),
|
16
|
+
%w(Libraries lib/),
|
17
|
+
%w(APIs app/apis),
|
18
|
+
%w(Components components),
|
19
|
+
%w(Integration\ tests test/integration),
|
20
|
+
%w(Functional\ tests test/functional),
|
21
|
+
%w(Unit\ tests test/unit),
|
22
|
+
].select { |name, dir| File.directory?(dir) }
|
23
|
+
|
24
|
+
def self.shame
|
25
|
+
stats = CodeStatistics.new(*STATS_DIRECTORIES)
|
26
|
+
code = stats.send :calculate_code
|
27
|
+
tests = stats.send :calculate_tests
|
28
|
+
msg = "Code To Test Ratio: 1:#{sprintf("%.2f", tests.to_f/code)}"
|
29
|
+
$-w = ! $-w
|
30
|
+
case @@chat_app
|
31
|
+
when :adium then
|
32
|
+
OSA.app('Adium').adium_controller.my_status_message = msg
|
33
|
+
when :ichat then
|
34
|
+
OSA.app('ichat').status_message = msg
|
35
|
+
else
|
36
|
+
raise "huh?"
|
37
|
+
end
|
38
|
+
$-w = ! $-w
|
39
|
+
$stderr.puts "Status set to: #{msg.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
Autotest.add_hook(:all_good) do |autotest|
|
43
|
+
shame
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# special thanks to: Patrick Hurley <phurley@gmail.com>
|
2
|
+
# requires the ruby-snarl gem.
|
3
|
+
|
4
|
+
begin require 'rubygems'; rescue LoadError; end
|
5
|
+
require 'snarl'
|
6
|
+
|
7
|
+
module Autotest::Snarl
|
8
|
+
def self.icon
|
9
|
+
# icons from http://www.famfamfam.com/lab/icons/silk/
|
10
|
+
path = File.join(File.dirname(__FILE__), "/../icons")
|
11
|
+
{
|
12
|
+
:green => "#{path}/accept.png",
|
13
|
+
:red => "#{path}/exclamation.png",
|
14
|
+
:info => "#{path}/information.png"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.snarl title, msg, ico = nil
|
19
|
+
Snarl.show_message(title, msg, icon[ico])
|
20
|
+
end
|
21
|
+
|
22
|
+
Autotest.add_hook :run do |at|
|
23
|
+
snarl "Run", "Run" unless $TESTING
|
24
|
+
end
|
25
|
+
|
26
|
+
Autotest.add_hook :red do |at|
|
27
|
+
failed_tests = at.files_to_test.inject(0){ |s,a| k,v = a; s + v.size}
|
28
|
+
snarl "Tests Failed", "#{failed_tests} tests failed", :red
|
29
|
+
end
|
30
|
+
|
31
|
+
Autotest.add_hook :green do |at|
|
32
|
+
snarl "Tests Passed", "All tests passed", :green #if at.tainted
|
33
|
+
end
|
34
|
+
|
35
|
+
Autotest.add_hook :run do |at|
|
36
|
+
snarl "autotest", "autotest was started", :info unless $TESTING
|
37
|
+
end
|
38
|
+
|
39
|
+
Autotest.add_hook :interrupt do |at|
|
40
|
+
snarl "autotest", "autotest was reset", :info unless $TESTING
|
41
|
+
end
|
42
|
+
|
43
|
+
Autotest.add_hook :quit do |at|
|
44
|
+
snarl "autotest", "autotest is exiting", :info unless $TESTING
|
45
|
+
end
|
46
|
+
|
47
|
+
Autotest.add_hook :all do |at|_hook
|
48
|
+
snarl "autotest", "Tests have fully passed", :green unless $TESTING
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/lib/unit_diff.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
##
|
4
|
+
# UnitDiff makes reading Test::Unit output easy and fun. Instead of a
|
5
|
+
# confusing jumble of text with nearly unnoticable changes like this:
|
6
|
+
#
|
7
|
+
# 1) Failure:
|
8
|
+
# test_to_gpoints(RouteTest) [test/unit/route_test.rb:29]:
|
9
|
+
# <"new GPolyline([\n new GPoint( 47.00000, -122.00000),\n new GPoint( 46.5000
|
10
|
+
# 0, -122.50000),\n new GPoint( 46.75000, -122.75000),\n new GPoint( 46.00000,
|
11
|
+
# -123.00000)])"> expected but was
|
12
|
+
# <"new Gpolyline([\n new GPoint( 47.00000, -122.00000),\n new GPoint( 46.5000
|
13
|
+
# 0, -122.50000),\n new GPoint( 46.75000, -122.75000),\n new GPoint( 46.00000,
|
14
|
+
# -123.00000)])">.
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# You get an easy-to-read diff output like this:
|
18
|
+
#
|
19
|
+
# 1) Failure:
|
20
|
+
# test_to_gpoints(RouteTest) [test/unit/route_test.rb:29]:
|
21
|
+
# 1c1
|
22
|
+
# < new GPolyline([
|
23
|
+
# ---
|
24
|
+
# > new Gpolyline([
|
25
|
+
#
|
26
|
+
# == Usage
|
27
|
+
#
|
28
|
+
# test.rb | unit_diff [options]
|
29
|
+
# options:
|
30
|
+
# -b ignore whitespace differences
|
31
|
+
# -c contextual diff
|
32
|
+
# -h show usage
|
33
|
+
# -k keep temp diff files around
|
34
|
+
# -l prefix line numbers on the diffs
|
35
|
+
# -u unified diff
|
36
|
+
# -v display version
|
37
|
+
|
38
|
+
class UnitDiff
|
39
|
+
|
40
|
+
WINDOZE = /win32/ =~ RUBY_PLATFORM unless defined? WINDOZE
|
41
|
+
DIFF = if WINDOZE
|
42
|
+
'diff.exe'
|
43
|
+
else
|
44
|
+
if system("gdiff", __FILE__, __FILE__)
|
45
|
+
'gdiff' # solaris and kin suck
|
46
|
+
else
|
47
|
+
'diff'
|
48
|
+
end
|
49
|
+
end unless defined? DIFF
|
50
|
+
|
51
|
+
##
|
52
|
+
# Handy wrapper for UnitDiff#unit_diff.
|
53
|
+
|
54
|
+
def self.unit_diff
|
55
|
+
trap 'INT' do exit 1 end
|
56
|
+
puts UnitDiff.new.unit_diff
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_input(input, output)
|
60
|
+
current = []
|
61
|
+
data = []
|
62
|
+
data << current
|
63
|
+
print_lines = true
|
64
|
+
|
65
|
+
term = "\nFinished".split(//).map { |c| c[0] }
|
66
|
+
term_length = term.size
|
67
|
+
|
68
|
+
old_sync = output.sync
|
69
|
+
output.sync = true
|
70
|
+
while line = input.gets
|
71
|
+
case line
|
72
|
+
when /^(Loaded suite|Started)/ then
|
73
|
+
print_lines = true
|
74
|
+
output.puts line
|
75
|
+
chars = []
|
76
|
+
while c = input.getc do
|
77
|
+
output.putc c
|
78
|
+
chars << c
|
79
|
+
tail = chars[-term_length..-1]
|
80
|
+
break if chars.size >= term_length and tail == term
|
81
|
+
end
|
82
|
+
output.puts input.gets # the rest of "Finished in..."
|
83
|
+
output.puts
|
84
|
+
next
|
85
|
+
when /^\s*$/, /^\(?\s*\d+\) (Failure|Error):/, /^\d+\)/ then
|
86
|
+
print_lines = false
|
87
|
+
current = []
|
88
|
+
data << current
|
89
|
+
when /^Finished in \d/ then
|
90
|
+
print_lines = false
|
91
|
+
end
|
92
|
+
output.puts line if print_lines
|
93
|
+
current << line
|
94
|
+
end
|
95
|
+
output.sync = old_sync
|
96
|
+
data = data.reject { |o| o == ["\n"] or o.empty? }
|
97
|
+
footer = data.pop
|
98
|
+
|
99
|
+
return data, footer
|
100
|
+
end
|
101
|
+
|
102
|
+
# Parses a single diff recording the header and what
|
103
|
+
# was expected, and what was actually obtained.
|
104
|
+
def parse_diff(result)
|
105
|
+
header = []
|
106
|
+
expect = []
|
107
|
+
butwas = []
|
108
|
+
footer = []
|
109
|
+
found = false
|
110
|
+
state = :header
|
111
|
+
|
112
|
+
until result.empty? do
|
113
|
+
case state
|
114
|
+
when :header then
|
115
|
+
header << result.shift
|
116
|
+
state = :expect if result.first =~ /^<|^Expected/
|
117
|
+
when :expect then
|
118
|
+
case result.first
|
119
|
+
when /^Expected (.*?) to equal (.*?):$/ then
|
120
|
+
expect << $1
|
121
|
+
butwas << $2
|
122
|
+
state = :footer
|
123
|
+
result.shift
|
124
|
+
when /^Expected (.*?), not (.*)$/m then
|
125
|
+
expect << $1
|
126
|
+
butwas << $2
|
127
|
+
state = :footer
|
128
|
+
result.shift
|
129
|
+
when /^Expected (.*?)$/ then
|
130
|
+
expect << "#{$1}\n"
|
131
|
+
result.shift
|
132
|
+
when /^to equal / then
|
133
|
+
state = :spec_butwas
|
134
|
+
bw = result.shift.sub(/^to equal (.*):?$/, '\1')
|
135
|
+
butwas << bw
|
136
|
+
else
|
137
|
+
state = :butwas if result.first.sub!(/ expected( but was|, not)/, '')
|
138
|
+
expect << result.shift
|
139
|
+
end
|
140
|
+
when :butwas then
|
141
|
+
butwas = result[0..-1]
|
142
|
+
result.clear
|
143
|
+
when :spec_butwas then
|
144
|
+
if result.first =~ /^\s+\S+ at |^:\s*$/
|
145
|
+
state = :footer
|
146
|
+
else
|
147
|
+
butwas << result.shift
|
148
|
+
end
|
149
|
+
when :footer then
|
150
|
+
butwas.last.sub!(/:$/, '')
|
151
|
+
footer = result.map {|l| l.chomp }
|
152
|
+
result.clear
|
153
|
+
else
|
154
|
+
raise "unknown state #{state}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
return header, expect, nil, footer if butwas.empty?
|
159
|
+
|
160
|
+
expect.last.chomp!
|
161
|
+
expect.first.sub!(/^<\"/, '')
|
162
|
+
expect.last.sub!(/\">$/, '')
|
163
|
+
|
164
|
+
butwas.last.chomp!
|
165
|
+
butwas.last.chop! if butwas.last =~ /\.$/
|
166
|
+
butwas.first.sub!( /^<\"/, '')
|
167
|
+
butwas.last.sub!(/\">$/, '')
|
168
|
+
|
169
|
+
return header, expect, butwas, footer
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Scans Test::Unit output +input+ looking for comparison failures and makes
|
174
|
+
# them easily readable by passing them through diff.
|
175
|
+
|
176
|
+
def unit_diff(input=ARGF, output=$stdout)
|
177
|
+
$b = false unless defined? $b
|
178
|
+
$c = false unless defined? $c
|
179
|
+
$k = false unless defined? $k
|
180
|
+
$u = false unless defined? $u
|
181
|
+
|
182
|
+
data, footer = self.parse_input(input, output)
|
183
|
+
|
184
|
+
output = []
|
185
|
+
|
186
|
+
# Output
|
187
|
+
data.each do |result|
|
188
|
+
first = []
|
189
|
+
second = []
|
190
|
+
|
191
|
+
if result.first =~ /Error/ then
|
192
|
+
output.push result.join('')
|
193
|
+
next
|
194
|
+
end
|
195
|
+
|
196
|
+
prefix, expect, butwas, result_footer = parse_diff(result)
|
197
|
+
|
198
|
+
output.push prefix.compact.map {|line| line.strip}.join("\n")
|
199
|
+
|
200
|
+
if butwas then
|
201
|
+
output.push self.diff(expect, butwas)
|
202
|
+
|
203
|
+
output.push result_footer
|
204
|
+
output.push ''
|
205
|
+
else
|
206
|
+
output.push expect.join('')
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
if footer then
|
211
|
+
footer.shift if footer.first.strip.empty?# unless footer.first.nil?
|
212
|
+
output.push footer.compact.map {|line| line.strip}.join("\n")
|
213
|
+
end
|
214
|
+
|
215
|
+
return output.flatten.join("\n")
|
216
|
+
end
|
217
|
+
|
218
|
+
def diff expect, butwas
|
219
|
+
output = nil
|
220
|
+
|
221
|
+
Tempfile.open("expect") do |a|
|
222
|
+
a.write(massage(expect))
|
223
|
+
a.rewind
|
224
|
+
Tempfile.open("butwas") do |b|
|
225
|
+
b.write(massage(butwas))
|
226
|
+
b.rewind
|
227
|
+
|
228
|
+
diff_flags = $u ? "-u" : $c ? "-c" : ""
|
229
|
+
diff_flags += " -b" if $b
|
230
|
+
|
231
|
+
result = `#{DIFF} #{diff_flags} #{a.path} #{b.path}`
|
232
|
+
output = if result.empty? then
|
233
|
+
"[no difference--suspect ==]"
|
234
|
+
else
|
235
|
+
result.split(/\n/)
|
236
|
+
end
|
237
|
+
|
238
|
+
if $k then
|
239
|
+
warn "moving #{a.path} to #{a.path}.keep"
|
240
|
+
File.rename a.path, a.path + ".keep"
|
241
|
+
warn "moving #{b.path} to #{b.path}.keep"
|
242
|
+
File.rename b.path, b.path + ".keep"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
output
|
248
|
+
end
|
249
|
+
|
250
|
+
def massage(data)
|
251
|
+
count = 0
|
252
|
+
# unescape newlines, strip <> from entire string
|
253
|
+
data = data.join
|
254
|
+
data = data.gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX') + "\n"
|
255
|
+
data += "\n" unless data[-1] == ?\n
|
256
|
+
data
|
257
|
+
end
|
258
|
+
end
|