ExtUtils::Constant 0.14
Nicholas Clark [Sun, 25 Aug 2002 18:06:00 +0000 (19:06 +0100)]
Message-ID: <20020825170600.GE322@Bagpuss.unfortu.net>

p4raw-id: //depot/perl@17801

lib/ExtUtils/Constant.pm
lib/ExtUtils/t/Constant.t

index 0772ee8..9730d91 100644 (file)
@@ -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<name>. If I<CHECKED_AT> is defined, then it
 is used to avoid C<memEQ> for short names, or to generate a comment to
 highlight the position of the character in the C<switch> statement.
 
+If I<CHECKED_AT> 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<NAME>).
+
 =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";
   }
index 6356ab4..4e5819d 100644 (file)
@@ -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 <OUTPUT>;
+    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(<XS>) {
+      $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 <OUTPUT>;
-  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(<XS>) {
-        $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 C<confess>ion)
+# 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;