leftright 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|