Implement Test::More.done_testing. Not perfect and require
[parrot.git] / runtime / parrot / library / Test / Builder.pir
blob25520cca25482bb594d1158d4d26cfcfd3611439
1 # $Id$
3 =head1 NAME
5 Test::Builder - Parrot extension for building test modules
7 =head1 SYNOPSIS
9     # load this library
10     load_bytecode 'Test/Builder.pbc'
12     # create a new Test::Builder object
13     .local pmc test
15     test = new [ 'Test'; 'Builder' ]
17     # plan to run ten tests
18     test.'plan'( 10 )
20     test.'ok'( 1, 'some test description' )
21     test.'ok'( 0, 'some test description' )
22     test.'diag'( 'the last test failed on purpose!' )
24     test.'skip'( 3, 'do not run these three tests' )
25     test.'todo'( 1, 'this is a todo test that passes', 'i am not sure' )
26     test.'todo'( 0, 'this is a todo test that fails', ' i am still not sure' )
28     test.'skip'( 4, 'cannot think of four more tests' )
30     # you must call this when you have finished!
31     test.'finish'()
33 =head1 DESCRIPTION
35 Test::Builder is a pure-Parrot library for building test modules.  It manages
36 test plans, formats and reports test results correctly, and has methods to
37 manage passing, failing, skip, and TODO tests.  It provides a simple, single
38 backend for multiple test modules to use within your tests.
40 =head1 METHODS
42 This class defines the following methods:
44 =over 4
46 =cut
48 .namespace [ 'Test'; 'Builder' ]
50 .sub '_initialize' :load
51     load_bytecode 'Test/Builder/Test.pbc'
52     load_bytecode 'Test/Builder/Output.pbc'
53     load_bytecode 'Test/Builder/TestPlan.pbc'
55     .local pmc tb_class
57     newclass     tb_class, [ 'Test'; 'Builder' ]
58     addattribute tb_class, 'output'
59     addattribute tb_class, 'testplan'
60     addattribute tb_class, 'results'
62     .local pmc single
63     single = new 'Undef'
65     set_hll_global [ 'Test'; 'Builder'; '_singleton' ], 'singleton', single
66 .end
68 =item C<new( args_hash )>
70 Given an optional C<Hash> of arguments, initializes the new object with the
71 provided arguments.  By default, you should rarely need to pass any arguments.
72 If you do, you know why.  The two allowed arguments are:
74 =over 4
76 =item C<testplan>
78 An object that C<does> C<Test::Builder::TestPlan> to manage the plan for this
79 test run.
81 =item C<output>
83 An object that does C<Test::Builder::Output> to manage the output for this test
84 run.
86 =back
88 C<new()> will not always return the I<same> object, but every object will share
89 the same state.
91 =cut
93 .sub 'init' :vtable :method
94     .local pmc args
95     .local pmc output
96     .local pmc testplan
97     .local pmc results
99     (output, testplan, results) = self.'_assign_default_args'( args )
100     self.'_assign_args'( output, testplan, results )
101 .end
103 .sub 'init_pmc' :vtable :method
104     .param pmc args
105     .local pmc output
106     .local pmc testplan
107     .local pmc results
109     (output, testplan, results) = self.'_assign_default_args'( args )
110     self.'_assign_args'( output, testplan, results )
111 .end
113 .sub '_assign_args' :method
114     .param pmc output
115     .param pmc testplan
116     .param pmc results
118     setattribute self, 'output', output
119     setattribute self, 'testplan', testplan
120     setattribute self, 'results', results
122     results = self.'results'()
123 .end
125 =item C<create( args_hash )>
127 Creates and returns a new Test::Builder object with different backend objects.
128 This probably doesn't work correctly yet, but you will probably never use it.
130 =cut
132 .sub 'create'
133     .param pmc args
135     .local pmc output
136     .local pmc testplan
137     .local pmc results
139     .local int is_defined
140     output     = args['output']
141     is_defined = exists args['output']
142     if is_defined goto OUTPUT_DEFINED
144     .local int output_class
145     output = new [ 'Test'; 'Builder'; 'Output' ]
147   OUTPUT_DEFINED:
148     is_defined = exists args['testplan']
149     unless is_defined goto DEFAULT_TESTPLAN
151     testplan   = args['testplan']
152     goto TESTPLAN_DEFINED
154   DEFAULT_TESTPLAN:
155     testplan   = new [ 'Test'; 'Builder'; 'TestPlan' ]
157   TESTPLAN_DEFINED:
158     results    = new 'ResizablePMCArray'
160     .local pmc test
161     test       = new [ 'Test'; 'Builder' ]
163     test.'_assign_args'( output, testplan, results )
164     .return( test )
165 .end
167 .sub '_assign_default_args' :method
168     .param pmc args
170     .local pmc single
171     single     = get_hll_global [ 'Test'; 'Builder'; '_singleton' ], 'singleton'
173     .local pmc output
174     .local pmc testplan
175     .local pmc results
176     .local int is_defined
178     # try for the global first
179     is_defined = isa single, [ 'Test'; 'Builder' ]
180     unless is_defined goto CREATE_ATTRIBUTES
182     output     = single.'output'()
183     testplan   = single.'testplan'()
184     results    = single.'results'()
186     goto RESULTS_DEFINED
188   CREATE_ATTRIBUTES:
189     # now look in the args hash
190     is_defined = exists args['output']
191     unless is_defined goto CREATE_OUTPUT
192     output     = args['output']
193     goto OUTPUT_DEFINED
195   CREATE_OUTPUT:
196     # create a Test::Builder::Output object
197     .local pmc args_hash
198     args_hash  = new 'Hash'
199     output     = new [ 'Test'; 'Builder'; 'Output' ], args_hash
201   OUTPUT_DEFINED:
202     # now try in the args hash
203     is_defined = exists args['testplan']
204     unless is_defined goto CREATE_TESTPLAN
205     testplan   = args['testplan']
206     goto TESTPLAN_DEFINED
208   CREATE_TESTPLAN:
209     testplan   = new [ 'Test'; 'Builder'; 'TestPlan' ]
211   TESTPLAN_DEFINED:
212     is_defined = defined results
213     if is_defined goto RESULTS_DEFINED
214     results    = new 'ResizablePMCArray'
216     # store this as the singleton
217     set_hll_global [ 'Test'; 'Builder'; '_singleton' ], 'singleton', self
219   RESULTS_DEFINED:
220     .return( output, testplan, results )
221 .end
223 .sub 'output' :method
224     .local pmc output
226     getattribute output, self, "output"
228     .return( output )
229 .end
231 .sub 'testplan' :method
232     .local pmc testplan
233     testplan = getattribute self, 'testplan'
234     .return( testplan )
235 .end
237 .sub 'results' :method
238     .local pmc results
240     getattribute results, self, "results"
242     .return( results )
243 .end
245 =item C<finish()>
247 Finishes this test run.  You should call this when you have finished running
248 all of the tests.  I know this is awful, but this has to be here until object
249 finalization works reliably.
251 This is probably not idempotent now, so try not to call it too many times,
252 where "too many" means "more than one".
254 =cut
256 .sub 'finish' :method
257     .local pmc output
258     .local pmc testplan
259     .local pmc results
261     output   = self.'output'()
262     testplan = self.'testplan'()
263     results  = self.'results'()
265     .local int elements
266     elements = results
268     .local string footer
269     footer   = testplan.'footer'( elements )
271     .local int is_defined
272     is_defined = length footer
273     unless is_defined goto DONE_PRINTING
274     output.'write'( footer )
276   DONE_PRINTING:
278   # XXX - delete globals
279 .end
281 =item C<plan( number_or_no_plan )>
283 Tells the object how many tests to run, either an integer greater than zero or
284 the string C<no_plan>.  This will throw an exception if you have already
285 declared a plan or if you pass an invalid argument.
287 =cut
289 .sub 'plan' :method
290     .param string tests
292     .local pmc testplan
293     testplan = self.'testplan'()
295     eq tests, 'no_plan', write_header
297     .local int num_tests
298     num_tests = tests
300     unless num_tests goto write_header
302     testplan.'set_tests'( num_tests )
304   write_header:
305     .local pmc output
306     output = self.'output'()
308     .local string header
309     header = testplan.'header'()
310     output.'write'( header )
312     .return()
313 .end
315 =item done_testing
317 =cut
319 .sub 'done_testing' :method
320     .param string tests     :optional
321     .param int    has_tests :opt_flag
323     .local pmc testplan
324     testplan = self.'testplan'()
326     unless has_tests goto write_footer
328     .local int num_tests
329     num_tests = tests
330     unless num_tests goto write_footer
332     testplan.'set_tests'( num_tests )
334   write_footer:
335     .local pmc output
336     output = self.'output'()
338     $S0 = testplan.'header'()
339     output.'write'( $S0 )
341     $I0 = self.'results'()
342     $S0 = testplan.'footer'( $I0 )
343     output.'write'( $S0 )
345 .end
347 =item C<diag( diagnostic_message, ... )>
349 Records a diagnostic message for output.
351 =cut
353 .sub 'diag' :method
354     .param pmc args :slurpy
356     .local pmc output
357     output = self.'output'()
358     .tailcall output.'diag'( args :flat )
359 .end
361 =item C<ok( passed, description )>
363 Records a test as pass or fail depending on the truth of the integer C<passed>,
364 recording it with the optional test description in C<description>.
366 =cut
368 .sub 'ok' :method
369     .param pmc passed
370     .param pmc description     :optional
371     .param int has_description :opt_flag
373     if has_description goto OK
374     description = new 'String'
375     description = ''
377   OK:
378     .local pmc results
379     results = self.'results'()
381     .local int results_count
382     results_count = results
383     inc results_count
385     .local pmc test_args
386     test_args = new 'Hash'
387     test_args['number']      = results_count
388     test_args['passed']      = passed
389     test_args['description'] = description
391     self.'report_test'( test_args )
393     .return( passed )
394 .end
396 =item C<todo( passed, description, reason )>
398 Records a test as pass or fail based on the truth of the integer C<passed>, but
399 marks it as TODO so it always appears as a success.  This also records the
400 optional C<description> of the test and the C<reason> you have marked it as
401 TODO.
403 =cut
405 .sub 'todo' :method
406     .param int    passed
407     .param string description     :optional
408     .param int    has_description :opt_flag
409     .param string reason          :optional
410     .param int    has_reason      :opt_flag
412     if has_description goto CHECK_REASON
413     description = ''
415   CHECK_REASON:
416     if has_reason goto TODO
417     reason = ''
419   TODO:
420     .local pmc results
421     results = self.'results'()
423     .local int results_count
424     results_count = results
425     inc results_count
427     .local pmc test_args
428     test_args = new 'Hash'
429     test_args['todo']       = 1
430     test_args['number']     = results_count
431     test_args['passed']     = passed
432     test_args['reason']     = reason
433     test_args['description']= description
435     self.'report_test'( test_args )
437     .return( passed )
438 .end
440 =item C<skip( number reason )>
442 Records C<number> of tests as skip tests, using the optional C<reason> to mark
443 why you've skipped them.
445 =cut
447 .sub 'skip' :method
448     .param int    number          :optional
449     .param int    has_number      :opt_flag
450     .param string reason          :optional
451     .param int    has_reason      :opt_flag
453     if has_number goto CHECK_NUMBER
454     number = 1
456   CHECK_NUMBER:
457     if number > 0 goto CHECK_REASON
458     .return() # nothing to skip
460   CHECK_REASON:
461     if has_reason goto SKIP_LOOP
462     reason = 'skipped'
464   SKIP_LOOP:
465     .local pmc results
466     results = self.'results'()
468     .local int results_count
469     results_count = results
471     .local int loop_count
472     loop_count = 1
474   LOOP:
475     inc results_count
477     .local pmc test_args
478     test_args = new 'Hash'
479     test_args['number'] = results_count
480     test_args['skip']   = 1
481     test_args['reason'] = reason
483     self.'report_test'( test_args )
484     inc loop_count
485     if loop_count <= number goto LOOP
487 .end
489 =item C<skip_all()>
491 Skips all of the tests in a test file.  You cannot call this if you have a
492 plan.  This calls C<exit>; there's little point in continuing.
494 =cut
496 .sub 'skip_all' :method
497     .local pmc testplan
498     testplan = self.'testplan'()
500     unless testplan goto SKIP_ALL
502     .local pmc plan_exception
503     plan_exception = new 'Exception'
504     plan_exception = 'Cannot skip_all() with a plan!'
505     throw plan_exception
507   SKIP_ALL:
508     .local pmc output
509     output = self.'output'()
510     output.'write'( "1..0" )
511     exit 0
512 .end
514 =item C<BAILOUT( reason )>
516 Ends the test immediately, giving the string C<reason> as explanation.  This
517 also calls C<exit>.
519 =cut
521 .sub 'BAILOUT' :method
522     .param string reason  :optional
523     .param int has_reason :opt_flag
525     .local pmc output
526     output   = self.'output'()
528     .local pmc bail_out
529     bail_out = new ['StringBuilder']
530     bail_out = 'Bail out!'
532     unless has_reason goto WRITE_REASON
533     bail_out .= '  '
534     bail_out .= reason
536   WRITE_REASON:
537     output.'write'( bail_out )
539     exit 0
540 .end
542 .sub 'report_test' :method
543     .param pmc test_args
545     .local pmc testplan
546     testplan = self.'testplan'()
548     .local pmc results
549     results = self.'results'()
551     .local pmc test
553     .local pmc number
554     number = new 'Integer'
556     .local int count
557     count  = results
558     number = count
559     inc number
561     test_args['number'] = number
563     push results, test
565     .local pmc tbt_create
566     get_hll_global tbt_create, [ 'Test'; 'Builder'; 'Test' ], 'create'
567     test = tbt_create( test_args )
569     .local pmc output
570     output = self.'output'()
572     .local string report
573     report = test.'report'()
575     output.'write'( report )
576 .end
578 =back
580 =head1 AUTHOR
582 Written and maintained by chromatic, C<< chromatic at wgz dot org >>, based on
583 the Perl 6 port he wrote, based on the original Perl 5 version he wrote with
584 ideas from Michael G. Schwern.  Please send patches, feedback, and suggestions
585 to the Perl 6 internals mailing list.
587 =head1 COPYRIGHT
589 Copyright (C) 2005-2008, Parrot Foundation.
591 =cut
593 # Local Variables:
594 #   mode: pir
595 #   fill-column: 100
596 # End:
597 # vim: expandtab shiftwidth=4 ft=pir: