xref: /openbsd-src/gnu/usr.bin/perl/t/op/tiehash.t (revision 256a93a44f36679bee503f12e49566c2183f6181)
1*256a93a4Safresh1#!./perl
2*256a93a4Safresh1
3*256a93a4Safresh1BEGIN {
4*256a93a4Safresh1    chdir 't' if -d 't';
5*256a93a4Safresh1    require './test.pl';
6*256a93a4Safresh1    set_up_inc('../lib');
7*256a93a4Safresh1}
8*256a93a4Safresh1
9*256a93a4Safresh1# This is purposefully simple - hence the O(n) linear searches.
10*256a93a4Safresh1package TestIterators {
11*256a93a4Safresh1    sub TIEHASH {
12*256a93a4Safresh1        bless [], $_[0];
13*256a93a4Safresh1    }
14*256a93a4Safresh1
15*256a93a4Safresh1    sub STORE {
16*256a93a4Safresh1        my ($self, $key, $value) = @_;
17*256a93a4Safresh1        push @{$self->[0]}, $key;
18*256a93a4Safresh1        push @{$self->[1]}, $value;
19*256a93a4Safresh1        return $value;
20*256a93a4Safresh1    }
21*256a93a4Safresh1
22*256a93a4Safresh1    sub FETCH {
23*256a93a4Safresh1        my ($self, $key) = @_;
24*256a93a4Safresh1        my $i = 0;
25*256a93a4Safresh1        while ($i < @{$self->[0]}) {
26*256a93a4Safresh1            return $self->[1][$i]
27*256a93a4Safresh1                if $self->[0][$i] eq $key;
28*256a93a4Safresh1            ++$i;
29*256a93a4Safresh1        }
30*256a93a4Safresh1        die "$key not found in FETCH";
31*256a93a4Safresh1    }
32*256a93a4Safresh1
33*256a93a4Safresh1    sub FIRSTKEY {
34*256a93a4Safresh1        my $self = shift;
35*256a93a4Safresh1        $self->[0][0];
36*256a93a4Safresh1    }
37*256a93a4Safresh1
38*256a93a4Safresh1    # As best I can tell, none of our other tie tests actually use the first
39*256a93a4Safresh1    # parameter to nextkey. It's actually (a copy of) the previously returned
40*256a93a4Safresh1    # key. We're not *so* thorough here as to actually hide some state and
41*256a93a4Safresh1    # cross-check that, but the longhand tests below should effectively validate
42*256a93a4Safresh1    # it.
43*256a93a4Safresh1    sub NEXTKEY {
44*256a93a4Safresh1        my ($self, $key) = @_;
45*256a93a4Safresh1        my $i = 0;
46*256a93a4Safresh1        while ($i < @{$self->[0]}) {
47*256a93a4Safresh1            return $self->[0][$i + 1]
48*256a93a4Safresh1                if $self->[0][$i] eq $key;
49*256a93a4Safresh1            ++$i;
50*256a93a4Safresh1        }
51*256a93a4Safresh1        die "$key not found in NEXTKEY";
52*256a93a4Safresh1    }
53*256a93a4Safresh1};
54*256a93a4Safresh1
55*256a93a4Safresh1{
56*256a93a4Safresh1    my %h;
57*256a93a4Safresh1    tie %h, 'TestIterators';
58*256a93a4Safresh1
59*256a93a4Safresh1    $h{beer} = "foamy";
60*256a93a4Safresh1    $h{perl} = "rules";
61*256a93a4Safresh1
62*256a93a4Safresh1    is($h{beer}, "foamy", "found first key");
63*256a93a4Safresh1    is($h{perl}, "rules", "found second key");
64*256a93a4Safresh1    is(eval {
65*256a93a4Safresh1        my $k = $h{decaf};
66*256a93a4Safresh1        1;
67*256a93a4Safresh1    }, undef, "missing key was not found");
68*256a93a4Safresh1    like($@, qr/\Adecaf not found in FETCH/, "with the correct error");
69*256a93a4Safresh1
70*256a93a4Safresh1    is(each %h, 'beer', "first iterator");
71*256a93a4Safresh1    is(each %h, 'perl', "second iterator");
72*256a93a4Safresh1    is(each %h, undef, "third iterator is undef");
73*256a93a4Safresh1}
74*256a93a4Safresh1
75*256a93a4Safresh1{
76*256a93a4Safresh1    require Tie::Hash;
77*256a93a4Safresh1
78*256a93a4Safresh1    my %h = (
79*256a93a4Safresh1        lolcat => "OH HAI!",
80*256a93a4Safresh1        lolrus => "I HAS A BUCKET",
81*256a93a4Safresh1    );
82*256a93a4Safresh1
83*256a93a4Safresh1    my @want = sort keys %h;
84*256a93a4Safresh1
85*256a93a4Safresh1    my @have;
86*256a93a4Safresh1    while (1) {
87*256a93a4Safresh1        my $k = each %h;
88*256a93a4Safresh1        last
89*256a93a4Safresh1            unless defined $k;
90*256a93a4Safresh1        push @have, $k;
91*256a93a4Safresh1    }
92*256a93a4Safresh1    @have = sort @have;
93*256a93a4Safresh1
94*256a93a4Safresh1    # This is a sanity test:
95*256a93a4Safresh1    is("@have", "@want", "get all keys from a loop");
96*256a93a4Safresh1
97*256a93a4Safresh1    @have = ();
98*256a93a4Safresh1    keys %h;
99*256a93a4Safresh1
100*256a93a4Safresh1    my $k1 = each %h;
101*256a93a4Safresh1
102*256a93a4Safresh1    ok(defined $k1, "Got a key");
103*256a93a4Safresh1
104*256a93a4Safresh1    # no tie/untie here
105*256a93a4Safresh1
106*256a93a4Safresh1    while(1) {
107*256a93a4Safresh1        my $k = each %h;
108*256a93a4Safresh1        last
109*256a93a4Safresh1            unless defined $k;
110*256a93a4Safresh1        push @have, $k;
111*256a93a4Safresh1    }
112*256a93a4Safresh1
113*256a93a4Safresh1    # As are these:
114*256a93a4Safresh1    is(scalar @have, 1, "just 1 key from the loop this time");
115*256a93a4Safresh1    isnt($k1, $have[0], "two different keys");
116*256a93a4Safresh1
117*256a93a4Safresh1    @have = sort @have, $k1;
118*256a93a4Safresh1    is("@have", "@want", "get all keys just once");
119*256a93a4Safresh1
120*256a93a4Safresh1    # And this is the real test.
121*256a93a4Safresh1    #
122*256a93a4Safresh1    # Previously pp_tie would mangle the hash iterator state - it would reset
123*256a93a4Safresh1    # EITER but not RITER, meaning that if the iterator happened to be partway
124*256a93a4Safresh1    # down a chain of entries, the rest of that chain would be skipped, but if
125*256a93a4Safresh1    # the iterator's next position was the start of a (new) chain, nothing would
126*256a93a4Safresh1    # be skipped.
127*256a93a4Safresh1    # We don't have space to store the complete older iterator state (and really
128*256a93a4Safresh1    # nothing should be relying on it), so it seems better to correctly reset
129*256a93a4Safresh1    # the iterator (every time), than leave it in a mess just occasionally.
130*256a93a4Safresh1
131*256a93a4Safresh1    @have = ();
132*256a93a4Safresh1    keys %h;
133*256a93a4Safresh1
134*256a93a4Safresh1    my $k1 = each %h;
135*256a93a4Safresh1
136*256a93a4Safresh1    ok(defined $k1, "Got a key");
137*256a93a4Safresh1
138*256a93a4Safresh1    tie %h, 'Tie::StdHash';
139*256a93a4Safresh1    untie %h;
140*256a93a4Safresh1
141*256a93a4Safresh1    while(1) {
142*256a93a4Safresh1        my $k = each %h;
143*256a93a4Safresh1        last
144*256a93a4Safresh1            unless defined $k;
145*256a93a4Safresh1        push @have, $k;
146*256a93a4Safresh1    }
147*256a93a4Safresh1
148*256a93a4Safresh1    @have = sort @have;
149*256a93a4Safresh1    is(scalar @have, 2, "2 keys from the loop this time");
150*256a93a4Safresh1    is("@have", "@want", "tie/untie resets the hash iterator");
151*256a93a4Safresh1}
152*256a93a4Safresh1
153*256a93a4Safresh1{
154*256a93a4Safresh1    require Tie::Hash;
155*256a93a4Safresh1    my $count;
156*256a93a4Safresh1
157*256a93a4Safresh1    package Tie::Count {
158*256a93a4Safresh1        use parent -norequire, 'Tie::StdHash';
159*256a93a4Safresh1        sub FETCH {
160*256a93a4Safresh1            ++$count;
161*256a93a4Safresh1            return $_[0]->SUPER::FETCH($_[1]);
162*256a93a4Safresh1        }
163*256a93a4Safresh1    }
164*256a93a4Safresh1
165*256a93a4Safresh1    $count = 0;
166*256a93a4Safresh1    my %tied;
167*256a93a4Safresh1    tie %tied, "Tie::Count";
168*256a93a4Safresh1    %tied = qw(perl rules beer foamy);
169*256a93a4Safresh1    my @a = %tied;
170*256a93a4Safresh1    if ($a[0] eq 'beer') {
171*256a93a4Safresh1        is("@a", "beer foamy perl rules", "tied hash in list context");
172*256a93a4Safresh1    } else {
173*256a93a4Safresh1        is("@a", "perl rules beer foamy", "tied hash in list context");
174*256a93a4Safresh1    }
175*256a93a4Safresh1    is($count, 2, "two FETCHes for tied hash in list context");
176*256a93a4Safresh1
177*256a93a4Safresh1    $count = 0;
178*256a93a4Safresh1
179*256a93a4Safresh1    @a = keys %tied;
180*256a93a4Safresh1    @a = sort @a;
181*256a93a4Safresh1    is("@a", "beer perl", "tied hash keys in list context");
182*256a93a4Safresh1    is($count, 0, "no FETCHes for tied hash keys in list context");
183*256a93a4Safresh1
184*256a93a4Safresh1    $count = 0;
185*256a93a4Safresh1    @a = values %tied;
186*256a93a4Safresh1    @a = sort @a;
187*256a93a4Safresh1
188*256a93a4Safresh1    is("@a", "foamy rules", "tied hash values in list context");
189*256a93a4Safresh1    is($count, 2, "two FETCHes for tied hash values in list context");
190*256a93a4Safresh1}
191*256a93a4Safresh1
192*256a93a4Safresh1{
193*256a93a4Safresh1    # tie/untie on a hash resets the iterator
194*256a93a4Safresh1
195*256a93a4Safresh1    # This is not intended as a test of *correctness*. This behaviour is
196*256a93a4Safresh1    # observable by code on CPAN, so potentially some of it will inadvertently
197*256a93a4Safresh1    # be relying on it (and likely not in any regression test). Hence this
198*256a93a4Safresh1    # "test" here is intended as a way to alert us if any core code change has
199*256a93a4Safresh1    # the side effect of alerting this observable behaviour.
200*256a93a4Safresh1
201*256a93a4Safresh1    my @keys = qw(bactrianus dromedarius ferus);
202*256a93a4Safresh1    my %Camelus;
203*256a93a4Safresh1    ++$Camelus{$_}
204*256a93a4Safresh1        for @keys;
205*256a93a4Safresh1
206*256a93a4Safresh1    my @got;
207*256a93a4Safresh1    push @got, scalar each %Camelus;
208*256a93a4Safresh1    push @got, scalar each %Camelus;
209*256a93a4Safresh1    push @got, scalar each %Camelus;
210*256a93a4Safresh1    is(scalar each %Camelus, undef, 'Fourth each returned undef');
211*256a93a4Safresh1    is(join(' ', sort @got), "@keys", 'The correct three keys');
212*256a93a4Safresh1
213*256a93a4Safresh1    @got = ();
214*256a93a4Safresh1    keys %Camelus;
215*256a93a4Safresh1
216*256a93a4Safresh1    push @got, scalar each %Camelus;
217*256a93a4Safresh1
218*256a93a4Safresh1    # This resets the hash iterator:
219*256a93a4Safresh1    tie %Camelus, 'Tie::StdHash';
220*256a93a4Safresh1    my @all = keys %Camelus;
221*256a93a4Safresh1    is(scalar @all, 0, 'Zero keys when tied');
222*256a93a4Safresh1    untie %Camelus;
223*256a93a4Safresh1
224*256a93a4Safresh1    push @got, scalar each %Camelus;
225*256a93a4Safresh1    push @got, scalar each %Camelus;
226*256a93a4Safresh1    my $fourth = scalar each %Camelus;
227*256a93a4Safresh1    isnt($fourth, undef, 'Fourth each did not return undef');
228*256a93a4Safresh1    push @got, $fourth;
229*256a93a4Safresh1    is(scalar each %Camelus, undef, 'Fifth each returned undef');
230*256a93a4Safresh1    my %have;
231*256a93a4Safresh1    @have{@got} = ();
232*256a93a4Safresh1    is(join(' ', sort keys %have), "@keys", 'Still the correct three keys');
233*256a93a4Safresh1}
234*256a93a4Safresh1done_testing();
235