whitestone 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ D "Outer" do
2
+ T { 1 + 1 == 2 }
3
+
4
+ D "Inner" do
5
+ F false
6
+ N "foo".gsub!(/x/,'y')
7
+ end
8
+ end
9
+
10
+ D "Fail fast on error (direct execution)" do
11
+ T { 1 + 1 == 2 } # will pass
12
+ T "foo".frobnosticate? # will cause error and should cause suite to aboure
13
+ Eq "whitestone".length, 10 # would pass if it ran
14
+ Eq "whitestone".length, 17 # would fail, but shouldn't get to this point
15
+
16
+ D "Won't get here" do
17
+ Eq "won't get here".size, 30 # shouldn't see a failure for this
18
+ end
19
+ end
20
+
21
+ D "Fail fast on error (indirect execution)" do
22
+ T { 1 + 1 == 2 } # will pass
23
+ T { "foo".frobnosticate? } # will cause error and should cause suite to aboure
24
+ T false # shouldn't see failure for this
25
+ end
26
+
27
+ # Not implemented at the time this code was committed.
28
+ D "Fail fast on assertion failure" do
29
+ T { 1 + 1 == 2 } # will pass
30
+ Eq 5.succ, 8 # will fail and thereby cause suite to abort
31
+ Eq "whitestone".length, 10 # would pass if it ran
32
+ Eq "whitestone".length, 17 # would fail, but shouldn't get to this point
33
+
34
+ D "Won't get here" do
35
+ Eq "won't get here".size, 30 # shouldn't see a failure for this
36
+ end
37
+ end
38
+
39
+ D "Sibling suites unaffected by error or failure" do
40
+ D "suite 1 pass" do
41
+ T true
42
+ end
43
+ D "suite 2 fail" do
44
+ T nil
45
+ end
46
+ D "suite 3 pass (unaffected by suite 2's failure)" do
47
+ T true
48
+ end
49
+ D "suite 4 error" do
50
+ T { "foo".frobnosticate? }
51
+ end
52
+ D "suite 5 pass (unaffected by suite 4's error)" do
53
+ T true
54
+ end
55
+ end
56
+
@@ -0,0 +1 @@
1
+ ruby -e 'puts "\n" * 100'; ruby -rubygems -Ilib test/output_examples.rb
@@ -0,0 +1 @@
1
+ ruby -rubygems -Ilib test/whitestone_test.rb
data/etc/ws ADDED
@@ -0,0 +1 @@
1
+ ruby -Ilib -rubygems -I../col/lib bin/whitestone $*
@@ -0,0 +1,710 @@
1
+ require 'whitestone/support' # String enhancements
2
+ require 'whitestone/version'
3
+ require 'col' # ANSI colours
4
+
5
+ # =================== T A B L E O F C O N T E N T S ==================== #
6
+ # #
7
+ # * Exceptions; Test and Scope classes #
8
+ # * Accessory methods: stats, current_test, caught_value, exception #
9
+ # * D, D!, <, >, <<, >>, S, S!, S? #
10
+ # * Assertions: T F N Eq Mt Ko Ft E C + custom assertions + 'action' #
11
+ # * run, stop, execute, call #
12
+ # * Instance variables: @stats, @current_scope, @current_test, etc. #
13
+ # * Code for mixing in: D = ::Whitestone; T, F, Eq, Etc. #
14
+ # #
15
+ # ============================================================================ #
16
+
17
+
18
+ module Whitestone
19
+
20
+ # --------------------------------------------------------------section---- #
21
+ # #
22
+ # Exception classes #
23
+ # Test and Scope classes #
24
+ # #
25
+ # ------------------------------------------------------------------------- #
26
+
27
+ class ErrorOccurred < StandardError; end
28
+ class FailureOccurred < StandardError
29
+ def initialize(context, message, backtrace)
30
+ @context = context
31
+ @message = message
32
+ @backtrace = backtrace
33
+ end
34
+ attr_reader :context, :message, :backtrace
35
+ end
36
+ class AssertionSpecificationError < StandardError; end
37
+
38
+ ##
39
+ # A Test object is what results when the following code is executed:
40
+ #
41
+ # D "civil" do
42
+ # Eq @d.year, 1972
43
+ # Eq @d.month, 5
44
+ # Eq @d.day, 13
45
+ # end
46
+ #
47
+ # Test objects gather in a tree structure, useful for reporting.
48
+ class Test
49
+ attr_accessor :description, :block, :sandbox
50
+ attr_accessor :result
51
+ attr_accessor :error
52
+ attr_accessor :parent
53
+ attr_reader :children
54
+ def initialize(description, block, sandbox)
55
+ @description, @block, @sandbox = description, block, sandbox
56
+ @result = :blank # A 'blank' result until an assertion is run.
57
+ @error = nil # The exception object, if any.
58
+ @parent = nil # The test object in whose scope this test is defined.
59
+ @children = [] # The children of this test.
60
+ end
61
+ def parent=(test)
62
+ @parent = test
63
+ if @parent
64
+ @parent.children << self
65
+ end
66
+ end
67
+ def passed?; @result == :pass; end
68
+ def failed?; @result == :fail; end
69
+ def error?; @result == :error; end
70
+ def blank?; @result == :blank; end
71
+ end # class Test
72
+
73
+ ##
74
+ # A Scope object contains a group of Test objects and the setup and teardown
75
+ # information for that group. A 'D' method opens a new scope.
76
+ class Scope
77
+ attr_reader :tests, :before_each, :after_each, :before_all, :after_all
78
+ def initialize
79
+ @tests = []
80
+ @before_each = []
81
+ @after_each = []
82
+ @before_all = []
83
+ @after_all = []
84
+ end
85
+ def filter(regex)
86
+ @tests = @tests.select { |t| t.description =~ regex }
87
+ end
88
+ end # class Scope
89
+
90
+
91
+ class << Whitestone
92
+
93
+ # ------------------------------------------------------------section---- #
94
+ # #
95
+ # Accessory methods: stats, current_test, caught_value, exception #
96
+ # #
97
+ # ----------------------------------------------------------------------- #
98
+
99
+ ##
100
+ # 'stats' is a hash with the following keys:
101
+ # :pass :fail :error :assertions :time
102
+ attr_reader :stats
103
+
104
+ ##
105
+ # The _description_ of the currently-running test. Very useful for
106
+ # conditional breakpoints in library code. E.g.
107
+ # debugger if Whitestone.current_test =~ /something.../
108
+ def current_test
109
+ (@current_test.nil?) ? "(toplevel)" : @current_test.description
110
+ end
111
+
112
+ ##
113
+ # When a C assertion is run (i.e. that the expected symbol will be thrown),
114
+ # the value that is thrown along with the symbol will be stored in
115
+ # Whitestone.caught_value in case it needs to be tested. If no value is
116
+ # thrown, this accessor will contain nil.
117
+ attr_accessor :caught_value
118
+
119
+ ##
120
+ # When an E assertion is run (i.e. that the expected error will be raised),
121
+ # the exception that is rescued will be stored in Whitestone.exception in case
122
+ # it needs to be tested.
123
+ attr_accessor :exception
124
+
125
+
126
+ # ------------------------------------------------------------section---- #
127
+ # #
128
+ # D, D!, <, >, <<, >>, S, S!, S? #
129
+ # #
130
+ # ----------------------------------------------------------------------- #
131
+
132
+ ##
133
+ # Defines a new test composed of the given
134
+ # description and the given block to execute.
135
+ #
136
+ # This test may contain nested tests.
137
+ #
138
+ # Tests at the outer-most level are automatically
139
+ # insulated from the top-level Ruby environment.
140
+ def D *description, &block
141
+ create_test @tests.empty?, *description, &block
142
+ end
143
+
144
+ ##
145
+ # Defines a new test that is explicitly insulated from the tests
146
+ # that contain it and also from the top-level Ruby environment.
147
+ #
148
+ # This test may contain nested tests.
149
+ def D! *description, &block
150
+ create_test true, *description, &block
151
+ end
152
+
153
+ def create_test insulate, *description, &block
154
+ raise ArgumentError, 'block must be given' unless block
155
+ description = description.join(' ')
156
+ sandbox = Object.new if insulate
157
+ new_test = Whitestone::Test.new(description, block, sandbox)
158
+ new_test.parent = @tests.last
159
+ @current_scope.tests << new_test
160
+ end
161
+ private :create_test
162
+
163
+ # Registers the given block to be executed
164
+ # before each nested test inside this test.
165
+ def <(*args, &block)
166
+ if args.empty?
167
+ raise ArgumentError, 'block must be given' unless block
168
+ @current_scope.before_each << block
169
+ else
170
+ # the < method is being used as a check for inheritance
171
+ super
172
+ end
173
+ end
174
+
175
+ # Registers the given block to be executed
176
+ # after each nested test inside this test.
177
+ def > &block
178
+ raise ArgumentError, 'block must be given' unless block
179
+ @current_scope.after_each << block
180
+ end
181
+
182
+ # Registers the given block to be executed
183
+ # before all nested tests inside this test.
184
+ def << &block
185
+ raise ArgumentError, 'block must be given' unless block
186
+ @current_scope.before_all << block
187
+ end
188
+
189
+ # Registers the given block to be executed
190
+ # after all nested tests inside this test.
191
+ def >> &block
192
+ raise ArgumentError, 'block must be given' unless block
193
+ @current_scope.after_all << block
194
+ end
195
+
196
+ # Mechanism for sharing code between tests.
197
+ #
198
+ # S :values do
199
+ # @values = [8,9,10]
200
+ # end
201
+ #
202
+ # D "some test" do
203
+ # S :values
204
+ # Eq @values.last, 10
205
+ # end
206
+ #
207
+ def S identifier, &block
208
+ if block_given?
209
+ if already_shared = @share[identifier]
210
+ msg = "A code block #{already_shared.inspect} has already " \
211
+ "been shared under the identifier #{identifier.inspect}."
212
+ raise ArgumentError, msg
213
+ end
214
+ @share[identifier] = block
215
+
216
+ elsif block = @share[identifier]
217
+ if @tests.empty?
218
+ msg = "Cannot inject code block #{block.inspect} shared under " \
219
+ "identifier #{identifier.inspect} outside of a Whitestone test."
220
+ raise
221
+ else
222
+ # Find the closest insulated parent test; this should always
223
+ # succeed because root-level tests are insulated by default.
224
+ test = @tests.reverse.find { |t| t.sandbox }
225
+ test.sandbox.instance_eval(&block)
226
+ end
227
+
228
+ else
229
+ raise ArgumentError, "No code block is shared under " \
230
+ "identifier #{identifier.inspect}."
231
+ end
232
+ end
233
+
234
+ # Shares the given code block AND inserts it in-place.
235
+ # (Well, by in-place, I mean the closest insulated block.)
236
+ def S! identifier, &block
237
+ raise 'block must be given' unless block_given?
238
+ S identifier, &block
239
+ S identifier
240
+ end
241
+
242
+ # Checks whether any code has been shared under the given identifier.
243
+ def S? identifier
244
+ @share.key? identifier
245
+ end
246
+
247
+
248
+ # ------------------------------------------------------------section---- #
249
+ # #
250
+ # Assertions: T F N Eq Mt Ko Ft E C #
251
+ # + custom assertions #
252
+ # + the 'action' method #
253
+ # #
254
+ # ----------------------------------------------------------------------- #
255
+
256
+ require 'whitestone/assertion_classes'
257
+ # ^^^ Assertion::True, Assertion::False, Assertion::Equality, etc.
258
+ require 'whitestone/custom_assertions'
259
+ # ^^^ Assertion::Custom
260
+
261
+ ASSERTION_CLASSES = {
262
+ :T => Assertion::True, :F => Assertion::False, :N => Assertion::Nil,
263
+ :Eq => Assertion::Equality, :Mt => Assertion::Match, :Ko => Assertion::KindOf,
264
+ :Ft => Assertion::FloatEqual, :Id => Assertion::Identity,
265
+ :E => Assertion::ExpectError, :C => Assertion::Catch,
266
+ :custom => Assertion::Custom
267
+ }
268
+
269
+ # Dynamically define the primitive assertion methods.
270
+
271
+ %w{T F N Eq Mt Ko Ft Id E C}.each do |base|
272
+ assert_method = base
273
+ negate_method = base + "!"
274
+ query_method = base + "?"
275
+
276
+ lineno = __LINE__
277
+ code = %{
278
+ def #{assert_method}(*args, &block)
279
+ action :#{base}, :assert, *args, &block
280
+ end
281
+
282
+ def #{negate_method}(*args, &block)
283
+ action :#{base}, :negate, *args, &block
284
+ end
285
+
286
+ def #{query_method}(*args, &block)
287
+ action :#{base}, :query, *args, &block
288
+ end
289
+ }
290
+ module_eval code, __FILE__, lineno+2
291
+ end
292
+
293
+ # === Whitestone.action
294
+ #
295
+ # This is an absolutely key method. It implements T, F, Eq, T!, F?, Eq?, etc.
296
+ # After some sanity checking, it creates an assertion object, runs it, and
297
+ # sees whether it passed or failed.
298
+ #
299
+ # If the assertion fails, we raise FailureOccurred, with the necessary
300
+ # information about the failure. If an error happens while the assertion is
301
+ # run, we don't catch it. Both the error and the failure are handled
302
+ # upstream, in Whitestone.call.
303
+ #
304
+ # It's worth noting that errors can occur while tests are run that are
305
+ # unconnected to this method. Consider these two examples:
306
+ # T { "foo".frobnosticate? } -- error occurs on our watch
307
+ # T "foo".frobnosticate? -- error occurs before T() is called
308
+ #
309
+ # By letting errors from here escape, the two cases can be dealt with
310
+ # together.
311
+ #
312
+ # T and F are special cases: they can be called with custom assertions.
313
+ #
314
+ # T :circle, c, [4,1, 10, :H]
315
+ # -> run_custom_test(:circle, :assert, [4,1,10,:H])
316
+ #
317
+ def action(base, assert_negate_query, *args, &block)
318
+ mode = assert_negate_query # :assert, :negate or :query
319
+
320
+ # Sanity checks: these should never fail!
321
+ unless [:assert, :negate, :query].include? mode
322
+ raise AssertionSpecificationError, "Invalid mode: #{mode.inspect}"
323
+ end
324
+ unless ASSERTION_CLASSES.key? base
325
+ raise AssertionSpecificationError, "Invalid base: #{base.inspect}"
326
+ end
327
+
328
+ # Special case: T may be used to invoke custom assertions.
329
+ # We catch the use of F as well, even though it's disallowed, so that
330
+ # we can give an appropriate error message.
331
+ if base == :T or base == :F and args.size > 1 and args.first.is_a? Symbol
332
+ if base == :T and mode == :assert
333
+ # Run a custom assertion.
334
+ inside_custom_assertion do
335
+ action(:custom, :assert, *args)
336
+ end
337
+ return nil
338
+ else
339
+ message = "You are attempting to run a custom assertion.\n"
340
+ message << "These can only be run with T, not F, T?, T!, F? etc."
341
+ raise AssertionSpecificationError, message
342
+ end
343
+ end
344
+
345
+ assertion = ASSERTION_CLASSES[base].new(mode, *args, &block)
346
+ # e.g. assertion = Assertion::Equality(:assert, 4, 4) # no block
347
+ # assertion = Assertion::Nil(:query) { names.find "Tobias" }
348
+ # assertion = Assertion::Custom(...)
349
+
350
+ stats[:assertions] += 1 unless @inside_custom_assertion
351
+
352
+ # We run the assertion (returns true for pass and false for fail).
353
+ passed = assertion.run
354
+
355
+ # We negate the result if neccesary...
356
+ case mode
357
+ when :negate then passed = ! passed
358
+ when :query then return passed
359
+ end
360
+ # ...and report a failure if necessary.
361
+ if passed
362
+ # We do this here because we only want the test to pass if it actually
363
+ # runs an assertion; otherwise its result is 'blank'. If a later
364
+ # assertion in the test fails or errors, the result will be rewritten.
365
+ @current_test.result = :pass if @current_test
366
+ else
367
+ calling_context = assertion.block || @calls.last
368
+ backtrace = caller
369
+ raise FailureOccurred.new(calling_context, assertion.message, backtrace)
370
+ end
371
+ end # action
372
+ private :action
373
+
374
+ ##
375
+ # {inside_custom_assertion} allows us (via {yield}) to run a custom
376
+ # assertion without racking up the assertion count for each of the
377
+ # assertions therein.
378
+ # Todo: consider making it a stack so that custom assertions can be nested.
379
+ def inside_custom_assertion
380
+ @inside_custom_assertion = true
381
+ stats[:assertions] += 1
382
+ yield
383
+ ensure
384
+ @inside_custom_assertion = false
385
+ end
386
+ private :inside_custom_assertion
387
+
388
+ ##
389
+ # Whitestone.custom _defines_ a custom assertion.
390
+ #
391
+ # Example usage:
392
+ # Whitestone.custom :circle, {
393
+ # :description => "Circle equality",
394
+ # :parameters => [ [:circle, Circle], [:values, Array] ],
395
+ # :run => lambda { |circle, values|
396
+ # x, y, r, label = values
397
+ # test('x') { Ft x, circle.centre.x }
398
+ # test('y') { Ft y, circle.centre.y }
399
+ # test('r') { Ft r, circle.radius }
400
+ # test('label') { Eq Label[label], circle.label }
401
+ # }
402
+ # }
403
+ def custom(name, definition)
404
+ define_custom_assertion(name, definition)
405
+ end
406
+
407
+ def define_custom_assertion(name, definition)
408
+ legitimate_keys = Set[:description, :parameters, :check, :run]
409
+ unless Symbol === name and Hash === definition and
410
+ (definition.keys + [:check]).all? { |key| legitimate_keys.include? key }
411
+ message = %{
412
+ #
413
+ #Usage:
414
+ # Whitestone.custom(name, definition)
415
+ # where name is a symbol
416
+ # and definition is a hash with keys :description, :parameters, :run
417
+ # and optionally :check
418
+ }.___margin
419
+ raise AssertionSpecificationError, Col[message].yb
420
+ end
421
+ Assertion::Custom.define(name, definition)
422
+ end
423
+ private :define_custom_assertion
424
+
425
+
426
+ # ------------------------------------------------------------section---- #
427
+ # #
428
+ # run, stop, execute, call #
429
+ # #
430
+ # Only 'run' and 'stop' are public, but 'execute' and 'call' are #
431
+ # fundamentally important methods for the operation of whitestone. #
432
+ # #
433
+ # ----------------------------------------------------------------------- #
434
+
435
+ #
436
+ # === Whitestone.run
437
+ #
438
+ # Executes all tests defined thus far. Tests are defined by 'D' blocks.
439
+ # Test objects live in a Scope. @current_scope is the top-level scope, but
440
+ # this variable is changed during execution to point to nested scopes as
441
+ # needed (and then changed back again).
442
+ #
443
+ # This method should therefore be run _after_ all the tests have been
444
+ # defined, e.g. in an at_exit clause. Requiring 'whitestone/auto' does that for
445
+ # you.
446
+ #
447
+ # Argument: options hash
448
+ # * {:filter} is a Regex. Only top-level tests whose descriptions
449
+ # match that regex will be run.
450
+ # * {:full_backtrace} is true or false: do you want the backtraces
451
+ # reported in event of failure or error to be filtered or not? Most of the
452
+ # time you would want them to be filtered (therefore _false_).
453
+ #
454
+ def run(options={})
455
+ test_filter_pattern = options[:filter]
456
+ @output.set_full_backtrace if options[:full_backtrace]
457
+ # Clear previous results.
458
+ @stats.clear
459
+ @tests.clear
460
+
461
+ # Filter the tests if asked to.
462
+ if test_filter_pattern
463
+ @top_level.filter(test_filter_pattern)
464
+ if @top_level.tests.empty?
465
+ msg = "!! Applied filter #{test_filter_pattern.inspect}, which left no tests to be run!"
466
+ STDERR.puts Col[msg].yb
467
+ exit
468
+ end
469
+ end
470
+
471
+ # Execute the tests.
472
+ @stats[:time] = record_execution_time do
473
+ catch(:stop_dfect_execution) do
474
+ execute # <-- This is where the real action takes place.
475
+ end
476
+ end
477
+
478
+ # Display reports.
479
+ @output.display_test_by_test_result(@top_level)
480
+ @output.display_details_of_failures_and_errors
481
+ @output.display_results_npass_nfail_nerror_etc(@stats)
482
+
483
+ @top_level = @current_scope = Whitestone::Scope.new
484
+ # ^^^ In case 'run' gets called again; we don't want to re-run the old tests.
485
+ end
486
+
487
+ #
488
+ # === Whitestone.stop
489
+ #
490
+ # Stops the execution of the {Whitestone.run} method or raises
491
+ # an exception if that method is not currently executing.
492
+ #
493
+ def stop
494
+ throw :stop_dfect_execution
495
+ end
496
+
497
+ # Record the elapsed time to execute the given block.
498
+ def record_execution_time
499
+ start = Time.now
500
+ yield
501
+ finish = Time.now
502
+ finish - start
503
+ end
504
+ private :record_execution_time
505
+
506
+ #
507
+ # === Whitestone.execute
508
+ #
509
+ # Executes the current test scope recursively. A SCOPE is a collection of D
510
+ # blocks, and the contents of each D block is a TEST, comprising a
511
+ # description and a block of code. Because a test block may contain D
512
+ # statements within it, when a test block is run @current_scope is set to
513
+ # Scope.new so that newly-encountered tests can be added to it. That scope
514
+ # is then executed recursively. The invariant is this: @current_scope is
515
+ # the CURRENT scope to which tests may be added. At the end of 'execute',
516
+ # @current_scope is restored to its previous value.
517
+ #
518
+ # The per-test guts of this method have been extracted to {execute_test} so
519
+ # that the structure of {execute} is easier to see. {execute_test} contains
520
+ # lots of exception handling and comments.
521
+ def execute
522
+ @current_scope.before_all.each {|b| call b } # Run pre-test setup
523
+ @current_scope.tests.each do |test| # Loop through tests
524
+ @current_scope.before_each.each {|b| call b } # Run per-test setup
525
+ @tests.push test; @current_test = test
526
+
527
+ execute_test(test) # Run the test
528
+
529
+ @tests.pop; @current_test = @tests.last
530
+ @current_scope.after_each.each {|b| call b } # Run per-test teardown
531
+ end
532
+ @current_scope.after_all.each {|b| call b } # Run post-test teardown
533
+ end
534
+ private :execute
535
+
536
+ #
537
+ # === Whitestone.execute_test
538
+ #
539
+ # Executes a single test (block containing assertions). That wouldn't be so
540
+ # hard, except that there could be new tests defined within that block, so
541
+ # we need to create a new scope into which such tests may be placed [in
542
+ # {create_test} -- {@current_scope.tests << Test.new(...)}].
543
+ #
544
+ # The old scope is restored at the end of the method.
545
+ #
546
+ # The new scope is executed recursively in order to run any tests created
547
+ # therein.
548
+ #
549
+ # Exception (and failure) handling is straightforward here. The hard work
550
+ # is done in {call}; we just catch them and do nothing. The point is to
551
+ # avoid the recursive {execute}: fail fast.
552
+ #
553
+ def execute_test(test)
554
+ stored_scope = @current_scope
555
+ begin
556
+ # Create nested scope in case a 'D' is encountered while running the test.
557
+ @current_scope = Whitestone::Scope.new
558
+
559
+ # Run the test block, which may create new tests along the way (if the
560
+ # block includes any calls to 'D').
561
+ call test.block, test.sandbox
562
+
563
+ # Increment the pass count _if_ the current test passed, which it only
564
+ # does if at least one assertion was run.
565
+ @stats[:pass] += 1 if @current_test.passed?
566
+
567
+ # Execute the nested scope. Nothing will happen if there are no tests
568
+ # in the nested scope because before_all, tests and after_all will be
569
+ # empty.
570
+ execute
571
+
572
+ rescue FailureOccurred => f
573
+ # See method-level comment regarding exception handling.
574
+ :noop
575
+ rescue ErrorOccurred => e
576
+ :noop
577
+ rescue Exception => e
578
+ # We absolutely should not be receiving an exception here. Exceptions
579
+ # are caught up the line, dealt with, and ErrorOccurred is raised. If
580
+ # we get here, something is strange and we should exit.
581
+ STDERR.puts "Internal error: #{__FILE__}:#{__LINE__}; exiting"
582
+ puts e.inspect
583
+ puts e.backtrace
584
+ exit!
585
+ ensure
586
+ # Restore the previous values of @current_scope
587
+ @current_scope = stored_scope
588
+ end
589
+ end # execute_test
590
+
591
+
592
+ # === Whitestone.call
593
+ #
594
+ # Invokes the given block and debugs any exceptions that may arise as a result.
595
+ # The block can be from a Test object or a "before-each"-style block.
596
+ #
597
+ # If an assertion fails or an error occurs during the running of a test, it
598
+ # is dealt with in this method (update the stats, update the test object,
599
+ # re-raise so the upstream method {execute} can abort the current test/scope.
600
+ #
601
+ def call(block, sandbox = nil)
602
+ begin
603
+ @calls.push block
604
+
605
+ if sandbox
606
+ sandbox.instance_eval(&block)
607
+ else
608
+ block.call
609
+ end
610
+
611
+ rescue FailureOccurred => f
612
+ ## A failure has occurred while running a test. We report the failure
613
+ ## and re-raise the exception so that the calling code knows not to
614
+ ## continue with this test.
615
+ @stats[:fail] += 1
616
+ @current_test.result = :fail
617
+ @output.report_failure( current_test, f.message, f.backtrace )
618
+ raise
619
+
620
+ rescue Exception, AssertionSpecificationError => e
621
+ ## An error has occurred while running a test.
622
+ ## OR
623
+ ## An assertion was not properly specified.
624
+ ##
625
+ ## We record and report the error and then raise Whitestone::ErrorOccurred
626
+ ## so that the code running the test knows an error occurred. It
627
+ ## doesn't need to do anything with the error; it's just a signal.
628
+ @stats[:error] += 1
629
+ @current_test.result = :error
630
+ @current_test.error = e
631
+ if e.class == AssertionSpecificationError
632
+ @output.report_uncaught_exception( current_test, e, @calls, :filter )
633
+ else
634
+ @output.report_uncaught_exception( current_test, e, @calls )
635
+ end
636
+ raise ErrorOccurred
637
+
638
+ ensure
639
+ @calls.pop
640
+ end
641
+ end # call
642
+ private :call
643
+
644
+ end # class << Whitestone
645
+
646
+
647
+ # --------------------------------------------------------------section---- #
648
+ # #
649
+ # Instance variables: #
650
+ # @stats, @current_scope, @current_test, @share, and others #
651
+ # #
652
+ # ------------------------------------------------------------------------- #
653
+
654
+ # Here we are in 'module Whitestone', not 'module << Whitestone', as it were.
655
+
656
+ @stats = Hash.new { |h,k| h[k] = 0 }
657
+
658
+ @top_level = Whitestone::Scope.new
659
+ # We maintain a handle on the top-level scope so we can
660
+ # walk the tree and produce a report.
661
+ @current_scope = @top_level
662
+ # The current scope in which tests are defined. Scopes
663
+ # nest; this is handled by saving and restoring state
664
+ # in the recursive method 'execute'.
665
+ @tests = [] # Stack of the current tests in scope (as opposed to a list
666
+ # of the tests in the current scope).
667
+ @current_test = nil # Should be equal to @tests.last.
668
+ @share = {}
669
+ @calls = [] # Stack of blocks that are executed, allowing access to
670
+ # the outer context for error reporting.
671
+ require 'whitestone/output'
672
+ @output = Output.new # Handles output of reports to the console.
673
+
674
+
675
+ # --------------------------------------------------------------section---- #
676
+ # #
677
+ # D: alias for Whitestone to allow D.< etc. #
678
+ # Mixin methods T, F, Eq, ... #
679
+ # #
680
+ # ------------------------------------------------------------------------- #
681
+
682
+ # Allows before and after hooks to be specified via the
683
+ # following method syntax when this module is mixed-in:
684
+ #
685
+ # D .<< { puts "before all nested tests" }
686
+ # D .< { puts "before each nested test" }
687
+ # D .> { puts "after each nested test" }
688
+ # D .>> { puts "after all nested tests" }
689
+ #
690
+ D = ::Whitestone
691
+
692
+ # Provide mixin-able assertion methods. These are defined in the module
693
+ # Whitestone (instead of being directly executable methods like Whitestone.Eq)
694
+ # and as such can be mixed in to the top level with an `include Whitestone`.
695
+ methods(false).grep(/^(x?[A-Z][a-z]?)?[<>!?]*$/).each do |name|
696
+ #
697
+ # XXX: using eval() on a string because Ruby 1.8's
698
+ # define_method() cannot take a block parameter
699
+ #
700
+ module_eval "def #{name}(*a, &b) ::#{self.name}.#{name}(*a, &b) end",
701
+ __FILE__, __LINE__
702
+ unless name =~ /[<>]/
703
+ # Also define 'x' method that is a no-op; e.g. xD, xT, ...
704
+ module_eval "def x#{name}(*a, &b) :no_op end", __FILE__, __LINE__
705
+ module_eval "def Whitestone.x#{name}(*a, &b) :no_op end", __FILE__, __LINE__
706
+ end
707
+ end
708
+
709
+ end # module Whitestone
710
+