devdaily home | apple | java | perl | unix | directory | blog

What this is

This file is included in the DevDaily.com "Perl Source Code Warehouse" project. The intent of this project is to help you "Learn Perl by Example" TM.

Other links

The source code

#!./perl -w

BEGIN {
    chdir 't' if -d 't';
    @INC = '../lib';
    require './test.pl';
}

plan tests => 5852;

use strict;
use warnings;
use Config;

my $Is_EBCDIC = (defined $Config{ebcdic} && $Config{ebcdic} eq 'define');
my $Perl = which_perl();

sub encode_list {
  my @result = map {_qq($_)} @_;
  if (@result == 1) {
    return @result;
  }
  return '(' . join (', ', @result) . ')';
}


sub list_eq ($$) {
  my ($l, $r) = @_;
  return 0 unless @$l == @$r;
  for my $i (0..$#$l) {
    if (defined $l->[$i]) {
      return 0 unless defined ($r->[$i]) && $l->[$i] eq $r->[$i];
    } else {
      return 0 if defined $r->[$i]
    }
  }
  return 1;
}

##############################################################################
#
# Here starteth the tests
#

{
    my $format = "c2 x5 C C x s d i l a6";
    # Need the expression in here to force ary[5] to be numeric.  This avoids
    # test2 failing because ary2 goes str->numeric->str and ary doesn't.
    my @ary = (1,-100,127,128,32767,987.654321098 / 100.0,12345,123456,
               "abcdef");
    my $foo = pack($format,@ary);
    my @ary2 = unpack($format,$foo);

    is($#ary, $#ary2);

    my $out1=join(':',@ary);
    my $out2=join(':',@ary2);
    # Using long double NVs may introduce greater accuracy than wanted.
    $out1 =~ s/:9\.87654321097999\d*:/:9.87654321098:/;
    $out2 =~ s/:9\.87654321097999\d*:/:9.87654321098:/;
    is($out1, $out2);

    like($foo, qr/def/);
}
# How about counting bits?

{
    my $x;
    is( ($x = unpack("%32B*", "\001\002\004\010\020\040\100\200\377")), 16 );

    is( ($x = unpack("%32b69", "\001\002\004\010\020\040\100\200\017")), 12 );

    is( ($x = unpack("%32B69", "\001\002\004\010\020\040\100\200\017")), 9 );
}

{
    my $sum = 129; # ASCII
    $sum = 103 if $Is_EBCDIC;

    my $x;
    is( ($x = unpack("%32B*", "Now is the time for all good blurfl")), $sum );

    my $foo;
    open(BIN, $Perl) || die "Can't open $Perl: $!\n";
    sysread BIN, $foo, 8192;
    close BIN;

    $sum = unpack("%32b*", $foo);
    my $longway = unpack("b*", $foo);
    is( $sum, $longway =~ tr/1/1/ );
}

{
  my $x;
  is( ($x = unpack("I",pack("I", 0xFFFFFFFF))), 0xFFFFFFFF );
}

{
    # check 'w'
    my @x = (5,130,256,560,32000,3097152,268435455,1073741844, 2**33,
             '4503599627365785','23728385234614992549757750638446');
    my $x = pack('w*', @x);
    my $y = pack 'H*', '0581028200843081fa0081bd8440ffffff7f8480808014A0808'.
                       '0800087ffffffffffdb19caefe8e1eeeea0c2e1e3e8ede1ee6e';

    is($x, $y);

    my @y = unpack('w*', $y);
    my $a;
    while ($a = pop @x) {
        my $b = pop @y;
        is($a, $b);
    }

    @y = unpack('w2', $x);

    is(scalar(@y), 2);
    is($y[1], 130);
    $x = pack('w*', 5000000000); $y = '';
    eval {
    use Math::BigInt;
    $y = pack('w*', Math::BigInt::->new(5000000000));
    };
    is($x, $y);

    $x = pack 'w', ~0;
    $y = pack 'w', (~0).'';
    is($x, $y);
    is(unpack ('w',$x), ~0);
    is(unpack ('w',$y), ~0);

    $x = pack 'w', ~0 - 1;
    $y = pack 'w', (~0) - 2;

    if (~0 - 1 == (~0) - 2) {
        is($x, $y, "NV arithmetic");
    } else {
        isnt($x, $y, "IV/NV arithmetic");
    }
    cmp_ok(unpack ('w',$x), '==', ~0 - 1);
    cmp_ok(unpack ('w',$y), '==', ~0 - 2);

    # These should spot that pack 'w' is using NV, not double, on platforms
    # where IVs are smaller than doubles, and harmlessly pass elsewhere.
    # (tests for change 16861)
    my $x0 = 2**54+3;
    my $y0 = 2**54-2;

    $x = pack 'w', $x0;
    $y = pack 'w', $y0;

    if ($x0 == $y0) {
        is($x, $y, "NV arithmetic");
    } else {
        isnt($x, $y, "IV/NV arithmetic");
    }
    cmp_ok(unpack ('w',$x), '==', $x0);
    cmp_ok(unpack ('w',$y), '==', $y0);
}


{
  print "# test exceptions\n";
  my $x;
  eval { $x = unpack 'w', pack 'C*', 0xff, 0xff};
  like($@, qr/^Unterminated compressed integer/);

  eval { $x = unpack 'w', pack 'C*', 0xff, 0xff, 0xff, 0xff};
  like($@, qr/^Unterminated compressed integer/);

  eval { $x = unpack 'w', pack 'C*', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
  like($@, qr/^Unterminated compressed integer/);

  eval { $x = pack 'w', -1 };
  like ($@, qr/^Cannot compress negative numbers/);

  eval { $x = pack 'w', '1'x(1 + length ~0) . 'e0' };
  like ($@, qr/^Can only compress unsigned integers/);

 SKIP: {
    # Is this a stupid thing to do on VMS, VOS and other unusual platforms?

    skip("-- the IEEE infinity model is unavailable in this configuration.", 1)
       if (($^O eq 'VMS') && !defined($Config{useieee}));

    skip("-- $^O has serious fp indigestion on w-packed infinities", 1)
       if (
	   ($^O eq 'mpeix')
	   ||
	   ($^O eq 'ultrix')
	   ||
	   ($^O =~ /^svr4/ && -f "/etc/issue" && -f "/etc/.relid") # NCR MP-RAS
	   );

    my $inf = eval '2**10000';

    skip("Couldn't generate infinity - got error '$@'", 1)
      unless defined $inf and $inf == $inf / 2 and $inf + 1 == $inf;

    local our $TODO;
    $TODO = "VOS needs a fix for posix-1022 to pass this test."
      if ($^O eq 'vos');

    eval { $x = pack 'w', $inf };
    like ($@, qr/^Cannot compress integer/, "Cannot compress integer");
  }

 SKIP: {

    skip("-- the full range of an IEEE double may not be available in this configuration.", 3)
       if (($^O eq 'VMS') && !defined($Config{useieee}));

    skip("-- $^O does not like 2**1023", 3)
       if (($^O eq 'ultrix'));

    # This should be about the biggest thing possible on an IEEE double
    my $big = eval '2**1023';

    skip("Couldn't generate 2**1023 - got error '$@'", 3)
      unless defined $big and $big != $big / 2;

    eval { $x = pack 'w', $big };
    is ($@, '', "Should be able to pack 'w', $big # 2**1023");

    my $y = eval {unpack 'w', $x};
    is ($@, '',
	"Should be able to unpack 'w' the result of pack 'w', $big # 2**1023");

    # I'm getting about 1e-16 on FreeBSD
    my $quotient = int (100 * ($y - $big) / $big);
    ok($quotient < 2 && $quotient > -2,
       "Round trip pack, unpack 'w' of $big is withing 1% ($quotient%)");
  }

}

print "# test the 'p' template\n";

# literals
is(unpack("p",pack("p","foo")), "foo");

# scalars
is(unpack("p",pack("p",239)), 239);

# temps
sub foo { my $a = "a"; return $a . $a++ . $a++ }
{
  use warnings;
  my $warning;
  local $SIG{__WARN__} = sub {
      $warning = $_[0];
  };
  my $junk = pack("p", &foo);

  like($warning, qr/temporary val/);
}

# undef should give null pointer
like(pack("p", undef), qr/^\0+/);

# Check for optimizer bug (e.g.  Digital Unix GEM cc with -O4 on DU V4.0B gives
#                                4294967295 instead of -1)
#				 see #ifdef __osf__ in pp.c pp_unpack
is((unpack("i",pack("i",-1))), -1);

print "# test the pack lengths of s S i I l L n N v V\n";

my @lengths = qw(s 2 S 2 i -4 I -4 l 4 L 4 n 2 N 4 v 2 V 4);
while (my ($format, $expect) = splice @lengths, 0, 2) {
  my $len = length(pack($format, 0));
  if ($expect > 0) {
    is($expect, $len, "format '$format'");
  } else {
    $expect = -$expect;
    ok ($len >= $expect, "format '$format'") ||
      print "# format '$format' has length $len, expected >= $expect\n";
  }
}


print "# test unpack-pack lengths\n";

my @templates = qw(c C i I s S l L n N v V f d q Q);

foreach my $t (@templates) {
    SKIP: {
        my @t = eval { unpack("$t*", pack("$t*", 12, 34)) };

        # quads not supported everywhere
        skip "Quads not supported", 4 if $@ =~ /Invalid type/;
        is( $@, '' );

        is(scalar @t, 2);

        SKIP: {
            skip "$t not expected to work for some reason", 2 if $t =~ /[nv]/i;

            is($t[0], 12);
            is($t[1], 34);
        }
    }
}

{
    # uuencode/decode

    # Note that first uuencoding known 'text' data and then checking the
    # binary values of the uuencoded version would not be portable between
    # character sets.  Uuencoding is meant for encoding binary data, not
    # text data.

    my $in = pack 'C*', 0 .. 255;

    # just to be anal, we do some random tr/`/ /
    my $uu = <<'EOUU';
M` $"`P0%!@<("0H+# T.#Q`1$A,4%187&!D:&QP='A\@(2(C)"4F)R@I*BLL
M+2XO,#$R,S0U-C'EZ>WQ]?G^`@8*#A(6&
MAXB)BHN,C8Z/D)&2DY25EI>8F9J;G)V>GZ"AHJ.DI::GJ*FJJZRMKJ^PL;*S
MM+6VM[BYNKN\O;Z_P,'"P\3%QL?(R+CY.7FY^CIZNOL[>[O\/'R\_3U]O?X^?K[_/W^_P `
EOUU

    $_ = $uu;
    tr/ /`/;

    is(pack('u', $in), $_);

    is(unpack('u', $uu), $in);

    $in = "\x1f\x8b\x08\x08\x58\xdc\xc4\x35\x02\x03\x4a\x41\x50\x55\x00\xf3\x2a\x2d\x2e\x51\x48\xcc\xcb\x2f\xc9\x48\x2d\x52\x08\x48\x2d\xca\x51\x28\x2d\x4d\xce\x4f\x49\x2d\xe2\x02\x00\x64\x66\x60\x5c\x1a\x00\x00\x00";
    $uu = <<'EOUU';
M'XL("%C("`&1F
&8%P:````
EOUU

    is(unpack('u', $uu), $in);

# This is identical to the above except that backquotes have been
# changed to spaces

    $uu = <<'EOUU';
M'XL("%C(" &1F
&8%P:
EOUU

    # ' # Grr
    is(unpack('u', $uu), $in);

}

# test the ascii template types (A, a, Z)

foreach (
['p', 'A*',  "foo\0bar\0 ", "foo\0bar\0 "],
['p', 'A11', "foo\0bar\0 ", "foo\0bar\0   "],
['u', 'A*',  "foo\0bar \0", "foo\0bar"],
['u', 'A8',  "foo\0bar \0", "foo\0bar"],
['p', 'a*',  "foo\0bar\0 ", "foo\0bar\0 "],
['p', 'a11', "foo\0bar\0 ", "foo\0bar\0 \0\0"],
['u', 'a*',  "foo\0bar \0", "foo\0bar \0"],
['u', 'a8',  "foo\0bar \0", "foo\0bar "],
['p', 'Z*',  "foo\0bar\0 ", "foo\0bar\0 \0"],
['p', 'Z11', "foo\0bar\0 ", "foo\0bar\0 \0\0"],
['p', 'Z3',  "foo",         "fo\0"],
['u', 'Z*',  "foo\0bar \0", "foo"],
['u', 'Z8',  "foo\0bar \0", "foo"],
) 
{
    my ($what, $template, $in, $out) = @$_;
    my $got = $what eq 'u' ? (unpack $template, $in) : (pack $template, $in);
    unless (is($got, $out)) {
        my $un = $what eq 'u' ? 'un' : '';
        print "# ${un}pack ('$template', "._qq($in).') gave '._qq($out).
            ' not '._qq($got)."\n";
    }
}

print "# packing native shorts/ints/longs\n";

is(length(pack("s!", 0)), $Config{shortsize});
is(length(pack("i!", 0)), $Config{intsize});
is(length(pack("l!", 0)), $Config{longsize});
ok(length(pack("s!", 0)) <= length(pack("i!", 0)));
ok(length(pack("i!", 0)) <= length(pack("l!", 0)));
is(length(pack("i!", 0)), length(pack("i", 0)));

sub numbers {
  my $format = shift;
  return numbers_with_total ($format, undef, @_);
}

sub numbers_with_total {
  my $format = shift;
  my $total = shift;
  if (!defined $total) {
    foreach (@_) {
      $total += $_;
    }
  }
  print "# numbers test for $format\n";
  foreach (@_) {
    SKIP: {
        my $out = eval {unpack($format, pack($format, $_))};
        skip "cannot pack '$format' on this perl", 2 if
          $@ =~ /Invalid type '$format'/;

        is($@, '');
        is($out, $_);
    }
  }

  my $skip_if_longer_than = ~0; # "Infinity"
  if (~0 - 1 == ~0) {
    # If we're running with -DNO_PERLPRESERVE_IVUV and NVs don't preserve all
    # UVs (in which case ~0 is NV, ~0-1 will be the same NV) then we can't
    # correctly in perl calculate UV totals for long checksums, as pp_unpack
    # is using UV maths, and we've only got NVs.
    $skip_if_longer_than = $Config{nv_preserves_uv_bits};
  }

  foreach ('', 1, 2, 3, 15, 16, 17, 31, 32, 33, 53, 54, 63, 64, 65) {
    SKIP: {
      my $sum = eval {unpack "%$_$format*", pack "$format*", @_};
      skip "cannot pack '$format' on this perl", 3
        if $@ =~ /Invalid type '$format'/;

      is($@, '');
      ok(defined $sum);

      my $len = $_; # Copy, so that we can reassign ''
      $len = 16 unless length $len;

      SKIP: {
        skip "cannot test checksums over $skip_if_longer_than bits", 1
          if $len > $skip_if_longer_than;

        # Our problem with testing this portably is that the checksum code in
        # pp_unpack is able to cast signed to unsigned, and do modulo 2**n
        # arithmetic in unsigned ints, which perl has no operators to do.
        # (use integer; does signed ints, which won't wrap on UTS, which is just
        # fine with ANSI, but not with most people's assumptions.
        # This is why we need to supply the totals for 'Q' as there's no way in
        # perl to calculate them, short of unpack '%0Q' (is that documented?)
        # ** returns NVs; make sure it's IV.
        my $max = 1 + 2 * (int (2 ** ($len-1))-1); # The max possible checksum
        my $max_p1 = $max + 1;
        my ($max_is_integer, $max_p1_is_integer);
        $max_p1_is_integer = 1 unless $max_p1 + 1 == $max_p1;
        $max_is_integer = 1 if $max - 1 < ~0;

        my $calc_sum;
        if (ref $total) {
            $calc_sum = &$total($len);
        } else {
            $calc_sum = $total;
            # Shift into range by some multiple of the total
            my $mult = $max_p1 ? int ($total / $max_p1) : undef;
            # Need this to make sure that -1 + (~0+1) is ~0 (ie still integer)
            $calc_sum = $total - $mult;
            $calc_sum -= $mult * $max;
            if ($calc_sum < 0) {
                $calc_sum += 1;
                $calc_sum += $max;
            }
        }
        if ($calc_sum == $calc_sum - 1 && $calc_sum == $max_p1) {
            # we're into floating point (either by getting out of the range of
            # UV arithmetic, or because we're doing a floating point checksum) 
            # and our calculation of the checksum has become rounded up to
            # max_checksum + 1
            $calc_sum = 0;
        }

        if ($calc_sum == $sum) { # HAS to be ==, not eq (so no is()).
            ok ("unpack '%$_$format' gave $sum");
        } else {
            my $delta = 1.000001;
            if ($format =~ tr /dDfF//
                && ($calc_sum <= $sum * $delta && $calc_sum >= $sum / $delta)) {
                pass ("unpack '%$_$format' gave $sum, expected $calc_sum");
            } else {
                my $text = ref $total ? &$total($len) : $total;
                fail;
                print "# For list (" . join (", ", @_) . ") (total $text)"
                    . " packed with $format unpack '%$_$format' gave $sum,"
                    . " expected $calc_sum\n";
            }
        }
      }
    }   
  }
}

numbers ('c', -128, -1, 0, 1, 127);
numbers ('C', 0, 1, 127, 128, 255);
numbers ('s', -32768, -1, 0, 1, 32767);
numbers ('S', 0, 1, 32767, 32768, 65535);
numbers ('i', -2147483648, -1, 0, 1, 2147483647);
numbers ('I', 0, 1, 2147483647, 2147483648, 4294967295);
numbers ('l', -2147483648, -1, 0, 1, 2147483647);
numbers ('L', 0, 1, 2147483647, 2147483648, 4294967295);
numbers ('s!', -32768, -1, 0, 1, 32767);
numbers ('S!', 0, 1, 32767, 32768, 65535);
numbers ('i!', -2147483648, -1, 0, 1, 2147483647);
numbers ('I!', 0, 1, 2147483647, 2147483648, 4294967295);
numbers ('l!', -2147483648, -1, 0, 1, 2147483647);
numbers ('L!', 0, 1, 2147483647, 2147483648, 4294967295);
numbers ('n', 0, 1, 32767, 32768, 65535);
numbers ('v', 0, 1, 32767, 32768, 65535);
numbers ('N', 0, 1, 2147483647, 2147483648, 4294967295);
numbers ('V', 0, 1, 2147483647, 2147483648, 4294967295);
# All these should have exact binary representations:
numbers ('f', -1, 0, 0.5, 42, 2**34);
numbers ('d', -(2**34), -1, 0, 1, 2**34);
## These don't, but 'd' is NV.  XXX wrong, it's double
#numbers ('d', -1, 0, 1, 1-exp(-1), -exp(1));

numbers_with_total ('q', -1,
                    -9223372036854775808, -1, 0, 1,9223372036854775807);
# This total is icky, but the true total is 2**65-1, and need a way to generate
# the epxected checksum on any system including those where NVs can preserve
# 65 bits. (long double is 128 bits on sparc, so they certainly can)
# or where rounding is down not up on binary conversion (crays)
numbers_with_total ('Q', sub {
                      my $len = shift;
                      $len = 65 if $len > 65; # unmasked total is 2**65-1 here
                      my $total = 1 + 2 * (int (2**($len - 1)) - 1);
                      return 0 if $total == $total - 1; # Overflowed integers
                      return $total; # NVs still accurate to nearest integer
                    },
                    0, 1,9223372036854775807, 9223372036854775808,
                    18446744073709551615);

print "# pack nvNV byteorders\n";

is(pack("n", 0xdead), "\xde\xad");
is(pack("v", 0xdead), "\xad\xde");
is(pack("N", 0xdeadbeef), "\xde\xad\xbe\xef");
is(pack("V", 0xdeadbeef), "\xef\xbe\xad\xde");

{
  # /

  my ($x, $y, $z);
  eval { ($x) = unpack '/a*','hello' };
  like($@, qr!'/' must follow a numeric type!);
  undef $x;
  eval { $x = unpack '/a*','hello' };
  like($@, qr!'/' must follow a numeric type!);

  undef $x;
  eval { ($z,$x,$y) = unpack 'a3/A C/a* C/Z', "003ok \003yes\004z\000abc" };
  is($@, '');
  is($z, 'ok');
  is($x, 'yes');
  is($y, 'z');
  undef $z;
  eval { $z = unpack 'a3/A C/a* C/Z', "003ok \003yes\004z\000abc" };
  is($@, '');
  is($z, 'ok');


  undef $x;
  eval { ($x) = pack '/a*','hello' };
  like($@,  qr!Invalid type '/'!);
  undef $x;
  eval { $x = pack '/a*','hello' };
  like($@,  qr!Invalid type '/'!);

  $z = pack 'n/a* N/Z* w/A*','string','hi there ','etc';
  my $expect = "\000\006string\0\0\0\012hi there \000\003etc";
  is($z, $expect);

  undef $x;
  $expect = 'hello world';
  eval { ($x) = unpack ("w/a", chr (11) . "hello world!")};
  is($x, $expect);
  is($@, '');

  undef $x;
  # Doing this in scalar context used to fail.
  eval { $x = unpack ("w/a", chr (11) . "hello world!")};
  is($@, '');
  is($x, $expect);

  foreach (
           ['a/a*/a*', '212ab345678901234567','ab3456789012'],
           ['a/a*/a*', '3012ab345678901234567', 'ab3456789012'],
           ['a/a*/b*', '212ab', $Is_EBCDIC ? '100000010100' : '100001100100'],
  ) 
  {
    my ($pat, $in, $expect) = @$_;
    undef $x;
    eval { ($x) = unpack $pat, $in };
    is($@, '');
    is($x, $expect) || 
      printf "# list unpack ('$pat', '$in') gave %s, expected '$expect'\n",
             encode_list ($x);

    undef $x;
    eval { $x = unpack $pat, $in };
    is($@, '');
    is($x, $expect) ||
      printf "# scalar unpack ('$pat', '$in') gave %s, expected '$expect'\n",
             encode_list ($x);
  }

  # / with #

  my $pattern = <<'EOU';
 a3/A			# Count in ASCII
 C/a*			# Count in a C char
 C/Z			# Count in a C char but skip after \0
EOU

  $x = $y = $z =undef;
  eval { ($z,$x,$y) = unpack $pattern, "003ok \003yes\004z\000abc" };
  is($@, '');
  is($z, 'ok');
  is($x, 'yes');
  is($y, 'z');
  undef $x;
  eval { $z = unpack $pattern, "003ok \003yes\004z\000abc" };
  is($@, '');
  is($z, 'ok');

  $pattern = <<'EOP';
  n/a*			# Count as network short
  w/A*			# Count a  BER integer
EOP
  $expect = "\000\006string\003etc";
  $z = pack $pattern,'string','etc';
  is($z, $expect);
}


SKIP: {
    skip("(EBCDIC and) version strings are bad idea", 2) if $Is_EBCDIC;

    is("1.20.300.4000", sprintf "%vd", pack("U*",1,20,300,4000));
    is("1.20.300.4000", sprintf "%vd", pack("  U*",1,20,300,4000));
}
isnt(v1.20.300.4000, sprintf "%vd", pack("C0U*",1,20,300,4000));

my $rslt = $Is_EBCDIC ? "156 67" : "199 162";
is(join(" ", unpack("C*", chr(0x1e2))), $rslt);

# does pack U create Unicode?
is(ord(pack('U', 300)), 300);

# does unpack U deref Unicode?
is((unpack('U', chr(300)))[0], 300);

# is unpack U the reverse of pack U for Unicode string?
is("@{[unpack('U*', pack('U*', 100, 200, 300))]}", "100 200 300");

# is unpack U the reverse of pack U for byte string?
is("@{[unpack('U*', pack('U*', 100, 200))]}", "100 200");


SKIP: {
    skip "Not for EBCDIC", 4 if $Is_EBCDIC;

    # does unpack C unravel pack U?
    is("@{[unpack('C*', pack('U*', 100, 200))]}", "100 195 136");

    # does pack U0C create Unicode?
    is("@{[pack('U0C*', 100, 195, 136)]}", v100.v200);

    # does pack C0U create characters?
    is("@{[pack('C0U*', 100, 200)]}", pack("C*", 100, 195, 136));

    # does unpack U0U on byte data warn?
    {
        local $SIG{__WARN__} = sub { $@ = "@_" };
        my @null = unpack('U0U', chr(255));
        like($@, /^Malformed UTF-8 character /);
    }
}

{
  my $p = pack 'i*', -2147483648, ~0, 0, 1, 2147483647;
  my (@a);
  # bug - % had to be at the start of the pattern, no leading whitespace or
  # comments. %i! didn't work at all.
  foreach my $pat ('%32i*', ' %32i*', "# Muhahahaha\n%32i*", '%32i*  ',
                   '%32i!*', ' %32i!*', "\n#\n#\n\r \t\f%32i!*", '%32i!*#') {
    @a = unpack $pat, $p;
    is($a[0], 0xFFFFFFFF) || print "# $pat\n";
    @a = scalar unpack $pat, $p;
    is($a[0], 0xFFFFFFFF) || print "# $pat\n";
  }


  $p = pack 'I*', 42, 12;
  # Multiline patterns in scalar context failed.
  foreach my $pat ('I', < 32 bits with floating
  # point, so a pathologically long pattern would wrap at 32 bits.
  my $pat = "\xff\xff"x65538; # Start with it long, to save any copying.
  foreach (4,3,2,1,0) {
    my $len = 65534 + $_;
    is(unpack ("%33n$len", $pat), 65535 * $len);
  }
}


# pack x X @
foreach (
         ['x', "N", "\0"],
         ['x4', "N", "\0"x4],
         ['xX', "N", ""],
         ['xXa*', "Nick", "Nick"],
         ['a5Xa5', "cameL", "llama", "camellama"],
         ['@4', 'N', "\0"x4],
         ['a*@8a*', 'Camel', 'Dromedary', "Camel\0\0\0Dromedary"],
         ['a*@4a', 'Perl rules', '!', 'Perl!'],
) 
{
  my ($template, @in) = @$_;
  my $out = pop @in;
  my $got = eval {pack $template, @in};
  is($@, '');
  is($out, $got) ||
    printf "# pack ('$template', %s) gave %s expected %s\n",
           encode_list (@in), encode_list ($got), encode_list ($out);
}

# unpack x X @
foreach (
         ['x', "N"],
         ['xX', "N"],
         ['xXa*', "Nick", "Nick"],
         ['a5Xa5', "camellama", "camel", "llama"],
         ['@3', "ice"],
         ['@2a2', "water", "te"],
         ['a*@1a3', "steam", "steam", "tea"],
) 
{
  my ($template, $in, @out) = @$_;
  my @got = eval {unpack $template, $in};
  is($@, '');
  ok (list_eq (\@got, \@out)) ||
    printf "# list unpack ('$template', %s) gave %s expected %s\n",
           _qq($in), encode_list (@got), encode_list (@out);

  my $got = eval {unpack $template, $in};
  is($@, '');
  @out ? is( $got, $out[0] ) # 1 or more items; should get first
       : ok( !defined $got ) # 0 items; should get undef
    or printf "# scalar unpack ('$template', %s) gave %s expected %s\n",
              _qq($in), encode_list ($got), encode_list ($out[0]);
}

{
    my $t = 'Z*Z*';
    my ($u, $v) = qw(foo xyzzy);
    my $p = pack($t, $u, $v);
    my @u = unpack($t, $p);
    is(scalar @u, 2);
    is($u[0], $u);
    is($u[1], $v);
}

{
    is((unpack("w/a*", "\x02abc"))[0], "ab");

    # "w/a*" should be seen as one unit

    is(scalar unpack("w/a*", "\x02abc"), "ab");
}

{
    # from Wolfgang Laun: fix in change #13163

    my $s = 'ABC' x 10;
    my $t = '*';
    my $x = ord($t);
    my $buf = pack( 'Z*/A* C',  $s, $x );
    my $y;

    my $h = $buf;
    $h =~ s/[^[:print:]]/./g;
    ( $s, $y ) = unpack( "Z*/A* C", $buf );
    is($h, "30.ABCABCABCABCABCABCABCABCABCABC$t");
    is(length $buf, 34);
    is($s, "ABCABCABCABCABCABCABCABCABCABC");
    is($y, $x);
}

{
    # from Wolfgang Laun: fix in change #13288

    eval { my $t=unpack("P*", "abc") };
    like($@, qr/'P' must have an explicit size/);
}

{   # Grouping constructs
    my (@a, @b);
    @a = unpack '(SL)',   pack 'SLSLSL', 67..90;
    is("@a", "67 68");
    @a = unpack '(SL)3',   pack 'SLSLSL', 67..90;
    @b = (67..72);
    is("@a", "@b");
    @a = unpack '(SL)3',   pack 'SLSLSLSL', 67..90;
    is("@a", "@b");
    @a = unpack '(SL)[3]', pack 'SLSLSLSL', 67..90;
    is("@a", "@b");
    @a = unpack '(SL)[2] SL', pack 'SLSLSLSL', 67..90;
    is("@a", "@b");
    @a = unpack 'A/(SL)',  pack 'ASLSLSLSL', 3, 67..90;
    is("@a", "@b");
    @a = unpack 'A/(SL)SL',  pack 'ASLSLSLSL', 2, 67..90;
    is("@a", "@b");
    @a = unpack '(SL)*',   pack 'SLSLSLSL', 67..90;
    @b = (67..74);
    is("@a", "@b");
    @a = unpack '(SL)*SL',   pack 'SLSLSLSL', 67..90;
    is("@a", "@b");
    eval { @a = unpack '(*SL)',   '' };
    like($@, qr/\(\)-group starts with a count/);
    eval { @a = unpack '(3SL)',   '' };
    like($@, qr/\(\)-group starts with a count/);
    eval { @a = unpack '([3]SL)',   '' };
    like($@, qr/\(\)-group starts with a count/);
    eval { @a = pack '(*SL)' };
    like($@, qr/\(\)-group starts with a count/);
    @a = unpack '(SL)3 SL',   pack '(SL)4', 67..74;
    is("@a", "@b");
    @a = unpack '(SL)3 SL',   pack '(SL)[4]', 67..74;
    is("@a", "@b");
    @a = unpack '(SL)3 SL',   pack '(SL)*', 67..74;
    is("@a", "@b");
}

{  # more on grouping (W.Laun)
  use warnings;
  my $warning;
  local $SIG{__WARN__} = sub {
      $warning = $_[0];
  };
  # @ absolute within ()-group
  my $badc = pack( '(a)*', unpack( '(@1a @0a @2)*', 'abcd' ) );
  is( $badc, 'badc' );
  my @b = ( 1, 2, 3 );
  my $buf = pack( '(@1c)((@2C)@3c)', @b );
  is( $buf, "\0\1\0\0\2\3" );
  my @a = unpack( '(@1c)((@2c)@3c)', $buf );
  is( "@a", "@b" );

  # various unpack count/code scenarios 
  my @Env = ( a => 'AAA', b => 'BBB' );
  my $env = pack( 'S(S/A*S/A*)*', @Env/2, @Env );

  # unpack full length - ok
  my @pup = unpack( 'S/(S/A* S/A*)', $env );
  is( "@pup", "@Env" );

  # warn when count/code goes beyond end of string
  # \0002 \0001 a \0003 AAA \0001 b \0003 BBB
  #     2     4 5     7  10    1213
  eval { @pup = unpack( 'S/(S/A* S/A*)', substr( $env, 0, 13 ) ) };
  like( $@, qr{length/code after end of string} );
  
  # postfix repeat count
  $env = pack( '(S/A* S/A*)' . @Env/2, @Env );

  # warn when count/code goes beyond end of string
  # \0001 a \0003 AAA \0001  b \0003 BBB
  #     2 3c    5   8    10 11    13  16
  eval { @pup = unpack( '(S/A* S/A*)' . @Env/2, substr( $env, 0, 11 ) ) };
  like( $@, qr{length/code after end of string} );

  # catch stack overflow/segfault
  eval { $_ = pack( ('(' x 105) . 'A' . (')' x 105) ); };
  like( $@, qr{Too deeply nested \(\)-groups} );
}

{ # syntax checks (W.Laun)
  use warnings;
  my @warning;
  local $SIG{__WARN__} = sub {
      push( @warning, $_[0] );
  };
  eval { my $s = pack( 'Ax![4c]A', 1..5 ); };
  like( $@, qr{Malformed integer in \[\]} );

  eval { my $buf = pack( '(c/*a*)', 'AAA', 'BB' ); };
  like( $@, qr{'/' does not take a repeat count} );

  eval { my @inf = unpack( 'c/1a', "\x03AAA\x02BB" ); };
  like( $@, qr{'/' does not take a repeat count} );

  eval { my @inf = unpack( 'c/*a', "\x03AAA\x02BB" ); };
  like( $@, qr{'/' does not take a repeat count} );

  # white space where possible 
  my @Env = ( a => 'AAA', b => 'BBB' );
  my $env = pack( ' S ( S / A*   S / A* )* ', @Env/2, @Env );
  my @pup = unpack( ' S / ( S / A*   S / A* ) ', $env );
  is( "@pup", "@Env" );

  # white space in 4 wrong places
  for my $temp (  'A ![4]', 'A [4]', 'A *', 'A 4' ){
      eval { my $s = pack( $temp, 'B' ); };
      like( $@, qr{Invalid type } );
  }

  # warning for commas
  @warning = ();
  my $x = pack( 'I,A', 4, 'X' );
  like( $warning[0], qr{Invalid type ','} );

  # comma warning only once
  @warning = ();
  $x = pack( 'C(C,C)C,C', 65..71  );
  like( scalar @warning, 1 );

  # forbidden code in []
  eval { my $x = pack( 'A[@4]', 'XXXX' ); };
  like( $@, qr{Within \[\]-length '\@' not allowed} );

  # @ repeat default 1
  my $s = pack( 'AA@A', 'A', 'B', 'C' );
  my @c = unpack( 'AA@A', $s );
  is( $s, 'AC' ); 
  is( "@c", "A C C" ); 

  # no unpack code after /
  eval { my @a = unpack( "C/", "\3" ); };
  like( $@, qr{Code missing after '/'} );

}

{  # Repeat count [SUBEXPR]
   my @codes = qw( x A Z a c C B b H h s v n S i I l V N L p P f F d
		   s! S! i! I! l! L! j J);
   my $G;
   if (eval { pack 'q', 1 } ) {
     push @codes, qw(q Q);
   } else {
     push @codes, qw(c C);	# Keep the count the same
   }
   if (eval { pack 'D', 1 } ) {
     push @codes, 'D';
   } else {
     push @codes, 'd';	# Keep the count the same
   }

   my %val;
   @val{@codes} = map { / [Xx]  (?{ undef })
			| [AZa] (?{ 'something' })
			| C     (?{ 214 })
			| c     (?{ 114 })
			| [Bb]  (?{ '101' })
			| [Hh]  (?{ 'b8' })
			| [svnSiIlVNLqQjJ]  (?{ 10111 })
			| [FfDd]  (?{ 1.36514538e67 })
			| [pP]  (?{ "try this buffer" })
			/x; $^R } @codes;
   my @end = (0x12345678, 0x23456781, 0x35465768, 0x15263748);
   my $end = "N4";

   for my $type (@codes) {
     my @list = $val{$type};
     @list = () unless defined $list[0];
     for my $count ('', '3', '[11]') {
       my $c = 1;
       $c = $1 if $count =~ /(\d+)/;
       my @list1 = @list;
       @list1 = (@list1) x $c unless $type =~ /[XxAaZBbHhP]/;
       for my $groupend ('', ')2', ')[8]') {
	   my $groupbegin = ($groupend ? '(' : '');
	   $c = 1;
	   $c = $1 if $groupend =~ /(\d+)/;
	   my @list2 = (@list1) x $c;

	   my $junk1 = "$groupbegin $type$count $groupend";
	   # print "# junk1=$junk1\n";
	   my $p = pack $junk1, @list2;
	   my $half = int( (length $p)/2 );
	   for my $move ('', "X$half", "X!$half", 'x1', 'x!8', "x$half") {
	     my $junk = "$junk1 $move";
	     # print "# junk='$junk', list=(@list2)\n";
	     $p = pack "$junk $end", @list2, @end;
	     my @l = unpack "x[$junk] $end", $p;
	     is(scalar @l, scalar @end);
	     is("@l", "@end", "skipping x[$junk]");
	   }
       }
     }
   }
}

# / is recognized after spaces in scalar context
# XXXX no spaces are allowed in pack...  In pack only before the slash...
is(scalar unpack('A /A Z20', pack 'A/A* Z20', 'bcde', 'xxxxx'), 'bcde');
is(scalar unpack('A /A /A Z20', '3004bcde'), 'bcde');

{ # X! and x!
  my $t = 'C[3]  x!8 C[2]';
  my @a = (0x73..0x77);
  my $p = pack($t, @a);
  is($p, "\x73\x74\x75\0\0\0\0\0\x76\x77");
  my @b = unpack $t, $p;
  is(scalar @b, scalar @a);
  is("@b", "@a", 'x!8');
  $t = 'x[5] C[6] X!8 C[2]';
  @a = (0x73..0x7a);
  $p = pack($t, @a);
  is($p, "\0\0\0\0\0\x73\x74\x75\x79\x7a");
  @b = unpack $t, $p;
  @a = (0x73..0x75, 0x79, 0x7a, 0x79, 0x7a);
  is(scalar @b, scalar @a);
  is("@b", "@a");
}

{ # struct {char c1; double d; char cc[2];}
  my $t = 'C x![d] d C[2]';
  my @a = (173, 1.283476517e-45, 42, 215);
  my $p = pack $t, @a;
  ok( length $p);
  my @b = unpack "$t X[$t] $t", $p;	# Extract, step back, extract again
  is(scalar @b, 2 * scalar @a);
  $b = "@b";
  $b =~ s/(?:17000+|16999+)\d+(e-45) /17$1 /gi; # stringification is gamble
  is($b, "@a @a");

  my $warning;
  local $SIG{__WARN__} = sub {
      $warning = $_[0];
  };
  @b = unpack "x[C] x[$t] X[$t] X[C] $t", "$p\0";

  is($warning, undef);
  is(scalar @b, scalar @a);
  $b = "@b";
  $b =~ s/(?:17000+|16999+)\d+(e-45) /17$1 /gi; # stringification is gamble
  is($b, "@a");
}

is(length(pack("j", 0)), $Config{ivsize});
is(length(pack("J", 0)), $Config{uvsize});
is(length(pack("F", 0)), $Config{nvsize});

numbers ('j', -2147483648, -1, 0, 1, 2147483647);
numbers ('J', 0, 1, 2147483647, 2147483648, 4294967295);
numbers ('F', -(2**34), -1, 0, 1, 2**34);
SKIP: {
    my $t = eval { unpack("D*", pack("D", 12.34)) };

    skip "Long doubles not in use", 56 if $@ =~ /Invalid type/;

    is(length(pack("D", 0)), $Config{longdblsize});
    numbers ('D', -(2**34), -1, 0, 1, 2**34);
}

# Maybe this knowledge needs to be "global" for all of pack.t
# Or a "can checksum" which would effectively be all the number types"
my %cant_checksum = map {$_=> 1} qw(A Z u w);
# not a b B h H
foreach my $template (qw(A Z c C s S i I l L n N v V q Q j J f d F D u U w)) {
  SKIP: {
    my $packed = eval {pack "${template}4", 1, 4, 9, 16};
    if ($@) {
      die unless $@ =~ /Invalid type '$template'/;
      skip ("$template not supported on this perl",
            $cant_checksum{$template} ? 4 : 8);
    }
    my @unpack4 = unpack "${template}4", $packed;
    my @unpack = unpack "${template}*", $packed;
    my @unpack1 = unpack "${template}", $packed;
    my @unpack1s = scalar unpack "${template}", $packed;
    my @unpack4s = scalar unpack "${template}4", $packed;
    my @unpacks = scalar unpack "${template}*", $packed;

    my @tests = ( ["${template}4 vs ${template}*", \@unpack4, \@unpack],
                  ["scalar ${template} ${template}", \@unpack1s, \@unpack1],
                  ["scalar ${template}4 vs ${template}", \@unpack4s, \@unpack1],
                  ["scalar ${template}* vs ${template}", \@unpacks, \@unpack1],
                );

    unless ($cant_checksum{$template}) {
      my @unpack4_c = unpack "\%${template}4", $packed;
      my @unpack_c = unpack "\%${template}*", $packed;
      my @unpack1_c = unpack "\%${template}", $packed;
      my @unpack1s_c = scalar unpack "\%${template}", $packed;
      my @unpack4s_c = scalar unpack "\%${template}4", $packed;
      my @unpacks_c = scalar unpack "\%${template}*", $packed;

      push @tests,
        ( ["% ${template}4 vs ${template}*", \@unpack4_c, \@unpack_c],
          ["% scalar ${template} ${template}", \@unpack1s_c, \@unpack1_c],
          ["% scalar ${template}4 vs ${template}*", \@unpack4s_c, \@unpack_c],
          ["% scalar ${template}* vs ${template}*", \@unpacks_c, \@unpack_c],
        );
    }
    foreach my $test (@tests) {
      ok (list_eq ($test->[1], $test->[2]), $test->[0]) ||
        printf "# unpack gave %s expected %s\n",
          encode_list (@{$test->[1]}), encode_list (@{$test->[2]});
    }
  }
}

ok(pack('u2', 'AA'), "[perl #8026]"); # used to hang and eat RAM in perl 5.7.2

ok(1, "fake success (change #18751, feature not present in 5.8.1)");

{
    my $a = "X\t01234567\n" x 100;
    my @a = unpack("(a1 c/a)*", $a);
    is(scalar @a, 200,       "[perl #15288]");
    is($a[-1], "01234567\n", "[perl #15288]");
    is($a[-2], "X",          "[perl #15288]");
}




Copyright 1998-2008 Alvin Alexander
All Rights Reserved.
 
devdaily.com is based in louisville, kentucky, and this web site is hosted by godaddy.com