tagged release 0.7.1
[parrot.git] / runtime / parrot / library / Test / More.pir
blobe278a8975a5015636058fc3ee00b7b6ca1105c06
1 =head1 NAME
3 Test::More - Parrot extension for testing modules
5 =head1 SYNOPSIS
7     # load this library
8     load_bytecode 'library/Test/More.pbc'
10     # get the testing functions
11     .local pmc exports, curr_namespace, test_namespace
12     curr_namespace = get_namespace
13     test_namespace = get_namespace [ 'Test'; 'More' ]
14     exports        = split ' ', 'plan diag ok is is_deeply like isa_ok isnt'
16     test_namespace.'export_to'(curr_namespace, exports)
18     # set a test plan
19     plan( 12 )
21     # run your tests
22     ok( 1 )
23     ok( 0, 'failing test with diagnostic' )
25     is( 100, 100 )
26     is( 200, 100, 'failing integer compare with diagnostic' )
28     is( 1.001, 1.001, 'passing float compare with diagnostic' )
29     is( 8.008, 4.004 )
31     is( 'foo', 'foo', 'passing string compare with diagnostic' )
32     is( 'foo', 'bar', 'failing string compare with diagnostic' )
34     is( some_pmc, another_pmc, 'pmc comparison uses "eq" op' )
36     diag( 'this may take a while' )
37     is_deeply( some_deep_pmc, another_deep_pmc, 'deep structure comparison' )
39     like( 'foo', 'f o**{2}', 'passing regex compare with diagnostic' )
40     skip(1, 'reason for skipping')
41     todo(0, 'this is a failed test', 'reason for todo')
43     $P0 = get_class "Squirrel"
44     $P0.new()
46     isa_ok($P0, "Squirrel", "new Squirrel")
48 =head1 DESCRIPTION
50 C<Test::More> is a pure-Parrot library for testing modules.  It provides the
51 C<ok()>, C<is()>, C<isnt()>, C<is_deeply()>, and C<like()> comparison functions
52 for you.  It also provides the C<plan()> and C<diag()> helper functions. It
53 uses C<Test::Builder>, a simple, single backend for multiple test modules
54 to use within your tests.
56 =head1 FUNCTIONS
58 This class defines the following functions:
60 =over 4
62 =cut
64 .namespace [ 'Test'; 'More' ]
66 .sub _initialize :load
67     load_bytecode 'library/Test/Builder.pbc'
69     .local pmc test
70     test = new [ 'Test'; 'Builder' ]
72     store_global [ 'Test'; 'More' ], '_test', test
73 .end
75 =item C<plan( number_or_no_plan )>
77 Declares the number of tests you plan to run, either an integer greater than
78 zero or the string C<no_plan>.  This will throw an exception if you have
79 already declared a plan or if you pass an invalid argument.
81 =cut
83 .sub plan
84     .param string tests
86     .local pmc test
87     find_global test, [ 'Test'; 'More' ], '_test'
88     test.plan( tests )
89 .end
91 =item C<ok( passed, description )>
93 Records a test as pass or fail depending on the truth of the integer C<passed>,
94 recording it with the optional test description in C<description>.
96 =cut
98 .sub ok
99     .param int    passed
100     .param string description     :optional
102     .local pmc test
103     find_global test, [ 'Test'; 'More' ], '_test'
105     test.ok( passed, description )
106 .end
108 =item C<nok( passed, description )>
110 Records a test as pass or fail depending on the falsehood of the integer
111 C<passed>, recording it with the optional test description in C<description>.
113 =cut
115 .sub nok
116     .param int passed
117     .param string description :optional
119     .local pmc test
120     find_global test, [ 'Test'; 'More' ], '_test'
122     .local int reverse_passed
123     reverse_passed = not passed
125     test.ok( reverse_passed, description )
126 .end
128 =item C<is( left, right, description )>
130 Compares the parameters passed as C<left> and C<right>, passing if they are
131 equal and failing otherwise.  This will report the results with the optional
132 test description in C<description>.
134 This is a multi-method, with separate implementations for int-int, float-float,
135 string-string, and PMC-PMC comparisons.  The latter uses the C<eq> opcode for
136 comparison.
138 If there is a mismatch, the current implementation takes the type of C<left> as
139 the proper type for the comparison, converting any numeric arguments to floats.
140 Note that there is a hard-coded precision check to avoid certain rounding
141 errors.  It's not entirely robust, but it's not completely awful either.
143 Patches very welcome.  Multi-dispatch is a bit tricky here.
145 This probably doesn't handle all of the comparisons you want, but it's easy to
146 add more.
148 =cut
150 .sub is :multi( int, int )
151     .param int    left
152     .param int    right
153     .param string description :optional
155     .local pmc test
156     find_global test, [ 'Test'; 'More' ], '_test'
158     .local int pass
159     pass       = 0
161     if left == right goto pass_it
162     goto report
164   pass_it:
165     pass = 1
167   report:
168     test.ok( pass, description )
169     if pass goto done
171     .local string diagnostic
172     .local string l_string
173     .local string r_string
175     l_string    = left
176     r_string    = right
178     diagnostic = _make_diagnostic( l_string, r_string )
179     test.diag( diagnostic )
180   done:
181 .end
183 .sub is :multi( num, num )
184     .param num  left
185     .param num  right
186     .param string description :optional
188     .local pmc test
189     find_global test, [ 'Test'; 'More' ], '_test'
191     .local int pass
192     pass = 0
194     eq left, right, pass_it
195     goto report
197   pass_it:
198     pass = 1
200   report:
201     test.ok( pass, description )
202     if pass goto done
204     .local string diagnostic
205     .local string l_string
206     .local string r_string
208     l_string    = left
209     r_string    = right
211     diagnostic = _make_diagnostic( l_string, r_string )
212     test.diag( diagnostic )
213   done:
214 .end
216 .sub is :multi( string, string )
217     .param string left
218     .param string right
219     .param string description :optional
221     .local pmc test
222     find_global test, [ 'Test'; 'More' ], '_test'
224     .local int pass
225     pass = 0
227     eq left, right, pass_it
228     goto report
230   pass_it:
231     pass = 1
233   report:
234     test.ok( pass, description )
235     if pass goto done
237     .local string diagnostic
238     .local string l_string
239     .local string r_string
241     l_string    = left
242     r_string    = right
244     diagnostic = _make_diagnostic( l_string, r_string )
245     test.diag( diagnostic )
246   done:
247 .end
249 .sub is :multi()
250     .param pmc    left
251     .param pmc    right
252     .param string description :optional
254     .local pmc test
255     find_global test, [ 'Test'; 'More' ], '_test'
257     .local int pass
258     pass = 0
260     .local string r_type
261     r_type = typeof right
263     if r_type == 'Float' goto num_compare
264     if r_type == 'Int'   goto num_compare
265     goto string_compare
267   num_compare:
268      .local num l_val
269      .local num r_val
270     l_val = left
271     r_val = right
273     if l_val == r_val goto pass_it
275     # XXX - significant places?  I don't care :)
276     .local num diff
277     diff = l_val - r_val
279     if diff < 0.000000000001 goto pass_it
281   string_compare:
282     .local string l_val
283     .local string r_val
284     l_val = left
285     r_val = right
286     eq l_val, r_val, pass_it
287     goto report
289   pass_it:
290     pass = 1
292   report:
293     test.ok( pass, description )
294     if pass goto done
296     .local string diagnostic
297     .local string l_string
298     .local string r_string
300     l_string    = left
301     r_string    = right
303     diagnostic = _make_diagnostic( l_string, r_string )
304     test.diag( diagnostic )
305   done:
306 .end
308 =item C<isnt( left, right, description )>
310 Like C<is>, but succeeds if the arguments I<don't> match.
312 =cut
314 .sub isnt :multi( int, int )
315     .param int    left
316     .param int    right
317     .param string description :optional
319     .local pmc test
320     find_global test, [ 'Test'; 'More' ], '_test'
322     .local int pass
323     pass       = 0
325     if left != right goto pass_it
326     goto report
328   pass_it:
329     pass = 1
331   report:
332     test.ok( pass, description )
333     if pass goto done
335     .local string diagnostic
336     .local string l_string
337     .local string r_string
339     l_string = left
340     r_string = right
341     r_string = 'not ' . r_string
343     diagnostic = _make_diagnostic( l_string, r_string )
344     test.diag( diagnostic )
345   done:
346 .end
348 .sub isnt :multi( num, num )
349     .param num  left
350     .param num  right
351     .param string description :optional
353     .local pmc test
354     find_global test, [ 'Test'; 'More' ], '_test'
356     .local int pass
357     pass = 0
359     ne left, right, pass_it
360     goto report
362   pass_it:
363     pass = 1
365   report:
366     test.ok( pass, description )
367     if pass goto done
369     .local string diagnostic
370     .local string l_string
371     .local string r_string
373     l_string = left
374     r_string = right
375     r_string = 'not ' . r_string
377     diagnostic = _make_diagnostic( l_string, r_string )
378     test.diag( diagnostic )
379   done:
380 .end
382 .sub isnt :multi( string, string )
383     .param string left
384     .param string right
385     .param string description :optional
387     .local pmc test
388     find_global test, [ 'Test'; 'More' ], '_test'
390     .local int pass
391     pass = 0
393     ne left, right, pass_it
394     goto report
396   pass_it:
397     pass = 1
399   report:
400     test.ok( pass, description )
401     if pass goto done
403     .local string diagnostic
404     .local string l_string
405     .local string r_string
407     l_string = left
408     r_string = right
409     r_string = 'not ' . r_string
411     diagnostic = _make_diagnostic( l_string, r_string )
412     test.diag( diagnostic )
413   done:
414 .end
416 .sub isnt :multi()
417     .param pmc    left
418     .param pmc    right
419     .param string description :optional
421     .local pmc test
422     find_global test, [ 'Test'; 'More' ], '_test'
424     .local int pass
425     pass = 0
427     .local string r_type
428     r_type = typeof right
430     if r_type == 'Float' goto num_compare
431     if r_type == 'Int'   goto num_compare
432     goto string_compare
434   num_compare:
435      .local num l_val
436      .local num r_val
437     l_val = left
438     r_val = right
440     if l_val != r_val goto pass_it
442     # XXX - significant places?  I don't care :)
443     .local num diff
444     diff = l_val - r_val
446     if diff < 0.000000000001 goto pass_it
448   string_compare:
449     .local string l_val
450     .local string r_val
451     l_val  = left
452     r_val  = right
454     ne l_val, r_val, pass_it
455     goto report
457   pass_it:
458     pass = 1
460   report:
461     test.ok( pass, description )
462     if pass goto done
464     .local string diagnostic
465     .local string l_string
466     .local string r_string
468     l_string = left
469     r_string = right
470     r_string = 'not ' . r_string
472     diagnostic = _make_diagnostic( l_string, r_string )
473     test.diag( diagnostic )
474   done:
475 .end
477 =item C<diag( diagnostic )>
479 Prints C<diagnostic> to the screen, without affecting test comparisons.
481 =cut
483 .sub diag
484     .param string diagnostic
486     .local pmc test
487     find_global test, [ 'Test'; 'More' ], '_test'
488     test.diag( diagnostic )
489 .end
492 =item C<is_deeply( left, right, description )>
494 Compares the data structures passed as C<left> and C<right>.  If data
495 structures are passed, C<is_deeply> does a deep comparison by walking each
496 structure.  It passes if they are equal and fails otherwise.  This will
497 report the results with the optional test description in C<description>.
499 This only handles comparisons of array-like structures.  It shouldn't be too
500 hard to extend it for hash-like structures, too.
502 =cut
504 .sub is_deeply :multi( pmc, pmc )
505     .param pmc left
506     .param pmc right
507     .param string description :optional
509     .local int    result
510     .local string diagnosis
512     .local pmc position
513     position = new 'ResizablePMCArray'
515     .local pmc test
516     find_global test, [ 'Test'; 'More' ], '_test'
518     .local int does_flag
519     does_flag = does left, 'array'
520     if does_flag goto compare_array
522     does_flag = does left, 'hash'
523     if does_flag goto compare_hash
525     diagnosis  = typeof left
526     diagnosis .= ' is not a nested data structure'
527     result     = 0
528     goto report_result
530   compare_array:
531     ( result, diagnosis ) = compare_array( left, right, position )
532     goto report_result
534   compare_hash:
535     (result, diagnosis ) = compare_hash( left, right, position )
536     goto report_result
538   report_result:
539     test.'ok'( result, description )
541     unless result goto report_diagnostic
542     .return( result )
544   report_diagnostic:
545     ne diagnosis, '', return_it
547     .local string left
548     .local string right
549     right = pop position
550     left  = pop position
552     .local string nested_path
553     nested_path = join '][', position
555     diagnosis   = 'Mismatch'
556     unless nested_path goto show_expected
558     diagnosis  .= ' at ['
559     diagnosis  .= nested_path
560     diagnosis  .= ']'
562   show_expected:
563     diagnosis  .= ': expected '
564     diagnosis  .= left
565     diagnosis  .= ', received '
566     diagnosis  .= right
568   return_it:
569     test.'diag'( diagnosis )
570     .return( result )
571 .end
573 .sub compare_array
574     .param pmc l_array
575     .param pmc r_array
576     .param pmc position
578     .local pmc test
579     find_global test, [ 'Test'; 'More' ], '_test'
581     .local int l_count
582     .local int r_count
583     l_count = l_array
584     r_count = r_array
585     if l_count == r_count goto compare_contents
587     .local string l_count_string
588     .local string r_count_string
589     l_count_string  = l_count
590     l_count_string .= ' element'
592     if l_count == 1 goto pluralization_done
593     l_count_string .= 's'
595   pluralization_done:
596     r_count_string  = r_count
598     push position, l_count_string
599     push position, r_count_string
601     .return( 0 )
603   compare_contents:
604     .local pmc l_iter
605     .local pmc r_iter
606     .local int count
608     l_iter = new 'Iterator', l_array
609     r_iter = new 'Iterator', r_array
610     l_iter = 0
611     r_iter = 0
612     count  = 0
614     .local pmc l_elem
615     .local pmc r_elem
616     .local int elems_equal
618   iter_start:
619     unless l_iter goto iter_end
620     l_elem = shift l_iter
621     r_elem = shift r_iter
623     $S0 = typeof l_elem
624     elems_equal = compare_elements( l_elem, r_elem, position )
625     unless elems_equal goto elems_not_equal
627     inc count
628     goto iter_start
630   elems_not_equal:
631     unshift position, count
632     .return( 0 )
634   iter_end:
635     .return( 1 )
636 .end
638 .sub compare_hash
639     .param pmc l_hash
640     .param pmc r_hash
641     .param pmc position
643     .local pmc test
644     find_global test, [ 'Test'; 'More' ], '_test'
646     .local int l_count
647     .local int r_count
648     l_count = l_hash
649     r_count = r_hash
650     if l_count == r_count goto compare_contents
652     .local string l_count_string
653     .local string r_count_string
654     l_count_string  = l_count
655     l_count_string .= ' element'
657     if l_count == 1 goto pluralization_done
658     l_count_string .= 's'
660   pluralization_done:
661     r_count_string  = r_count
663     push position, l_count_string
664     push position, r_count_string
666     .return( 0 )
668   compare_contents:
669     .local pmc l_iter
670     .local pmc r_iter
671     .local int count
673     l_iter = new 'Iterator', l_hash
674     r_iter = new 'Iterator', r_hash
675     l_iter = 0
676     r_iter = 0
677     count  = 0
679     .local pmc l_key
680     .local pmc r_key
681     .local pmc l_elem
682     .local pmc r_elem
683     .local int elems_equal
685   iter_start:
686     unless l_iter goto iter_end
687     l_key  = shift l_iter
688     r_key  = shift r_iter
689     l_elem = l_hash[ l_key ]
690     r_elem = r_hash[ r_key ]
692     elems_equal = compare_elements( l_elem, r_elem, position )
693     unless elems_equal goto elems_not_equal
695     inc count
696     goto iter_start
698   elems_not_equal:
699     unshift position, l_key
700     .return( 0 )
702   iter_end:
703     .return( 1 )
704 .end
706 .sub compare_elements :multi( string, string, PMC )
707     .param string left
708     .param string right
709     .param pmc position
711     .local int equal
713     eq left, right, are_equal
715   are_not_equal:
716     .return( 0 )
718   are_equal:
719     .return( 1 )
720 .end
722 .sub compare_elements :multi( int, int, PMC )
723     .param int left
724     .param int right
725     .param pmc position
727     .local int equal
728     eq left, right, are_equal
730   are_not_equal:
731     push position, left
732     push position, right
733     .return( 0 )
735   are_equal:
736     .return( 1 )
737 .end
739 .sub compare_elements :multi( String, String, PMC )
740     .param pmc left
741     .param pmc right
742     .param pmc position
744     .local int equal
745     eq left, right, are_equal
747   are_not_equal:
748     push position, left
749     push position, right
750     .return( 0 )
752   are_equal:
753     .return( 1 )
754 .end
756 .sub compare_elements :multi( Integer, Integer, PMC )
757     .param pmc left
758     .param pmc right
759     .param pmc position
761     .local int equal
762     eq left, right, are_equal
764   are_not_equal:
765     push position, left
766     push position, right
767     .return( 0 )
769   are_equal:
770     .return( 1 )
771 .end
773 .sub compare_elements :multi( Array, Array, PMC )
774     .param pmc left
775     .param pmc right
776     .param pmc position
778     .local int equal
779     equal = compare_array( left, right, position )
780     .return( equal )
781 .end
783 .sub compare_elements :multi( Hash, Hash, PMC )
784     .param pmc left
785     .param pmc right
786     .param pmc position
788     .local int equal
789     equal = compare_hash( left, right, position )
790     .return( equal )
791 .end
793 .sub compare_elements :multi( Undef, Undef, PMC )
794     .param pmc left
795     .param pmc right
796     .param pmc position
798     .return( 1 )
799 .end
801 .sub compare_elements :multi( Undef, PMC, PMC )
802     .param pmc left
803     .param pmc right
804     .param pmc position
806     .local string l_undef
807     l_undef = '(undef)'
808     push position, l_undef
809     push position, right
810     .return( 0 )
811 .end
813 .sub compare_elements :multi( PMC, Undef, PMC )
814     .param pmc left
815     .param pmc right
816     .param pmc position
818     .local string r_undef
819     r_undef = '(undef)'
820     push position, left
821     push position, r_undef
822     .return( 0 )
823 .end
825 .sub compare_elements :multi( PMC, PMC, PMC )
826     .param pmc left
827     .param pmc right
828     .param pmc position
830     .local int does_flag
831     .local int equal
833   check_array:
834     does_flag = does left, 'array'
835     unless does_flag goto check_hash
836     equal = compare_array( left, right, position )
837     .return( equal )
839   check_hash:
840     does_flag = does left, 'hash'
841     if does_flag goto compare_hash
842     .return( 0 )
844   compare_hash:
845     equal = compare_hash( left, right, position )
846     .return( equal )
847 .end
849 =item C<like( target, pattern, description )>
851 Similar to is, but using the Parrot Grammar Engine to compare the string
852 passed as C<target> to the pattern passed as C<pattern>.  It passes if the
853 pattern matches and fails otherwise.  This will report the results with the
854 optional test description in C<description>.
856 =cut
858 .sub like
859     .param string target
860     .param string pattern
861     .param string description :optional
863     .local pmc test
864     find_global test, [ 'Test'; 'More' ], '_test'
866     .local pmc p6rule_compile
867     load_bytecode "PGE.pbc"
868     load_bytecode "PGE/Dumper.pbc"
869     load_bytecode "PGE/Text.pbc"
870     load_bytecode "PGE/Util.pbc"
871     p6rule_compile = compreg "PGE::Perl6Regex"
873     .local string diagnostic
874     .local int pass
875     pass = 0
877   match_pattern:
878     .local pmc rulesub
879     .local pmc match
880     .local pmc code
881     .local pmc exp
882     (rulesub, code, exp) = p6rule_compile(pattern)
883     if_null rulesub, rule_fail
884     match = rulesub(target)
885     unless match goto match_fail
886   match_success:
887     goto pass_it
888   match_fail:
889     diagnostic = "match failed"
890     goto report
891   rule_fail:
892     diagnostic = "rule error"
893     goto report
895   pass_it:
896     pass = 1
898   report:
899     test.ok( pass, description )
900     if pass goto done
902     test.diag( diagnostic )
903   done:
904 .end
906 =item C<skip( how_many, why )>
908 Pass a number of tests, but with a comment that marks the test was
909 actually skipped.  Arguments are optional.
911 =cut
913 .sub skip :multi(int, string)
914     .param int how_many
915     .param string description
917     .local pmc test
918     find_global test, [ 'Test'; 'More' ], '_test'
919     test.'skip'(how_many, description)
920 .end
922 .sub skip :multi(int)
923     .param int how_many
925     .local pmc test
926     find_global test, [ 'Test'; 'More' ], '_test'
927     test.'skip'(how_many)
928 .end
930 .sub skip :multi(string)
931     .param string description
933     .local pmc test
934     find_global test, [ 'Test'; 'More' ], '_test'
935     test.'skip'(1, description)
936 .end
938 .sub skip :multi()
939     .local pmc test
940     find_global test, [ 'Test'; 'More' ], '_test'
941     test.'skip'()
942 .end
944 =item C<todo( passed, description, reason )>
946 Records a test as pass or fail (like C<ok>, but marks it as TODO so it always
947 appears as a success. This also records the optional C<description> of the test
948 and the C<reason> you have marked it as TODO.
950 =cut
952 .sub todo
953     .param pmc args :slurpy
955     .local pmc test
956     find_global test, [ 'Test'; 'More' ], '_test'
958     test.todo( args :flat )
959 .end
961 =item C<isa_ok( object, class_name, object_name )>
963 Pass if the object C<isa> class of the given class name.  The object
964 name passed in is not a full description, but a name to be included in
965 the description. The description is presented as "<object_name> isa
966 <class>".
968 Good input: "C<new MyObject>", "C<return from bar()>"
970 Bad input: "C<test that the return from Foo is correct type>"
972 =cut
974 .sub isa_ok
975     .param pmc thingy
976     .param pmc class_name
977     .param pmc object_name :optional
978     .param int got_name :opt_flag
980     .local pmc test
981     find_global test, [ 'Test'; 'More' ], '_test'
983     .local string description, diagnostic
984     description = "The object"
985     unless got_name goto keep_default
986     description = object_name
987   keep_default:
988     diagnostic = description
989     description .= " isa "
990     $S0 = class_name
991     description .= $S0
993     $I0 = isa thingy, class_name
994     test.'ok'($I0, description)
995     if $I0 goto out
996     diagnostic .= " isn't a "
997     $S1 = class_name
998     diagnostic .= $S1
999     diagnostic .= " it's a "
1000     $S2 = typeof thingy
1001     diagnostic .= $S2
1002     test.'diag'(diagnostic)
1003   out:
1004 .end
1006 .sub _make_diagnostic
1007     .param string received
1008     .param string expected
1009     .local string diagnostic
1011     diagnostic  = 'Have: '
1012     diagnostic .= received
1013     diagnostic .= "\nWant: "
1014     diagnostic .= expected
1016     .return( diagnostic )
1017 .end
1019 =back
1021 =head1 AUTHOR
1023 Written and maintained by chromatic, C<< chromatic at wgz dot org >>, based on
1024 the Perl 6 port he wrote, based on the original Perl 5 version he wrote with
1025 ideas from Michael G. Schwern.  Please send patches, feedback, and suggestions
1026 to the Perl 6 internals mailing list.
1028 =head1 COPYRIGHT
1030 Copyright (C) 2005-2008, The Perl Foundation.
1032 =cut
1034 # Local Variables:
1035 #   mode: pir
1036 #   fill-column: 100
1037 # End:
1038 # vim: expandtab shiftwidth=4 ft=pir: