whitestone 1.0.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,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
+