Revert most of the conversion to Test::Fatal so we can redo it
[gitmo/Moose.git] / t / 070_native_traits / 010_trait_array.t
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5
6 use lib 't/lib';
7
8 use Moose ();
9 use Moose::Util::TypeConstraints;
10 use NoInlineAttribute;
11 use Test::More;
12 use Test::Exception;
13 use Test::Moose;
14
15 {
16     my %handles = (
17         count    => 'count',
18         elements => 'elements',
19         is_empty => 'is_empty',
20         push     => 'push',
21         push_curried =>
22             [ push => 42, 84 ],
23         unshift => 'unshift',
24         unshift_curried =>
25             [ unshift => 42, 84 ],
26         pop           => 'pop',
27         shift         => 'shift',
28         get           => 'get',
29         get_curried   => [ get => 1 ],
30         set           => 'set',
31         set_curried_1 => [ set => 1 ],
32         set_curried_2 => [ set => ( 1, 98 ) ],
33         accessor      => 'accessor',
34         accessor_curried_1 => [ accessor => 1 ],
35         accessor_curried_2 => [ accessor => ( 1, 90 ) ],
36         clear          => 'clear',
37         delete         => 'delete',
38         delete_curried => [ delete => 1 ],
39         insert         => 'insert',
40         insert_curried => [ insert => ( 1, 101 ) ],
41         splice         => 'splice',
42         splice_curried_1   => [ splice => 1 ],
43         splice_curried_2   => [ splice => 1, 2 ],
44         splice_curried_all => [ splice => 1, 2, ( 3, 4, 5 ) ],
45         sort          => 'sort',
46         sort_curried  => [ sort => ( sub { $_[1] <=> $_[0] } ) ],
47         sort_in_place => 'sort_in_place',
48         sort_in_place_curried =>
49             [ sort_in_place => ( sub { $_[1] <=> $_[0] } ) ],
50         map           => 'map',
51         map_curried   => [ map => ( sub { $_ + 1 } ) ],
52         grep          => 'grep',
53         grep_curried  => [ grep => ( sub { $_ < 5 } ) ],
54         first         => 'first',
55         first_curried => [ first => ( sub { $_ % 2 } ) ],
56         join          => 'join',
57         join_curried => [ join => '-' ],
58         shuffle      => 'shuffle',
59         uniq         => 'uniq',
60         reduce       => 'reduce',
61         reduce_curried => [ reduce => ( sub { $_[0] * $_[1] } ) ],
62         natatime       => 'natatime',
63         natatime_curried => [ natatime => 2 ],
64     );
65
66     my $name = 'Foo1';
67
68     sub build_class {
69         my %attr = @_;
70
71         my $class = Moose::Meta::Class->create(
72             $name++,
73             superclasses => ['Moose::Object'],
74         );
75
76         my @traits = 'Array';
77         push @traits, 'NoInlineAttribute'
78             if delete $attr{no_inline};
79
80         $class->add_attribute(
81             _values => (
82                 traits  => \@traits,
83                 is      => 'rw',
84                 isa     => 'ArrayRef[Int]',
85                 default => sub { [] },
86                 handles => \%handles,
87                 clearer => '_clear_values',
88                 %attr,
89             ),
90         );
91
92         return ( $class->name, \%handles );
93     }
94 }
95
96 {
97     package Overloader;
98
99     use overload
100         '&{}' => sub { ${ $_[0] } },
101         bool  => sub {1};
102
103     sub new {
104         bless \$_[1], $_[0];
105     }
106 }
107
108 {
109     run_tests(build_class);
110     run_tests( build_class( lazy => 1, default => sub { [ 42, 84 ] } ) );
111     run_tests( build_class( trigger => sub { } ) );
112     run_tests( build_class( no_inline => 1 ) );
113
114     # Will force the inlining code to check the entire arrayref when it is modified.
115     subtype 'MyArrayRef', as 'ArrayRef', where { 1 };
116
117     run_tests( build_class( isa => 'MyArrayRef' ) );
118
119     coerce 'MyArrayRef', from 'ArrayRef', via { $_ };
120
121     run_tests( build_class( isa => 'MyArrayRef', coerce => 1 ) );
122 }
123
124 sub run_tests {
125     my ( $class, $handles ) = @_;
126
127     can_ok( $class, $_ ) for sort keys %{$handles};
128
129     with_immutable {
130         my $obj = $class->new( _values => [ 10, 12, 42 ] );
131
132         is_deeply(
133             $obj->_values, [ 10, 12, 42 ],
134             'values can be set in constructor'
135         );
136
137         ok( !$obj->is_empty, 'values is not empty' );
138         is( $obj->count, 3, 'count returns 3' );
139
140         throws_ok { $obj->count(22) }
141         qr/Cannot call count with any arguments/,
142             'throws an error when passing an argument passed to count';
143
144         lives_ok { $obj->push( 1, 2, 3 ) }
145         'pushed three new values and lived';
146
147         lives_ok { $obj->push() } 'call to push without arguments lives';
148
149         lives_and {
150             is( $obj->unshift( 101, 22 ), 8,
151                 'unshift returns size of the new array' );
152         }
153         'unshifted two values and lived';
154
155         is_deeply(
156             $obj->_values, [ 101, 22, 10, 12, 42, 1, 2, 3 ],
157             'unshift changed the value of the array in the object'
158         );
159
160         lives_ok { $obj->unshift() }
161         'call to unshift without arguments lives';
162
163         is( $obj->pop, 3, 'pop returns the last value in the array' );
164
165         is_deeply(
166             $obj->_values, [ 101, 22, 10, 12, 42, 1, 2 ],
167             'pop changed the value of the array in the object'
168         );
169
170         throws_ok { $obj->pop(42) }
171         qr/Cannot call pop with any arguments/,
172             'call to pop with arguments dies';
173
174         is( $obj->shift, 101, 'shift returns the first value' );
175
176         throws_ok { $obj->shift(42) }
177         qr/Cannot call shift with any arguments/,
178             'call to shift with arguments dies';
179
180         is_deeply(
181             $obj->_values, [ 22, 10, 12, 42, 1, 2 ],
182             'shift changed the value of the array in the object'
183         );
184
185         is_deeply(
186             [ $obj->elements ], [ 22, 10, 12, 42, 1, 2 ],
187             'call to elements returns values as a list'
188         );
189
190         throws_ok { $obj->elements(22) }
191         qr/Cannot call elements with any arguments/,
192             'throws an error when passing an argument passed to elements';
193
194         $obj->_values( [ 1, 2, 3 ] );
195
196         is( $obj->get(0),      1, 'get values at index 0' );
197         is( $obj->get(1),      2, 'get values at index 1' );
198         is( $obj->get(2),      3, 'get values at index 2' );
199         is( $obj->get_curried, 2, 'get_curried returns value at index 1' );
200
201         throws_ok { $obj->get() }
202         qr/Cannot call get without at least 1 argument/,
203             'throws an error when get is called without any arguments';
204
205         throws_ok { $obj->get( {} ) }
206         qr/The index passed to get must be an integer/,
207             'throws an error when get is called with an invalid argument';
208
209         throws_ok { $obj->get(2.2) }
210         qr/The index passed to get must be an integer/,
211             'throws an error when get is called with an invalid argument';
212
213         throws_ok { $obj->get('foo') }
214         qr/The index passed to get must be an integer/,
215             'throws an error when get is called with an invalid argument';
216
217         throws_ok { $obj->get_curried(2) }
218         qr/Cannot call get with more than 1 argument/,
219             'throws an error when get_curried is called with an argument';
220
221         lives_and {
222             is( $obj->set( 1, 100 ), 100, 'set returns new value' );
223         }
224         'set value at index 1 lives';
225
226         is( $obj->get(1), 100, 'get value at index 1 returns new value' );
227
228
229         throws_ok { $obj->set( 1, 99, 42 ) }
230         qr/Cannot call set with more than 2 arguments/,
231             'throws an error when set is called with three arguments';
232
233         lives_ok { $obj->set_curried_1(99) } 'set_curried_1 lives';
234
235         is( $obj->get(1), 99, 'get value at index 1 returns new value' );
236
237         throws_ok { $obj->set_curried_1( 99, 42 ) }
238         qr/Cannot call set with more than 2 arguments/,
239             'throws an error when set_curried_1 is called with two arguments';
240
241         lives_ok { $obj->set_curried_2 } 'set_curried_2 lives';
242
243         is( $obj->get(1), 98, 'get value at index 1 returns new value' );
244
245         throws_ok { $obj->set_curried_2(42) }
246         qr/Cannot call set with more than 2 arguments/,
247             'throws an error when set_curried_2 is called with one argument';
248
249         is(
250             $obj->accessor(1), 98,
251             'accessor with one argument returns value at index 1'
252         );
253
254         lives_and {
255             is( $obj->accessor( 1 => 97 ), 97, 'accessor returns new value' );
256         }
257         'accessor as writer lives';
258
259         is(
260             $obj->get(1), 97,
261             'accessor set value at index 1'
262         );
263
264         throws_ok { $obj->accessor( 1, 96, 42 ) }
265         qr/Cannot call accessor with more than 2 arguments/,
266             'throws an error when accessor is called with three arguments';
267
268         is(
269             $obj->accessor_curried_1, 97,
270             'accessor_curried_1 returns expected value when called with no arguments'
271         );
272
273         lives_ok { $obj->accessor_curried_1(95) }
274         'accessor_curried_1 as writer lives';
275
276         is(
277             $obj->get(1), 95,
278             'accessor_curried_1 set value at index 1'
279         );
280
281         throws_ok { $obj->accessor_curried_1( 96, 42 ) }
282         qr/Cannot call accessor with more than 2 arguments/,
283             'throws an error when accessor_curried_1 is called with two arguments';
284
285         lives_ok { $obj->accessor_curried_2 }
286         'accessor_curried_2 as writer lives';
287
288         is(
289             $obj->get(1), 90,
290             'accessor_curried_2 set value at index 1'
291         );
292
293         throws_ok { $obj->accessor_curried_2(42) }
294         qr/Cannot call accessor with more than 2 arguments/,
295             'throws an error when accessor_curried_2 is called with one argument';
296
297         lives_ok { $obj->clear } 'clear lives';
298
299         ok( $obj->is_empty, 'values is empty after call to clear' );
300
301         $obj->set( 0 => 42 );
302
303         throws_ok { $obj->clear(50) }
304         qr/Cannot call clear with any arguments/,
305             'throws an error when clear is called with an argument';
306
307         ok(
308             !$obj->is_empty,
309             'values is not empty after failed call to clear'
310         );
311
312         throws_ok { $obj->is_empty(50) }
313         qr/Cannot call is_empty with any arguments/,
314             'throws an error when is_empty is called with an argument';
315
316         $obj->clear;
317         is(
318             $obj->push( 1, 5, 10, 42 ), 4,
319             'pushed 4 elements, got number of elements in the array back'
320         );
321
322         lives_and {
323             is( $obj->delete(2), 10, 'delete returns deleted value' );
324         }
325         'delete lives';
326
327         is_deeply(
328             $obj->_values, [ 1, 5, 42 ],
329             'delete removed the specified element'
330         );
331
332         throws_ok { $obj->delete( 2, 3 ) }
333         qr/Cannot call delete with more than 1 argument/,
334             'throws an error when delete is called with two arguments';
335
336         lives_ok { $obj->delete_curried } 'delete_curried lives';
337
338         is_deeply(
339             $obj->_values, [ 1, 42 ],
340             'delete removed the specified element'
341         );
342
343         throws_ok { $obj->delete_curried(2) }
344         qr/Cannot call delete with more than 1 argument/,
345             'throws an error when delete_curried is called with one argument';
346
347         lives_ok { $obj->insert( 1, 21 ) } 'insert lives';
348
349         is_deeply(
350             $obj->_values, [ 1, 21, 42 ],
351             'insert added the specified element'
352         );
353
354         throws_ok { $obj->insert( 1, 22, 44 ) }
355         qr/Cannot call insert with more than 2 arguments/,
356             'throws an error when insert is called with three arguments';
357
358         lives_and {
359             is_deeply(
360                 [ $obj->splice( 1, 0, 2, 3 ) ],
361                 [],
362                 'return value of splice is empty list when not removing elements'
363             );
364         }
365         'splice lives';
366
367         is_deeply(
368             $obj->_values, [ 1, 2, 3, 21, 42 ],
369             'splice added the specified elements'
370         );
371
372         lives_and {
373             is_deeply(
374                 [ $obj->splice( 1, 2, 99 ) ],
375                 [ 2, 3 ],
376                 'splice returns list of removed values'
377             );
378         }
379         'splice lives';
380
381         is_deeply(
382             $obj->_values, [ 1, 99, 21, 42 ],
383             'splice added the specified elements'
384         );
385
386         throws_ok { $obj->splice() }
387         qr/Cannot call splice without at least 1 argument/,
388             'throws an error when splice is called with no arguments';
389
390         throws_ok { $obj->splice( 1, 'foo', ) }
391         qr/The length argument passed to splice must be an integer/,
392             'throws an error when splice is called with an invalid length';
393
394         lives_ok { $obj->splice_curried_1( 2, 101 ) }
395         'splice_curried_1 lives';
396
397         is_deeply(
398             $obj->_values, [ 1, 101, 42 ],
399             'splice added the specified elements'
400         );
401
402         lives_ok { $obj->splice_curried_2(102) } 'splice_curried_2 lives';
403
404         is_deeply(
405             $obj->_values, [ 1, 102 ],
406             'splice added the specified elements'
407         );
408
409         lives_ok { $obj->splice_curried_all } 'splice_curried_all lives';
410
411         is_deeply(
412             $obj->_values, [ 1, 3, 4, 5 ],
413             'splice added the specified elements'
414         );
415
416         is_deeply(
417             scalar $obj->splice( 1, 2 ),
418             4,
419             'splice in scalar context returns last element removed'
420         );
421
422         is_deeply(
423             scalar $obj->splice( 1, 0, 42 ),
424             undef,
425             'splice in scalar context returns undef when no elements are removed'
426         );
427
428         $obj->_values( [ 3, 9, 5, 22, 11 ] );
429
430         is_deeply(
431             [ $obj->sort ], [ 11, 22, 3, 5, 9 ],
432             'sort returns sorted values'
433         );
434
435         is_deeply(
436             [ $obj->sort( sub { $_[0] <=> $_[1] } ) ], [ 3, 5, 9, 11, 22 ],
437             'sort returns values sorted by provided function'
438         );
439
440         throws_ok { $obj->sort(1) }
441         qr/The argument passed to sort must be a code reference/,
442             'throws an error when passing a non coderef to sort';
443
444         throws_ok {
445             $obj->sort( sub { }, 27 );
446         }
447         qr/Cannot call sort with more than 1 argument/,
448             'throws an error when passing two arguments to sort';
449
450         $obj->_values( [ 3, 9, 5, 22, 11 ] );
451
452         $obj->sort_in_place;
453
454         is_deeply(
455             $obj->_values, [ 11, 22, 3, 5, 9 ],
456             'sort_in_place sorts values'
457         );
458
459         $obj->sort_in_place( sub { $_[0] <=> $_[1] } );
460
461         is_deeply(
462             $obj->_values, [ 3, 5, 9, 11, 22 ],
463             'sort_in_place with function sorts values'
464         );
465
466         throws_ok {
467             $obj->sort_in_place( 27 );
468         }
469         qr/The argument passed to sort_in_place must be a code reference/,
470             'throws an error when passing a non coderef to sort_in_place';
471
472         throws_ok {
473             $obj->sort_in_place( sub { }, 27 );
474         }
475         qr/Cannot call sort_in_place with more than 1 argument/,
476             'throws an error when passing two arguments to sort_in_place';
477
478         $obj->_values( [ 3, 9, 5, 22, 11 ] );
479
480         $obj->sort_in_place_curried;
481
482         is_deeply(
483             $obj->_values, [ 22, 11, 9, 5, 3 ],
484             'sort_in_place_curried sorts values'
485         );
486
487         throws_ok { $obj->sort_in_place_curried(27) }
488         qr/Cannot call sort_in_place with more than 1 argument/,
489             'throws an error when passing one argument passed to sort_in_place_curried';
490
491         $obj->_values( [ 1 .. 5 ] );
492
493         is_deeply(
494             [ $obj->map( sub { $_ + 1 } ) ],
495             [ 2 .. 6 ],
496             'map returns the expected values'
497         );
498
499         throws_ok { $obj->map }
500         qr/Cannot call map without at least 1 argument/,
501             'throws an error when passing no arguments to map';
502
503         throws_ok {
504             $obj->map( sub { }, 2 );
505         }
506         qr/Cannot call map with more than 1 argument/,
507             'throws an error when passing two arguments to map';
508
509         throws_ok { $obj->map( {} ) }
510         qr/The argument passed to map must be a code reference/,
511             'throws an error when passing a non coderef to map';
512
513         $obj->_values( [ 1 .. 5 ] );
514
515         is_deeply(
516             [ $obj->map_curried ],
517             [ 2 .. 6 ],
518             'map_curried returns the expected values'
519         );
520
521         throws_ok {
522             $obj->map_curried( sub { } );
523         }
524         qr/Cannot call map with more than 1 argument/,
525             'throws an error when passing one argument passed to map_curried';
526
527         $obj->_values( [ 2 .. 9 ] );
528
529         is_deeply(
530             [ $obj->grep( sub { $_ < 5 } ) ],
531             [ 2 .. 4 ],
532             'grep returns the expected values'
533         );
534
535         throws_ok { $obj->grep }
536         qr/Cannot call grep without at least 1 argument/,
537             'throws an error when passing no arguments to grep';
538
539         throws_ok {
540             $obj->grep( sub { }, 2 );
541         }
542         qr/Cannot call grep with more than 1 argument/,
543             'throws an error when passing two arguments to grep';
544
545         throws_ok { $obj->grep( {} ) }
546         qr/The argument passed to grep must be a code reference/,
547             'throws an error when passing a non coderef to grep';
548
549         my $overloader = Overloader->new( sub { $_ < 5 } );
550         is_deeply(
551             [ $obj->grep($overloader) ],
552             [ 2 .. 4 ],
553             'grep works with obj that overload code dereferencing'
554         );
555
556         is_deeply(
557             [ $obj->grep_curried ],
558             [ 2 .. 4 ],
559             'grep_curried returns the expected values'
560         );
561
562         throws_ok {
563             $obj->grep_curried( sub { } );
564         }
565         qr/Cannot call grep with more than 1 argument/,
566             'throws an error when passing one argument passed to grep_curried';
567
568         $obj->_values( [ 2, 4, 22, 99, 101, 6 ] );
569
570         is(
571             $obj->first( sub { $_ % 2 } ),
572             99,
573             'first returns expected value'
574         );
575
576         throws_ok { $obj->first }
577         qr/Cannot call first without at least 1 argument/,
578             'throws an error when passing no arguments to first';
579
580         throws_ok {
581             $obj->first( sub { }, 2 );
582         }
583         qr/Cannot call first with more than 1 argument/,
584             'throws an error when passing two arguments to first';
585
586         throws_ok { $obj->first( {} ) }
587         qr/The argument passed to first must be a code reference/,
588             'throws an error when passing a non coderef to first';
589
590         is(
591             $obj->first_curried,
592             99,
593             'first_curried returns expected value'
594         );
595
596         throws_ok {
597             $obj->first_curried( sub { } );
598         }
599         qr/Cannot call first with more than 1 argument/,
600             'throws an error when passing one argument passed to first_curried';
601
602         $obj->_values( [ 1 .. 4 ] );
603
604         is(
605             $obj->join('-'), '1-2-3-4',
606             'join returns expected result'
607         );
608
609         is(
610             $obj->join(q{}), '1234',
611             'join returns expected result when joining with empty string'
612         );
613
614         throws_ok { $obj->join }
615         qr/Cannot call join without at least 1 argument/,
616             'throws an error when passing no arguments to join';
617
618         throws_ok { $obj->join( '-', 2 ) }
619         qr/Cannot call join with more than 1 argument/,
620             'throws an error when passing two arguments to join';
621
622         throws_ok { $obj->join( {} ) }
623         qr/The argument passed to join must be a string/,
624             'throws an error when passing a non string to join';
625
626         is_deeply(
627             [ sort $obj->shuffle ],
628             [ 1 .. 4 ],
629             'shuffle returns all values (cannot check for a random order)'
630         );
631
632         throws_ok { $obj->shuffle(2) }
633         qr/Cannot call shuffle with any arguments/,
634             'throws an error when passing an argument passed to shuffle';
635
636         $obj->_values( [ 1 .. 4, 2, 5, 3, 7, 3, 3, 1 ] );
637
638         is_deeply(
639             [ $obj->uniq ],
640             [ 1 .. 4, 5, 7 ],
641             'uniq returns expected values (in original order)'
642         );
643
644         throws_ok { $obj->uniq(2) }
645         qr/Cannot call uniq with any arguments/,
646             'throws an error when passing an argument passed to uniq';
647
648         $obj->_values( [ 1 .. 5 ] );
649
650         is(
651             $obj->reduce( sub { $_[0] * $_[1] } ),
652             120,
653             'reduce returns expected value'
654         );
655
656         throws_ok { $obj->reduce }
657         qr/Cannot call reduce without at least 1 argument/,
658             'throws an error when passing no arguments to reduce';
659
660         throws_ok {
661             $obj->reduce( sub { }, 2 );
662         }
663         qr/Cannot call reduce with more than 1 argument/,
664             'throws an error when passing two arguments to reduce';
665
666         throws_ok { $obj->reduce( {} ) }
667         qr/The argument passed to reduce must be a code reference/,
668             'throws an error when passing a non coderef to reduce';
669
670         is(
671             $obj->reduce_curried,
672             120,
673             'reduce_curried returns expected value'
674         );
675
676         throws_ok {
677             $obj->reduce_curried( sub { } );
678         }
679         qr/Cannot call reduce with more than 1 argument/,
680             'throws an error when passing one argument passed to reduce_curried';
681
682         $obj->_values( [ 1 .. 6 ] );
683
684         my $it = $obj->natatime(2);
685         my @nat;
686         while ( my @v = $it->() ) {
687             push @nat, \@v;
688         }
689
690         is_deeply(
691             [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ],
692             \@nat,
693             'natatime returns expected iterator'
694         );
695
696         @nat = ();
697         $obj->natatime( 2, sub { push @nat, [@_] } );
698
699         is_deeply(
700             [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ],
701             \@nat,
702             'natatime with function returns expected value'
703         );
704
705         throws_ok { $obj->natatime( {} ) }
706         qr/The n value passed to natatime must be an integer/,
707             'throws an error when passing a non integer to natatime';
708
709         throws_ok { $obj->natatime( 2, {} ) }
710         qr/The second argument passed to natatime must be a code reference/,
711             'throws an error when passing a non code ref to natatime';
712
713         $it = $obj->natatime_curried();
714         @nat = ();
715         while ( my @v = $it->() ) {
716             push @nat, \@v;
717         }
718
719         is_deeply(
720             [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ],
721             \@nat,
722             'natatime_curried returns expected iterator'
723         );
724
725         @nat = ();
726         $obj->natatime_curried( sub { push @nat, [@_] } );
727
728         is_deeply(
729             [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ],
730             \@nat,
731             'natatime_curried with function returns expected value'
732         );
733
734         throws_ok { $obj->natatime_curried( {} ) }
735         qr/The second argument passed to natatime must be a code reference/,
736             'throws an error when passing a non code ref to natatime_curried';
737
738         if ( $class->meta->get_attribute('_values')->is_lazy ) {
739             my $obj = $class->new;
740
741             is( $obj->count, 2, 'count is 2 (lazy init)' );
742
743             $obj->_clear_values;
744
745             is_deeply(
746                 [ $obj->elements ], [ 42, 84 ],
747                 'elements contains default with lazy init'
748             );
749
750             $obj->_clear_values;
751
752             $obj->push(2);
753
754             is_deeply(
755                 $obj->_values, [ 42, 84, 2 ],
756                 'push works with lazy init'
757             );
758
759             $obj->_clear_values;
760
761             $obj->unshift( 3, 4 );
762
763             is_deeply(
764                 $obj->_values, [ 3, 4, 42, 84 ],
765                 'unshift works with lazy init'
766             );
767         }
768     }
769     $class;
770 }
771
772 done_testing;