From 365e3124eb076b1e3ec1dfe6ba2dcadb02955683 Mon Sep 17 00:00:00 2001 From: Nigel Horne Date: Tue, 5 Nov 2024 15:28:59 -0500 Subject: [PATCH] Use Scalar::Util::blessed to check for object --- Makefile.PL | 1 + lib/Geo/Coder/Free/MaxMind.pm | 12 +- t/maxmind.t | 377 +++++++++++++++++----------------- 3 files changed, 198 insertions(+), 192 deletions(-) diff --git a/Makefile.PL b/Makefile.PL index 9db48285..fd3b3c7f 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -349,6 +349,7 @@ WriteMakefile( 'Module::Info' => 0, 'File::Spec' => 0, 'CHI' => 0, + 'Scalar::Util' => 0, 'Storable' => 0, 'Text::CSV' => 0, 'Text::xSV::Slurp' => 0, diff --git a/lib/Geo/Coder/Free/MaxMind.pm b/lib/Geo/Coder/Free/MaxMind.pm index 6ef6c63c..aa0f60bf 100644 --- a/lib/Geo/Coder/Free/MaxMind.pm +++ b/lib/Geo/Coder/Free/MaxMind.pm @@ -24,6 +24,7 @@ use Locale::CA; use Locale::US; use CHI; use Locale::Country; +use Scalar::Util; our %admin1cache; our %admin2cache; # e.g. maps 'Kent' => 'P5' @@ -107,6 +108,8 @@ The admin2.db is far from comprehensive, see Makefile.PL for some entries that a sub new { my $class = shift; + + # Handle hash or hashref arguments my %args = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_; if(!defined($class)) { @@ -116,14 +119,18 @@ sub new # FIXME: this only works when no arguments are given $class = __PACKAGE__; - } elsif(ref($class)) { - # clone the given object + } elsif(Scalar::Util::blessed($class)) { + # If $class is an object, clone it with new arguments return bless { %{$class}, %args }, ref($class); } my $directory = $args{'directory'} || Module::Info->new_from_loaded(__PACKAGE__)->file(); $directory =~ s/\.pm$//; + if(!-d $directory) { + Carp::croak(ref($class), ": directory $directory doesn't exist"); + } + Database::Abstraction::init({ cache_duration => '1 day', %args, @@ -131,6 +138,7 @@ sub new cache => $args{cache} || CHI->new(driver => 'Memory', datastore => {}), }); + # Return the blessed object return bless { }, $class; } diff --git a/t/maxmind.t b/t/maxmind.t index 1442fc85..104c2521 100644 --- a/t/maxmind.t +++ b/t/maxmind.t @@ -2,6 +2,8 @@ use warnings; use strict; + +use Test::DescribeMe qw(author); use Test::Most tests => 103; use Test::Carp; use Test::Deep; @@ -20,228 +22,223 @@ BEGIN { MAXMIND: { SKIP: { - if($ENV{AUTHOR_TESTING}) { - if(-f DATABASE) { - delete $ENV{'OPENADDR_HOME'}; - delete $ENV{'WHOSONFIRST_HOME'}; - diag('This may take some time and consume a lot of memory if the database is not SQLite'); - - if($ENV{'TEST_VERBOSE'}) { - Database::Abstraction::init(logger => MyLogger->new()); - } - - my $geo_coder = new_ok('Geo::Coder::Free::MaxMind'); - - cmp_deeply($geo_coder->geocode('Detroit, MI, USA'), - methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); - cmp_deeply($geo_coder->geocode('Detroit, Michigan, United States'), - methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); - cmp_deeply($geo_coder->geocode('Detroit, Wayne, MI, USA'), - methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); - cmp_deeply($geo_coder->geocode('Detroit, Wayne, Michigan, US'), - methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); - - check($geo_coder, 'Westoe, South Tyneside, England', 54.98, -1.42); - - # my $location = $geo_coder->geocode('Woolwich, London, England'); - # ok(defined($location)); - # delta_within($location->{latitude}, 51.47, 1e-2); - # delta_within($location->{longitude}, 0.20, 1e-2); - cmp_deeply($geo_coder->geocode('Woolwich, London, England'), - methods('lat' => num(51.47, 1e-2), 'long' => num(0.20, 1e-2))); - - TODO: { - local $TODO = "Don't know how to parse 'London, England'"; - - eval { - my $location = $geo_coder->geocode('London, England'); - ok(defined($location)); - }; - } - - my $l = $geo_coder->geocode('Indianapolis, Indiana, USA'); - cmp_deeply($l, - methods('lat' => num(39.77, 1e-2), 'long' => num(-86.15, 1e-2))); - cmp_deeply($geo_coder->geocode('Ramsgate, Kent, England'), - methods('lat' => num(51.33, 1e-2), 'long' => num(1.43, 1e-2))); - - cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, United Kingdom'), - methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - - cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, UK'), - methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); + if(-f DATABASE) { + delete $ENV{'OPENADDR_HOME'}; + delete $ENV{'WHOSONFIRST_HOME'}; + diag('This may take some time and consume a lot of memory if the database is not SQLite'); - cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, GB'), - methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - - cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, England'), - methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - - - # FIXME: This finds the Wokingham in England because of a problem in the unitary city handling - # which actually looks for Wokingham, GB. - - # $location = $geo_coder->geocode('Wokingham, Berkshire, Scotland'); - # ok(!defined($location)); + if($ENV{'TEST_VERBOSE'}) { + Database::Abstraction::init(logger => MyLogger->new()); + } - TODO: { - local $TODO = 'Minster, Thanet not yet supported'; + my $geo_coder = new_ok('Geo::Coder::Free::MaxMind'); + + cmp_deeply($geo_coder->geocode('Detroit, MI, USA'), + methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); + cmp_deeply($geo_coder->geocode('Detroit, Michigan, United States'), + methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); + cmp_deeply($geo_coder->geocode('Detroit, Wayne, MI, USA'), + methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); + cmp_deeply($geo_coder->geocode('Detroit, Wayne, Michigan, US'), + methods('lat' => num(42.33, 1e-2), 'long' => num(-83.04, 1e-2))); + + check($geo_coder, 'Westoe, South Tyneside, England', 54.98, -1.42); + + # my $location = $geo_coder->geocode('Woolwich, London, England'); + # ok(defined($location)); + # delta_within($location->{latitude}, 51.47, 1e-2); + # delta_within($location->{longitude}, 0.20, 1e-2); + cmp_deeply($geo_coder->geocode('Woolwich, London, England'), + methods('lat' => num(51.47, 1e-2), 'long' => num(0.20, 1e-2))); + + TODO: { + local $TODO = "Don't know how to parse 'London, England'"; + + eval { + my $location = $geo_coder->geocode('London, England'); + ok(defined($location)); + }; + } - my $location = $geo_coder->geocode('Minster, Thanet, Kent, England'); - ok(!defined($location)); - } + my $l = $geo_coder->geocode('Indianapolis, Indiana, USA'); + cmp_deeply($l, + methods('lat' => num(39.77, 1e-2), 'long' => num(-86.15, 1e-2))); + cmp_deeply($geo_coder->geocode('Ramsgate, Kent, England'), + methods('lat' => num(51.33, 1e-2), 'long' => num(1.43, 1e-2))); - $l = $geo_coder->geocode('Silver Spring, Maryland, USA'); - cmp_deeply($l, - methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); - check($geo_coder, 'Silver Spring, MD, USA', 39.00, -77.0263889); + cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, United Kingdom'), + methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - cmp_deeply($geo_coder->geocode('Silver Spring, MD, USA'), - methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); + cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, UK'), + methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - cmp_deeply($geo_coder->geocode('Silver Spring, Montgomery, MD, USA'), - methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); + cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, GB'), + methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - cmp_deeply($geo_coder->geocode('Silver Spring, Montgomery, MD, United States'), - methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); + cmp_deeply($geo_coder->geocode('Wokingham, Berkshire, England'), + methods('lat' => num(51.42, 1e-2), 'long' => num(-0.83, 1e-2))); - $l = $geo_coder->geocode('Silver Spring, Montgomery County, Maryland, United States'); - cmp_deeply($l, - methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); - $l = $geo_coder->geocode('Montgomery County, Maryland, USA'); - ok(!defined($l)); + # FIXME: This finds the Wokingham in England because of a problem in the unitary city handling + # which actually looks for Wokingham, GB. - cmp_deeply($geo_coder->geocode('St Nicholas-at-Wade, Kent, UK'), - methods('lat' => num(51.35, 1e-2), 'long' => num(1.25, 1e-2))); + # $location = $geo_coder->geocode('Wokingham, Berkshire, Scotland'); + # ok(!defined($location)); - TODO: { - local $TODO = "Don't know how to parse counties in the USA"; - my $location = $geo_coder->geocode('Rockville Pike, Rockville, Montgomery County, MD, USA'); - ok(!defined($location)); - } + TODO: { + local $TODO = 'Minster, Thanet not yet supported'; - # FIXME: this actually does a look up that fails - my $location = $geo_coder->geocode('Rockville Pike, Rockville, MD, USA'); + my $location = $geo_coder->geocode('Minster, Thanet, Kent, England'); ok(!defined($location)); + } - cmp_deeply($geo_coder->geocode('Rockville, Montgomery County, MD, USA'), - methods('lat' => num(39.08, 1e-2), 'long' => num(-77.15, 1e-2))); - - $l = $geo_coder->geocode('Rockville, Montgomery County, Maryland, USA'); - cmp_deeply($l, - methods('lat' => num(39.08, 1e-2), 'long' => num(-77.15, 1e-2))); - - cmp_deeply($geo_coder->geocode('Temple Ewell, Kent, England'), - methods('lat' => num(51.15, 1e-2), 'long' => num(1.27, 1e-2))); - - like($geo_coder->reverse_geocode('51.15,1.27'), qr/Ewell,/, 'test reverse'); - - # Hatteras Island - ok(!defined($geo_coder->reverse_geocode('35.2440910277778,-75.6151199166667'))); - like($geo_coder->reverse_geocode('51.5029,-0.1197'), qr/London, GB$/, 'test reverse in London'); - like($geo_coder->reverse_geocode('51.15,1.27'), qr/Ewell,/, 'test reverse'); - like($geo_coder->reverse_geocode('39.0075611111111,-77.0476'), qr/Forest Glen/i, 'test reverse'); - - $l = $geo_coder->geocode('Temple Ewell, Kent, England'); - cmp_deeply($l, - methods('lat' => num(51.15, 1e-2), 'long' => num(1.27, 1e-2))); - - $l = $geo_coder->geocode('Edmonton, Alberta, Canada'); - cmp_deeply($l, - methods('lat' => num(53.55, 1e-2), 'long' => num(-113.50, 1e-2))); - - my @locations = $geo_coder->geocode(location => 'Temple Ewell, Kent, England'); - ok(defined($locations[0])); - cmp_deeply($locations[0], - methods('lat' => num(51.15, 1e-2), 'long' => num(1.27, 1e-2))); + $l = $geo_coder->geocode('Silver Spring, Maryland, USA'); + cmp_deeply($l, + methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); + check($geo_coder, 'Silver Spring, MD, USA', 39.00, -77.0263889); - $l = $geo_coder->geocode('Newport Pagnell, Buckinghamshire, England'); - ok(defined($l)); - cmp_deeply($l, - methods('lat' => num(52.08, 1e-2), 'long' => num(-0.72, 1e-2))); + cmp_deeply($geo_coder->geocode('Silver Spring, MD, USA'), + methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); - $location = $geo_coder->geocode('Thanet, Kent, England'); - ok(defined($location)); + cmp_deeply($geo_coder->geocode('Silver Spring, Montgomery, MD, USA'), + methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); - # The apache database is woefully out of date and has Kent in E5 rather than G5 - # diag(__LINE__); - # $l = $geo_coder->geocode('Kent, England'); - # ok(defined($l)); - # cmp_deeply($l, - # methods('lat' => num(51, 1), 'long' => num(0.75, 1e-2))); + cmp_deeply($geo_coder->geocode('Silver Spring, Montgomery, MD, United States'), + methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); - $l = $geo_coder->geocode('Maryland, USA'); - cmp_deeply($l, - methods('lat' => num(38.25, 1e-2), 'long' => num(-76.74, 1e-2))); + $l = $geo_coder->geocode('Silver Spring, Montgomery County, Maryland, United States'); + cmp_deeply($l, + methods('lat' => num(39.00, 1e-2), 'long' => num(-77.03, 1e-2))); - @locations = $geo_coder->geocode('Sheppey, Kent, England'); - ok(scalar(@locations) == 0); - # diag(scalar(@locations)); + $l = $geo_coder->geocode('Montgomery County, Maryland, USA'); + ok(!defined($l)); - $location = $geo_coder->geocode('Nebraska, USA'); - ok(defined($location)); + cmp_deeply($geo_coder->geocode('St Nicholas-at-Wade, Kent, UK'), + methods('lat' => num(51.35, 1e-2), 'long' => num(1.25, 1e-2))); - $location = $geo_coder->geocode('Vessels, Misc Ships At sea or abroad, England'); + TODO: { + local $TODO = "Don't know how to parse counties in the USA"; + my $location = $geo_coder->geocode('Rockville Pike, Rockville, Montgomery County, MD, USA'); ok(!defined($location)); + } - # Check a second lookup still works - check($geo_coder, 'Westoe, South Tyneside, England', 54.98, -1.42); - - @locations = $geo_coder->geocode('New Brunswick, Canada'); - ok(scalar(@locations) == 1); - - @locations = $geo_coder->geocode('Putnam, Indiana, USA'); - ok(scalar(@locations) == 0); - - @locations = $geo_coder->geocode('Maryland, USA'); - ok(scalar(@locations) == 1); - cmp_deeply($locations[0], - methods('lat' => num(38.25, 1e-2), 'long' => num(-76.74, 1e-2))); - - @locations = $geo_coder->geocode('Kent, England'); - ok(scalar(@locations) == 1); - # like($geo_coder->reverse_geocode(latlng => '51.50,-0.13'), qr/London/i, 'test reverse'); - @locations = $geo_coder->reverse_geocode(latlng => '51.50,-0.13'); - ok(scalar(@locations) > 1); - my $found = 0; - foreach my $location(@locations) { - if($location =~ /London,/i) { - $found = 1; - last; - } + # FIXME: this actually does a look up that fails + my $location = $geo_coder->geocode('Rockville Pike, Rockville, MD, USA'); + ok(!defined($location)); + + cmp_deeply($geo_coder->geocode('Rockville, Montgomery County, MD, USA'), + methods('lat' => num(39.08, 1e-2), 'long' => num(-77.15, 1e-2))); + + $l = $geo_coder->geocode('Rockville, Montgomery County, Maryland, USA'); + cmp_deeply($l, + methods('lat' => num(39.08, 1e-2), 'long' => num(-77.15, 1e-2))); + + cmp_deeply($geo_coder->geocode('Temple Ewell, Kent, England'), + methods('lat' => num(51.15, 1e-2), 'long' => num(1.27, 1e-2))); + + like($geo_coder->reverse_geocode('51.15,1.27'), qr/Ewell,/, 'test reverse'); + + # Hatteras Island + ok(!defined($geo_coder->reverse_geocode('35.2440910277778,-75.6151199166667'))); + like($geo_coder->reverse_geocode('51.5029,-0.1197'), qr/London, GB$/, 'test reverse in London'); + like($geo_coder->reverse_geocode('51.15,1.27'), qr/Ewell,/, 'test reverse'); + like($geo_coder->reverse_geocode('39.0075611111111,-77.0476'), qr/Forest Glen/i, 'test reverse'); + + $l = $geo_coder->geocode('Temple Ewell, Kent, England'); + cmp_deeply($l, + methods('lat' => num(51.15, 1e-2), 'long' => num(1.27, 1e-2))); + + $l = $geo_coder->geocode('Edmonton, Alberta, Canada'); + cmp_deeply($l, + methods('lat' => num(53.55, 1e-2), 'long' => num(-113.50, 1e-2))); + + my @locations = $geo_coder->geocode(location => 'Temple Ewell, Kent, England'); + ok(defined($locations[0])); + cmp_deeply($locations[0], + methods('lat' => num(51.15, 1e-2), 'long' => num(1.27, 1e-2))); + + $l = $geo_coder->geocode('Newport Pagnell, Buckinghamshire, England'); + ok(defined($l)); + cmp_deeply($l, + methods('lat' => num(52.08, 1e-2), 'long' => num(-0.72, 1e-2))); + + $location = $geo_coder->geocode('Thanet, Kent, England'); + ok(defined($location)); + + # The apache database is woefully out of date and has Kent in E5 rather than G5 + # diag(__LINE__); + # $l = $geo_coder->geocode('Kent, England'); + # ok(defined($l)); + # cmp_deeply($l, + # methods('lat' => num(51, 1), 'long' => num(0.75, 1e-2))); + + $l = $geo_coder->geocode('Maryland, USA'); + cmp_deeply($l, + methods('lat' => num(38.25, 1e-2), 'long' => num(-76.74, 1e-2))); + + @locations = $geo_coder->geocode('Sheppey, Kent, England'); + ok(scalar(@locations) == 0); + # diag(scalar(@locations)); + + $location = $geo_coder->geocode('Nebraska, USA'); + ok(defined($location)); + + $location = $geo_coder->geocode('Vessels, Misc Ships At sea or abroad, England'); + ok(!defined($location)); + + # Check a second lookup still works + check($geo_coder, 'Westoe, South Tyneside, England', 54.98, -1.42); + + @locations = $geo_coder->geocode('New Brunswick, Canada'); + ok(scalar(@locations) == 1); + + @locations = $geo_coder->geocode('Putnam, Indiana, USA'); + ok(scalar(@locations) == 0); + + @locations = $geo_coder->geocode('Maryland, USA'); + ok(scalar(@locations) == 1); + cmp_deeply($locations[0], + methods('lat' => num(38.25, 1e-2), 'long' => num(-76.74, 1e-2))); + + @locations = $geo_coder->geocode('Kent, England'); + ok(scalar(@locations) == 1); + # like($geo_coder->reverse_geocode(latlng => '51.50,-0.13'), qr/London/i, 'test reverse'); + @locations = $geo_coder->reverse_geocode(latlng => '51.50,-0.13'); + ok(scalar(@locations) > 1); + my $found = 0; + foreach my $location(@locations) { + if($location =~ /London,/i) { + $found = 1; + last; } + } - if(!$found) { - diag("Failed reverse lookup $location"); - diag(Data::Dumper->new([\@locations])->Dump()); - } - ok($found); + if(!$found) { + diag("Failed reverse lookup $location"); + diag(Data::Dumper->new([\@locations])->Dump()); + } + ok($found); - does_croak(sub { - $location = $geo_coder->geocode(); - }); + does_croak(sub { + $location = $geo_coder->geocode(); + }); - does_croak(sub { - $location = $geo_coder->reverse_geocode(); - }); + does_croak(sub { + $location = $geo_coder->reverse_geocode(); + }); - ok(scalar(keys %Geo::Coder::Free::MaxMind::admin1cache) > 0); - ok(scalar(keys %Geo::Coder::Free::MaxMind::admin2cache) > 0); + ok(scalar(keys %Geo::Coder::Free::MaxMind::admin1cache) > 0); + ok(scalar(keys %Geo::Coder::Free::MaxMind::admin2cache) > 0); - eval 'use Test::Memory::Cycle'; - if($@) { - skip('Test::Memory::Cycle required to check for cicular memory references', 1); - } else { - memory_cycle_ok($geo_coder); - } + eval 'use Test::Memory::Cycle'; + if($@) { + skip('Test::Memory::Cycle required to check for cicular memory references', 1); } else { - diag(DATABASE, ' is missing'); - skip(DATABASE . ' is missing', 102); + memory_cycle_ok($geo_coder); } } else { - diag('Author tests not required for installation'); - skip('Author tests not required for installation', 102); + diag(DATABASE, ' is missing'); + skip(DATABASE . ' is missing', 102); } } }