mcmire-protest 0.2.4
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/.gitignore +2 -0
- data/LICENSE +22 -0
- data/README.rdoc +200 -0
- data/Rakefile +24 -0
- data/lib/protest/rails.rb +80 -0
- data/lib/protest/report.rb +121 -0
- data/lib/protest/reports/documentation.rb +77 -0
- data/lib/protest/reports/progress.rb +46 -0
- data/lib/protest/reports.rb +2 -0
- data/lib/protest/runner.rb +39 -0
- data/lib/protest/test_case.rb +248 -0
- data/lib/protest/tests.rb +90 -0
- data/lib/protest/utils/backtrace_filter.rb +25 -0
- data/lib/protest/utils/colorful_output.rb +67 -0
- data/lib/protest/utils/summaries.rb +129 -0
- data/lib/protest/utils.rb +6 -0
- data/lib/protest.rb +108 -0
- data/protest.gemspec +38 -0
- metadata +72 -0
@@ -0,0 +1,248 @@
|
|
1
|
+
module Protest
|
2
|
+
# Define a top level test context where to define tests. This works exactly
|
3
|
+
# the same as subclassing TestCase explicitly.
|
4
|
+
#
|
5
|
+
# Protest.context "A user" do
|
6
|
+
# ...
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# is just syntax sugar to write:
|
10
|
+
#
|
11
|
+
# class TestUser < Protest::TestCase
|
12
|
+
# self.description = "A user"
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
def self.context(description, &block)
|
16
|
+
TestCase.context(description, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
alias_method :describe, :context
|
21
|
+
alias_method :story, :context
|
22
|
+
end
|
23
|
+
|
24
|
+
# A TestCase defines a suite of related tests. You can further categorize
|
25
|
+
# your tests by declaring nested contexts inside the class. See
|
26
|
+
# TestCase.context.
|
27
|
+
class TestCase
|
28
|
+
begin
|
29
|
+
require "test/unit/assertions"
|
30
|
+
include Test::Unit::Assertions
|
31
|
+
rescue LoadError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Run all tests in this context. Takes a Report instance in order to
|
35
|
+
# provide output.
|
36
|
+
def self.run(runner)
|
37
|
+
runner.report(TestWrapper.new(:setup, self), true)
|
38
|
+
tests.each {|test| runner.report(test, false) }
|
39
|
+
runner.report(TestWrapper.new(:teardown, self), true)
|
40
|
+
rescue Exception => e
|
41
|
+
# If any exception bubbles up here, then it means it was during the
|
42
|
+
# global setup/teardown blocks, so let's just skip the rest of this
|
43
|
+
# context.
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
# Tests added to this context.
|
48
|
+
def self.tests
|
49
|
+
@tests ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add a test to be run in this context. This method is aliased as +it+ and
|
53
|
+
# +should+ for your comfort.
|
54
|
+
def self.test(name, &block)
|
55
|
+
tests << new(name, caller.at(0), &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add a setup block to be run before each test in this context. This method
|
59
|
+
# is aliased as +before+ for your comfort.
|
60
|
+
def self.setup(&block)
|
61
|
+
define_method :setup do
|
62
|
+
super()
|
63
|
+
instance_eval(&block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add a +setup+ block that will be run *once* for the entire test case,
|
68
|
+
# before the first test is run.
|
69
|
+
#
|
70
|
+
# Keep in mind that while +setup+ blocks are evaluated on the context of the
|
71
|
+
# test, and thus you can share state between them, your tests will not be
|
72
|
+
# able to access instance variables set in a +global_setup+ block.
|
73
|
+
#
|
74
|
+
# This is usually not needed (and generally using it is a code smell, since
|
75
|
+
# you could make a test dependent on the state of other tests, which is a
|
76
|
+
# huge problem), but it comes in handy when you need to do expensive
|
77
|
+
# operations in your test setup/teardown and the tests won't modify the
|
78
|
+
# state set on this operations. For example, creating large amount of
|
79
|
+
# records in a database or filesystem, when your tests will only read these
|
80
|
+
# records.
|
81
|
+
def self.global_setup(&block)
|
82
|
+
(class << self; self; end).class_eval do
|
83
|
+
define_method :do_global_setup do
|
84
|
+
super()
|
85
|
+
instance_eval(&block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add a teardown block to be run after each test in this context. This
|
91
|
+
# method is aliased as +after+ for your comfort.
|
92
|
+
def self.teardown(&block)
|
93
|
+
define_method :teardown do
|
94
|
+
instance_eval(&block)
|
95
|
+
super()
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add a +teardown+ block that will be run *once* for the entire test case,
|
100
|
+
# after the last test is run.
|
101
|
+
#
|
102
|
+
# Keep in mind that while +teardown+ blocks are evaluated on the context of
|
103
|
+
# the test, and thus you can share state between the tests and the
|
104
|
+
# teardown blocks, you will not be able to access instance variables set in
|
105
|
+
# a test from your +global_teardown+ block.
|
106
|
+
#
|
107
|
+
# See TestCase.global_setup for a discussion on why these methods are best
|
108
|
+
# avoided unless you really need them and use them carefully.
|
109
|
+
def self.global_teardown(&block)
|
110
|
+
(class << self; self; end).class_eval do
|
111
|
+
define_method :do_global_teardown do
|
112
|
+
instance_eval(&block)
|
113
|
+
super()
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Define a new test context nested under the current one. All +setup+ and
|
119
|
+
# +teardown+ blocks defined on the current context will be inherited by the
|
120
|
+
# new context. This method is aliased as +describe+ for your comfort.
|
121
|
+
def self.context(description, &block)
|
122
|
+
subclass = Class.new(self)
|
123
|
+
subclass.class_eval(&block) if block
|
124
|
+
subclass.description = description
|
125
|
+
const_set(sanitize_description(description), subclass)
|
126
|
+
end
|
127
|
+
|
128
|
+
class << self
|
129
|
+
# Fancy name for your test case, reports can use this to give nice,
|
130
|
+
# descriptive output when running your tests.
|
131
|
+
attr_accessor :description
|
132
|
+
|
133
|
+
alias_method :describe, :context
|
134
|
+
alias_method :story, :context
|
135
|
+
|
136
|
+
alias_method :before, :setup
|
137
|
+
alias_method :after, :teardown
|
138
|
+
|
139
|
+
alias_method :before_all, :global_setup
|
140
|
+
alias_method :after_all, :global_setup
|
141
|
+
|
142
|
+
alias_method :it, :test
|
143
|
+
alias_method :should, :test
|
144
|
+
alias_method :scenario, :test
|
145
|
+
end
|
146
|
+
|
147
|
+
# Initialize a new instance of a single test. This test can be run in
|
148
|
+
# isolation by calling TestCase#run.
|
149
|
+
def initialize(name, location, &block)
|
150
|
+
@test = block
|
151
|
+
@location = location
|
152
|
+
@name = name
|
153
|
+
end
|
154
|
+
|
155
|
+
# Run a test in isolation. Any +setup+ and +teardown+ blocks defined for
|
156
|
+
# this test case will be run as expected.
|
157
|
+
#
|
158
|
+
# You need to provide a Runner instance to handle errors/pending tests/etc.
|
159
|
+
#
|
160
|
+
# If the test's block is nil, then the test will be marked as pending and
|
161
|
+
# nothing will be run.
|
162
|
+
def run(report)
|
163
|
+
@report = report
|
164
|
+
pending if test.nil?
|
165
|
+
|
166
|
+
setup
|
167
|
+
instance_eval(&test)
|
168
|
+
teardown
|
169
|
+
@report = nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# Ensure a condition is met. This will raise AssertionFailed if the
|
173
|
+
# condition isn't met. You can override the default failure message
|
174
|
+
# by passing it as an argument.
|
175
|
+
def assert(condition, message="Expected condition to be satisfied")
|
176
|
+
@report.add_assertion
|
177
|
+
raise AssertionFailed, message unless condition
|
178
|
+
end
|
179
|
+
|
180
|
+
# Provided for Test::Unit compatibility, this lets you include
|
181
|
+
# Test::Unit::Assertions and everything works seamlessly.
|
182
|
+
def assert_block(message="Expected condition to be satisified") #:nodoc:
|
183
|
+
assert(yield, message)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Make the test be ignored as pending. You can override the default message
|
187
|
+
# that will be sent to the report by passing it as an argument.
|
188
|
+
def pending(message="Not Yet Implemented")
|
189
|
+
raise Pending, message, [@location, *caller].uniq
|
190
|
+
end
|
191
|
+
|
192
|
+
# Name of the test
|
193
|
+
def name
|
194
|
+
@name
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def setup #:nodoc:
|
200
|
+
end
|
201
|
+
|
202
|
+
def teardown #:nodoc:
|
203
|
+
end
|
204
|
+
|
205
|
+
def test
|
206
|
+
@test
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.sanitize_description(description)
|
210
|
+
"Test#{description.gsub(/\W+/, ' ').strip.gsub(/(^| )(\w)/) { $2.upcase }}".to_sym
|
211
|
+
end
|
212
|
+
private_class_method :sanitize_description
|
213
|
+
|
214
|
+
def self.do_global_setup
|
215
|
+
end
|
216
|
+
private_class_method :do_global_setup
|
217
|
+
|
218
|
+
def self.do_global_teardown
|
219
|
+
end
|
220
|
+
private_class_method :do_global_teardown
|
221
|
+
|
222
|
+
def self.description #:nodoc:
|
223
|
+
parent = ancestors[1..-1].detect {|a| a < Protest::TestCase }
|
224
|
+
"#{parent.description rescue nil} #{@description}".strip
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.inherited(child)
|
228
|
+
Protest.add_test_case(child)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Provides the TestCase API for global setup/teardown blocks, so they can be
|
232
|
+
# "faked" as tests into the reporter (they aren't counted towards the total
|
233
|
+
# number of tests but they could count towards the number of failures/errors.)
|
234
|
+
class TestWrapper #:nodoc:
|
235
|
+
attr_reader :name
|
236
|
+
|
237
|
+
def initialize(type, test_case)
|
238
|
+
@type = type
|
239
|
+
@test = test_case
|
240
|
+
@name = "Global #{@type} for #{test_case.description}"
|
241
|
+
end
|
242
|
+
|
243
|
+
def run(report)
|
244
|
+
@test.send("do_global_#{@type}")
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Protest
|
2
|
+
# Encapsulates the relevant information about a test. Useful for certain
|
3
|
+
# reports.
|
4
|
+
class Test
|
5
|
+
# Instance of the test case that was run.
|
6
|
+
attr_reader :test
|
7
|
+
|
8
|
+
# Name of the test that passed. Useful for certain reports.
|
9
|
+
attr_reader :test_name
|
10
|
+
|
11
|
+
def initialize(test) #:nodoc:
|
12
|
+
@test = test
|
13
|
+
@test_name = test.name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Mixin for tests that had an error (this could be either a failed assertion,
|
18
|
+
# unrescued exceptions, or just a pending tests.)
|
19
|
+
module TestWithErrors
|
20
|
+
# The triggered exception (AssertionFailed, Pending, or any
|
21
|
+
# subclass of Exception in the case of an ErroredTest.)
|
22
|
+
attr_reader :error
|
23
|
+
|
24
|
+
# Message with which it failed the assertion.
|
25
|
+
def error_message
|
26
|
+
error.message
|
27
|
+
end
|
28
|
+
|
29
|
+
# Line of the file where the assertion failed.
|
30
|
+
def line
|
31
|
+
backtrace.first.split(":")[1]
|
32
|
+
end
|
33
|
+
|
34
|
+
# File where the assertion failed.
|
35
|
+
def file
|
36
|
+
backtrace.first.split(":")[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Filtered backtrace of the assertion. See Protest::Utils::BacktraceFilter
|
40
|
+
# for details on the filtering.
|
41
|
+
def backtrace
|
42
|
+
@backtrace ||= Protest.backtrace_filter.filter_backtrace(raw_backtrace)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raw backtrace, as provided by the error.
|
46
|
+
def raw_backtrace
|
47
|
+
error.backtrace
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Encapsulate the relevant information for a test that passed.
|
52
|
+
class PassedTest < Test
|
53
|
+
end
|
54
|
+
|
55
|
+
# Encapsulates the relevant information for a test which failed an
|
56
|
+
# assertion.
|
57
|
+
class FailedTest < Test
|
58
|
+
include TestWithErrors
|
59
|
+
|
60
|
+
def initialize(test, error) #:nodoc:
|
61
|
+
super(test)
|
62
|
+
@error = error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Encapsulates the relevant information for a test which raised an
|
67
|
+
# unrescued exception.
|
68
|
+
class ErroredTest < Test
|
69
|
+
include TestWithErrors
|
70
|
+
|
71
|
+
def initialize(test, error) #:nodoc:
|
72
|
+
super(test)
|
73
|
+
@error = error
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Encapsulates the relevant information for a test that the user marked as
|
78
|
+
# pending.
|
79
|
+
class PendingTest < Test
|
80
|
+
include TestWithErrors
|
81
|
+
|
82
|
+
# Message passed to TestCase#pending, if any.
|
83
|
+
alias_method :pending_message, :error_message
|
84
|
+
|
85
|
+
def initialize(test, error) #:nodoc:
|
86
|
+
super(test)
|
87
|
+
@error = error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Protest
|
2
|
+
module Utils
|
3
|
+
# Small utility object to filter an error's backtrace and remove any mention
|
4
|
+
# of Protest's own files.
|
5
|
+
class BacktraceFilter
|
6
|
+
ESCAPE_PATHS = [
|
7
|
+
# Path to the library's 'lib' dir.
|
8
|
+
/^#{Regexp.escape(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))))}/,
|
9
|
+
|
10
|
+
# Users certainly don't care about what test loader is being used
|
11
|
+
%r[lib/rake/rake_test_loader.rb], %r[bin/testrb]
|
12
|
+
]
|
13
|
+
|
14
|
+
# Filter the backtrace, removing any reference to files located in
|
15
|
+
# BASE_PATH.
|
16
|
+
def filter_backtrace(backtrace, prefix=nil)
|
17
|
+
paths = ESCAPE_PATHS + [prefix].compact
|
18
|
+
backtrace.reject do |line|
|
19
|
+
file = line.split(":").first
|
20
|
+
paths.any? {|path| File.expand_path(file) =~ path }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Protest
|
2
|
+
module Utils
|
3
|
+
# Mixin that provides colorful output to your console based reports. This uses
|
4
|
+
# bash's escape sequences, so it won't work on windows.
|
5
|
+
#
|
6
|
+
# TODO: Make this work on windows with ansicolor or whatever the gem is named
|
7
|
+
module ColorfulOutput
|
8
|
+
# Returns a hash with the color values for different states. Override this
|
9
|
+
# method safely to change the output colors. The defaults are:
|
10
|
+
#
|
11
|
+
# :passed:: Light green
|
12
|
+
# :pending:: Light yellow
|
13
|
+
# :errored:: Light purple
|
14
|
+
# :failed:: Light red
|
15
|
+
#
|
16
|
+
# See http://www.hypexr.org/bash_tutorial.php#colors for a description of
|
17
|
+
# Bash color codes.
|
18
|
+
def self.colors
|
19
|
+
{ :passed => "1;32",
|
20
|
+
:pending => "1;33",
|
21
|
+
:errored => "1;35",
|
22
|
+
:failed => "1;31" }
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Whether to use colors in the output or not. The default is +true+.
|
27
|
+
attr_accessor :colorize
|
28
|
+
end
|
29
|
+
|
30
|
+
self.colorize = true
|
31
|
+
|
32
|
+
# Print the string followed by a newline to whatever IO stream is defined in
|
33
|
+
# the method #stream using the correct color depending on the state passed.
|
34
|
+
def puts(string=nil, state=:normal)
|
35
|
+
if string.nil? # calling IO#puts with nil is not the same as with no args
|
36
|
+
stream.puts
|
37
|
+
else
|
38
|
+
stream.puts colorize(string, state)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Print the string to whatever IO stream is defined in the method #stream
|
43
|
+
# using the correct color depending on the state passed.
|
44
|
+
def print(string=nil, state=:normal)
|
45
|
+
if string.nil? # calling IO#puts with nil is not the same as with no args
|
46
|
+
stream.print
|
47
|
+
else
|
48
|
+
stream.print colorize(string, state)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def colorize(string, state)
|
55
|
+
if state == :normal || !ColorfulOutput.colorize
|
56
|
+
string
|
57
|
+
else
|
58
|
+
"\033[#{color_for_state(state)}m#{string}\033[0m"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def color_for_state(state)
|
63
|
+
ColorfulOutput.colors.fetch(state)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Protest
|
2
|
+
module Utils
|
3
|
+
# Mixin that provides summaries for your text based test runs.
|
4
|
+
module Summaries
|
5
|
+
# Call on +:end+ to output the amount of tests (passed, pending, failed
|
6
|
+
# and errored), the amount of assertions, and the time elapsed.
|
7
|
+
#
|
8
|
+
# For example:
|
9
|
+
#
|
10
|
+
# on :end do |report|
|
11
|
+
# report.puts
|
12
|
+
# report.summarize_test_totals
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# This relies on the public Report API, and on the presence of a #puts
|
16
|
+
# method to write to whatever source you are writing your report.
|
17
|
+
def summarize_test_totals
|
18
|
+
puts test_totals
|
19
|
+
puts running_time
|
20
|
+
end
|
21
|
+
|
22
|
+
# Call on +:end+ to print a list of pending tests, including file and line
|
23
|
+
# number where the call to TestCase#pending+ was made.
|
24
|
+
#
|
25
|
+
# It will not output anything if there weren't any pending tests.
|
26
|
+
#
|
27
|
+
# For example:
|
28
|
+
#
|
29
|
+
# on :end do |report|
|
30
|
+
# report.puts
|
31
|
+
# report.summarize_pending_tests
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# This relies on the public Report API, and on the presence of a #puts
|
35
|
+
# method to write to whatever source you are writing your report.
|
36
|
+
def summarize_pending_tests
|
37
|
+
return if pendings.empty?
|
38
|
+
|
39
|
+
puts "Pending tests:"
|
40
|
+
puts
|
41
|
+
|
42
|
+
pad_indexes = pendings.size.to_s.size
|
43
|
+
pendings.each_with_index do |pending, index|
|
44
|
+
puts " #{pad(index+1, pad_indexes)}) #{pending.test_name} (#{pending.pending_message})", :pending
|
45
|
+
puts indent("On line #{pending.line} of `#{pending.file}'", 6 + pad_indexes), :pending
|
46
|
+
puts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Call on +:end+ to print a list of failures (failed assertions) and errors
|
51
|
+
# (unrescued exceptions), including file and line number where the test
|
52
|
+
# failed, and a short backtrace.
|
53
|
+
#
|
54
|
+
# It will not output anything if there weren't any pending tests.
|
55
|
+
#
|
56
|
+
# For example:
|
57
|
+
#
|
58
|
+
# on :end do |report|
|
59
|
+
# report.puts
|
60
|
+
# report.summarize_pending_tests
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# This relies on the public Report API, and on the presence of a #puts
|
64
|
+
# method to write to whatever source you are writing your report.
|
65
|
+
def summarize_errors
|
66
|
+
return if failures_and_errors.empty?
|
67
|
+
|
68
|
+
puts "Failures:"
|
69
|
+
puts
|
70
|
+
|
71
|
+
pad_indexes = failures_and_errors.size.to_s.size
|
72
|
+
failures_and_errors.each_with_index do |error, index|
|
73
|
+
colorize_as = ErroredTest === error ? :errored : :failed
|
74
|
+
puts " #{pad(index+1, pad_indexes)}) #{test_type(error)}: `#{error.test_name}' (on line #{error.line} of `#{error.file}')", colorize_as
|
75
|
+
# If error message has line breaks, indent the message
|
76
|
+
if error.error_message =~ /\n/
|
77
|
+
puts indent("with #{error.error.class}: <<", 6 + pad_indexes), colorize_as
|
78
|
+
puts indent(error.error_message, 6 + pad_indexes + 2), colorize_as
|
79
|
+
puts indent(">>", 6 + pad_indexes), colorize_as
|
80
|
+
else
|
81
|
+
puts indent("with #{error.error.class} `#{error.error_message}'", 6 + pad_indexes), colorize_as
|
82
|
+
end
|
83
|
+
indent(error.backtrace[0..2], 6 + pad_indexes).each {|backtrace| puts backtrace, colorize_as }
|
84
|
+
puts
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def running_time
|
91
|
+
"Ran in #{time_elapsed} seconds"
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_totals
|
95
|
+
"%d test%s, %d assertion%s (%d passed, %d pending, %d failed, %d errored)" % [total_tests,
|
96
|
+
total_tests == 1 ? "" : "s",
|
97
|
+
assertions,
|
98
|
+
assertions == 1 ? "" : "s",
|
99
|
+
passes.size,
|
100
|
+
pendings.size,
|
101
|
+
failures.size,
|
102
|
+
errors.size]
|
103
|
+
end
|
104
|
+
|
105
|
+
def indent(strings, size=2, indent_with=" ")
|
106
|
+
# Don't use Array(strings) here in case strings contains line breaks. Here's why:
|
107
|
+
# <Array(strings)> is equivalent to <strings.to_a>
|
108
|
+
# <strings.to_a> is equivalent to <s = ""; strings.each {|x| s << x }; s>
|
109
|
+
# and in Ruby 1.8, String#each is equivalent to String#each_line
|
110
|
+
# so effectively it's equivalent to .split("\n") but keeping the \n's
|
111
|
+
strings = [strings] unless Array === strings
|
112
|
+
strings.map do |str|
|
113
|
+
str.to_s.split("\n").map {|s| indent_with * size + s }.join("\n")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def pad(str, amount)
|
118
|
+
" " * (amount - str.to_s.size) + str.to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_type(test)
|
122
|
+
case test # order is important since ErroredTest < FailedTest
|
123
|
+
when ErroredTest; "Error"
|
124
|
+
when FailedTest; "Failure"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/protest.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
module Protest
|
2
|
+
VERSION = "0.2.4"
|
3
|
+
|
4
|
+
# Exception raised when an assertion fails. See TestCase#assert
|
5
|
+
class AssertionFailed < StandardError; end
|
6
|
+
|
7
|
+
# Exception raised to mark a test as pending. See TestCase#pending
|
8
|
+
class Pending < StandardError; end
|
9
|
+
|
10
|
+
# Register a new Report. This will make your report available to Protest,
|
11
|
+
# allowing you to run your tests through this report. For example
|
12
|
+
#
|
13
|
+
# module Protest
|
14
|
+
# class Reports::MyAwesomeReport < Report
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# add_report :awesomesauce, MyAwesomeReport
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# See Protest.report_with to see how to select which report will be used.
|
21
|
+
def self.add_report(name, report)
|
22
|
+
available_reports[name] = report
|
23
|
+
end
|
24
|
+
|
25
|
+
# Register a test case to be run with Protest. This is done automatically
|
26
|
+
# whenever you subclass Protest::TestCase, so you probably shouldn't pay
|
27
|
+
# much attention to this method.
|
28
|
+
def self.add_test_case(test_case)
|
29
|
+
available_test_cases << test_case
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set to +false+ to avoid running tests +at_exit+. Default is +true+.
|
33
|
+
def self.autorun=(flag)
|
34
|
+
@autorun = flag
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks to see if tests should be run +at_exit+ or not. Default is +true+.
|
38
|
+
# See Protest.autorun=
|
39
|
+
def self.autorun?
|
40
|
+
!!@autorun
|
41
|
+
end
|
42
|
+
|
43
|
+
# Run all registered test cases through the selected report. You can pass
|
44
|
+
# arguments to the Report constructor here.
|
45
|
+
#
|
46
|
+
# See Protest.add_test_case and Protest.report_with
|
47
|
+
def self.run_all_tests!(*report_args)
|
48
|
+
Runner.new(@report).run(*available_test_cases)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Select the name of the Report to use when running tests. See
|
52
|
+
# Protest.add_report for more information on registering a report.
|
53
|
+
#
|
54
|
+
# Any extra arguments will be forwarded to the report's #initialize method.
|
55
|
+
#
|
56
|
+
# The default report is Protest::Reports::Progress
|
57
|
+
def self.report_with(name, *report_args)
|
58
|
+
@report = report(name, *report_args)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Load a report by name, initializing it with the extra arguments provided.
|
62
|
+
# If the given +name+ doesn't match a report registered via
|
63
|
+
# Protest.add_report then the method will raise IndexError.
|
64
|
+
def self.report(name, *report_args)
|
65
|
+
available_reports.fetch(name).new(*report_args)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set what object will filter the backtrace. It must respond to
|
69
|
+
# +filter_backtrace+, taking a backtrace array and a prefix path.
|
70
|
+
def self.backtrace_filter=(filter)
|
71
|
+
@backtrace_filter = filter
|
72
|
+
end
|
73
|
+
|
74
|
+
# The object that filters the backtrace
|
75
|
+
def self.backtrace_filter
|
76
|
+
@backtrace_filter
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.available_test_cases
|
80
|
+
@test_cases ||= []
|
81
|
+
end
|
82
|
+
private_class_method :available_test_cases
|
83
|
+
|
84
|
+
def self.available_reports
|
85
|
+
@available_reports ||= {}
|
86
|
+
end
|
87
|
+
private_class_method :available_reports
|
88
|
+
end
|
89
|
+
|
90
|
+
require "protest/utils"
|
91
|
+
require "protest/utils/backtrace_filter"
|
92
|
+
require "protest/utils/summaries"
|
93
|
+
require "protest/utils/colorful_output"
|
94
|
+
require "protest/test_case"
|
95
|
+
require "protest/tests"
|
96
|
+
require "protest/runner"
|
97
|
+
require "protest/report"
|
98
|
+
require "protest/reports"
|
99
|
+
require "protest/reports/progress"
|
100
|
+
require "protest/reports/documentation"
|
101
|
+
|
102
|
+
Protest.autorun = true
|
103
|
+
Protest.report_with(:progress)
|
104
|
+
Protest.backtrace_filter = Protest::Utils::BacktraceFilter.new
|
105
|
+
|
106
|
+
at_exit do
|
107
|
+
Protest.run_all_tests! if Protest.autorun?
|
108
|
+
end
|