1package My::Module::Test; 2 3use strict; 4use warnings; 5 6use Exporter; 7 8our @ISA = ( qw{ Exporter } ); 9 10use PPIx::Regexp; 11use PPIx::Regexp::Dumper; 12use PPIx::Regexp::Element; 13use PPIx::Regexp::Tokenizer; 14use PPIx::Regexp::Util qw{ __choose_tokenizer_class __instance }; 15use Scalar::Util qw{ looks_like_number refaddr }; 16use Test::More 0.88; 17 18our $VERSION = '0.082'; 19 20use constant ARRAY_REF => ref []; 21 22our @EXPORT_OK = qw{ 23 builder 24 cache_count 25 choose 26 klass 27 cmp_ok 28 content 29 count 30 diag 31 different 32 done_testing 33 dump_result 34 error 35 fail 36 false 37 finis 38 equals 39 is 40 navigate 41 note 42 ok 43 parse 44 pass 45 plan 46 ppi 47 result 48 replace_characters 49 skip 50 tokenize 51 true 52 value 53}; 54 55our @EXPORT = @EXPORT_OK; ## no critic (ProhibitAutomaticExportation) 56 57push @EXPORT_OK, qw{ __quote }; 58 59my ( 60 $initial_class, # For static methods; set by parse() or tokenize() 61 $kind, # of thing; set by parse() or tokenize() 62 $nav, # Navigation used to get to current object, as a 63 # string. 64 $obj, # Current object: 65 # PPIx::Regexp::Tokenizer if set by tokenize(), 66 # PPIx::Regexp if set by parse(), or 67 # PPIx::Regexp::Element if set by navigate(). 68 $parse, # Result of parse: 69 # array ref if set by tokenize(), or 70 # PPIx::Regexp object if set by parse() 71 %replace_characters, # Troublesome characters replaced in output 72 # before testing 73 $result, # Operation result. 74); 75 76sub builder { 77 return Test::More->builder(); 78} 79 80sub cache_count { 81 my ( $expect ) = @_; 82 defined $expect or $expect = 0; 83 $obj = undef; 84 $parse = undef; 85 _pause(); 86 $result = PPIx::Regexp->__cache_size(); 87 # cperl does not seem to like goto &xxx; it throws a deep recursion 88 # error if you do it enough times. 89 $Test::Builder::Level = $Test::Builder::Level + 1; 90 return is( $result, $expect, 91 "Should be $expect leftover cache contents" ); 92} 93 94sub choose { 95 my @args = @_; 96 $obj = $parse; 97 return navigate( @args ); 98} 99 100sub klass { 101 my ( $class ) = @_; 102 $result = ref $obj || $obj; 103 # cperl does not seem to like goto &xxx; it throws a deep recursion 104 # error if you do it enough times. 105 $Test::Builder::Level = $Test::Builder::Level + 1; 106 if ( defined $class ) { 107 return isa_ok( $obj, $class, "$kind $nav" ); 108 } else { 109 return is( ref $obj || undef, $class, "Class of $kind $nav" ); 110 } 111} 112 113sub content { ## no critic (RequireArgUnpacking) 114 # For some reason cperl seems to have no problem with this 115 unshift @_, 'content'; 116 goto &_method_result; 117} 118 119sub count { 120 my ( @args ) = @_; 121 my $expect = pop @args; 122 # cperl does not seem to like goto &xxx; it throws a deep recursion 123 # error if you do it enough times. 124 $Test::Builder::Level = $Test::Builder::Level + 1; 125 if ( ARRAY_REF eq ref $parse ) { 126 $result = @{ $parse }; 127 return is( $result, $expect, "Expect $expect tokens" ); 128 } elsif ( ARRAY_REF eq ref $obj ) { 129 $result = @{ $obj }; 130 return is( $result, $expect, "Expect $expect tokens" ); 131 } elsif ( $obj->can( 'children' ) ) { 132 $result = $obj->children(); 133 return is( $result, $expect, "Expect $expect children" ); 134 } else { 135 $result = $obj->can( 'children' ); 136 return ok( $result, ref( $obj ) . "->can( 'children')" ); 137 } 138} 139 140sub different { 141 my @args = @_; 142 @args < 3 and unshift @args, $obj; 143 my ( $left, $right, $name ) = @args; 144 # cperl does not seem to like goto &xxx; it throws a deep recursion 145 # error if you do it enough times. 146 $Test::Builder::Level = $Test::Builder::Level + 1; 147 if ( ! defined $left && ! defined $right ) { 148 return ok( undef, $name ); 149 } elsif ( ! defined $left || ! defined $right ) { 150 return ok( 1, $name ); 151 } elsif ( ref $left && ref $right ) { 152 return ok( refaddr( $left ) != refaddr( $right ), $name ); 153 } elsif ( ref $left || ref $right ) { 154 return ok( 1, $name ); 155 } elsif ( looks_like_number( $left ) && looks_like_number( $right ) ) { 156 return ok( $left != $right, $name ); 157 } else { 158 return ok( $left ne $right, $name ); 159 } 160} 161 162sub dump_result { 163 my ( $opt, @args ) = _parse_constructor_args( { test => 1 }, @_ ); 164 if ( $opt->{test} ) { 165 my ( $expect, $name ) = splice @args, -2; 166 my $got = PPIx::Regexp::Dumper->new( $obj, @args )->string(); 167 # cperl does not seem to like goto &xxx; it throws a deep 168 # recursion error if you do it enough times. 169 $Test::Builder::Level = $Test::Builder::Level + 1; 170 return is( $got, $expect, $name ); 171 } elsif ( __instance( $result, 'PPIx::Regexp::Tokenizer' ) || 172 __instance( $result, 'PPIx::Regexp::Element' ) ) { 173 diag( PPIx::Regexp::Dumper->new( $obj, @args )->string() ); 174 } elsif ( eval { require YAML; 1; } ) { 175 diag( "Result dump:\n", YAML::Dump( $result ) ); 176 } elsif ( eval { require Data::Dumper; 1 } ) { 177 diag( "Result dump:\n", Data::Dumper::Dumper( $result ) ); 178 } else { 179 diag( "Result dump unavailable.\n" ); 180 } 181 return; 182} 183 184sub equals { 185 my @args = @_; 186 @args < 3 and unshift @args, $obj; 187 my ( $left, $right, $name ) = @args; 188 # cperl does not seem to like goto &xxx; it throws a deep recursion 189 # error if you do it enough times. 190 $Test::Builder::Level = $Test::Builder::Level + 1; 191 if ( ! defined $left && ! defined $right ) { 192 return ok( 1, $name ); 193 } elsif ( ! defined $left || ! defined $right ) { 194 return ok( undef, $name ); 195 } elsif ( ref $left && ref $right ) { 196 return ok( refaddr( $left ) == refaddr( $right ), $name ); 197 } elsif ( ref $left || ref $right ) { 198 return ok( undef, $name ); 199 } elsif ( looks_like_number( $left ) && looks_like_number( $right ) ) { 200 return ok( $left == $right, $name ); 201 } else { 202 return ok( $left eq $right, $name ); 203 } 204} 205 206sub error { ## no critic (RequireArgUnpacking) 207 unshift @_, 'error'; 208 goto &_method_result; 209} 210 211sub false { 212 my ( $method, $args ) = @_; 213 ARRAY_REF eq ref $args 214 or $args = [ $args ]; 215 my $class = ref $obj; 216 # cperl does not seem to like goto &xxx; it throws a deep recursion 217 # error if you do it enough times. 218 $Test::Builder::Level = $Test::Builder::Level + 1; 219 if ( $obj->can( $method ) ) { 220 $result = $obj->$method( @{ $args } ); 221 my $fmtd = _format_args( $args ); 222 return ok( ! $result, "$class->$method$fmtd is false" ); 223 } else { 224 $result = undef; 225 return ok( undef, "$class->$method() exists" ); 226 } 227} 228 229sub finis { 230 $obj = $parse = $result = undef; 231 _pause(); 232 $result = PPIx::Regexp::Element->__parent_keys(); 233 # cperl does not seem to like goto &xxx; it throws a deep recursion 234 # error if you do it enough times. 235 $Test::Builder::Level = $Test::Builder::Level + 1; 236 return is( $result, 0, 'Should be no leftover objects' ); 237} 238 239{ 240 241 my %array = map { $_ => 1 } qw{ 242 children delimiters finish schildren start tokens type 243 }; 244 245 sub navigate { 246 my @args = @_; 247 my $scalar = 1; 248 @args > 1 249 and ARRAY_REF eq ref $args[-1] 250 and @{ $args[-1] } == 0 251 and $array{$args[-2]} 252 and $scalar = 0; 253 my @nav = (); 254 while ( @args ) { 255 if ( __instance( $args[0], 'PPIx::Regexp::Element' ) ) { 256 $obj = shift @args; 257 } elsif ( ARRAY_REF eq ref $obj ) { 258 my $inx = shift @args; 259 push @nav, $inx; 260 $obj = $obj->[$inx]; 261 } else { 262 my $method = shift @args; 263 my $args = shift @args; 264 ARRAY_REF eq ref $args 265 or $args = [ $args ]; 266 push @nav, $method, $args; 267 $obj->can( $method ) or return; 268 if ( @args || $scalar ) { 269 $obj = $obj->$method( @{ $args } ) or return; 270 } else { 271 $obj = [ $obj->$method( @{ $args } ) ]; 272 } 273 } 274 } 275 $nav = __quote( @nav ); 276 $nav =~ s/ ' ( \w+ ) ' , /$1 =>/smxg; 277 $nav =~ s/ \[ \s+ \] /[]/smxg; 278 $result = $obj; 279 return $obj; 280 } 281 282} 283 284sub parse { ## no critic (RequireArgUnpacking) 285 my ( $opt, $regexp, @args ) = _parse_constructor_args( 286 { test => 1 }, @_ ); 287 $initial_class = 'PPIx::Regexp'; 288 $kind = 'element'; 289 $result = $obj = $parse = PPIx::Regexp->new( $regexp, @args ); 290 $nav = ''; 291 $opt->{test} or return; 292 # cperl does not seem to like goto &xxx; it throws a deep recursion 293 # error if you do it enough times. 294 $Test::Builder::Level = $Test::Builder::Level + 1; 295 return isa_ok( $parse, 'PPIx::Regexp', 296 _replace_characters( $regexp ) ); 297} 298 299sub ppi { ## no critic (RequireArgUnpacking) 300 my @args = @_; 301 my $expect = pop @args; 302 $result = undef; 303 defined $obj and $result = $obj->ppi()->content(); 304 my $safe; 305 if ( defined $result ) { 306 ($safe = $result) =~ s/([\\'])/\\$1/smxg; 307 } else { 308 $safe = 'undef'; 309 } 310 # cperl does not seem to like goto &xxx; it throws a deep recursion 311 # error if you do it enough times. 312 $Test::Builder::Level = $Test::Builder::Level + 1; 313 return is( $result, $expect, "$kind $nav ppi() content '$safe'" ); 314} 315 316sub replace_characters { 317 %replace_characters = @_; 318 return; 319} 320 321sub result { 322 return $result; 323} 324 325sub tokenize { ## no critic (RequireArgUnpacking) 326 my ( $opt, $regexp, @args ) = _parse_constructor_args( 327 { test => 1, tokens => 1 }, @_ ); 328 my %args = @args; 329 $initial_class = __choose_tokenizer_class( $regexp, \%args ); 330 $kind = 'token'; 331 $obj = $initial_class->new( $regexp, @args ); 332 if ( $obj && $opt->{tokens} ) { 333 $parse = [ $obj->tokens() ]; 334 } else { 335 $parse = []; 336 } 337 $result = $parse; 338 $nav = ''; 339 $opt->{test} or return; 340 $Test::Builder::Level = $Test::Builder::Level + 1; 341 return isa_ok( $obj, 'PPIx::Regexp::Tokenizer', 342 _replace_characters( $regexp ) ); 343} 344 345sub true { ## no critic (RequireArgUnpacking) 346 my ( $method, $args ) = @_; 347 ARRAY_REF eq ref $args 348 or $args = [ $args ]; 349 my $class = ref $obj; 350 # cperl does not seem to like goto &xxx; it throws a deep recursion 351 # error if you do it enough times. 352 $Test::Builder::Level = $Test::Builder::Level + 1; 353 if ( $obj->can( $method ) ) { 354 $result = $obj->$method( @{ $args } ); 355 my $fmtd = _format_args( $args ); 356 return ok( $result, "$class->$method$fmtd is true" ); 357 } else { 358 $result = undef; 359 return ok( undef, "$class->$method() exists" ); 360 } 361} 362 363sub value { ## no critic (RequireArgUnpacking) 364 my ( $method, $args, $expect ) = @_; 365 ARRAY_REF eq ref $args 366 or $args = [ $args ]; 367 368 my $invocant = $obj || $initial_class; 369 my $class = ref $obj || $obj || $initial_class; 370 # cperl does not seem to like goto &xxx; it throws a deep recursion 371 # error if you do it enough times. 372 $Test::Builder::Level = $Test::Builder::Level + 1; 373 if ( ! $invocant->can( $method ) ) { 374 return ok( undef, "$class->$method() exists" ); 375 } 376 377 $result = ARRAY_REF eq ref $expect ? 378 [ $invocant->$method( @{ $args } ) ] : 379 $invocant->$method( @{ $args } ); 380 381 my $fmtd = _format_args( $args ); 382 my $answer = _format_args( [ $expect ], bare => 1 ); 383 if ( ref $result ) { 384 return is_deeply( $result, $expect, 385 "${class}->$method$fmtd is $answer" ); 386 } else { 387 return is( $result, $expect, "${class}->$method$fmtd is $answer" ); 388 } 389} 390 391sub _format_args { 392 my ( $args, %opt ) = @_; 393 my @rslt; 394 foreach my $arg ( @{ $args } ) { 395 if ( ! defined $arg ) { 396 push @rslt, 'undef'; 397 } elsif ( looks_like_number( $arg ) ) { 398 push @rslt, $arg; 399 } else { 400 push @rslt, $arg; 401 $rslt[-1] =~ s/ ' /\\'/smxg; 402 $rslt[-1] = "'$rslt[-1]'"; 403 } 404 } 405 my $string = join ', ', @rslt; 406 $opt{bare} and return $string; 407 @rslt or return '()'; 408 return "( $string )"; 409} 410 411sub _method_result { ## no critic (RequireArgUnpacking) 412 my ( $method, @args ) = @_; 413 my $expect = pop @args; 414 $result = undef; 415 defined $obj and $result = $obj->$method(); 416 my $safe; 417 if ( defined $result ) { 418 ($safe = $result) =~ s/([\\'])/\\$1/smxg; 419 $safe = "'$safe'"; 420 } else { 421 $safe = 'undef'; 422 } 423 @_ = _replace_characters( $result, $expect, "$kind $nav $method $safe" ); 424 goto &is; 425} 426 427sub _parse_constructor_args { 428 my ( $opt, @args ) = @_; 429 my @rslt = ( $opt ); 430 foreach my $arg ( @args ) { 431 if ( $arg =~ m/ \A - -? (no)? (\w+) \z /smx && 432 exists $opt->{$2} ) { 433 $opt->{$2} = !$1; 434 } else { 435 push @rslt, $arg; 436 } 437 } 438 return @rslt; 439} 440 441sub _pause { 442 if ( eval { require Time::HiRes; 1 } ) { # Cargo cult programming. 443 Time::HiRes::sleep( 0.1 ); # Something like this is 444 } else { # in PPI's 445 sleep 1; # t/08_regression.t, and 446 } # who am I to argue? 447 return; 448} 449 450# quote a string. 451sub __quote { 452 my @args = @_; 453 my @rslt; 454 foreach my $item ( @args ) { 455 if ( __instance( $item, 'PPIx::Regexp::Element' ) ) { 456 $item = $item->content(); 457 } 458 if ( ! defined $item ) { 459 push @rslt, 'undef'; 460 } elsif ( ARRAY_REF eq ref $item ) { 461 push @rslt, join( ' ', '[', __quote( @{ $item } ), ']' ); 462 } elsif ( looks_like_number( $item ) ) { 463 push @rslt, $item; 464 } else { 465 $item =~ s/ ( [\\'] ) /\\$1/smxg; 466 push @rslt, "'$item'"; 467 } 468 } 469 return join( ', ', @rslt ); 470} 471 472sub _replace_characters { 473 my @arg = @_; 474 if ( keys %replace_characters ) { 475 foreach ( @arg ) { 476 $_ = join '', 477 # The following assumes I will never want to replace 0. 478 map { $replace_characters{$_} || $_ } 479 split qr<>; 480 } 481 } 482 wantarray 483 or return join '', @arg; 484 return @arg; 485} 486 4871; 488 489__END__ 490 491=head1 NAME 492 493My::Module::Test - support for testing PPIx::Regexp 494 495=head1 SYNOPSIS 496 497 use lib qw{ inc }; 498 use My::Module::Test; 499 500 parse ( '/foo/' ); 501 value ( failures => [], 0 ); 502 klass ( 'PPIx::Regexp' ); 503 choose ( child => 0 ); 504 klass ( 'PPIx::Regexp::Token::Structure' ); 505 content ( '' ); 506 # and so on 507 508=head1 DETAILS 509 510This module is B<private> to the C<PPIx-Regexp> module. Its contents can 511be changed without warning. This was always the intent, and this 512paragraph should have been included in the POD much earlier than it 513actually was. 514 515This module exports various subroutines in support of testing 516C<PPIx::Regexp>. Most of these are tests, with C<Test::More> doing the 517dirty work. A few simply set up data for tests. 518 519The whole test rig works by parsing (or tokenizing) a regular 520expression, followed by a series of unit tests on the results of the 521parse. Each set of unit tests is performed by selecting an object to 522test using the C<choose> or C<navigate> subroutine, followed by the 523tests to be performed on that object. A few tests do not test parse 524objects, but rather the state of the system as a whole. 525 526The following subroutines are exported: 527 528=head2 builder 529 530This subroutine returns the underlying L<Test::Builder|Test::Builder> 531object. 532 533=head2 cache_count 534 535 cache_count( 1 ); 536 537This test compares the number of objects in the C<new_from_cache> cache 538to its argument, succeeding if they are equal. If no argument is passed, 539the default is 0. 540 541=head2 choose 542 543 choose( 2 ); # For tokenizer 544 choose( child => 1, child => 2, type => 0 ); # For full parse 545 546This subroutine does not itself represent a test. It chooses an object 547from the parse tree for further testing. If testing a tokenizer, the 548argument is the token number (from 0) to select. If testing a full 549parse, the arguments are the navigation methods used to reach the 550object to be tested, starting from the C<PPIx::Regexp> object. The 551arguments to the methods are passed in an array reference, but if there 552is a single argument it can be passed as a scalar, as in the example. 553 554=head2 klass 555 556 klass( 'PPIx::Regexp::Token::Structure' ); 557 558This test checks to see if the current object is of the given class, and 559succeeds if it is. If the current object is C<undef>, the test fails. 560 561This test was C<class>, but that tends to conflict with object systems. 562 563=head2 content 564 565 content( '\N{LATIN SMALL LETTER A}' ); 566 567This test checks to see if the C<content> method of the current object 568is equal to the given string. If the current object is C<undef>, the 569test fails. 570 571=head2 cmp_ok 572 573This subroutine is exported from L<Test::More|Test::More>. 574 575=head2 count 576 577 count( 42 ); 578 579This test checks the number of objects returned by an operation that 580returns more than one object. It succeeds if the number of objects 581returned is equal to the given number. 582 583This test is valid only after C<tokenize>, or a C<choose> or C<navigate> 584whose argument list ends in one of 585 586 children => [] 587 finish => [] 588 start => [] 589 type => [] 590 591=head2 different 592 593 different( $o1, $o2, 'Test name' ); 594 595This test compares two things, succeeding if they are different. 596References are compared by reference address and scalars by value 597(numeric or string comparison as appropriate). If the first argument is 598omitted it defaults to the current object. 599 600=head2 dump_result 601 602 dump_result( tokens => 1, <<'EOD', 'Test tokenization dump' ); 603 ... expected dump here ... 604 EOD 605 606This test performs the specified dump on the current object and succeeds 607if the result matches the expectation. The name of the test is the last 608argument, and the expected result is the next-to-last argument. All 609other arguments are passed to 610L<PPIx::Regexp::Dumper|PPIx::Regexp::Dumper>. 611 612Well, almost all other arguments are passed to the dumper. You can 613specify C<--notest> to skip the test. In this case the result of the 614last operation is dumped. L<PPIx::Regexp::Dumper|PPIx::Regexp::Dumper> 615is used if appropriate; otherwise you get a L<YAML|YAML> dump if that is 616available, or a L<Data::Dumper|Data::Dumper> dump if not. If no dumper 617class can be found, a diagnostic is produced. You can also specify 618C<--test>, but this is the default. This option is removed from the 619argument list before the test name (etc) is determined. 620 621=head2 equals 622 623 equals( $o1, $o2, 'Test name' ); 624 625This test compares two things, succeeding if they are equal. References 626are compared by reference address and scalars by value (numeric or string 627comparison as appropriate). If the first argument is omitted it defaults 628to the current object. 629 630=head2 false 631 632 false( significant => [] ); 633 634This test succeeds if the given method, with the given arguments, called 635on the current object, returns a false value. 636 637=head2 finis 638 639 finis(); 640 641This test should be last in a series, and no references to parse objects 642should be held when it is run. It checks the number of objects in the 643internal C<%parent> hash, and succeeds if it is zero. 644 645=head2 navigate 646 647 navigate( snext_sibling => [] ); 648 649Like C<choose>, this is not a test, but selects an object for testing. 650Unlike C<choose>, selection starts from the current object, not the top 651of the parse tree. 652 653=head2 parse 654 655 parse( 's/foo/bar/g' ); 656 657This test parses the given regular expression into a C<PPIx::Regexp> 658object, and succeeds if a C<PPIx::Regexp> object was in fact generated. 659 660If you specify argument C<--notest>, the parse is done but no test is 661performed. You would do this if you expected the parse to fail (e.g. you 662are testing error handling). You can also explicitly specify C<--test>, 663but this is the default. 664 665All other arguments are passed to the L<PPIx::Regexp|PPIx::Regexp> 666constructor. 667 668=head2 plan 669 670This subroutine is exported from L<Test::More|Test::More>. 671 672=head2 content 673 674 ppi( '$foo' ); 675 676This test calls the current object's C<ppi()> method, and checks to see 677if the content of the returned L<PPI::Document|PPI::Document> is equal 678to the given string. If the current object is C<undef> or does not have 679a C<ppi()> method, the test fails. 680 681=head2 result 682 683 my $val = result(); 684 685This subroutine returns the result of the most recent operation that 686actually produces one. It should be called immediately after the 687operation, mostly because I have not documented all the subroutines that 688produce a result. 689 690=head2 tokenize 691 692 tokenize( 'm/foo/smx' ); 693 694This test tokenizes the given regular expression into a 695C<PPIx::Regexp::Tokenizer> object, and succeeds if a 696C<PPIx::Regexp::Tokenizer> object was in fact generated. 697 698If you specify argument C<--notest>, the parse is done but no test is 699performed. You would do this if you expected the parse to fail (e.g. you 700are testing error handling). You can also explicitly specify C<--test>, 701but this is the default. 702 703If you specify argument C<--notokens>, the tokenizer is built, but the 704tokens are not extracted. You would do this when you want a subsequent 705operation to call C<tokens()>. You can also explicitly specify 706C<--tokens>, but this is the default. 707 708All other arguments are passed to the 709L<PPIx::Regexp::Tokenizer|PPIx::Regexp::Tokenizer> constructor. 710 711=head2 true 712 713 true( significant => [] ); 714 715This test succeeds if the given method, with the given arguments, called 716on the current object, returns a true value. 717 718=head2 value 719 720 value( max_capture_number => [], 3 ); 721 722This test succeeds if the given method, with the given arguments, called 723on the current object, returns the given value. 724 725If the current object is undefined, the given method is called on the 726intended initial class, otherwise there would be no way to test the 727errstr() method. 728 729The result of the method call is accessable via the L<result()|/result> 730subroutine. 731 732=head1 SUPPORT 733 734Support is by the author. Please file bug reports at 735L<https://rt.cpan.org/Public/Dist/Display.html?Name=PPIx-Regexp>, 736L<https://github.com/trwyant/perl-PPIx-Regexp/issues>, or in 737electronic mail to the author. 738 739=head1 AUTHOR 740 741Thomas R. Wyant, III F<wyant at cpan dot org> 742 743=head1 COPYRIGHT AND LICENSE 744 745Copyright (C) 2009-2021 by Thomas R. Wyant, III 746 747This program is free software; you can redistribute it and/or modify it 748under the same terms as Perl 5.10.0. For more details, see the full text 749of the licenses in the directory LICENSES. 750 751This program is distributed in the hope that it will be useful, but 752without any warranty; without even the implied warranty of 753merchantability or fitness for a particular purpose. 754 755=cut 756 757# ex: set textwidth=72 : 758