NAME

Test::Map::Tube - Module for testing Map::Tube data and features.

VERSION

Version 3.96

DESCRIPTION

The module may be used for two complementary purposes: during development of a tube map, it checks for map authors whether the data structures are consistent and follow the rules for Map::Tube data. The other use case is during installation of a map by the user, where it ensures a functioning installation.

SYNOPSIS

Validate ONLY map data.

use strict;
use warnings;
use Test::More;

my $min_ver = 3.93;
eval "use Test::Map::Tube $min_ver";
plan skip_all => "Test::Map::Tube $min_ver required" if $@;

use Map::Tube::London;
ok_map_data(Map::Tube::London->new);

Validate ONLY map functions.

use strict;
use warnings;
use Test::More;

my $min_ver = 3.93;
eval "use Test::Map::Tube $min_ver";
plan skip_all => "Test::Map::Tube $min_ver required" if $@;
plan skip_all => 'These tests are for authors only!'
    unless ( $ENV{AUTHOR_TESTING} || $ENV{RELEASE_TESTING} );

use Map::Tube::London; # or any other Map::Tube map
ok_map_functions(Map::Tube::London->new);

Validate BOTH map data and functions.

use strict;
use warnings;
use Test::More;

my $min_ver = 3.93;
eval "use Test::Map::Tube $min_ver tests => 2";
plan skip_all => "Test::Map::Tube $min_ver required" if $@;

use Map::Tube::London; # or any other Map::Tube map
my $map = Map::Tube::London->new;
ok_map_data($map);
ok_map_functions($map);

Validate map data, functions and routes.

use strict;
use warnings;
use Test::More;

my $min_ver = 3.75;
eval "use Test::Map::Tube $min_ver tests => 3";
plan skip_all => "Test::Map::Tube $min_ver required" if $@;

use Map::Tube::London; # or any other Map::Tube map
my $map = Map::Tube::London->new;
ok_map_data($map);
ok_map_functions($map);

my @routes = (
    "Route 1|Tower Gateway|Aldgate|Tower Gateway, Tower Hill, Aldgate",
    "Route 2|Liverpool Street|Monument|Liverpool Street, Bank, Monument",
);
ok_map_routes($map, \@routes);

For testing at install time, there are only two tests: ok_map_functions( ) and ok_map_routes( ). ok_map_functions( ) internally runs several different subtests.

For testing during development, there are a number of separate tests, each checking for a particular issue. Usually, though, a portmanteau test, ok_map_data( ), is employed which will run almost all the individual tests in one go. Details are provided below.

METHODS

The portmanteau tests

ok_map_functions( $map [, $message ] )

Validates the map functions. As its first argument, it expects an object of a package that has taken the Moo role Map::Tube. You can optionally pass a $message that will be printed in case of a problem. For an example, see the "SYNOPSIS". For this method, you would require Map::Tube v3.75 or above.

This test should always run when installing.

ok_map_routes( $map, \@routes [, $message ] )

Validates the given routes. It expects an object of a package that has taken the Moo role Map::Tube and a reference to an array of strings each of which describes a route's details in the format shown below:

my @routes = (
    "Route 1|A1|A3|A1,A2,A3",
    "Route 2|A1|B1|A1,A2,B1",
);

There are four elements for each route, separated by the | character. The first element of each route specification is a purely informative name. The next element is the name of a starting station in the tube system, then the name of a target station. Finally, there is a list of stations that the route is expected to be comprised of, including the starting and the target station. The stations must be separated by a blank and a comma. For an example, see the "SYNOPSIS". You can optionally pass a $message. For this method, you would require Test::Map::Tube v0.15 or above.

This test should always run when installing.

ok_map_data($map [, \%args, $message ] )

Validates the map data. It expects an object of a package that has taken the Moo role Map::Tube. You can optionally pass arguments as described below and/or a $message. (If the second argument is a string, it is assumed to be the $message.) For full use of this method, you would require Test::Map::Tube v3.93 or above.

This method is mainly of use while developing a map. It is designed to catch most (formal) problems that are frequently introduced into the map data structures, usually by accident or oversight, due to the complex nature of the structures. As such, it is a good strategy to configure this test so that it is not run during installation by the end user, as shown in the first example in the "SYNOPSIS" above.

By default, this method just runs almost all of the individual tests described below. The only exceptions (by default) are tests that have a substantial chance of flagging false positives, i.e., ok_station_names_different( ), ok_station_names_complete( ), and ok_links_bidirectional( ). For details, see those individual methods below. A test using ok_map_data( ) counts as just one test, no matter how many individual tests will be performed.

Thus, the most common way of using this method is as indicated in the first example in the "SYNOPSIS". However, you are encouraged to include also the optional tests as described below.

The tests to be run can be configured by the (optional) second argument, which should be a hash reference. The underlying hash may contain one or more of the following:

  • Under the key name a name which will be displayed (possibly alongside a $message) in case of a failing test.

  • Under the key message an additional message. This is just an alternative to specifying $message as a third parameter.

  • Under the key max_messages a maximum number of messages on failed test items. This may be useful during early development phases in order not become overwhelmed.

  • The name of any of the individual tests below may be used as a key. In this case, the value may be one of the following:

    • undef or any other false value. In this case, the named test will not be performed (although by default it might).

    • 1 or any other scalar regarded by Perl as true. In this case, the named test will be performed (although by default it might not), providing no further arguments to the test.

    • A hash reference. In this case, the named test will be performed (although by default it might not), where the hash reference will be passed to the test as arguments.

If the method is called in a void or a scalar context, the tests are performed and diagnostics on any issues will be output to STDERR. The return value will be true (if all individual tests have passed) or false (fail). If the method is called in a list context, no diagnostics are output. Instead, they are all gathered. The return value is either a list containing a single true value (in case of all individual tests having passed), or it will be a list of a false value, followed by the individual diagnostic messages. In this case it is the responsibility of the caller to display these values in an appropriate fashion.

Here are two full examples:

    ok_map_data( Map::Tube::London->new,
                 { ok_map_connected => undef, # Do not check whether the map is connected
				   ok_links_bidirectional => { exclude => [ 'Elizabeth', 'Piccadilly' ] },
                                              # Do check whether all lines are fully bidirectional,
                                              # except the two named lines
                   ok_station_names_different => 1, # Check for similar-looking names
                 }
               );

    my( $ok, @messages) = ok_map_data( Map::Tube::London->new ); # perform default tests

ok_map( $map [, \%args, $message ] )

This is a synonym for ok_map_data( ), for backward compatibility.

not_ok_map_data( $map [, \%args, $message ] )

This is the negation of ok_map_data( ). It is used very rarely, if ever.

not_ok_map( $map [, \%args, $message ] )

This is a synonym for not_ok_map_data( ), for backward compatibility.

The individual developer tests for the map as a whole

ok_map_loadable( $map [, \%args] )

Checks whether the map object is defined, looks like a Map::Tube object and contains valid data as per Map::Tube built-in base line checks. It does not perform any deep data validity checks beyond.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

The other individual tests presuppose that the map has successfully loaded, so it is good practice to always check this condition first.

ok_map_connected( $map [, \%args] )

Checks whether the whole map is connected, i.e., whether there are routes between any two stations. (This test disregards whether some links are unidirectional.) If so, it checks reachability when honouring unidirectionality (if any); this is a stricter test. If either form of universal reachability is not given, the diagnostics provide examples of stations in different components.

It is extremely rare for maps not to be connected; hence, this test will catch many cases of erroneously unidirectional links. However, for the case where a map is known not to be connected, the expected maximum number of components may be specified in the optional arguments, using the key max_allowed. The value defaults to 1.

Otherwise, the only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

The individual developer tests focussing on lines

ok_line_definitions( $map [, \%args] )

Checks whether any line IDs contain a comma or a colon, which is forbidden for syntactical reasons. Also checks whether color specifications are either a standard hex HTML color code (#RRGGBB) or a name from a pre-defined set of color names (see Map::Tube::Utils).

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_line_names_unique( $map [, \%args] )

Checks whether line names are unique, even when disregarding case. (Station names must also be unique, but this is checked by Map::Tube on load, so we do not need to repeat that test.)

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_line_ids_unique( $map [, \%args] )

Checks whether line ids are unique. Note that line IDs (as opposed to station IDs) are case-sensitive, i.e., "LINE" and "line" are two different lines.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_lines_used( $map [, \%args] )

Checks whether all the lines defined in the map are actually serving at least two stations (either as an ordinary line or using the other_link construct). (Conversely, all lines serving some station must be defined in the map (except for lines used within the other_link construct), but this is already checked by Map::Tube, so we do not ned to repeat that test.) Also, lines must not come up both in ordinary links and in other_link.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_lines_indexed( $map [, \%args] )

Checks whether each line has either all or no station at all indexed (but not some aye, some nay). Example: If there is a station with an attribute such as line="LINE1:42", then there must not be any station with the attribute line="LINE1". (This is a syntactical requirement of Map::Tube needed for proper functioning.)

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_lines_run_through( $map [, \%args] )

Checks whether each line is at least weakly connected, i.e., there should be no gaps in any lines (at least not when disregarding directionality).

It is extremely rare for lines not to be (weakly) connected; hence, this test will catch many cases of erroneously missing links. However, for the case where a line is known not to be connected, one or more lines can be exempted from this check. Use the optional hash ref argument with the key exclude; the value may be a single line ID (not a line name!) or a reference to a list of line IDs. Here are two examples:

ok_lines_run_through( Map::Tube::London->new, { exclude => 'Piccadilly' } );

ok_lines_run_through( Map::Tube::London->new, { exclude => [ 'Piccadilly', 'Jubilee' ] } );

Otherwise, the only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

Checks whether all links are bidirectional, i.e., whether all links work symmetrically in both directions. Note that it is perfectly legal for this not to be the case, however, so this is a fallible test. For this reason, this is one of the tests that is not performed by ok_map_data( ) by default, because the rate of false positives is considered to be too high.

It is, however, good practice to include this optional test when starting to develop a map, because it catches a substantial part of hard-to-notice accidental omissions of links. If your map turns out to include lines that have (at least partially) unidirectional links, this will most likely be confined to one or very few lines. In this case, use the optional hash ref argument with the key exclude; the value may be a single line ID (not a line name!) or a reference to a list of line IDs. Here are two examples:

ok_links_bidirectional( Map::Tube::London->new, { exclude => 'Piccadilly' } );

ok_links_bidirectional( Map::Tube::London->new, { exclude => [ 'Piccadilly', 'Elizabeth' ] } );

Otherwise, the only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

The individual developer tests focussing on stations

ok_station_ids( $map [, \%args] )

Checks whether any station IDs contain a comma or a colon, which is not allowed for syntactical reasons.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_station_names_different( $map [, \%args] )

Station names must be unique, but since Map::Tube already tests this on init, we do not repeat this test here. Instead, this method checks whether no two station names look "similar enough" to assume one might be a typo for the other. Name similarity is determined using the Levenshtein edit distance (see Text::Levenshtein::XS). By default, any two names having a distance of at most 2 are considered "too similar".

It is, of course, perfectly legal for two station names to look "similar". Hence, this is in many ways a fallible test and cannot be applied indiscriminately. For that reason, it is not among the tests carried out by ok_map_data( ) by default. During development, it is nevertheless a good idea to use this test. It can be tweaked in two ways using the optional hash ref argument in order to prevent over-alerting. Both ways may be used at the same time.

  • Use the hash key dist_limit to set the maximum ciritical distance to a value other than 2. Setting it to 3 or more will in general produce more hits, setting it to 1 will in general produce fewer hits, and setting it to 0 will disable this test.

  • Use the hash key max_allowed to declare that a certain number of hits is expected and is not considered to be problematic. Only if this threshold is exceeded will the test be considered a fail.

Two examples:

ok_station_names_different( Map::Tube::London->new, { dist_limit => 1 } )

ok_station_names_different( Map::Tube::London->new, { dist_limit => 1, max_allowed => 3 } )

While this test is useful to find accidental typos, there do exist maps where the false positive rate is so high that this test becomes useless.

Otherwise, the only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_station_names_complete( $map [, \%args] )

This checks whether no station name is a proper prefix of another station's name. This catches accidental double entries for a single station, e.g. once as Baker St and once as Baker Street.

It is, of course, perfectly legal for such a situation to happen, e.g., New Cross and New Cross Gate. Hence, this is in many ways a fallible test and cannot be applied indiscriminately. For that reason, it is not among the tests carried out by ok_map_data( ) by default. During development, it is nevertheless a good idea to use this test. It can be tweaked using the optional hash ref argument with the key max_allowed to declare that a certain number of hits is expected and is not considered to be problematic. Only if this threshold is exceeded will the test be considered a fail. The default threshold is 0 so that all issues will be reported, but if you know that your map contains five legitimate pairs of such station names, specify { max_allowed => 5 }.

Otherwise, the only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_stations_linked( $map [, \%args] )

This checks whether all stations that are named as the target of a link are also explicitly declared as a station.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_stations_self_linked( $map [, \%args] )

This checks whether any station declares a link to itself, which should not happen.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_stations_multilinked( $map [, \%args] )

This checks whether any station lists another station as the target of a link more than once. (Any two stations may be linked by more than one line, but the link should still name each linked station once only.)

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_stations_multilined( $map [, \%args] )

This checks whether any station lists the same line more than once. (This applies only to regular lines using the line attribute. By contrast, in other_link it is perfectly normal for any given "line" to occur more than once.

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

ok_stations_linked_share_lines( $map [, \%args] )

This checks whether two stations that are linked share at least one line (via an ordinary link or via other_link). Conversely, it checks whether lines serving a particular station (as per the ordinary line attribute) are also listed as serving one of the linked stations using the line attribute. E.g., if a station with ID A has the attributes line="Line1" link="B,C", then at least one of the stations B and C must also contain line="Line1".

Finally, lines named within an other_link at some station must also occur within an other_link at the target station. E.g., if station with ID D has the attribute other_link="tunnel:E", then station E must have the attribute other_link="tunnel:D".

The only optional arguments used are name and max_messages, as described for ok_map_data( ). The return value is as for ok_map_data( ).

CONTRIBUTORS

  • Ed J

  • Gisbert W. Selke, <gws@cpan.org>

AUTHOR

Mohammad Sajid Anwar, <mohammad.anwar at yahoo.com>

REPOSITORY

https://github.com/manwar/Map-Tube

BUGS

Please report any bugs or feature requests through the web interface at https://github.com/manwar/Map-Tube/issues. I will be notified and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Test::Map::Tube

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright (C) 2015 - 2025 Mohammad Sajid Anwar.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License.By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you,you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement,then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.