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