baretest 0.1.0

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.
@@ -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