baretest 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,268 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'test/assertion/failure'
10
+ begin
11
+ require 'thread'
12
+ rescue LoadError; end # no thread support in this ruby
13
+
14
+
15
+
16
+ module Test
17
+ @touch = {}
18
+ # We don't want to litter in Assertion
19
+ # Touches are associated with
20
+ def self.touch(assertion, thing=nil)
21
+ @touch[assertion] ||= Hash.new(0)
22
+ @touch[assertion][thing] += 1
23
+ end
24
+
25
+ def self.touched(assertion, thing=nil)
26
+ @touch[assertion] ||= Hash.new(0)
27
+ @touch[assertion][thing]
28
+ end
29
+
30
+ def self.clean_touches(assertion)
31
+ @touch.delete(assertion)
32
+ end
33
+ end
34
+
35
+ module Test
36
+ class Assertion
37
+ module Support
38
+ module SetupAndTeardown
39
+ def self.extended(run_obj)
40
+ run_obj.init do
41
+ suite.teardown do
42
+ Test.clean_touches(self) # instance evaled, self is the assertion
43
+ end
44
+ end
45
+ end
46
+
47
+ Test.extender << self
48
+ end
49
+
50
+ # Will raise a Failure if the given block doesn't raise or raises a different
51
+ # exception than the one provided
52
+ # You can optionally give an options :with_message, which is tested with === against
53
+ # the exception message.
54
+ # Examples:
55
+ # raises do raise "will work" end # => true
56
+ # raises SomeException do raise SomeException end # => true
57
+ # raises :with_message => "bar" do raise "bar" end # => true
58
+ # raises SomeException, :with_message => "bar"; raise SomeException, "bar" end # => true
59
+ # raises :with_message => /\Aknown \w+\z/; raise "known unknown" end # => true
60
+ def raises(exception_class=StandardError, opts={})
61
+ begin
62
+ yield
63
+ rescue exception_class => exception
64
+ if opts[:with_message] && !(opts[:with_message] === exception.message) then
65
+ failure "Expected block to raise with the message %p, but the message was %p",
66
+ exception.message, opts[:with_message]
67
+ else
68
+ true
69
+ end
70
+ rescue => exception
71
+ failure "Expected block to raise #{exception_class}, but it raised #{exception.class}."
72
+ else
73
+ failure "Expected block to raise #{exception_class}, but nothing was raised."
74
+ end
75
+ end
76
+
77
+ # Will raise a Failure if the given block raises.
78
+ def raises_nothing
79
+ yield
80
+ rescue => exception
81
+ failure "Expected block to raise nothing, but it raised #{exception.class}."
82
+ else
83
+ true
84
+ end
85
+
86
+ # For comparisons of Floats you shouldn't use == but
87
+ # for example a delta comparison instead, to take care
88
+ # of the possible rounding differences.
89
+ def within_delta(a, b, delta)
90
+ (a-b).abs < delta
91
+ end
92
+
93
+ # Use this method to test whether certain code (e.g. a callback) was reached.
94
+ # touch marks that it was reached, #touched tests for whether it was reached.
95
+ # Example:
96
+ # assert "Code in a Proc object is executed when invoking #call on it." do
97
+ # a_proc = proc { touch :executed }
98
+ # a_proc.call
99
+ # touched(:executed)
100
+ # end
101
+ def touch(thing=nil)
102
+ ::Test.touch(self, thing)
103
+ end
104
+
105
+ # See #touch
106
+ def touched(thing=nil, times=nil)
107
+ touched_times = ::Test.touched(self, thing)
108
+ if times then
109
+ unless touched_times == times then
110
+ if thing then
111
+ failure "Expected the code to touch %p %s times, but did %s times.", thing, times, touched_times
112
+ else
113
+ failure "Expected the code to touch %s times, but did %s times.", times, touched_times
114
+ end
115
+ end
116
+ elsif touched_times < 1 then
117
+ if thing then
118
+ failure "Expected the code to touch %p, but it was not touched.", thing
119
+ else
120
+ failure "Expected the code to touch, but no touch happened."
121
+ end
122
+ end
123
+ true
124
+ end
125
+
126
+ # See #touch
127
+ def not_touched(thing=nil)
128
+ touched(thing, 0)
129
+ end
130
+
131
+ # Uses equal? to test whether the objects are the same
132
+ # same expected, actual
133
+ # same :expected => expected, :actual => actual
134
+ def same(*args)
135
+ expected, actual, message = extract_args(args, :expected, :actual, :message)
136
+
137
+ unless expected.equal?(actual) then
138
+ if message then
139
+ failure "Expected %s to be the same (equal?) as %p but was %p.", message, expected, actual
140
+ else
141
+ failure "Expected %p but got %p.", expected, actual
142
+ end
143
+ end
144
+ true
145
+ end
146
+
147
+ # Uses eql? to test whether the objects are equal
148
+ # equal expected, actual
149
+ # equal :expected => expected, :actual => actual
150
+ def hash_key_equal(*args)
151
+ expected, actual, message = extract_args(args, :expected, :actual, :message)
152
+
153
+ unless expected.eql?(actual) then
154
+ if message then
155
+ failure "Expected %s to be hash-key equal (eql?) to %p but was %p.", message, expected, actual
156
+ else
157
+ failure "Expected %p but got %p.", expected, actual
158
+ end
159
+ end
160
+ true
161
+ end
162
+
163
+ # Uses == to test whether the objects are equal
164
+ # equal expected, actual
165
+ # equal :expected => expected, :actual => actual
166
+ def order_equal(*args)
167
+ expected, actual, message = extract_args(args, :expected, :actual, :message)
168
+
169
+ unless expected == actual then
170
+ if message then
171
+ failure "Expected %s to be order equal (==) to %p but was %p.", message, expected, actual
172
+ else
173
+ failure "Expected %p but got %p.", expected, actual
174
+ end
175
+ end
176
+ true
177
+ end
178
+ alias equal order_equal
179
+
180
+ # Uses === to test whether the objects are equal
181
+ # equal expected, actual
182
+ # equal :expected => expected, :actual => actual
183
+ def case_equal(*args)
184
+ expected, actual, message = extract_args(args, :expected, :actual, :message)
185
+
186
+ unless expected === actual then
187
+ failure_with_optional_message \
188
+ "Expected %s to be case equal (===) to %p but was %p.",
189
+ "Expected %p but got %p.",
190
+ message, expected, actual
191
+ end
192
+ true
193
+ end
194
+
195
+ # To compare two collections (which must implement #each)
196
+ # without considering order. E.g. two sets, or the keys of
197
+ # two hashes.
198
+ def equal_unordered(*args)
199
+ expected, actual, message = extract_args(args, :expected, :actual, :message)
200
+
201
+ count = Hash.new(0)
202
+ expected.each { |element| count[element] += 1 }
203
+ actual.each { |element| count[element] -= 1 }
204
+ unless count.all? { |key, value| value.zero? } then
205
+ only_in_expected = count.select { |ele, n| n > 0 }.map { |ele, n| ele }
206
+ only_in_actual = count.select { |ele, n| n < 0 }.map { |ele, n| ele }
207
+ if message then
208
+ failure "Expected %s to have the same items the same number of times, " \
209
+ "but %p are only in expected, and %p only in actual.",
210
+ message, only_in_expected, only_in_actual
211
+ else
212
+ failure "Expected %p and %p to have the same items the same number of times, " \
213
+ "but %p are only in expected, and %p only in actual.",
214
+ expected, actual, only_in_expected, only_in_actual
215
+ end
216
+ end
217
+ true
218
+ end
219
+
220
+ # Raises a Failure if the given object is not an instance of the given class
221
+ # or a descendant thereof
222
+ def kind_of(*args)
223
+ expected, actual, message = extract_args(args, :expected, :actual, :message)
224
+ unless actual.kind_of?(expected) then
225
+ failure_with_optional_message \
226
+ "Expected %1$s to be a kind of %3$p, but was a %4$p",
227
+ "Expected %2$p to be a kind of %1$p, but was a %3$p",
228
+ message, expected, actual, actual.class
229
+ end
230
+ true
231
+ end
232
+
233
+ def failure_with_optional_message(with_message, without_message, message, *args)
234
+ if message then
235
+ failure(with_message, message, *args)
236
+ else
237
+ failure(without_message, *args)
238
+ end
239
+ end
240
+
241
+ # Raises Test::Assertion::Failure and runs sprintf over message with *args
242
+ # Particularly useful with %p and %s.
243
+ def failure(message, *args)
244
+ raise Test::Assertion::Failure, sprintf(message, *args)
245
+ end
246
+
247
+ private
248
+ def extract_args(args, *named)
249
+ if args.size == 1 && Hash === args.first then
250
+ args.first.values_at(*named)
251
+ else
252
+ args.first(named.size)
253
+ end
254
+ end
255
+ end # Support
256
+
257
+ include Support
258
+ end # Assertion
259
+ end # Test
260
+
261
+ module Enumerable
262
+ def equal_unordered?(other)
263
+ count = Hash.new(0)
264
+ other.each { |element| count[element] += 1 }
265
+ each { |element| count[element] -= 1 }
266
+ count.all? { |key, value| value.zero? }
267
+ end
268
+ end
@@ -0,0 +1,117 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'test/assertion/failure'
10
+
11
+
12
+
13
+ module Test
14
+
15
+ # Defines an assertion
16
+ # An assertion belongs to a suite and consists of a description and a block.
17
+ # The verify the assertion, the suite's (and its ancestors) setup blocks are
18
+ # executed, then the assertions block is executed and after that, the suite's
19
+ # (and ancestors) teardown blocks are invoked.
20
+ #
21
+ # An assertion has 5 possible states, see Assertion#status for a list of them.
22
+ #
23
+ # There are various helper methods in lib/test/support.rb which help you
24
+ # defining nicer diagnostics or just easier ways to test common scenarios.
25
+ # The following are test helpers:
26
+ # * Kernel#raises(exception_class=StandardError)
27
+ # * Kernel#within_delta(a, b, delta)
28
+ # * Kernel#equal_unordered(a,b)
29
+ # * Enumerable#equal_unordered(other)
30
+ class Assertion
31
+
32
+ # An assertion has 5 possible states:
33
+ # :success
34
+ # : The assertion passed. This means the block returned a trueish value.
35
+ # :failure
36
+ # : The assertion failed. This means the block returned a falsish value.
37
+ # Alternatively it raised a Test::Failure (NOT YET IMPLEMENTED).
38
+ # The latter has the advantage that it can provide nicer diagnostics.
39
+ # :pending
40
+ # : No block given to the assertion to be run
41
+ # :skipped
42
+ # : If one of the parent suites is missing a dependency, its assertions
43
+ # will be skipped
44
+ # :error
45
+ # : The assertion errored out. This means the block raised an exception
46
+ attr_reader :status
47
+
48
+ # If an exception occured in Assertion#execute, this will contain the
49
+ # Exception object raised.
50
+ attr_reader :exception
51
+
52
+ # The description of this assertion.
53
+ attr_reader :description
54
+
55
+ # The failure reason.
56
+ attr_reader :failure_reason
57
+
58
+ # The suite this assertion belongs to
59
+ attr_reader :suite
60
+
61
+ # The block specifying the assertion
62
+ attr_reader :block
63
+
64
+ # suite
65
+ # : The suite the Assertion belongs to
66
+ # description
67
+ # : A descriptive string about what this Assertion tests.
68
+ # &block
69
+ # : The block definition. Without one, the Assertion will have a :pending
70
+ # status.
71
+ def initialize(suite, description, &block)
72
+ @suite = suite
73
+ @status = nil
74
+ @failure_reason = nil
75
+ @exception = nil
76
+ @description = description || "No description given"
77
+ @block = block
78
+ end
79
+
80
+ # Run all setups in the order of their nesting (outermost first, innermost last)
81
+ def setup
82
+ @suite.ancestry_setup.each { |setup| instance_eval(&setup) } if @suite
83
+ end
84
+
85
+ # Run all teardowns in the order of their nesting (innermost first, outermost last)
86
+ def teardown
87
+ @suite.ancestry_teardown.each { |setup| instance_eval(&setup) } if @suite
88
+ end
89
+
90
+ # Runs the assertion and sets the status and exception
91
+ def execute
92
+ @exception = nil
93
+ if @block then
94
+ setup
95
+ # run the assertion
96
+ begin
97
+ @status = instance_eval(&@block) ? :success : :failure
98
+ rescue ::Test::Assertion::Failure => failure
99
+ @status = :failure
100
+ @failure_reason = failure
101
+ rescue => exception
102
+ @failure_reason = "An error occurred"
103
+ @exception = exception
104
+ @status = :error
105
+ end
106
+ teardown
107
+ else
108
+ @status = :pending
109
+ end
110
+ self
111
+ end
112
+
113
+ def clean_copy(use_class=nil)
114
+ (use_class || self.class).new(@suite, @description, &@block)
115
+ end
116
+ end
117
+ end
data/lib/test/debug.rb ADDED
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'pp'
10
+ require 'yaml'
11
+
12
+
13
+
14
+ module Test
15
+ class Suite
16
+ def to_s
17
+ sprintf "%s %s", self.class, @description
18
+ end
19
+
20
+ def inspect
21
+ sprintf "#<%s:%08x %p>", self.class, object_id>>1, @description
22
+ end
23
+ end
24
+
25
+ class Assertion
26
+ def to_s
27
+ sprintf "%s %s", self.class, @description
28
+ end
29
+
30
+ def inspect
31
+ sprintf "#<%s:%08x @suite=%p %p>", self.class, object_id>>1, @suite, @description
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,104 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'test/debug'
10
+
11
+
12
+
13
+ module Test
14
+ module IRBMode
15
+ module AssertionExtensions
16
+ end
17
+
18
+ class AssertionContext < ::Test::Assertion
19
+ attr_accessor :original_assertion
20
+
21
+ def to_s
22
+ "Assertion"
23
+ end
24
+
25
+ def e!
26
+ em!
27
+ bt!
28
+ end
29
+
30
+ def em!
31
+ puts @original_assertion.exception
32
+ end
33
+
34
+ def bt!
35
+ size = caller.size+3
36
+ puts @original_assertion.exception.backtrace[0..-size]
37
+ end
38
+ end
39
+
40
+ def self.extended(by)
41
+ by.init do
42
+ require 'irb'
43
+ require 'test/debug'
44
+ IRB.setup(nil) # must only be called once
45
+ end
46
+ end
47
+
48
+ # Formatter callback.
49
+ # Invoked once for every assertion.
50
+ # Gets the assertion to run as single argument.
51
+ def run_test(assertion)
52
+ rv = super
53
+ # drop into irb if assertion failed
54
+ if [:failure, :error].include?(rv.status) then
55
+ start_irb_mode(assertion)
56
+ irb_mode_for_assertion(assertion)
57
+ stop_irb_mode(assertion)
58
+ end
59
+
60
+ @count[:test] += 1
61
+ @count[assertion.status] += 1
62
+ rv
63
+ end
64
+
65
+ def start_irb_mode(assertion)
66
+ ancestry = assertion.suite.ancestors.reverse.map { |suite| suite.name }
67
+
68
+ puts
69
+ puts "#{assertion.status.to_s.capitalize} in #{ancestry.join(' > ')}"
70
+ puts " #{assertion.description}"
71
+ puts "#{assertion.exception} - #{assertion.exception.backtrace.first}"
72
+ super
73
+ rescue NoMethodError # HAX, not happy about that. necessary due to order of extend
74
+ end
75
+
76
+ # This method is highlevel hax, try to add necessary API to
77
+ # Test::Assertion
78
+ def irb_mode_for_assertion(assertion)
79
+ irb_context = assertion.clean_copy(AssertionContext)
80
+ irb_context.original_assertion = assertion
81
+ irb_context.setup
82
+ @irb = IRB::Irb.new(IRB::WorkSpace.new(irb_context.send(:binding)))
83
+ irb = @irb # for closure
84
+
85
+ # HAX - cargo cult, taken from irb.rb, not yet really understood.
86
+ IRB.conf[:IRB_RC].call(irb.context) if IRB.conf[:IRB_RC] # loads the irbrc?
87
+ IRB.conf[:MAIN_CONTEXT] = irb.context # why would the main context be set here?
88
+ # /HAX
89
+
90
+ trap("SIGINT") do
91
+ irb.signal_handle
92
+ end
93
+ catch(:IRB_EXIT) do irb.eval_input end
94
+
95
+ irb_context.teardown
96
+ end
97
+
98
+ def stop_irb_mode(assertion)
99
+ puts
100
+ super
101
+ rescue NoMethodError # HAX, not happy about that. necessary due to order of extend
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,79 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ module Test
10
+ class Run
11
+ module CLI
12
+ Formats = {
13
+ :pending => "\e[43m%9s\e[0m %s%s\n",
14
+ :skipped => "\e[43m%9s\e[0m %s%s\n",
15
+ :success => "\e[42m%9s\e[0m %s%s\n",
16
+ :failure => "\e[41m%9s\e[0m %s%s\n",
17
+ :error => "\e[37;40;1m%9s\e[0m %s%s\n" # ]]]]]]]] - bbedit hates open brackets...
18
+ }
19
+
20
+ FooterFormats = {
21
+ :incomplete => "\e[43m%9s\e[0m\n",
22
+ :success => "\e[42m%9s\e[0m\n",
23
+ :failure => "\e[41m%9s\e[0m\n",
24
+ :error => "\e[37;40;1m%9s\e[0m\n" # ]]]]]]]] - bbedit hates open brackets...
25
+ }
26
+
27
+ def run_all(*args)
28
+ @depth = 0
29
+ puts "Running all tests\n"
30
+ start = Time.now
31
+ super # run all suites
32
+ status = global_status
33
+ printf "\n%2$d tests run in %1$.1fs\n%3$d successful, %4$d pending, %5$d failures, %6$d errors\n",
34
+ Time.now-start, *@count.values_at(:test, :success, :pending, :failure, :error)
35
+ print "Final status: "
36
+ printf FooterFormats[status], status_label(status)
37
+ end
38
+
39
+ def run_suite(suite)
40
+ return super unless suite.description
41
+ #label, size = ' '*@depth+suite.description, suite.tests.size.to_s
42
+ #printf "\n\e[1m%-*s\e[0m (%d tests)\n", 71-size.length, label, size
43
+ case size = suite.tests.size
44
+ when 0
45
+ puts "\n \e[1m#{' '*@depth+suite.description}\e[0m"
46
+ when 1
47
+ puts "\n \e[1m#{' '*@depth+suite.description}\e[0m (1 test)"
48
+ else
49
+ puts "\n \e[1m#{' '*@depth+suite.description}\e[0m (#{size} tests)"
50
+ end
51
+ @depth += 1
52
+ super # run the suite
53
+ @depth -= 1
54
+ end
55
+
56
+ def run_test(assertion)
57
+ rv = super # run the assertion
58
+ printf(Formats[rv.status], status_label(rv.status), ' '*@depth, rv.description)
59
+ if rv.status == :error then
60
+ indent = ' '+' '*@depth
61
+ print(indent, rv.exception.message, "\n", indent, rv.exception.backtrace.first, "\n")
62
+ elsif rv.status == :failure && rv.failure_reason then
63
+ print(' ', ' '*@depth, rv.failure_reason, "\n")
64
+ end
65
+ rv
66
+ end
67
+
68
+ def word_wrap(string, cols)
69
+ str.scan(/[^ ]+ /)
70
+ end
71
+
72
+ def status_label(status)
73
+ status.to_s.capitalize.center(9)
74
+ end
75
+ end
76
+ end
77
+
78
+ @format["test/run/cli"] = Run::CLI # register the extender
79
+ end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ module Test
10
+ class Run
11
+ module Errors
12
+ def run_all
13
+ @depth = 0
14
+ puts "Running all tests, reporting errors"
15
+ super
16
+ start = Time.now
17
+ puts "\nDone, #{@count[:error]} errors encountered."
18
+ end
19
+
20
+ def run_suite(suite)
21
+ return super unless suite.description
22
+ puts "#{' '*@depth+suite.description} (#{suite.tests.size} tests)"
23
+ @depth += 1
24
+ super # run the suite
25
+ @depth -= 1
26
+ end
27
+
28
+ def run_test(assertion)
29
+ rv = super # run the assertion
30
+ puts(' '*@depth+rv.description)
31
+ if rv.exception then
32
+ size = caller.size+5
33
+ puts((['-'*80, rv.exception]+rv.exception.backtrace[0..-size]+['-'*80, '']).map { |l|
34
+ (' '*(@depth+1))+l
35
+ })
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ @format["test/run/errors"] = Run::Errors # register the extender
42
+ end
@@ -0,0 +1,60 @@
1
+ #--
2
+ # Copyright 2009 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ module Test
10
+ class Run
11
+ module Interactive
12
+ def self.extended(obj)
13
+ obj.init do
14
+ require "test/irb_mode"
15
+ extend(Test::IRBMode)
16
+ end
17
+ end
18
+
19
+ def run_all
20
+ count = proc { |acc,csuite| acc+csuite.tests.size+csuite.suites.inject(0, &count) }
21
+ @counted = count[0, suite] # count the number of tests to run
22
+ @depth = 0
23
+ @width = (Math.log(@counted)/Math.log(10)).floor+1
24
+
25
+ puts "Running all tests in interactive mode\n"
26
+ $stdout.sync = true
27
+ print_count
28
+
29
+ super
30
+
31
+ puts "\n\nDone"
32
+ $stdout.sync = false
33
+ $stdout.flush
34
+ end
35
+
36
+ def run_suite(suite)
37
+ return super unless suite.description
38
+ @depth += 1
39
+ super
40
+ @depth -= 1
41
+ end
42
+
43
+ def run_test(assertion)
44
+ rv = super
45
+ print_count
46
+ rv
47
+ end
48
+
49
+ def print_count
50
+ printf "\rRan %*d of %*d assertions", @width, @count[:test], @width, @counted
51
+ end
52
+
53
+ def stop_irb_mode(assertion)
54
+ puts "\n\n"
55
+ end
56
+ end
57
+ end
58
+
59
+ @format["test/run/interactive"] = Run::Interactive # register the extender
60
+ end