tagged release 0.6.4
[parrot.git] / lib / Parrot / Test.pm
blobc68dbc17ddfa681eaa39efaae0088e30ce2cf7cd
1 # Copyright (C) 2004-2007, The Perl Foundation.
2 # $Id$
4 =head1 NAME
6 Parrot::Test - testing routines for Parrot and language implementations
8 =head1 SYNOPSIS
10 Set the number of tests to be run like this:
12 use Parrot::Test tests => 8;
14 Write individual tests like this:
16 pasm_output_is(<<'CODE', <<'OUTPUT', "description of test");
17 print "this is ok\n"
18 end
19 CODE
20 this is ok
21 OUTPUT
23 =head1 DESCRIPTION
25 This module provides various Parrot-specific test functions.
27 =head2 Functions
29 The parameter C<$language> is the language of the code.
30 The parameter C<$code> is the code that should be executed or transformed.
31 The parameter C<$expected> is the expected result.
32 The parameter C<$unexpected> is the unexpected result.
33 The parameter C<$description> should describe the test.
35 Any optional parameters can follow. For example, to mark a test as a TODO test
36 (where you know the implementation does not yet work), pass:
38 todo => 'reason to consider this TODO'
40 at the end of the argument list. Valid reasons include C<bug>,
41 C<unimplemented>, and so on.
43 B<Note:> you I<must> use a C<$description> with TODO tests.
45 =over 4
47 =item C<language_output_is( $language, $code, $expected, $description)>
49 =item C<language_error_output_is( $language, $code, $expected, $description)>
51 Runs a language test and passes the test if a string comparison
52 of the output with the expected result it true.
53 For C<language_error_output_is()> the exit code also has to be non-zero.
55 =item C<language_output_like( $language, $code, $expected, $description)>
57 =item C<language_error_output_like( $language, $code, $expected, $description)>
59 Runs a language test and passes the test if the output matches the expected
60 result.
61 For C<language_error_output_like()> the exit code also has to be non-zero.
63 =item C<language_output_isnt( $language, $code, $expected, $description)>
65 =item C<language_error_output_isnt( $language, $code, $expected, $description)>
67 Runs a language test and passes the test if a string comparison
68 if a string comparison of the output with the unexpected result is false.
69 For C<language_error_output_isnt()> the exit code also has to be non-zero.
71 =item C<pasm_output_is($code, $expected, $description)>
73 Runs the Parrot Assembler code and passes the test if a string comparison of
74 the output with the expected result it true.
76 =item C<pasm_error_output_is($code, $expected, $description)>
78 Runs the Parrot Assembler code and passes the test if a string comparison of
79 the output with the expected result it true I<and> if Parrot exits with a
80 non-zero exit code.
82 =item C<pasm_output_like($code, $expected, $description)>
84 Runs the Parrot Assembler code and passes the test if the output matches
85 C<$expected>.
87 =item C<pasm_error_output_like($code, $expected, $description)>
89 Runs the Parrot Assembler code and passes the test if the output matches
90 C<$expected> I<and> if Parrot exits with a non-zero exit code.
92 =item C<pasm_output_isnt($code, $unexpected, $description)>
94 Runs the Parrot Assembler code and passes the test if a string comparison of
95 the output with the unexpected result is false.
97 =item C<pasm_error_output_isnt($code, $unexpected, $description)>
99 Runs the Parrot Assembler code and passes the test if a string comparison of
100 the output with the unexpected result is false I<and> if Parrot exits with a
101 non-zero exit code.
103 =item C<pir_output_is($code, $expected, $description)>
105 Runs the PIR code and passes the test if a string comparison of output with the
106 expected result is true.
108 =item C<pir_error_output_is($code, $expected, $description)>
110 Runs the PIR code and passes the test if a string comparison of output with the
111 expected result is true I<and> if Parrot exits with a non-zero exit code.
113 =item C<pir_output_like($code, $expected, $description)>
115 Runs the PIR code and passes the test if output matches the expected result.
117 =item C<pir_error_output_like($code, $expected, $description)>
119 Runs the PIR code and passes the test if output matches the expected result
120 I<and> if Parrot exits with a non-zero exit code.
122 =item C<pir_output_isnt($code, $unexpected, $description)>
124 Runs the PIR code and passes the test if a string comparison of the output with
125 the unexpected result is false.
127 =item C<pir_error_output_isnt($code, $unexpected, $description)>
129 Runs the PIR code and passes the test if a string comparison of the output with
130 the unexpected result is false I<and> if Parrot exits with a non-zero exit
131 code.
133 =item C<pbc_output_is($code, $expected, $description)>
135 Runs the Parrot Bytecode and passes the test if a string comparison of output
136 with the expected result is true.
138 =item C<pbc_error_output_is($code, $expected, $description)>
140 Runs the Parrot Bytecode and passes the test if a string comparison of the output
141 with the expected result is true I<and> if Parrot exits with a non-zero exit code.
143 =item C<pbc_output_like($code, $expected, $description)>
145 Runs the Parrot Bytecode and passes the test if output matches the expected
146 result.
148 =item C<pbc_error_output_like($code, $expected, $description)>
150 Runs the Parrot Bytecode and passes the test if output matches the expected
151 result I<and> if Parrot exits with a non-zero exit code.
153 =item C<pbc_output_isnt($code, $unexpected, $description)>
155 Runs the Parrot Bytecode and passes the test if a string comparison of output
156 with the unexpected result is false.
158 =item C<pbc_error_output_isnt($code, $unexpected, $description)>
160 Runs the Parrot Bytecode and passes the test if a string comparison of output
161 with the unexpected result is false I<and> if Parrot exits with a non-zero exit
162 code.
164 =item C<pir_2_pasm_is($code, $expected, $description)>
166 Compile the Parrot Intermediate Representation and generate Parrot Assembler Code.
167 Pass if the generated PASM is $expected.
169 =item C<pir_2_pasm_like($code, $expected, $description)>
171 Compile the Parrot Intermediate Representation and generate Parrot Assembler Code.
172 Pass if the generated PASM matches $expected.
174 =item C<pir_2_pasm_isnt($code, $unexpected, $description)>
176 Compile the Parrot Intermediate Representation and generate Parrot Assembler
177 Code. Pass unless the generated PASM is $expected.
179 =item C<c_output_is($code, $expected, $description, %options)>
181 Compiles and runs the C code, passing the test if a string comparison of output
182 with the expected result it true. Valid options are 'todo' => 'reason' to mark
183 a TODO test.
185 =item C<c_output_like($code, $expected, $description, %options)>
187 Compiles and runs the C code, passing the test if output matches the expected
188 result. Valid options are 'todo' => 'reason' to mark a TODO test.
190 =item C<c_output_isnt($code, $unexpected, $description, %options)>
192 Compiles and runs the C code, passing the test if a string comparison of output
193 with the unexpected result is false. Valid options are 'todo' => 'reason' to
194 mark a TODO test.
196 =item C<example_output_is( $example_f, $expected, @todo )>
198 =item C<example_output_like( $example_f, $expected, @todo )>
200 =item C<example_output_isnt( $example_f, $expected, @todo )>
202 Determines the language, PIR or PASM, from the extension of C<$example_f> and runs
203 the appropriate C<^language_output_(is|kike|isnt)> sub.
204 C<$example_f> is used as a description, so don't pass one.
206 =item C<skip($why, $how_many)>
208 Use within a C<SKIP: { ... }> block to indicate why and how many tests to skip,
209 just like in Test::More.
211 =item C<run_command($command, %options)>
213 Run the given $command in a cross-platform manner.
215 %options include...
217 STDOUT filehandle to redirect STDOUT to
218 STDERR filehandle to redirect STDERR to
219 CD directory to run the command in
221 For example:
223 # equivalent to "cd some_dir && make test"
224 run_command("make test", CD => "some_dir");
226 =item C<slurp_file($file_name)>
228 Read the whole file $file_name and return the content as a string.
230 =item C<convert_line_endings($text)>
232 Convert Win32 style line endins with Unix style line endings.
234 =item C<path_to_parrot()>
236 Construct a relative path from the current dir to the parrot root dir.
238 =item C<per_test( $ext, $test_no )>
240 Construct a path for a temporary files.
241 Takes C<$0> into account.
243 =item C<pasm_fp_equality_macros()>
245 Returns a PASM macro that can be used to test fp equality/inequality.
247 =item C<write_code_to_file($code, $code_f)>
249 Writes C<$code> into the file C<$code_f>.
251 =item C<generate_languages_functions>
253 Generate functions that are only used by a couple of
254 Parrot::Test::<lang> modules.
255 See RT#43266.
256 This implementation is experimental and currently only works
257 for languages/pipp.
259 =back
261 =cut
263 package Parrot::Test;
265 use strict;
266 use warnings;
268 use Cwd;
269 use File::Spec;
270 use File::Basename;
271 use Memoize ();
273 use Parrot::Config;
275 require Exporter;
276 require Test::Builder;
277 require Test::More;
279 our @EXPORT = qw( plan run_command skip slurp_file pasm_fp_equality_macro );
281 use base qw( Exporter );
283 # Memoize functions with a fixed output
284 Memoize::memoize('path_to_parrot');
286 # Tell parrot it's being tested--disables searching of installed libraries.
287 # (see Parrot_get_runtime_prefix in src/library.c).
288 $ENV{PARROT_TEST} = 1 unless defined $ENV{PARROT_TEST};
290 my $builder = Test::Builder->new();
292 # Generate subs where the name serves as an
293 # extra parameter.
294 _generate_test_functions();
296 sub import {
297 my ( $class, $plan, @args ) = @_;
299 $builder->plan( $plan, @args );
301 __PACKAGE__->export_to_level( 2, __PACKAGE__ );
304 # this kludge is an hopefully portable way of having
305 # redirections ( tested on Linux and Win2k )
306 # An alternative is using Test::Output
307 sub run_command {
308 my ( $command, %options ) = @_;
310 # To run the command in a different directory.
311 my $chdir = delete $options{CD};
313 while ( my ( $key, $value ) = each %options ) {
314 $key =~ m/^STD(OUT|ERR)$/
315 or die "I don't know how to redirect '$key' yet!";
316 $value = File::Spec->devnull()
317 if $value eq '/dev/null';
320 my $out = $options{'STDOUT'} || '';
321 my $err = $options{'STDERR'} || '';
323 if ( $out and $err and $out eq $err ) {
324 $err = "&STDOUT";
327 local *OLDOUT if $out; ## no critic Variables::ProhibitConditionalDeclarations
328 local *OLDERR if $err; ## no critic Variables::ProhibitConditionalDeclarations
330 # Save the old filehandles; we must not let them get closed.
331 open OLDOUT, '>&STDOUT' ## no critic InputOutput::ProhibitBarewordFileHandles
332 or die "Can't save stdout"
333 if $out;
334 open OLDERR, '>&STDERR' ## no critic InputOutput::ProhibitBarewordFileHandles
335 or die "Can't save stderr"
336 if $err;
338 open STDOUT, '>', $out or die "Can't redirect stdout to $out" if $out;
340 # See 'Obscure Open Tricks' in perlopentut
341 open STDERR, ">$err" ## no critic InputOutput::ProhibitTwoArgOpen
342 or die "Can't redirect stderr to $err"
343 if $err;
345 # If $command isn't already an arrayref (because of a multi-command
346 # test), make it so now so the code below can treat everybody the
347 # same.
348 $command = [$command] unless ( ref $command );
350 if ( defined $ENV{VALGRIND} ) {
351 $_ = "$ENV{VALGRIND} $_" for (@$command);
354 my $orig_dir;
355 if ($chdir) {
356 $orig_dir = cwd;
357 chdir $chdir;
360 # Execute all commands
361 # [#42161] [BUG] Parrot::Test throws "Can't spawn" warning on windows
362 # ...if a system call returns a negative value
363 # removed exec warnings to prevent this warning from messing up test results
365 no warnings 'exec';
366 system($_) for ( @{$command} );
369 if ($chdir) {
370 chdir $orig_dir;
373 my $exit_code = $?;
375 close STDOUT or die "Can't close stdout" if $out;
376 close STDERR or die "Can't close stderr" if $err;
378 open STDOUT, ">&", \*OLDOUT or die "Can't restore stdout" if $out;
379 open STDERR, ">&", \*OLDERR or die "Can't restore stderr" if $err;
381 return (
382 ( $exit_code < 0 ) ? $exit_code
383 : ( $exit_code & 0xFF ) ? "[SIGNAL $exit_code]"
384 : ( $? >> 8 )
388 sub per_test {
389 my ( $ext, $test_no ) = @_;
391 return unless defined $ext and defined $test_no;
393 my $t = $0; # $0 is name of the test script
394 $t =~ s/\.t$/_$test_no$ext/;
396 return $t;
399 sub pasm_fp_equality_macro {
400 my $fp_equality_macro = <<'ENDOFMACRO';
401 .macro fp_eq ( J, K, L )
402 set N10, .J
403 set N11, .K
404 sub N12, N11, N10
405 abs N12, N12
406 gt N12, 0.000001, .$FPEQNOK
408 branch .L
409 .label $FPEQNOK:
410 .endm
411 .macro fp_ne( J,K,L)
412 set N10, .J
413 set N11, .K
414 sub N12, N11, N10
415 abs N12, N12
416 lt N12, 0.000001, .$FPNENOK
418 branch .L
419 .label $FPNENOK:
420 .endm
421 ENDOFMACRO
422 return $fp_equality_macro;
426 sub write_code_to_file {
427 my ( $code, $code_f ) = @_;
429 open my $CODE, '>', $code_f or die "Unable to open '$code_f'";
430 binmode $CODE;
431 print $CODE $code;
432 close $CODE;
434 return;
437 # We can inherit from Test::More, so we do it.
438 *plan = \&Test::More::plan;
439 *skip = \&Test::More::skip;
441 # What about File::Slurp?
442 sub slurp_file {
443 my ($file_name) = @_;
445 open( my $SLURP, '<', $file_name ) or die "open '$file_name': $!";
446 local $/ = undef;
447 my $file = <$SLURP> . '';
448 $file =~ s/\cM\cJ/\n/g;
449 close $SLURP;
451 return $file;
454 sub convert_line_endings {
455 my ($text) = @_;
457 $text =~ s/\cM\cJ/\n/g;
459 return;
462 sub path_to_parrot {
464 my $path = $INC{'Parrot/Config.pm'};
465 $path =~ s{ /lib/Parrot/Config.pm \z}{}xms;
466 return $path eq q{}
467 ? File::Spec->curdir()
468 : $path;
471 sub generate_languages_functions {
473 my %test_map = (
474 output_is => 'is_eq',
475 error_output_is => 'is_eq',
476 output_like => 'like',
477 error_output_like => 'like',
478 output_isnt => 'isnt_eq',
479 error_output_isnt => 'isnt_eq',
482 foreach my $func ( keys %test_map ) {
484 my $test_sub = sub {
485 local *__ANON__ = $func;
486 my $self = shift;
487 my ( $code, $expected, $desc, %options ) = @_;
489 # set a todo-item for Test::Builder to find
490 my $call_pkg = $self->{builder}->exported_to() || '';
492 no strict 'refs';
494 local *{ $call_pkg . '::TODO' } = ## no critic Variables::ProhibitConditionalDeclarations
495 \$options{todo}
496 if defined $options{todo};
498 my $count = $self->{builder}->current_test() + 1;
500 # These are the thing that depend on the actual language implementation
501 my $out_f = $self->get_out_fn( $count, \%options );
502 my $lang_f = $self->get_lang_fn( $count, \%options );
503 my $cd = $self->get_cd( \%options );
504 my @test_prog = $self->get_test_prog( $count, \%options );
506 Parrot::Test::write_code_to_file( $code, $lang_f );
508 # set a todo-item for Test::Builder to find
509 my $skip_why = $self->skip_why( \%options );
510 if ($skip_why) {
511 $self->{builder}->skip($skip_why);
513 else {
515 # STDERR is written into same output file
516 my $exit_code = Parrot::Test::run_command(
517 \@test_prog,
518 CD => $cd,
519 STDOUT => $out_f,
520 STDERR => $out_f
522 my $real_output = slurp_file($out_f);
524 if ( $func =~ m/^ error_/xms ) {
525 return _handle_error_output( $self->{builder}, $real_output, $expected, $desc )
526 unless $exit_code;
528 elsif ($exit_code) {
529 $self->{builder}->ok( 0, $desc );
531 my $test_prog = join ' && ', @test_prog;
532 $self->{builder}->diag("'$test_prog' failed with exit code $exit_code.");
534 return 0;
537 my $meth = $test_map{$func};
538 $self->{builder}->$meth( $real_output, $expected, $desc );
541 # The generated files are left in the t/* directories.
542 # Let 'make clean' and 'svn:ignore' take care of them.
544 return;
547 my ($package) = caller();
549 no strict 'refs';
551 *{ $package . '::' . $func } = $test_sub;
555 # The following methods are private.
556 # They should not be used by modules inheriting from Parrot::Test.
558 sub _handle_error_output {
559 my ( $builder, $real_output, $expected, $desc ) = @_;
561 my $level = $builder->level();
562 $builder->level( $level + 1 );
563 $builder->ok( 0, $desc );
564 $builder->diag(
565 "Expected error but exited cleanly\n" . "Received:\n$real_output\nExpected:\n$expected\n" );
566 $builder->level($level);
568 return 0;
571 sub _run_test_file {
572 local $SIG{__WARN__} = \&_report_odd_hash;
573 my ( $func, $code, $expected, $desc, %extra ) = @_;
575 my $path_to_parrot = path_to_parrot();
576 my $parrot = File::Spec->join( File::Spec->curdir(), 'parrot' . $PConfig{exe} );
578 # Strange Win line endings
579 convert_line_endings($expected);
581 # set up default description
582 unless ($desc) {
583 ( undef, my $file, my $line ) = caller();
584 $desc = "($file line $line)";
587 # $test_no will be part of temporary file
588 my $test_no = $builder->current_test() + 1;
590 # Name of the file where output is written.
591 # Switch to a different extension when we are generating code.
592 my $out_f = per_test( '.out', $test_no );
594 # Name of the file with test code.
595 # This depends on which kind of code we are testing.
596 my $code_f;
597 if ( $func =~ m/^pir_.*?output/ ) {
598 $code_f = per_test( '.pir', $test_no );
600 elsif ( $func =~ m/^pasm_.*?output_/ ) {
601 $code_f = per_test( '.pasm', $test_no );
603 elsif ( $func =~ m/^pbc_.*?output_/ ) {
604 $code_f = per_test( '.pbc', $test_no );
606 else {
607 die "Unknown test function: $func";
609 $code_f = File::Spec->rel2abs($code_f);
610 my $code_basef = basename($code_f);
612 # native tests are just run, others need to write code first
613 if ( $code_f !~ /\.pbc$/ ) {
614 write_code_to_file( $code, $code_f );
617 # honor opt* filename to actually run code with -Ox
618 my $args = $ENV{TEST_PROG_ARGS} || '';
619 my $opt = $code_basef =~ m!opt(.)! ? "-O$1" : "";
620 $args .= " $opt";
622 my $run_exec = 0;
623 if ( $args =~ s/--run-exec// ) {
624 $run_exec = 1;
625 my $pbc_f = per_test( '.pbc', $test_no );
626 my $o_f = per_test( '_pbcexe' . $PConfig{o}, $test_no );
627 my $exe_f =
628 per_test( '_pbcexe' . $PConfig{exe}, $test_no )
629 ; # Make cleanup and svn:ignore more simple
630 my $exec_f = per_test( '_pbcexe', $test_no ); # Make cleanup and svn:ignore more simple
631 $exe_f =~ s@[\\/:]@$PConfig{slash}@g;
633 # RT#43751 put this into sub generate_pbc()
634 run_command(
635 qq{$parrot $args -o $pbc_f "$code_f"},
636 CD => $path_to_parrot,
637 STDOUT => $out_f,
638 STDERR => $out_f
640 if ( -e $pbc_f ) {
641 run_command(
642 qq{$parrot $args -o $o_f "$pbc_f"},
643 CD => $path_to_parrot,
644 STDOUT => $out_f,
645 STDERR => $out_f
647 if ( -e $o_f ) {
648 run_command(
649 qq{$PConfig{make} EXEC=$exec_f exec},
650 CD => $path_to_parrot,
651 STDOUT => $out_f,
652 STDERR => $out_f
654 if ( -e $exe_f ) {
655 run_command(
656 $exe_f,
657 CD => $path_to_parrot,
658 STDOUT => $out_f,
659 STDERR => $out_f
666 my ( $exit_code, $cmd );
667 unless ($run_exec) {
668 if ( $args =~ s/--run-pbc// || $args =~ s/-r // ) {
669 my $pbc_f = per_test( '.pbc', $test_no );
670 $args = qq{$args -o "$pbc_f"};
672 # In this case, we need to execute more than one command. Instead
673 # of a single scalar, build an array of commands.
674 $cmd = [ qq{$parrot $args "$code_f"}, qq{$parrot "$pbc_f"}, ];
676 else {
677 $cmd = qq{$parrot $args "$code_f"};
679 $exit_code = run_command(
680 $cmd,
681 CD => $path_to_parrot,
682 STDOUT => $out_f,
683 STDERR => $out_f
687 return ( $out_f, $cmd, $exit_code );
690 sub _report_odd_hash {
691 my $warning = shift;
692 if ( $warning =~ m/Odd number of elements in hash assignment/ ) {
693 require Carp;
694 my @args = DB::uplevel_args();
695 shift @args;
696 my $func = ( caller() )[2];
698 Carp::carp("Odd $func invocation; probably missing description for TODO test");
700 else {
701 warn $warning;
705 sub _generate_test_functions {
707 my $package = 'Parrot::Test';
708 my $path_to_parrot = path_to_parrot();
709 my $parrot = File::Spec->join( File::Spec->curdir(), 'parrot' . $PConfig{exe} );
711 my %parrot_test_map = map {
712 $_ . '_output_is' => 'is_eq',
713 $_ . '_error_output_is' => 'is_eq',
714 $_ . '_output_isnt' => 'isnt_eq',
715 $_ . '_error_output_isnt' => 'isnt_eq',
716 $_ . '_output_like' => 'like',
717 $_ . '_error_output_like' => 'like',
718 $_ . '_output_unlike' => 'unlike',
719 $_ . '_error_output_unlike' => 'unlike',
720 } qw( pasm pbc pir );
721 for my $func ( keys %parrot_test_map ) {
722 push @EXPORT, $func;
724 my $test_sub = sub {
725 local *__ANON__ = $func;
726 my ( $code, $expected, $desc, %extra ) = @_;
727 my $args = $ENV{TEST_PROG_ARGS} || '';
729 if ( $func =~ /^pbc_output_/ && $args =~ /-r / ) {
730 # native tests with --run-pbc don't make sense
731 return $builder->skip("no native tests with -r");
734 my ( $out_f, $cmd, $exit_code ) = _run_test_file( $func, @_ );
736 my $meth = $parrot_test_map{$func};
737 my $real_output = slurp_file($out_f);
739 unlink $out_f unless $ENV{POSTMORTEM};
741 # set a todo-item for Test::Builder to find
742 my $call_pkg = $builder->exported_to() || '';
744 no strict 'refs';
745 local *{ $call_pkg . '::TODO' } = ## no critic Variables::ProhibitConditionalDeclarations
746 \$extra{todo}
747 if defined $extra{todo};
749 if ( $func =~ /_error_/ ) {
750 return _handle_error_output( $builder, $real_output, $expected, $desc )
751 unless $exit_code;
753 elsif ($exit_code) {
754 $builder->ok( 0, $desc );
755 $builder->diag( "Exited with error code: $exit_code\n"
756 . "Received:\n$real_output\nExpected:\n$expected\n" );
758 return 0;
761 my $pass = $builder->$meth( $real_output, $expected, $desc );
762 $builder->diag("'$cmd' failed with exit code $exit_code")
763 if not $pass and $exit_code;
765 return $pass;
768 no strict 'refs';
770 *{ $package . '::' . $func } = $test_sub;
773 my %pir_2_pasm_test_map = (
774 pir_2_pasm_is => 'is_eq',
775 pir_2_pasm_isnt => 'isnt_eq',
776 pir_2_pasm_like => 'like',
777 pir_2_pasm_unlike => 'unlike',
780 foreach my $func ( keys %pir_2_pasm_test_map ) {
781 push @EXPORT, $func;
782 no strict 'refs';
784 my $test_sub = sub {
785 local *__ANON__ = $func;
786 my ( $code, $expected, $desc, %extra ) = @_;
788 # Strange Win line endings
789 convert_line_endings($expected);
791 # set up default description
792 unless ($desc) {
793 ( undef, my $file, my $line ) = caller();
794 $desc = "($file line $line)";
797 # $test_no will be part of temporary file
798 my $test_no = $builder->current_test() + 1;
800 # Name of the file with test code.
801 my $code_f = File::Spec->rel2abs( per_test( '.pir', $test_no ) );
802 my $code_basef = basename($code_f);
804 # output file
805 my $out_f = per_test( '.pasm', $test_no );
807 my $opt = $code_basef =~ m!opt(.)! ? "-O$1" : "-O1";
808 my $args = $ENV{TEST_PROG_ARGS} || '';
809 $args .= " $opt --output=$out_f";
810 $args =~ s/--run-exec//;
812 write_code_to_file( $code, $code_f );
814 my $cmd = qq{$parrot $args "$code_f"};
815 my $exit_code = run_command(
816 $cmd,
817 CD => $path_to_parrot,
818 STDOUT => $out_f,
819 STDERR => $out_f
822 my $meth = $pir_2_pasm_test_map{$func};
823 my $real_output = slurp_file($out_f);
826 # The parrot open '--outfile=file.pasm' seems to create unnecessary whitespace
827 $real_output =~ s/^\s*$//gm;
828 $real_output =~ s/[\t ]+/ /gm;
829 $real_output =~ s/ +$//gm;
831 $expected =~ s/[\t ]+/ /gm;
834 # set a todo-item for Test::Builder to find
835 my $call_pkg = $builder->exported_to() || '';
837 local *{ $call_pkg . '::TODO' } = ## no critic Variables::ProhibitConditionalDeclarations
838 \$extra{todo}
839 if defined $extra{todo};
841 my $pass = $builder->$meth( $real_output, $expected, $desc );
842 $builder->diag("'$cmd' failed with exit code $exit_code")
843 if $exit_code and not $pass;
845 if ( !$ENV{POSTMORTEM} ) {
846 unlink $out_f;
849 return $pass;
852 no strict 'refs';
854 *{ $package . '::' . $func } = $test_sub;
857 my %builtin_language_prefix = (
858 PIR_IMCC => 'pir',
859 PASM_IMCC => 'pasm',
862 my %language_test_map = (
863 language_output_is => 'output_is',
864 language_error_output_is => 'error_output_is',
865 language_output_like => 'output_like',
866 language_error_output_like => 'error_output_like',
867 language_output_isnt => 'output_isnt',
868 language_error_output_isnt => 'error_output_isnt',
871 foreach my $func ( keys %language_test_map ) {
872 push @EXPORT, $func;
874 my $test_sub = sub {
875 local *__ANON__ = $func;
876 my ( $language, @remaining ) = @_;
878 my $meth = $language_test_map{$func};
879 if ( my $prefix = $builtin_language_prefix{$language} ) {
881 # builtin languages are no tested with the example_output_xx() functions
882 my $level = $builder->level();
883 $builder->level( $level + 2 );
884 my $test_func = "${package}::${prefix}_${meth}";
886 no strict 'refs';
888 $test_func->(@remaining);
889 $builder->level($level);
891 else {
893 $language = ucfirst($language);
895 # make sure todo-items will work, by telling Test::Builder which
896 # package the .t file is in (one more than usual, due to the
897 # extra layer of package indirection
898 my $level = $builder->level();
899 $builder->level(2);
901 # Load module that knows how to test the language implementation
902 require "Parrot/Test/$language.pm";
903 my $class = "Parrot::Test::${language}";
905 # set the builder object, and parrot config.
906 my $obj = $class->new();
907 $obj->{builder} = $builder;
908 $obj->{relpath} = $path_to_parrot;
909 $obj->{parrot} = $parrot;
910 $obj->$meth(@remaining);
912 # restore prior level, just in case.
913 $builder->level($level);
917 no strict 'refs';
919 *{ $package . '::' . $func } = $test_sub;
922 my %example_test_map = (
923 example_output_is => 'language_output_is',
924 example_output_like => 'language_output_like',
925 example_output_isnt => 'language_output_isnt',
928 foreach my $func ( keys %example_test_map ) {
929 push @EXPORT, $func;
931 my $test_sub = sub {
932 local *__ANON__ = $func;
933 my ( $example_f, $expected, @options ) = @_;
935 my %lang_for_extension = (
936 pasm => 'PASM_IMCC',
937 pir => 'PIR_IMCC',
940 my ($extension) = $example_f =~ m{ [.] # introducing extension
941 ( pasm | pir ) # match and capture the extension
942 \z # at end of string
943 }ixms or Usage();
944 if ( defined $extension ) {
945 my $code = slurp_file($example_f);
946 my $test_func = join( '::', $package, $example_test_map{$func} );
948 no strict 'refs';
950 $test_func->(
951 $lang_for_extension{$extension},
952 $code, $expected, $example_f, @options
955 else {
956 fail( defined $extension, "no extension recognized for $example_f" );
960 no strict 'refs';
962 *{ $package . '::' . $func } = $test_sub;
965 my %c_test_map = (
966 c_output_is => 'is_eq',
967 c_output_isnt => 'isnt_eq',
968 c_output_like => 'like'
971 foreach my $func ( keys %c_test_map ) {
972 push @EXPORT, $func;
974 my $test_sub = sub {
975 local *__ANON__ = $func;
976 my ( $source, $expected, $desc, %options ) = @_;
978 # $test_no will be part of temporary files
979 my $test_no = $builder->current_test() + 1;
981 convert_line_endings($expected);
983 my $obj_f = per_test( $PConfig{o}, $test_no );
984 my $exe_f = per_test( $PConfig{exe}, $test_no );
985 $exe_f =~ s@[\\/:]@$PConfig{slash}@g;
986 my $out_f = per_test( '.out', $test_no );
987 my $build_f = per_test( '.build', $test_no );
989 # set todo-option before trying to compile or link
990 local *main::TODO;
991 *main::TODO = \$options{todo} if $options{todo};
993 # compile the source
995 my $source_f = per_test( '.c', $test_no );
996 write_code_to_file( $source, $source_f );
998 my $cmd =
999 "$PConfig{cc} $PConfig{ccflags} $PConfig{cc_debug} "
1000 . " -I./include -c "
1001 . "$PConfig{cc_o_out}$obj_f $source_f";
1002 my $exit_code = run_command(
1003 $cmd,
1004 'STDOUT' => $build_f,
1005 'STDERR' => $build_f
1007 $builder->diag("'$cmd' failed with exit code $exit_code")
1008 if $exit_code;
1010 if ( !-e $obj_f ) {
1011 $builder->diag( "Failed to build '$obj_f': " . slurp_file($build_f) );
1012 unlink $build_f;
1013 $builder->ok( 0, $desc );
1015 return 0;
1019 # link the compiled source, get an executable
1021 my $cfg = File::Spec->join( 'src', "parrot_config$PConfig{o}" );
1022 my $iculibs = $PConfig{has_icu} ? $PConfig{icu_shared} : q{};
1023 my $libparrot =
1024 $PConfig{parrot_is_shared}
1025 ? "$PConfig{rpath_blib} -L$PConfig{blib_dir} "
1027 $^O =~ m/MSWin32/
1028 ? $PConfig{libparrot_ldflags}
1029 : "-lparrot"
1031 : File::Spec->join( $PConfig{blib_dir}, $PConfig{libparrot_static} );
1032 my $cmd =
1033 "$PConfig{link} $PConfig{linkflags} $PConfig{ld_debug} "
1034 . "$obj_f $cfg $PConfig{ld_out}$exe_f "
1035 . "$libparrot $iculibs $PConfig{libs}";
1036 my $exit_code = run_command(
1037 $cmd,
1038 'STDOUT' => $build_f,
1039 'STDERR' => $build_f
1041 $builder->diag("'$cmd' failed with exit code $exit_code")
1042 if $exit_code;
1044 if ( !-e $exe_f ) {
1045 $builder->diag( "Failed to build '$exe_f': " . slurp_file($build_f) );
1046 unlink $build_f;
1047 $builder->ok( 0, $desc );
1049 return 0;
1053 # run the generated executable
1054 my $pass;
1056 my $cmd = File::Spec->join( File::Spec->curdir(), $exe_f );
1057 my $exit_code = run_command(
1058 $cmd,
1059 'STDOUT' => $out_f,
1060 'STDERR' => $out_f
1062 my $output = slurp_file($out_f);
1064 if ($exit_code) {
1065 $pass = $builder->ok( 0, $desc );
1066 $builder->diag( "Exited with error code: $exit_code\n"
1067 . "Received:\n$output\nExpected:\n$expected\n" );
1069 else {
1070 my $meth = $c_test_map{$func};
1071 $pass = $builder->$meth( $output, $expected, $desc );
1072 $builder->diag("'$cmd' failed with exit code $exit_code")
1073 unless $pass;
1077 unless ( $ENV{POSTMORTEM} ) {
1078 unlink $out_f, $build_f, $exe_f, $obj_f;
1079 unlink per_test( '.ilk', $test_no );
1080 unlink per_test( '.pdb', $test_no );
1083 return $pass;
1086 no strict 'refs';
1088 *{ $package . '::' . $func } = $test_sub;
1091 return;
1094 =head1 SEE ALSO
1096 =over 4
1098 =item F<t/harness>
1100 =item F<docs/tests.pod>
1102 =item L<Test/More>
1104 =item L<Test/Builder>
1106 =back
1108 =cut
1110 package DB;
1112 sub uplevel_args {
1113 my @foo = caller(2);
1115 return @DB::args;
1120 # Local Variables:
1121 # mode: cperl
1122 # cperl-indent-level: 4
1123 # fill-column: 100
1124 # End:
1125 # vim: expandtab shiftwidth=4: