leftright 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +19 -0
- data/README.rdoc +45 -0
- data/leftright.gemspec +30 -0
- data/lib/leftright.rb +172 -0
- data/lib/leftright/autorun.rb +12 -0
- data/lib/leftright/color.rb +28 -0
- data/lib/leftright/runner.rb +113 -0
- data/lib/leftright/version.rb +3 -0
- metadata +62 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Jordi Bunster <jordi@bunster.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= leftright
|
2
|
+
|
3
|
+
leftright is kind of like the redgreen gem. It makes passing tests look
|
4
|
+
green, exceptions yellow, and failures red. But then there's more:
|
5
|
+
|
6
|
+
* It lets you know which TestCase class is being tested
|
7
|
+
* It shows you the full text of failures and exceptions as they happen
|
8
|
+
* It skips all remaining tests for a TestCase class if one fails
|
9
|
+
|
10
|
+
== Dependencies
|
11
|
+
|
12
|
+
Right now this is pretty heavily dependent on Test::Unit, so it won't work
|
13
|
+
in Ruby 1.9+ using MiniTest. Support is planned as soon as I find myself
|
14
|
+
using the Ruby 1.9 + Rails 3 combo day to day.
|
15
|
+
|
16
|
+
== Installation instructions
|
17
|
+
|
18
|
+
From Rubyforge's gem server (might not be there):
|
19
|
+
|
20
|
+
gem install leftright
|
21
|
+
|
22
|
+
From Gemcutter:
|
23
|
+
|
24
|
+
gem install leftright --source http://gemcutter.org
|
25
|
+
|
26
|
+
Both are the same, and are loaded the same way:
|
27
|
+
|
28
|
+
require 'leftright'
|
29
|
+
|
30
|
+
== Example usage
|
31
|
+
|
32
|
+
require 'leftright'
|
33
|
+
|
34
|
+
class SomeTest < Test::Unit::TestCase
|
35
|
+
def test_that_true_is_indeed_true
|
36
|
+
assert_equal true, true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Then run the file with ruby. Mind you, it gets a lot more exciting with
|
41
|
+
your own tests, specially if they fail. :)
|
42
|
+
|
43
|
+
== Legal
|
44
|
+
|
45
|
+
Copyright (c) 2009 Jordi Bunster, released under the MIT license
|
data/leftright.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'lib/leftright/version'
|
2
|
+
|
3
|
+
XMLOBJECT_GEMSPEC = Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'leftright'
|
5
|
+
gem.version = LeftRight::VERSION
|
6
|
+
|
7
|
+
gem.author, gem.email = 'Jordi Bunster', 'jordi@bunster.org'
|
8
|
+
|
9
|
+
gem.summary = "Cool replacement for Test::Unit's TestRunner"
|
10
|
+
gem.description = %{ leftright is kind of like the redgreen gem. It makes
|
11
|
+
passing tests look green, exceptions yellow, and failures red. It also
|
12
|
+
has a few features that make your workflow a bit faster (see README).
|
13
|
+
}.strip!.gsub! /\s+/, ' '
|
14
|
+
|
15
|
+
gem.has_rdoc = false
|
16
|
+
|
17
|
+
gem.date = Date.today
|
18
|
+
gem.files = %w[
|
19
|
+
MIT-LICENSE
|
20
|
+
README.rdoc
|
21
|
+
leftright.gemspec
|
22
|
+
lib
|
23
|
+
lib/leftright.rb
|
24
|
+
lib/leftright
|
25
|
+
lib/leftright/autorun.rb
|
26
|
+
lib/leftright/color.rb
|
27
|
+
lib/leftright/runner.rb
|
28
|
+
lib/leftright/version.rb
|
29
|
+
]
|
30
|
+
end
|
data/lib/leftright.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/ui/console/testrunner'
|
3
|
+
|
4
|
+
require 'leftright/version' # to open the module
|
5
|
+
|
6
|
+
require 'leftright/color'
|
7
|
+
require 'leftright/runner'
|
8
|
+
require 'leftright/autorun'
|
9
|
+
|
10
|
+
module LeftRight
|
11
|
+
# In counts of ' ':
|
12
|
+
MID_SEPARATOR = 1
|
13
|
+
RIGHT_MARGIN = 1
|
14
|
+
LEFT_MARGIN = 1
|
15
|
+
|
16
|
+
# This whole thing is fairly gnarly, needing to keep state across multiple
|
17
|
+
# parts of the crazyness that is Test::Unit, so we keep it all here.
|
18
|
+
#
|
19
|
+
def self.state
|
20
|
+
@state ||= begin
|
21
|
+
fields = [
|
22
|
+
:dots, # the number of dots ('.') printed on this line
|
23
|
+
:class, # the TestCase-extending class being tested
|
24
|
+
:fault, # the current Test::Unit Failure/Error object
|
25
|
+
:last_class_printed, # last class printed on the left side
|
26
|
+
:previous_failed, # true if the previous test failed/exceptioned
|
27
|
+
:skip, # true if the current test was a skip
|
28
|
+
:skipped_count # total number of skipped tests so far
|
29
|
+
]
|
30
|
+
|
31
|
+
state = Struct.new(*fields).new
|
32
|
+
state.skipped_count = 0
|
33
|
+
state.dots = 0
|
34
|
+
state
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Gets all descendants of Class that also happen to be descendants of
|
39
|
+
# Test::Unit::TestCase that have non-inherited instance methods that
|
40
|
+
# begin with the word 'test':
|
41
|
+
#
|
42
|
+
def self.testcase_classes
|
43
|
+
@testcase_classes ||= ObjectSpace.each_object(Class).find_all do |klass|
|
44
|
+
Test::Unit::TestCase > klass &&
|
45
|
+
klass.instance_methods(false).detect { |m| 'test' == m.to_s[0,4] }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Replaces all instance methods beginning with 'test' in the given class
|
50
|
+
# with stubs that skip testing.
|
51
|
+
#
|
52
|
+
def self.skip_testing_class(klass)
|
53
|
+
klass.instance_methods.each do |m|
|
54
|
+
if 'test' == m.to_s[0,4]
|
55
|
+
klass.send :define_method, m.to_sym do
|
56
|
+
::LeftRight.state.skip = true
|
57
|
+
::LeftRight.state.skipped_count += 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Formats a class name to display on the left side.
|
64
|
+
#
|
65
|
+
def self.format_class_name(class_name)
|
66
|
+
class_name.chomp 'Test'
|
67
|
+
end
|
68
|
+
|
69
|
+
# Tries to get the terminal width in columns.
|
70
|
+
#
|
71
|
+
def self.terminal_width
|
72
|
+
@terminal_width ||= STDOUT.tty? ? `stty size`.split[-1].to_i : 0 rescue 0
|
73
|
+
end
|
74
|
+
|
75
|
+
# Tries to get the left side width in columns.
|
76
|
+
#
|
77
|
+
def self.left_side_width
|
78
|
+
@left_side_width ||= begin
|
79
|
+
testcase_classes.map do |c|
|
80
|
+
format_class_name(c.name).size + LEFT_MARGIN
|
81
|
+
end.max
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Tries to get the right side width in columns.
|
86
|
+
#
|
87
|
+
def self.right_side_width
|
88
|
+
terminal_width - left_side_width
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the given string, right-justified onto the left side.
|
92
|
+
#
|
93
|
+
def self.justify_left_side(str = '')
|
94
|
+
str.to_s.rjust(left_side_width) + (' ' * MID_SEPARATOR)
|
95
|
+
end
|
96
|
+
|
97
|
+
# This gets the class name from the 'test_name' method on a
|
98
|
+
# Test::Unit Failure or Error. They look like test_method_name(TestCase),
|
99
|
+
#
|
100
|
+
def self.extract_class_name(test_name)
|
101
|
+
test_name.scan(/\(([^(|)]+)\)/x).flatten.last
|
102
|
+
end
|
103
|
+
|
104
|
+
# Wraps the given lines at word boundaries. Ripped right out of
|
105
|
+
# http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
|
106
|
+
#
|
107
|
+
def self.wrap(line)
|
108
|
+
return line unless STDOUT.tty?
|
109
|
+
width = right_side_width - MID_SEPARATOR - RIGHT_MARGIN
|
110
|
+
line.gsub /(.{1,#{width}})( +|$)\n?|(.{#{width}})/, "\\1\\3\n"
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns the current fault as a formatted failure message.
|
114
|
+
#
|
115
|
+
def self.F(color = C.red)
|
116
|
+
# First, we wrap each line individually, to keep existing line breaks:
|
117
|
+
lines = state.fault.long_display.split("\n")
|
118
|
+
|
119
|
+
# Drop the redundant "Failure: ", "test: " (shoulda), "test_", etc
|
120
|
+
lines.shift if lines.first.match /Failure:|Error:/
|
121
|
+
lines.first.sub! /^test[\ |:|_]?/i, ''
|
122
|
+
|
123
|
+
# Drop the class name in () from the test method name
|
124
|
+
lines.first.sub! /\(#{state.class}\)/, ''
|
125
|
+
|
126
|
+
# shoulda puts '. :' at the end of method names
|
127
|
+
lines.first.sub! /\.\ :\s?/, ':'
|
128
|
+
|
129
|
+
# Wrap lines before coloring, since the wrapping would get confused
|
130
|
+
# by non-printables.
|
131
|
+
buffer = lines.map { |line| wrap line.strip }.join.strip
|
132
|
+
|
133
|
+
# We make interesting parts of the failure message bold:
|
134
|
+
[ /(`[^']+')/m, # Stuff in `quotes'
|
135
|
+
/("[^"]+")/m, # Stuff in "quotes"
|
136
|
+
/([^\/|\[]+\.rb:\d+)/, # Filenames with line numbers (without [box])
|
137
|
+
/(\s+undefined\s+)/ ].each do |interesting|
|
138
|
+
buffer.gsub! interesting, ( C.bold + '\0' + C.reset + color )
|
139
|
+
end
|
140
|
+
|
141
|
+
# These are great for assert_equal and similar:
|
142
|
+
buffer.sub! /(<)(.*)(>\s+expected)/,
|
143
|
+
'\1' + C.bold + '\2' + C.reset + color + '\3'
|
144
|
+
buffer.sub! /(but\s+was\s+<)(.*)(>\.)/,
|
145
|
+
'\1' + C.bold + '\2' + C.reset + color + '\3'
|
146
|
+
|
147
|
+
color + buffer + C.reset + "\n"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns the current fault as a formatted error message.
|
151
|
+
#
|
152
|
+
def self.E
|
153
|
+
F C.yellow
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns a passing dot, aware of how many to print per-line.
|
157
|
+
#
|
158
|
+
def self.P
|
159
|
+
return '.' unless STDOUT.tty?
|
160
|
+
|
161
|
+
state.dots += 1
|
162
|
+
|
163
|
+
max_dots = right_side_width - RIGHT_MARGIN - MID_SEPARATOR
|
164
|
+
|
165
|
+
if state.dots >= max_dots
|
166
|
+
state.dots = 1
|
167
|
+
"\n" + C.green('.')
|
168
|
+
else
|
169
|
+
C.green '.'
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This is the only monkeypatching we do in LeftRight, since
|
2
|
+
# Test::Unit::AutoRunner has no API for changing which runner to use. In
|
3
|
+
# fact, it has a hardcoded list of runners.
|
4
|
+
|
5
|
+
class Test::Unit::AutoRunner
|
6
|
+
alias :initialize_without_leftright :initialize
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
initialize_without_leftright *args
|
10
|
+
@runner = lambda { |r| LeftRight::Runner }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# This is just here to avoid depending on Term::ANSIColor and such, since
|
2
|
+
# we need so little, and need it to transparently do nothing when
|
3
|
+
# STDOUT is not a terminal.
|
4
|
+
|
5
|
+
module LeftRight
|
6
|
+
module C
|
7
|
+
if STDOUT.tty?
|
8
|
+
def self.color(args)
|
9
|
+
name, code = args.keys.first, args.values.first
|
10
|
+
|
11
|
+
eval %' def self.#{name}(string = nil)
|
12
|
+
string.nil? ? "\e[#{code}m" : "\e[#{code}m" + string + "\e[0m"
|
13
|
+
end ', binding, __FILE__, __LINE__
|
14
|
+
end
|
15
|
+
|
16
|
+
color :red => 31
|
17
|
+
color :green => 32
|
18
|
+
color :yellow => 33
|
19
|
+
color :cyan => 36
|
20
|
+
color :reset => 0
|
21
|
+
color :bold => 1
|
22
|
+
else
|
23
|
+
def self.method_missing(color, *args)
|
24
|
+
args.first.nil? ? '' : args.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# This is the replacement for Test::Unit::UI::Console::TestRunner
|
2
|
+
|
3
|
+
module LeftRight
|
4
|
+
class Runner < Test::Unit::UI::Console::TestRunner
|
5
|
+
# Access to the LeftRight module from the Runner instance. Hopefully to
|
6
|
+
# reduce the likelyhood of future name clashes.
|
7
|
+
#
|
8
|
+
def lr
|
9
|
+
LeftRight
|
10
|
+
end
|
11
|
+
|
12
|
+
# We intercept this to be able to set some pertinent state.
|
13
|
+
#
|
14
|
+
def test_started(test_name)
|
15
|
+
name = lr.extract_class_name test_name
|
16
|
+
lr.state.class = lr.testcase_classes.detect { |c| c.name == name }
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
# We intercept this to be able to set some pertinent state, as well as
|
22
|
+
# change all remaining test methods in the current class to just skip,
|
23
|
+
# since we already failed once at this point.
|
24
|
+
#
|
25
|
+
def add_fault(fault)
|
26
|
+
lr.state.fault = fault
|
27
|
+
lr.skip_testing_class lr.state.class
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
# Test::Unit uses this method to print '.', 'F', 'E', and possibly
|
33
|
+
# others. We do most of the work here, using the state saved in
|
34
|
+
# 'add_fault' and 'test_finished'.
|
35
|
+
#
|
36
|
+
def output_single(captured, *etc)
|
37
|
+
# Make sure we are printing a test result
|
38
|
+
return super unless %w[ . F E ].include? captured
|
39
|
+
|
40
|
+
# Do nothing if the method was a skipper
|
41
|
+
return if lr.state.skip && '.' == captured
|
42
|
+
|
43
|
+
output = case captured
|
44
|
+
when '.' then lr.P
|
45
|
+
when 'F' then lr.F
|
46
|
+
when 'E' then lr.E
|
47
|
+
end
|
48
|
+
|
49
|
+
if lr.state.last_class_printed != lr.state.class
|
50
|
+
# If we're here, we need to print a new class name on the left side
|
51
|
+
lr.state.last_class_printed = lr.state.class
|
52
|
+
lr.state.dots = 0
|
53
|
+
@io.write "\n"
|
54
|
+
@io.write lr.justify_left_side(
|
55
|
+
lr.format_class_name(lr.state.class.name))
|
56
|
+
elsif captured != '.'
|
57
|
+
# This handles the edge case when the first test for a class fails
|
58
|
+
@io.write "\n"
|
59
|
+
@io.write lr.justify_left_side
|
60
|
+
end
|
61
|
+
|
62
|
+
# Justify all lines but first:
|
63
|
+
output.gsub! "\n", "\n" + lr.justify_left_side
|
64
|
+
|
65
|
+
@io.write output
|
66
|
+
ensure # reset all of the nasty state stuff
|
67
|
+
@io.flush
|
68
|
+
lr.state.previous_failed = captured != '.'
|
69
|
+
lr.state.skip = false
|
70
|
+
end
|
71
|
+
|
72
|
+
# This prints the final summary at the end of all tests.
|
73
|
+
#
|
74
|
+
def finished(elapsed_time)
|
75
|
+
passed_count = @result.run_count -
|
76
|
+
@result.failure_count -
|
77
|
+
@result.error_count - lr.state.skipped_count
|
78
|
+
|
79
|
+
total = { :passed => passed_count,
|
80
|
+
:failed => @result.failure_count,
|
81
|
+
:errors => @result.error_count,
|
82
|
+
:tests => @result.run_count,
|
83
|
+
:skipped => lr.state.skipped_count }
|
84
|
+
|
85
|
+
results = []
|
86
|
+
|
87
|
+
unless passed_count.zero?
|
88
|
+
total[:passed] = 'all' if passed_count == @result.run_count
|
89
|
+
results << lr::C.green("#{total[:passed]} passed")
|
90
|
+
end
|
91
|
+
|
92
|
+
unless lr.state.skipped_count.zero?
|
93
|
+
results << lr::C.cyan("#{total[:skipped]} skipped")
|
94
|
+
end
|
95
|
+
|
96
|
+
unless @result.failure_count.zero?
|
97
|
+
results << lr::C.red("#{total[:failed]} failed")
|
98
|
+
end
|
99
|
+
|
100
|
+
unless @result.error_count.zero?
|
101
|
+
plural = @result.error_count > 1 ? 'errors' : 'error'
|
102
|
+
results << lr::C.yellow("#{total[:errors]} #{plural}")
|
103
|
+
end
|
104
|
+
|
105
|
+
@io.write "\n"
|
106
|
+
@io.write "\n" unless lr.state.previous_failed
|
107
|
+
@io.write "#{total[:tests]} test#{'s' if @result.run_count > 1}: "
|
108
|
+
@io.write results.join(', ').reverse.sub(',', 'dna ').reverse # :(
|
109
|
+
@io.write "\n" + "\n"
|
110
|
+
@io.puts "(#{elapsed_time} seconds)"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: leftright
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordi Bunster
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-28 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: leftright is kind of like the redgreen gem. It makes passing tests look green, exceptions yellow, and failures red. It also has a few features that make your workflow a bit faster (see README).
|
17
|
+
email: jordi@bunster.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- MIT-LICENSE
|
26
|
+
- README.rdoc
|
27
|
+
- leftright.gemspec
|
28
|
+
- lib/leftright.rb
|
29
|
+
- lib/leftright/autorun.rb
|
30
|
+
- lib/leftright/color.rb
|
31
|
+
- lib/leftright/runner.rb
|
32
|
+
- lib/leftright/version.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage:
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Cool replacement for Test::Unit's TestRunner
|
61
|
+
test_files: []
|
62
|
+
|