From: Nicholas Clark Date: Sun, 25 Aug 2002 18:06:00 +0000 (+0100) Subject: ExtUtils::Constant 0.14 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=7783f9f6001b19735b378d8e18f3c5a6ac717876;p=p5sagit%2Fp5-mst-13.2.git ExtUtils::Constant 0.14 Message-ID: <20020825170600.GE322@Bagpuss.unfortu.net> p4raw-id: //depot/perl@17801 --- diff --git a/lib/ExtUtils/Constant.pm b/lib/ExtUtils/Constant.pm index 0772ee8..9730d91 100644 --- a/lib/ExtUtils/Constant.pm +++ b/lib/ExtUtils/Constant.pm @@ -1,6 +1,6 @@ package ExtUtils::Constant; use vars qw (@ISA $VERSION %XS_Constant %XS_TypeSet @EXPORT_OK %EXPORT_TAGS); -$VERSION = '0.13'; +$VERSION = '0.14'; =head1 NAME @@ -263,6 +263,11 @@ is equal to the C variable C. If I is defined, then it is used to avoid C for short names, or to generate a comment to highlight the position of the character in the C statement. +If I is a reference to a scalar, then instead it gives +the characters pre-checked at the beginning, (and the number of chars by +which the C variable name has been advanced. These need to be chopped from +the front of I). + =cut sub memEQ_clause { @@ -270,6 +275,14 @@ sub memEQ_clause { # Which could actually be a character comparison or even "" my ($name, $checked_at, $indent) = @_; $indent = ' ' x ($indent || 4); + my $front_chop; + if (ref $checked_at) { + # regexp won't work on 5.6.1 without use utf8; in turn that won't work + # on 5.005_03. + substr ($name, 0, length $$checked_at,) = ''; + $front_chop = C_stringify ($$checked_at); + undef $checked_at; + } my $len = length $name; if ($len < 2) { @@ -289,12 +302,38 @@ sub memEQ_clause { return $indent . "if (name[$check] == '$char') {\n"; } } - # Could optimise a memEQ on 3 to 2 single character checks here + if (($len == 2 and !defined $checked_at) + or ($len == 3 and defined ($checked_at) and $checked_at == 2)) { + my $char1 = C_stringify (substr $name, 0, 1); + my $char2 = C_stringify (substr $name, 1, 1); + return $indent . "if (name[0] == '$char1' && name[1] == '$char2') {\n"; + } + if (($len == 3 and defined ($checked_at) and $checked_at == 1)) { + my $char1 = C_stringify (substr $name, 0, 1); + my $char2 = C_stringify (substr $name, 2, 1); + return $indent . "if (name[0] == '$char1' && name[2] == '$char2') {\n"; + } + + my $pointer = '^'; + my $have_checked_last = defined ($checked_at) && $len == $checked_at + 1; + if ($have_checked_last) { + # Checked at the last character, so no need to memEQ it. + $pointer = C_stringify (chop $name); + $len--; + } + $name = C_stringify ($name); my $body = $indent . "if (memEQ(name, \"$name\", $len)) {\n"; - $body .= $indent . "/* ". (' ' x $checked_at) . '^' - . (' ' x ($len - $checked_at + length $len)) . " */\n" - if defined $checked_at; + # Put a little ^ under the letter we checked at + # Screws up for non printable and non-7 bit stuff, but that's too hard to + # get right. + if (defined $checked_at) { + $body .= $indent . "/* ". (' ' x $checked_at) . $pointer + . (' ' x ($len - $checked_at + length $len)) . " */\n"; + } elsif (defined $front_chop) { + $body .= $indent . "/* $front_chop" + . (' ' x ($len + 1 + length $len)) . " */\n"; + } return $body; } @@ -504,7 +543,9 @@ sub switch_clause { # Figure out what to switch on. # (RMS, Spread of jump table, Position, Hashref) my @best = (1e38, ~0); - foreach my $i (0 .. ($namelen - 1)) { + # Prefer the last character over the others. (As it lets us shortern the + # memEQ clause at no cost). + foreach my $i ($namelen - 1, 0 .. ($namelen - 2)) { my ($min, $max) = (~0, 0); my %spread; if ($is_perl56) { @@ -533,6 +574,8 @@ sub switch_clause { # the string wins. Because if that passes but the memEQ fails, it may # only need the start of the string to bin the choice. # I think. But I'm micro-optimising. :-) + # OK. Trump that. Now favour the last character of the string, before the + # rest. my $ss; $ss += @$_ * @$_ foreach values %spread; my $rms = sqrt ($ss / keys %spread); @@ -540,12 +583,18 @@ sub switch_clause { @best = ($rms, $max - $min, $i, \%spread); } } - die "Internal error. Failed to pick a switch point for @names" + confess "Internal error. Failed to pick a switch point for @names" unless defined $best[2]; # use Data::Dumper; print Dumper (@best); my ($offset, $best) = @best[2,3]; $body .= $indent . "/* Offset $offset gives the best switch position. */\n"; - $body .= $indent . "switch (name[$offset]) {\n"; + + my $do_front_chop = $offset == 0 && $namelen > 2; + if ($do_front_chop) { + $body .= $indent . "switch (*name++) {\n"; + } else { + $body .= $indent . "switch (name[$offset]) {\n"; + } foreach my $char (sort keys %$best) { confess sprintf "'$char' is %d bytes long, not 1", length $char if length ($char) != 1; @@ -554,7 +603,11 @@ sub switch_clause { foreach my $name (sort @{$best->{$char}}) { my $thisone = $items->{$name}; # warn "You are here"; - $body .= match_clause ($thisone, $offset, 2 + length $indent); + if ($do_front_chop) { + $body .= match_clause ($thisone, \$char, 2 + length $indent); + } else { + $body .= match_clause ($thisone, $offset, 2 + length $indent); + } } $body .= $indent . " break;\n"; } diff --git a/lib/ExtUtils/t/Constant.t b/lib/ExtUtils/t/Constant.t index 6356ab4..4e5819d 100644 --- a/lib/ExtUtils/t/Constant.t +++ b/lib/ExtUtils/t/Constant.t @@ -1,7 +1,5 @@ #!/usr/bin/perl -w -print "1..52\n"; - BEGIN { if( $ENV{PERL_CORE} ) { chdir 't' if -d 't'; @@ -15,205 +13,333 @@ use ExtUtils::MakeMaker; use ExtUtils::Constant qw (constant_types C_constant XS_constant autoload); use Config; use File::Spec; +use Cwd; my $do_utf_tests = $] > 5.006; my $better_than_56 = $] > 5.007; +# For debugging set this to 1. +my $keep_files = 0; +$| = 1; # Because were are going to be changing directory before running Makefile.PL my $perl = $^X; # 5.005 doesn't have new enough File::Spec to have rel2abs. But actually we # only need it when $^X isn't absolute, which is going to be 5.8.0 or later # (where ExtUtils::Constant is in the core, and tests against the uninstalled -# perl +# perl) $perl = File::Spec->rel2abs ($perl) unless $] < 5.006; # ExtUtils::Constant::C_constant uses $^X inside a comment, and we want to # compare output to ensure that it is the same. We were probably run as ./perl # whereas we will run the child with the full path in $perl. So make $^X for # us the same as our child will see. $^X = $perl; - +my $lib = $ENV{PERL_CORE} ? '../../../lib' : '../../blib/lib'; +my $runperl = "$perl \"-I$lib\""; print "# perl=$perl\n"; -my $lib = $ENV{PERL_CORE} ? '../../lib' : '../blib/lib'; -my $runperl = "$perl \"-I$lib\""; +my $make = $Config{make}; +$make = $ENV{MAKE} if exists $ENV{MAKE}; +if ($^O eq 'MSWin32' && $make eq 'nmake') { $make .= " -nologo"; } -$| = 1; +# Renamed by make clean +my $makefile = ($^O eq 'VMS' ? 'descrip' : 'Makefile'); +my $makefile_ext = ($^O eq 'VMS' ? '.mms' : ''); +my $makefile_rename = $makefile . ($^O eq 'VMS' ? '.mms' : '.old'); +my $output = "output"; +my $package = "ExtTest"; my $dir = "ext-$$"; -my @files; +my $subdir = 0; +# The real test counter. +my $realtest = 1; + +my $orig_cwd = cwd; +my $updir = File::Spec->updir; +die "Can't get current directory: $!" unless defined $orig_cwd; print "# $dir being created...\n"; mkdir $dir, 0777 or die "mkdir: $!\n"; -my $output = "output"; - -# For debugging set this to 1. -my $keep_files = 0; - END { + if (defined $orig_cwd and length $orig_cwd) { + chdir $orig_cwd or die "Can't chdir back to '$orig_cwd': $!"; use File::Path; print "# $dir being removed...\n"; rmtree($dir) unless $keep_files; + } else { + # Can't get here. + die "cwd at start was empty, but directory '$dir' was created" if $dir; + } } -my $package = "ExtTest"; +chdir $dir or die $!; +push @INC, '../../lib', '../../../lib'; -# Test the code that generates 1 and 2 letter name comparisons. -my %compass = ( -N => 0, 'NE' => 45, E => 90, SE => 135, S => 180, SW => 225, W => 270, NW => 315 -); +sub check_for_bonus_files { + my $dir = shift; + my %expect = map {($^O eq 'VMS' ? lc($_) : $_), 1} @_; -my $parent_rfc1149 = - 'A Standard for the Transmission of IP Datagrams on Avian Carriers'; -# Check that 8 bit and unicode names don't cause problems. -my $pound; -if (ord('A') == 193) { # EBCDIC platform - $pound = chr 177; # A pound sign. (Currency) -} else { # ASCII platform - $pound = chr 163; # A pound sign. (Currency) -} + my $fail; + opendir DIR, $dir or die "opendir '$dir': $!"; + while (defined (my $entry = readdir DIR)) { + $entry =~ s/\.$// if $^O eq 'VMS'; # delete trailing dot that indicates no extension + next if $expect{$entry}; + print "# Extra file '$entry'\n"; + $fail = 1; + } -my ($inf, $pound_bytes, $pound_utf8); -if ($do_utf_tests) { - $inf = chr 0x221E; - # Check that we can distiguish the pathological case of a string, and the - # utf8 representation of that string. - $pound_utf8 = $pound . '1'; - if ($better_than_56) { - $pound_bytes = $pound_utf8; - utf8::encode ($pound_bytes); + closedir DIR or warn "closedir '.': $!"; + if ($fail) { + print "not ok $realtest\n"; } else { - # Must have that "U*" to generate a zero length UTF string that forces - # top bit set chars (such as the pound sign) into UTF8, so that the - # unpack 'C*' then gets the byte form of the UTF8. - $pound_bytes = pack 'C*', unpack 'C*', $pound_utf8 . pack "U*"; + print "ok $realtest\n"; } + $realtest++; } -my @names = ("FIVE", {name=>"OK6", type=>"PV",}, - {name=>"OK7", type=>"PVN", - value=>['"not ok 7\\n\\0ok 7\\n"', 15]}, - {name => "FARTHING", type=>"NV"}, - {name => "NOT_ZERO", type=>"UV", value=>"~(UV)0"}, - {name => "OPEN", type=>"PV", value=>'"/*"', macro=>1}, - {name => "CLOSE", type=>"PV", value=>'"*/"', - macro=>["#if 1\n", "#endif\n"]}, - {name => "ANSWER", default=>["UV", 42]}, "NOTDEF", - {name => "Yes", type=>"YES"}, - {name => "No", type=>"NO"}, - {name => "Undef", type=>"UNDEF"}, -# OK. It wasn't really designed to allow the creation of dual valued constants. -# It was more for INADDR_ANY INADDR_BROADCAST INADDR_LOOPBACK INADDR_NONE - {name=>"RFC1149", type=>"SV", value=>"sv_2mortal(temp_sv)", - pre=>"SV *temp_sv = newSVpv(RFC1149, 0); " - . "(void) SvUPGRADE(temp_sv,SVt_PVIV); SvIOK_on(temp_sv); " - . "SvIVX(temp_sv) = 1149;"}, - {name=>"perl", type=>"PV",}, -); +sub build_and_run { + my ($tests, $expect, $files) = @_; + my $core = $ENV{PERL_CORE} ? ' PERL_CORE=1' : ''; + my @perlout = `$runperl Makefile.PL $core`; + if ($?) { + print "not ok $realtest # $runperl Makefile.PL failed: $?\n"; + print "# $_" foreach @perlout; + exit($?); + } else { + print "ok $realtest\n"; + } + $realtest++; -push @names, $_ foreach keys %compass; + if (-f "$makefile$makefile_ext") { + print "ok $realtest\n"; + } else { + print "not ok $realtest\n"; + } + $realtest++; -# Automatically compile the list of all the macro names, and make them -# exported constants. -my @names_only = map {(ref $_) ? $_->{name} : $_} @names; + my @makeout; -# Exporter::Heavy (currently) isn't able to export these names: -push @names, ({name=>"*/", type=>"PV", value=>'"CLOSE"', macro=>1}, - {name=>"/*", type=>"PV", value=>'"OPEN"', macro=>1}, - {name=>$pound, type=>"PV", value=>'"Sterling"', macro=>1}, - ); + if ($^O eq 'VMS') { $make .= ' all'; } -if ($do_utf_tests) { - push @names, ({name=>$inf, type=>"PV", value=>'"Infinity"', macro=>1}, - {name=>$pound_utf8, type=>"PV", value=>'"1 Pound"', macro=>1}, - {name=>$pound_bytes, type=>"PV", value=>'"1 Pound (as bytes)"', - macro=>1}, - ); -} + print "# make = '$make'\n"; + @makeout = `$make`; + if ($?) { + print "not ok $realtest # $make failed: $?\n"; + print "# $_" foreach @makeout; + exit($?); + } else { + print "ok $realtest\n"; + } + $realtest++; -=pod + if ($^O eq 'VMS') { $make =~ s{ all}{}; } -The above set of names seems to produce a suitably bad set of compile -problems on a Unicode naive version of ExtUtils::Constant (ie 0.11): + if ($Config{usedl}) { + print "ok $realtest # This is dynamic linking, so no need to make perl\n"; + } else { + my $makeperl = "$make perl"; + print "# make = '$makeperl'\n"; + @makeout = `$makeperl`; + if ($?) { + print "not ok $realtest # $makeperl failed: $?\n"; + print "# $_" foreach @makeout; + exit($?); + } else { + print "ok $realtest\n"; + } + } + $realtest++; -nick@thinking-cap 15439-32-utf$ PERL_CORE=1 ./perl lib/ExtUtils/t/Constant.t -1..33 -# perl=/stuff/perl5/15439-32-utf/perl -# ext-30370 being created... -Wide character in print at lib/ExtUtils/t/Constant.t line 140. -ok 1 -ok 2 -# make = 'make' -ExtTest.xs: In function `constant_1': -ExtTest.xs:80: warning: multi-character character constant -ExtTest.xs:80: warning: case value out of range -ok 3 + my $maketest = "$make test"; + print "# make = '$maketest'\n"; -=cut + @makeout = `$maketest`; -# Grr ` + if (open OUTPUT, "<$output") { + local $/; # Slurp it - faster. + print ; + close OUTPUT or print "# Close $output failed: $!\n"; + } else { + # Harness will report missing test results at this point. + print "# Open <$output failed: $!\n"; + } -my $types = {}; -my $constant_types = constant_types(); # macro defs -my $C_constant = join "\n", - C_constant ($package, undef, "IV", $types, undef, undef, @names); -my $XS_constant = XS_constant ($package, $types); # XS for ExtTest::constant - -################ Header -my $header = File::Spec->catdir($dir, "test.h"); -push @files, "test.h"; -open FH, ">$header" or die "open >$header: $!\n"; -print FH <<"EOT"; -#define FIVE 5 -#define OK6 "ok 6\\n" -#define OK7 1 -#define FARTHING 0.25 -#define NOT_ZERO 1 -#define Yes 0 -#define No 1 -#define Undef 1 -#define RFC1149 "$parent_rfc1149" -#undef NOTDEF -#define perl "rules" + $realtest += $tests; + if ($?) { + print "not ok $realtest # $maketest failed: $?\n"; + print "# $_" foreach @makeout; + } else { + print "ok $realtest - maketest\n"; + } + $realtest++; + + # -x is busted on Win32 < 5.6.1, so we emulate it. + my $regen; + if( $^O eq 'MSWin32' && $] <= 5.006001 ) { + open(REGENTMP, ">regentmp") or die $!; + open(XS, "$package.xs") or die $!; + my $saw_shebang; + while() { + $saw_shebang++ if /^#!.*/i ; + print REGENTMP $_ if $saw_shebang; + } + close XS; close REGENTMP; + $regen = `$runperl regentmp`; + unlink 'regentmp'; + } + else { + $regen = `$runperl -x $package.xs`; + } + if ($?) { + print "not ok $realtest # $runperl -x $package.xs failed: $?\n"; + } else { + print "ok $realtest - regen\n"; + } + $realtest++; + + if ($expect eq $regen) { + print "ok $realtest - regen worked\n"; + } else { + print "not ok $realtest - regen worked\n"; + # open FOO, ">expect"; print FOO $expect; + # open FOO, ">regen"; print FOO $regen; close FOO; + } + $realtest++; + + my $makeclean = "$make clean"; + print "# make = '$makeclean'\n"; + @makeout = `$makeclean`; + if ($?) { + print "not ok $realtest # $make failed: $?\n"; + print "# $_" foreach @makeout; + } else { + print "ok $realtest\n"; + } + $realtest++; + + check_for_bonus_files ('.', @$files, $output, $makefile_rename, '.', '..'); + + rename $makefile_rename, $makefile + or die "Can't rename '$makefile_rename' to '$makefile': $!"; + + unlink $output or warn "Can't unlink '$output': $!"; + + # Need to make distclean to remove ../../lib/ExtTest.pm + my $makedistclean = "$make distclean"; + print "# make = '$makedistclean'\n"; + @makeout = `$makedistclean`; + if ($?) { + print "not ok $realtest # $make failed: $?\n"; + print "# $_" foreach @makeout; + } else { + print "ok $realtest\n"; + } + $realtest++; + + check_for_bonus_files ('.', @$files, '.', '..'); + + unless ($keep_files) { + foreach (@$files) { + unlink $_ or warn "unlink $_: $!"; + } + } + + check_for_bonus_files ('.', '.', '..'); +} + +sub Makefile_PL { + my $package = shift; + ################ Makefile.PL + # We really need a Makefile.PL because make test for a no dynamic linking perl + # will run Makefile.PL again as part of the "make perl" target. + my $makefilePL = "Makefile.PL"; + open FH, ">$makefilePL" or die "open >$makefilePL: $!\n"; + print FH <<"EOT"; +#!$perl -w +use ExtUtils::MakeMaker; +WriteMakefile( + 'NAME' => "$package", + 'VERSION_FROM' => "$package.pm", # finds \$VERSION + (\$] >= 5.005 ? + (#ABSTRACT_FROM => "$package.pm", # XXX add this + AUTHOR => "$0") : ()) + ); EOT -while (my ($point, $bearing) = each %compass) { - print FH "#define $point $bearing\n" + close FH or die "close $makefilePL: $!\n"; + return $makefilePL; +} + +sub MANIFEST { + my (@files) = @_; + ################ MANIFEST + # We really need a MANIFEST because make distclean checks it. + my $manifest = "MANIFEST"; + push @files, $manifest; + open FH, ">$manifest" or die "open >$manifest: $!\n"; + print FH "$_\n" foreach @files; + close FH or die "close $manifest: $!\n"; + return @files; } -close FH or die "close $header: $!\n"; -################ XS -my $xs = File::Spec->catdir($dir, "$package.xs"); -push @files, "$package.xs"; -open FH, ">$xs" or die "open >$xs: $!\n"; +sub write_and_run_extension { + my ($name, $items, $export_names, $package, $header, $testfile, $num_tests) + = @_; + my $types = {}; + my $constant_types = constant_types(); # macro defs + my $C_constant = join "\n", + C_constant ($package, undef, "IV", $types, undef, undef, @$items); + my $XS_constant = XS_constant ($package, $types); # XS for ExtTest::constant + + my $expect = $constant_types . $C_constant . + "\n#### XS Section:\n" . $XS_constant; + + print "# $name\n# $dir/$subdir being created...\n"; + mkdir $subdir, 0777 or die "mkdir: $!\n"; + chdir $subdir or die $!; -print FH <<'EOT'; + my @files; + + ################ Header + my $header_name = "test.h"; + push @files, $header_name; + open FH, ">$header_name" or die "open >$header_name: $!\n"; + print FH $header or die $!; + close FH or die "close $header_name: $!\n"; + + ################ XS + my $xs = "$package.xs"; + push @files, $xs; + open FH, ">$xs" or die "open >$xs: $!\n"; + + print FH <<'EOT'; #include "EXTERN.h" #include "perl.h" #include "XSUB.h" EOT -print FH "#include \"test.h\"\n\n"; -print FH $constant_types; -print FH $C_constant, "\n"; -print FH "MODULE = $package PACKAGE = $package\n"; -print FH "PROTOTYPES: ENABLE\n"; -print FH $XS_constant; -close FH or die "close $xs: $!\n"; - -################ PM -my $pm = File::Spec->catdir($dir, "$package.pm"); -push @files, "$package.pm"; -open FH, ">$pm" or die "open >$pm: $!\n"; -print FH "package $package;\n"; -print FH "use $];\n"; + # XXX Here doc these: + print FH "#include \"$header_name\"\n\n"; + print FH $constant_types; + print FH $C_constant, "\n"; + print FH "MODULE = $package PACKAGE = $package\n"; + print FH "PROTOTYPES: ENABLE\n"; + print FH $XS_constant; + close FH or die "close $xs: $!\n"; + + ################ PM + my $pm = "$package.pm"; + push @files, $pm; + open FH, ">$pm" or die "open >$pm: $!\n"; + print FH "package $package;\n"; + print FH "use $];\n"; -print FH <<'EOT'; + print FH <<'EOT'; use strict; EOT -printf FH "use warnings;\n" unless $] < 5.006; -print FH <<'EOT'; + printf FH "use warnings;\n" unless $] < 5.006; + print FH <<'EOT'; use Carp; require Exporter; @@ -222,50 +348,156 @@ use vars qw ($VERSION @ISA @EXPORT_OK $AUTOLOAD); $VERSION = '0.01'; @ISA = qw(Exporter DynaLoader); -@EXPORT_OK = qw( EOT + # Having this qw( in the here doc confuses cperl mode far too much to be + # helpful. And I'm using cperl mode to edit this, even if you're not :-) + print FH "\@EXPORT_OK = qw(\n"; + + # Print the names of all our autoloaded constants + print FH "\t$_\n" foreach (@$export_names); + print FH ");\n"; + # Print the AUTOLOAD subroutine ExtUtils::Constant generated for us + print FH autoload ($package, $]); + print FH "bootstrap $package \$VERSION;\n1;\n__END__\n"; + close FH or die "close $pm: $!\n"; + + ################ test.pl + my $testpl = "test.pl"; + push @files, $testpl; + open FH, ">$testpl" or die "open >$testpl: $!\n"; + # Standard test header (need an option to suppress this?) + print FH <<"EOT" or die $!; +use strict; +use $package qw(@$export_names); -# Print the names of all our autoloaded constants -print FH "\t$_\n" foreach (@names_only); -print FH ");\n"; -# Print the AUTOLOAD subroutine ExtUtils::Constant generated for us -print FH autoload ($package, $]); -print FH "bootstrap $package \$VERSION;\n1;\n__END__\n"; -close FH or die "close $pm: $!\n"; - -################ test.pl -my $testpl = File::Spec->catdir($dir, "test.pl"); -push @files, "test.pl"; -open FH, ">$testpl" or die "open >$testpl: $!\n"; - -print FH "use strict;\n"; -print FH "use $package qw(@names_only);\n\n"; - -print FH "use utf8\n\n" if $do_utf_tests; - -print FH <<"EOT"; - -print "1..1\n"; +print "1..2\n"; if (open OUTPUT, ">$output") { print "ok 1\n"; select OUTPUT; } else { - print "not ok 1 # Failed to open '$output': $!\n"; + print "not ok 1 # Failed to open '$output': \$!\n"; exit 1; } EOT + print FH $testfile or die $!; + print FH <<"EOT" or die $!; +select STDOUT; +if (close OUTPUT) { + print "ok 2\n"; +} else { + print "not ok 2 # Failed to close '$output': \$!\n"; +} +EOT + close FH or die "close $testpl: $!\n"; -print FH << 'EOT'; + push @files, Makefile_PL($package); + @files = MANIFEST (@files); -my $better_than_56 = $] > 5.007; + build_and_run ($num_tests, $expect, \@files); + + chdir $updir or die "chdir '$updir': $!"; + ++$subdir; +} +# Tests are arrayrefs of the form +# $name, [items], [export_names], $package, $header, $testfile, $num_tests +my @tests; +my $before_tests = 4; # Number of "ok"s emitted to build extension +my $after_tests = 8; # Number of "ok"s emitted after make test run +my $dummytest = 1; + +my $here; +sub start_tests { + $dummytest += $before_tests; + $here = $dummytest; +} +sub end_tests { + my ($name, $items, $export_names, $header, $testfile) = @_; + push @tests, [$name, $items, $export_names, $package, $header, $testfile, + $dummytest - $here]; + $dummytest += $after_tests; +} + +my $pound; +if (ord('A') == 193) { # EBCDIC platform + $pound = chr 177; # A pound sign. (Currency) +} else { # ASCII platform + $pound = chr 163; # A pound sign. (Currency) +} +my @common_items = ( + {name=>"perl", type=>"PV",}, + {name=>"*/", type=>"PV", value=>'"CLOSE"', macro=>1}, + {name=>"/*", type=>"PV", value=>'"OPEN"', macro=>1}, + {name=>$pound, type=>"PV", value=>'"Sterling"', macro=>1}, + ); + +{ + # Simple tests + start_tests(); + my $parent_rfc1149 = + 'A Standard for the Transmission of IP Datagrams on Avian Carriers'; + # Test the code that generates 1 and 2 letter name comparisons. + my %compass = ( + N => 0, 'NE' => 45, E => 90, SE => 135, + S => 180, SW => 225, W => 270, NW => 315 + ); + + my $header = << "EOT"; +#define FIVE 5 +#define OK6 "ok 6\\n" +#define OK7 1 +#define FARTHING 0.25 +#define NOT_ZERO 1 +#define Yes 0 +#define No 1 +#define Undef 1 +#define RFC1149 "$parent_rfc1149" +#undef NOTDEF +#define perl "rules" +EOT + + while (my ($point, $bearing) = each %compass) { + $header .= "#define $point $bearing\n" + } + my @items = ("FIVE", {name=>"OK6", type=>"PV",}, + {name=>"OK7", type=>"PVN", + value=>['"not ok 7\\n\\0ok 7\\n"', 15]}, + {name => "FARTHING", type=>"NV"}, + {name => "NOT_ZERO", type=>"UV", value=>"~(UV)0"}, + {name => "OPEN", type=>"PV", value=>'"/*"', macro=>1}, + {name => "CLOSE", type=>"PV", value=>'"*/"', + macro=>["#if 1\n", "#endif\n"]}, + {name => "ANSWER", default=>["UV", 42]}, "NOTDEF", + {name => "Yes", type=>"YES"}, + {name => "No", type=>"NO"}, + {name => "Undef", type=>"UNDEF"}, + # OK. It wasn't really designed to allow the creation of dual valued + # constants. + # It was more for INADDR_ANY INADDR_BROADCAST INADDR_LOOPBACK INADDR_NONE + {name=>"RFC1149", type=>"SV", value=>"sv_2mortal(temp_sv)", + pre=>"SV *temp_sv = newSVpv(RFC1149, 0); " + . "(void) SvUPGRADE(temp_sv,SVt_PVIV); SvIOK_on(temp_sv); " + . "SvIVX(temp_sv) = 1149;"}, + ); + + push @items, $_ foreach keys %compass; + + # Automatically compile the list of all the macro names, and make them + # exported constants. + my @export_names = map {(ref $_) ? $_->{name} : $_} @items; + + # Exporter::Heavy (currently) isn't able to export the last 3 of these: + push @items, @common_items; + + # XXX there are hardwired still. + my $test_body = <<'EOT'; # What follows goes to the temporary file. # IV my $five = FIVE; if ($five == 5) { print "ok 5\n"; } else { - print "not ok 5 # $five\n"; + print "not ok 5 # \$five\n"; } # PV @@ -354,7 +586,6 @@ unless (defined $undef) { print "not ok 16 # \$undef='$undef'\n"; } - # invalid macro (chosen to look like a mix up between No and SW) $notdef = eval { &ExtTest::So }; if (defined $notdef) { @@ -379,10 +610,10 @@ my %compass = ( EOT while (my ($point, $bearing) = each %compass) { - print FH "'$point' => $bearing, " + $test_body .= "'$point' => $bearing, " } -print FH <<'EOT'; +$test_body .= <<'EOT'; ); @@ -408,7 +639,7 @@ if ($fail) { EOT -print FH <<"EOT"; +$test_body .= <<"EOT"; my \$rfc1149 = RFC1149; if (\$rfc1149 ne "$parent_rfc1149") { print "not ok 20 # '\$rfc1149' ne '$parent_rfc1149'\n"; @@ -424,7 +655,7 @@ if (\$rfc1149 != 1149) { EOT -print FH <<'EOT'; +$test_body .= <<'EOT'; # test macro=>1 my $open = OPEN; if ($open eq '/*') { @@ -433,8 +664,59 @@ if ($open eq '/*') { print "not ok 22 # \$open='$open'\n"; } EOT +$dummytest+=18; + + end_tests("Simple tests", \@items, \@export_names, $header, $test_body); +} if ($do_utf_tests) { + # utf8 tests + start_tests(); + my ($inf, $pound_bytes, $pound_utf8); + + $inf = chr 0x221E; + # Check that we can distiguish the pathological case of a string, and the + # utf8 representation of that string. + $pound_utf8 = $pound . '1'; + if ($better_than_56) { + $pound_bytes = $pound_utf8; + utf8::encode ($pound_bytes); + } else { + # Must have that "U*" to generate a zero length UTF string that forces + # top bit set chars (such as the pound sign) into UTF8, so that the + # unpack 'C*' then gets the byte form of the UTF8. + $pound_bytes = pack 'C*', unpack 'C*', $pound_utf8 . pack "U*"; + } + + my @items = (@common_items, + {name=>$inf, type=>"PV", value=>'"Infinity"', macro=>1}, + {name=>$pound_utf8, type=>"PV", value=>'"1 Pound"', macro=>1}, + {name=>$pound_bytes, type=>"PV", value=>'"1 Pound (as bytes)"', + macro=>1}, + ); + +=pod + +The above set of names seems to produce a suitably bad set of compile +problems on a Unicode naive version of ExtUtils::Constant (ie 0.11): + +nick@thinking-cap 15439-32-utf$ PERL_CORE=1 ./perl lib/ExtUtils/t/Constant.t +1..33 +# perl=/stuff/perl5/15439-32-utf/perl +# ext-30370 being created... +Wide character in print at lib/ExtUtils/t/Constant.t line 140. +ok 1 +ok 2 +# make = 'make' +ExtTest.xs: In function `constant_1': +ExtTest.xs:80: warning: multi-character character constant +ExtTest.xs:80: warning: case value out of range +ok 3 + +=cut + +# Grr ` + # Do this in 7 bit in case someone is testing with some settings that cause # 8 bit files incapable of storing this character. my @values @@ -442,18 +724,20 @@ if ($do_utf_tests) { ($pound, $inf, $pound_bytes, $pound_utf8); # Values is a list of strings, such as ('194,163,49', '163,49') - print FH <<'EOT'; + my $test_body .= "my \$test = $dummytest;\n"; + $dummytest += 7 * 3; # 3 tests for each of the 7 things: + + $test_body .= << 'EOT'; - # I can see that this child test program might be about to use parts of - # Test::Builder +use utf8; +my $better_than_56 = $] > 5.007; - my $test = 23; - my ($pound, $inf, $pound_bytes, $pound_utf8) = map {eval "pack 'U*', $_"} +my ($pound, $inf, $pound_bytes, $pound_utf8) = map {eval "pack 'U*', $_"} EOT - print FH join ",", @values; + $test_body .= join ",", @values; - print FH << 'EOT'; + $test_body .= << 'EOT'; ; foreach (["perl", "rules", "rules"], @@ -479,9 +763,9 @@ foreach (["perl", "rules", "rules"], } EOT - print FH "my (\$error, \$got) = ${package}::constant (\$string);\n"; + $test_body .= "my (\$error, \$got) = ${package}::constant (\$string);\n"; - print FH <<'EOT'; + $test_body .= <<'EOT'; if ($error or $got ne $expect) { print "not ok $test # error '$error', got '$got'\n"; } else { @@ -496,9 +780,9 @@ EOT } EOT - print FH "my (\$error, \$got) = ${package}::constant (\$string);\n"; + $test_body .= "my (\$error, \$got) = ${package}::constant (\$string);\n"; - print FH <<'EOT'; + $test_body .= <<'EOT'; if ($error or $got ne $expect) { print "not ok $test # error '$error', got '$got'\n"; } else { @@ -515,9 +799,9 @@ EOT } EOT - print FH "my (\$error, \$got) = ${package}::constant (\$string);\n"; + $test_body .= "my (\$error, \$got) = ${package}::constant (\$string);\n"; - print FH <<'EOT'; + $test_body .= <<'EOT'; if (ref $expect_bytes) { # Error expected. if ($error) { @@ -534,229 +818,101 @@ EOT } } EOT -} else { - # Don't utf tests; - print FH <<'EOT'; -print "ok $_ # Skipped on non Unicode perl\n" foreach 23..43; -EOT -} - -close FH or die "close $testpl: $!\n"; - -# This is where the test numbers carry on after the test number above are -# relayed -my $test = 44; -################ Makefile.PL -# We really need a Makefile.PL because make test for a no dynamic linking perl -# will run Makefile.PL again as part of the "make perl" target. -my $makefilePL = File::Spec->catdir($dir, "Makefile.PL"); -push @files, "Makefile.PL"; -open FH, ">$makefilePL" or die "open >$makefilePL: $!\n"; -print FH <<"EOT"; -#!$perl -w -use ExtUtils::MakeMaker; -WriteMakefile( - 'NAME' => "$package", - 'VERSION_FROM' => "$package.pm", # finds \$VERSION - (\$] >= 5.005 ? - (#ABSTRACT_FROM => "$package.pm", # XXX add this - AUTHOR => "$0") : ()) - ); -EOT - -close FH or die "close $makefilePL: $!\n"; - -################ MANIFEST -# We really need a MANIFEST because make distclean checks it. -my $manifest = File::Spec->catdir($dir, "MANIFEST"); -push @files, "MANIFEST"; -open FH, ">$manifest" or die "open >$manifest: $!\n"; -print FH "$_\n" foreach @files; -close FH or die "close $manifest: $!\n"; - -chdir $dir or die $!; push @INC, '../../lib'; -END {chdir ".." or warn $!}; - -my $core = $ENV{PERL_CORE} ? ' PERL_CORE=1' : ''; -my @perlout = `$runperl Makefile.PL $core`; -if ($?) { - print "not ok 1 # $runperl Makefile.PL failed: $?\n"; - print "# $_" foreach @perlout; - exit($?); -} else { - print "ok 1\n"; + end_tests("utf8 tests", \@items, [], "#define perl \"rules\"\n", $test_body); } +# XXX I think that I should merge this into the utf8 test above. +sub explict_call_constant { + my ($string, $expect) = @_; + # This does assume simple strings suitable for '' + my $test_body = <<"EOT"; +{ + my (\$error, \$got) = ${package}::constant ('$string');\n; +EOT -my $makefile = ($^O eq 'VMS' ? 'descrip' : 'Makefile'); -my $makefile_ext = ($^O eq 'VMS' ? '.mms' : ''); -if (-f "$makefile$makefile_ext") { - print "ok 2\n"; -} else { - print "not ok 2\n"; -} - -# Renamed by make clean -my $makefile_rename = $makefile . ($^O eq 'VMS' ? '.mms' : '.old'); - -my $make = $Config{make}; - -$make = $ENV{MAKE} if exists $ENV{MAKE}; - -if ($^O eq 'MSWin32' && $make eq 'nmake') { $make .= " -nologo"; } - -my @makeout; - -if ($^O eq 'VMS') { $make .= ' all'; } -print "# make = '$make'\n"; -@makeout = `$make`; -if ($?) { - print "not ok 3 # $make failed: $?\n"; - print "# $_" foreach @makeout; - exit($?); -} else { - print "ok 3\n"; -} - -if ($^O eq 'VMS') { $make =~ s{ all}{}; } - -if ($Config{usedl}) { - print "ok 4\n"; -} else { - my $makeperl = "$make perl"; - print "# make = '$makeperl'\n"; - @makeout = `$makeperl`; - if ($?) { - print "not ok 4 # $makeperl failed: $?\n"; - print "# $_" foreach @makeout; - exit($?); + if (defined $expect) { + # No error expected + $test_body .= <<"EOT"; + if (\$error or \$got ne "$expect") { + print "not ok $dummytest # error '\$error', expect '$expect', got '\$got'\n"; } else { - print "ok 4\n"; + print "ok $dummytest\n"; + } } +EOT + } else { + # Error expected. + $test_body .= <<"EOT"; + if (\$error) { + print "ok $dummytest # error='\$error' (as expected)\n"; + } else { + print "not ok $dummytest # expected error, got no error and '\$got'\n"; + } +EOT + } + $dummytest++; + return $test_body . <<'EOT'; } - -my $maketest = "$make test"; -print "# make = '$maketest'\n"; - -@makeout = `$maketest`; - -if (open OUTPUT, "<$output") { - print while ; - close OUTPUT or print "# Close $output failed: $!\n"; -} else { - # Harness will report missing test results at this point. - print "# Open <$output failed: $!\n"; +EOT } -if ($?) { - print "not ok $test # $maketest failed: $?\n"; - print "# $_" foreach @makeout; +# Simple tests to verify bits of the switch generation system work. +sub simple { + start_tests(); + # Deliberately leave $name in @_, so that it is indexed from 1. + my ($name, @items) = @_; + my $test_header; + my $test_body = "my \$value;\n"; + foreach my $counter (1 .. $#_) { + my $thisname = $_[$counter]; + $test_header .= "#define $thisname $counter\n"; + $test_body .= <<"EOT"; +\$value = $thisname; +if (\$value == $counter) { + print "ok $dummytest\n"; } else { - print "ok $test - maketest\n"; + print "not ok $dummytest # $thisname gave \$value\n"; } -$test++; - - -# -x is busted on Win32 < 5.6.1, so we emulate it. -my $regen; -if( $^O eq 'MSWin32' && $] <= 5.006001 ) { - open(REGENTMP, ">regentmp") or die $!; - open(XS, "$package.xs") or die $!; - my $saw_shebang; - while() { - $saw_shebang++ if /^#!.*/i ; - print REGENTMP $_ if $saw_shebang; +EOT + ++$dummytest; + # Yes, the last time round the loop appends a z to the string. + for my $i (0 .. length $thisname) { + my $copyname = $thisname; + substr ($copyname, $i, 1) = 'z'; + $test_body .= explict_call_constant ($copyname, + $copyname eq $thisname + ? $thisname : undef); } - close XS; close REGENTMP; - $regen = `$runperl regentmp`; - unlink 'regentmp'; -} -else { - $regen = `$runperl -x $package.xs`; -} -if ($?) { - print "not ok $test # $runperl -x $package.xs failed: $?\n"; -} else { - print "ok $test - regen\n"; -} -$test++; - -my $expect = $constant_types . $C_constant . - "\n#### XS Section:\n" . $XS_constant; - -if ($expect eq $regen) { - print "ok $test - regen worked\n"; -} else { - print "not ok $test - regen worked\n"; - # open FOO, ">expect"; print FOO $expect; - # open FOO, ">regen"; print FOO $regen; close FOO; -} -$test++; - -my $makeclean = "$make clean"; -print "# make = '$makeclean'\n"; -@makeout = `$makeclean`; -if ($?) { - print "not ok $test # $make failed: $?\n"; - print "# $_" foreach @makeout; -} else { - print "ok $test\n"; -} -$test++; - -sub check_for_bonus_files { - my $dir = shift; - my %expect = map {($^O eq 'VMS' ? lc($_) : $_), 1} @_; - - my $fail; - opendir DIR, $dir or die "opendir '$dir': $!"; - while (defined (my $entry = readdir DIR)) { - $entry =~ s/\.$// if $^O eq 'VMS'; # delete trailing dot that indicates no extension - next if $expect{$entry}; - print "# Extra file '$entry'\n"; - $fail = 1; } - - closedir DIR or warn "closedir '.': $!"; - if ($fail) { - print "not ok $test\n"; - } else { - print "ok $test\n"; - } - $test++; + # Ho. This seems to be buggy in 5.005_03: + # # Now remove $name from @_: + # shift @_; + end_tests($name, \@items, \@items, $test_header, $test_body); } -check_for_bonus_files ('.', @files, $output, $makefile_rename, '.', '..'); - -rename $makefile_rename, $makefile - or die "Can't rename '$makefile_rename' to '$makefile': $!"; - -unlink $output or warn "Can't unlink '$output': $!"; +# Check that the memeq clauses work correctly when there isn't a switch +# statement to bump off a character +simple ("Singletons", "A", "AB", "ABC", "ABCD", "ABCDE"); +# Check the three code. +simple ("Three start", qw(Bea kea Lea lea nea pea rea sea tea Wea yea Zea)); +# There were 162 2 letter words in /usr/share/dict/words on FreeBSD 4.6, which +# I felt was rather too many. So I used words with 2 vowels. +simple ("Twos and three middle", qw(aa ae ai ea eu ie io oe era eta)); +# Given the choice go for the end, else the earliest point +simple ("Three end and four symetry", qw(ean ear eat barb marm tart)); -# Need to make distclean to remove ../../lib/ExtTest.pm -my $makedistclean = "$make distclean"; -print "# make = '$makedistclean'\n"; -@makeout = `$makedistclean`; -if ($?) { - print "not ok $test # $make failed: $?\n"; - print "# $_" foreach @makeout; -} else { - print "ok $test\n"; -} -$test++; - -check_for_bonus_files ('.', @files, '.', '..'); -unless ($keep_files) { - foreach (@files) { - unlink $_ or warn "unlink $_: $!"; - } -} +# Need this if the single test below is rolled into @tests : +# --$dummytest; +print "1..$dummytest\n"; -check_for_bonus_files ('.', '.', '..'); +write_and_run_extension @$_ foreach @tests; # This was causing an assertion failure (a Cion) +# Any single byte > 128 should do it. C_constant ($package, undef, undef, undef, undef, undef, chr 255); +print "ok $realtest\n"; $realtest++; -print "ok $test\n"; $test++; +print STDERR "# You were running with \$keep_files set to $keep_files\n" + if $keep_files;