mcmire-protest 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|