summaryrefslogtreecommitdiff
path: root/dist/constant/t/constant.t
blob: 00eddfb6579ee4c1c412ff8103f7660bed60d159 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
#!./perl -T

use warnings;
use vars qw{ @warnings $fagwoosh $putt $kloong};
BEGIN {				# ...and save 'em for later
    $SIG{'__WARN__'} = sub { push @warnings, @_ }
}
END { @warnings && print STDERR join "\n- ", "accumulated warnings:", @warnings }


use strict;
use Test::More tests => 109;
my $TB = Test::More->builder;

BEGIN { use_ok('constant'); }

use constant PI		=> 4 * atan2 1, 1;

ok defined PI,                          'basic scalar constant';
is substr(PI, 0, 7), '3.14159',         '    in substr()';

sub deg2rad { PI * $_[0] / 180 }

my $ninety = deg2rad 90;

cmp_ok abs($ninety - 1.5707), '<', 0.0001, '    in math expression';

use constant UNDEF1	=> undef;	# the right way
use constant UNDEF2	=>	;	# the weird way
use constant 'UNDEF3'		;	# the 'short' way
use constant EMPTY	=> ( )  ;	# the right way for lists

is UNDEF1, undef,       'right way to declare an undef';
is UNDEF2, undef,       '    weird way';
is UNDEF3, undef,       '    short way';

# XXX Why is this way different than the other ones?
my @undef = UNDEF1;
is @undef, 1;
is $undef[0], undef;

@undef = UNDEF2;
is @undef, 0;
@undef = UNDEF3;
is @undef, 0;
@undef = EMPTY;
is @undef, 0;

use constant COUNTDOWN	=> scalar reverse 1, 2, 3, 4, 5;
use constant COUNTLIST	=> reverse 1, 2, 3, 4, 5;
use constant COUNTLAST	=> (COUNTLIST)[-1];

is COUNTDOWN, '54321';
my @cl = COUNTLIST;
is @cl, 5;
is COUNTDOWN, join '', @cl;
is COUNTLAST, 1;
is((COUNTLIST)[1], 4);

use constant ABC	=> 'ABC';
is "abc${\( ABC )}abc", "abcABCabc";

use constant DEF	=> 'D', 'E', chr ord 'F';
is "d e f @{[ DEF ]} d e f", "d e f D E F d e f";

use constant SINGLE	=> "'";
use constant DOUBLE	=> '"';
use constant BACK	=> '\\';
my $tt = BACK . SINGLE . DOUBLE ;
is $tt, q(\\'");

use constant MESS	=> q('"'\\"'"\\);
is MESS, q('"'\\"'"\\);
is length(MESS), 8;

use constant LEADING	=> " \t1234";
cmp_ok LEADING, '==', 1234;
is LEADING, " \t1234";

use constant ZERO1	=> 0;
use constant ZERO2	=> 0.0;
use constant ZERO3	=> '0.0';
is ZERO1, '0';
is ZERO2, '0';
is ZERO3, '0.0';

{
    package Other;
    use constant PI	=> 3.141;
}

cmp_ok(abs(PI - 3.1416), '<', 0.0001);
is Other::PI, 3.141;

use constant E2BIG => $! = 7;
cmp_ok E2BIG, '==', 7;
# This is something like "Arg list too long", but the actual message
# text may vary, so we can't test much better than this.
cmp_ok length(E2BIG), '>', 6;

is @warnings, 0 or diag join "\n- ", "unexpected warning:", @warnings;
@warnings = ();		# just in case
undef &PI;
ok @warnings && ($warnings[0] =~ /Constant sub.* undefined/) or
  diag join "\n", "unexpected warning", @warnings;
shift @warnings;

is @warnings, 0, "unexpected warning";

my $curr_test = $TB->current_test;
use constant CSCALAR	=> \"ok 35\n";
use constant CHASH	=> { foo => "ok 36\n" };
use constant CARRAY	=> [ undef, "ok 37\n" ];
use constant CCODE	=> sub { "ok $_[0]\n" };

my $output = $TB->output ;
print $output ${+CSCALAR};
print $output CHASH->{foo};
print $output CARRAY->[1];
print $output CCODE->($curr_test+4);

$TB->current_test($curr_test+4);

eval q{ CCODE->{foo} };
ok scalar($@ =~ /^Constant is not a HASH|Not a HASH reference/);


# Allow leading underscore
use constant _PRIVATE => 47;
is _PRIVATE, 47;

# Disallow doubled leading underscore
eval q{
    use constant __DISALLOWED => "Oops";
};
like $@, qr/begins with '__'/;

# Check on declared() and %declared. This sub should be EXACTLY the
# same as the one quoted in the docs!
sub declared ($) {
    use constant 1.01;              # don't omit this!
    my $name = shift;
    $name =~ s/^::/main::/;
    my $pkg = caller;
    my $full_name = $name =~ /::/ ? $name : "${pkg}::$name";
    $constant::declared{$full_name};
}

ok declared 'PI';
ok $constant::declared{'main::PI'};

ok !declared 'PIE';
ok !$constant::declared{'main::PIE'};

{
    package Other;
    use constant IN_OTHER_PACK => 42;
    ::ok ::declared 'IN_OTHER_PACK';
    ::ok $constant::declared{'Other::IN_OTHER_PACK'};
    ::ok ::declared 'main::PI';
    ::ok $constant::declared{'main::PI'};
}

ok declared 'Other::IN_OTHER_PACK';
ok $constant::declared{'Other::IN_OTHER_PACK'};

@warnings = ();
eval q{
    no warnings;
    use warnings 'constant';
    use constant 'BEGIN' => 1 ;
    use constant 'INIT' => 1 ;
    use constant 'CHECK' => 1 ;
    use constant 'END' => 1 ;
    use constant 'DESTROY' => 1 ;
    use constant 'AUTOLOAD' => 1 ;
    use constant 'STDIN' => 1 ;
    use constant 'STDOUT' => 1 ;
    use constant 'STDERR' => 1 ;
    use constant 'ARGV' => 1 ;
    use constant 'ARGVOUT' => 1 ;
    use constant 'ENV' => 1 ;
    use constant 'INC' => 1 ;
    use constant 'SIG' => 1 ;
    use constant 'UNITCHECK' => 1;
};

my @Expected_Warnings = 
  (
   qr/^Constant name 'BEGIN' is a Perl keyword at/,
   qr/^Constant subroutine BEGIN redefined at/,
   qr/^Constant name 'INIT' is a Perl keyword at/,
   qr/^Constant name 'CHECK' is a Perl keyword at/,
   qr/^Constant name 'END' is a Perl keyword at/,
   qr/^Constant name 'DESTROY' is a Perl keyword at/,
   qr/^Constant name 'AUTOLOAD' is a Perl keyword at/,
   qr/^Constant name 'STDIN' is forced into package main:: a/,
   qr/^Constant name 'STDOUT' is forced into package main:: at/,
   qr/^Constant name 'STDERR' is forced into package main:: at/,
   qr/^Constant name 'ARGV' is forced into package main:: at/,
   qr/^Constant name 'ARGVOUT' is forced into package main:: at/,
   qr/^Constant name 'ENV' is forced into package main:: at/,
   qr/^Constant name 'INC' is forced into package main:: at/,
   qr/^Constant name 'SIG' is forced into package main:: at/,
   qr/^Constant name 'UNITCHECK' is a Perl keyword at/,
);

unless ($] > 5.009) {
    # Remove the UNITCHECK warning
    pop @Expected_Warnings;
    # But keep the count the same
    push @Expected_Warnings, qr/^$/;
    push @warnings, "";
}

# when run under "make test"
if (@warnings == 16) {
    push @warnings, "";
    push @Expected_Warnings, qr/^$/;
}
# when run directly: perl -wT -Ilib t/constant.t
elsif (@warnings == 17) {
    splice @Expected_Warnings, 1, 0, 
        qr/^Prototype mismatch: sub main::BEGIN \(\) vs none at/;
}
# when run directly under 5.6.2: perl -wT -Ilib t/constant.t
elsif (@warnings == 15) {
    splice @Expected_Warnings, 1, 1;
    push @warnings, "", "";
    push @Expected_Warnings, qr/^$/, qr/^$/;
}
else {
    my $rule = " -" x 20;
    diag "/!\\ unexpected case: ", scalar @warnings, " warnings\n$rule\n";
    diag map { "  $_" } @warnings;
    diag $rule, $/;
}

is @warnings, 17;

for my $idx (0..$#warnings) {
    like $warnings[$idx], $Expected_Warnings[$idx];
}

@warnings = ();


use constant {
	THREE  => 3,
	FAMILY => [ qw( John Jane Sally ) ],
	AGES   => { John => 33, Jane => 28, Sally => 3 },
	RFAM   => [ [ qw( John Jane Sally ) ] ],
	SPIT   => sub { shift },
};

is @{+FAMILY}, THREE;
is @{+FAMILY}, @{RFAM->[0]};
is FAMILY->[2], RFAM->[0]->[2];
is AGES->{FAMILY->[1]}, 28;
is THREE**3, SPIT->(@{+FAMILY}**3);

# Allow name of digits/underscores only if it begins with underscore
{
    use warnings FATAL => 'constant';
    eval q{
        use constant _1_2_3 => 'allowed';
    };
    ok( $@ eq '' );
}

sub slotch ();

{
    my @warnings;
    local $SIG{'__WARN__'} = sub { push @warnings, @_ };
    eval 'use constant slotch => 3; 1' or die $@;

    is ("@warnings", "", "No warnings if a prototype exists");

    my $value = eval 'slotch';
    is ($@, '');
    is ($value, 3);
}

sub zit;

{
    my @warnings;
    local $SIG{'__WARN__'} = sub { push @warnings, @_ };
    eval 'use constant zit => 4; 1' or die $@;

    # empty prototypes are reported differently in different versions
    my $no_proto = $] < 5.008004 ? "" : ": none";

    is(scalar @warnings, 1, "1 warning");
    like ($warnings[0], qr/^Prototype mismatch: sub main::zit$no_proto vs \(\)/,
	  "about the prototype mismatch");

    my $value = eval 'zit';
    is ($@, '');
    is ($value, 4);
}

$fagwoosh = 'geronimo';
$putt = 'leutwein';
$kloong = 'schlozhauer';

{
    my @warnings;
    local $SIG{'__WARN__'} = sub { push @warnings, @_ };
    eval 'use constant fagwoosh => 5; 1' or die $@;

    is ("@warnings", "", "No warnings if the typeglob exists already");

    my $value = eval 'fagwoosh';
    is ($@, '');
    is ($value, 5);

    my @value = eval 'fagwoosh';
    is ($@, '');
    is_deeply (\@value, [5]);

    eval 'use constant putt => 6, 7; 1' or die $@;

    is ("@warnings", "", "No warnings if the typeglob exists already");

    @value = eval 'putt';
    is ($@, '');
    is_deeply (\@value, [6, 7]);

    eval 'use constant "klong"; 1' or die $@;

    is ("@warnings", "", "No warnings if the typeglob exists already");

    $value = eval 'klong';
    is ($@, '');
    is ($value, undef);

    @value = eval 'klong';
    is ($@, '');
    is_deeply (\@value, []);
}

{
    local $SIG{'__WARN__'} = sub { die "WARNING: $_[0]" };
    eval 'use constant undef, 5; 1';
    like $@, qr/\ACan't use undef as constant name at /;
}

# Constants created by "use constant" should be read-only

# This test will not test what we are trying to test if this glob entry
# exists already, so test that, too.
ok !exists $::{immutable};
eval q{
    use constant immutable => 23987423874;
    for (immutable) { eval { $_ = 22 } }
    like $@, qr/^Modification of a read-only value attempted at /,
	'constant created in empty stash slot is immutable';
    eval { for (immutable) { ${\$_} = 432 } };
    SKIP: {
	require Config;
	if ($Config::Config{useithreads}) {
	    skip "fails under threads", 1 if $] < 5.019003;
	}
	like $@, qr/^Modification of a read-only value attempted at /,
	    '... and immutable through refgen, too';
    }
};
() = \&{"immutable"}; # reify
eval 'for (immutable) { $_ = 42 }';
like $@, qr/^Modification of a read-only value attempted at /,
    '... and after reification';

# Use an existing stash element this time.
# This next line is sufficient to trigger a different code path in
# constant.pm.
() = \%::existing_stash_entry;
use constant existing_stash_entry => 23987423874;
for (existing_stash_entry) { eval { $_ = 22 } }
like $@, qr/^Modification of a read-only value attempted at /,
    'constant created in existing stash slot is immutable';
eval { for (existing_stash_entry) { ${\$_} = 432 } };
SKIP: {
    if ($Config::Config{useithreads}) {
	skip "fails under threads", 1 if $] < 5.019003;
    }
    like $@, qr/^Modification of a read-only value attempted at /,
	'... and immutable through refgen, too';
}

# Test that list constants are also immutable.  This only works under
# 5.19.3 and later.
SKIP: {
    skip "fails under 5.19.2 and earlier", 3 if $] < 5.019003;
    local $TODO = "disabled for now; breaks CPAN; see perl #119045";
    use constant constant_list => 1..2;
    for (constant_list) {
	my $num = $_;
	eval { $_++ };
	like $@, qr/^Modification of a read-only value attempted at /,
	    "list constant has constant elements ($num)";
    }
    undef $TODO;
    # Whether values are modifiable or no, modifying them should not affect
    # future return values.
    my @values;
    for(1..2) {
	for ((constant_list)[0]) {
	    push @values, $_;
	    eval {$_++};
	}
    }
    is $values[1], $values[0],
	'modifying list const elements does not affect future retavls';
}

use constant { "tahi" => 1, "rua::rua" => 2, "toru'toru" => 3 };
use constant "wha::wha" => 4;
is tahi, 1, 'unqualified constant declared with constants in other pkgs';
is rua::rua, 2, 'constant declared with ::';
is toru::toru, 3, "constant declared with '";
is wha::wha, 4, 'constant declared by itself with ::';