use strict;
use warnings;
use DateTime::TimeZone::Local;
use DateTime::TimeZone::Local::Unix;
use File::Basename qw( basename );
use File::Spec;
use Sys::Hostname qw( hostname );
use Test::More;
plan skip_all => 'HPUX is weird'
if $^O eq 'hpux';
use lib File::Spec->catdir( File::Spec->curdir, 't' );
BEGIN { require 'check_datetime_version.pl' }
my $IsMaintainer = hostname() =~ /houseabsolute|quasar/ && -d '.hg';
my $CanWriteEtcLocaltime = -w '/etc/localtime' && -l '/etc/localtime';
# Ensures that we can load our OS-specific subclass. Otherwise this
# might happen later in an eval, and the error will get lost.
DateTime::TimeZone::Local->_load_subclass();
{
my %links = DateTime::TimeZone->links();
for my $alias ( sort keys %{ DateTime::TimeZone::links() } ) {
local $ENV{TZ} = $alias;
my $tz = eval { DateTime::TimeZone::Local->TimeZone() };
is(
$tz->name(), $links{$alias},
"$alias in \$ENV{TZ} for Local->TimeZone()"
);
}
}
{
for my $name ( sort DateTime::TimeZone::all_names() ) {
local $ENV{TZ} = $name;
my $tz = eval { DateTime::TimeZone::Local->TimeZone() };
is(
$tz->name(), $name,
"$name in \$ENV{TZ} for Local->TimeZone()"
);
}
}
{
local $ENV{TZ} = 'this will not work';
my $tz = DateTime::TimeZone::Local::Unix->FromEnv();
is(
$tz, undef,
'invalid time zone name in $ENV{TZ} fails'
);
local $ENV{TZ} = '123/456';
$tz = DateTime::TimeZone::Local::Unix->FromEnv();
is(
$tz, undef,
'invalid time zone name in $ENV{TZ} fails'
);
}
{
local $ENV{TZ} = 'Africa/Kinshasa';
my $tz = DateTime::TimeZone::Local::Unix->FromEnv();
is( $tz->name(), 'Africa/Kinshasa',
'tz object name() is Africa::Kinshasa' );
local $ENV{TZ} = 0;
$tz = eval { DateTime::TimeZone::Local->TimeZone() };
is(
$tz->name(), 'UTC',
"\$ENV{TZ} set to 0 returns UTC"
);
}
{
# This passes the _IsValidName() check but when passed to
# DT::TZ->new() will throw an exception.
{
package Foo;
use overload '""' => sub {"Foo"}, 'eq' => sub { "$_[0]" eq "$_[1]" };
}
local $ENV{TZ} = bless [], 'Foo';
DateTime::TimeZone::Local::Unix->FromEnv();
is( $@, '', 'FromEnv does not leave $@ set' );
}
{
local $^O = 'DoesNotExist';
my @err;
local $SIG{__DIE__} = sub { push @err, shift };
eval { DateTime::TimeZone::Local->_load_subclass() };
is_deeply(
\@err, [],
'error loading local time zone module is not seen by __DIE__ handler'
);
}
no warnings 'redefine';
SKIP:
{
skip "/etc/localtime is not a symlink", 6
unless -l '/etc/localtime';
local *DateTime::TimeZone::Local::Unix::_Readlink
= sub {'/usr/share/zoneinfo/US/Eastern'};
my $tz;
eval { $tz = DateTime::TimeZone::Local::Unix->FromEtcLocaltime() };
is( $@, '',
'valid time zone name in /etc/localtime symlink should not die' );
is(
$tz->name(), 'America/New_York',
'FromEtchLocaltime() with _Readlink returning /usr/share/zoneinfo/US/Eastern'
);
local *DateTime::TimeZone::Local::Unix::_Readlink
= sub {'/usr/share/zoneinfo/Foo/Bar'};
$tz = DateTime::TimeZone::Local::Unix->FromEtcLocaltime();
is( $@, '',
'valid time zone name in /etc/localtime symlink should not leave $@ set'
);
ok( !$tz, 'no time zone was found' );
local *DateTime::TimeZone::Local::Unix::_Readlink = sub {undef};
local *DateTime::TimeZone::Local::Unix::_FindMatchingZoneinfoFile
= sub {'America/Los_Angeles'};
eval { $tz = DateTime::TimeZone::Local::Unix->FromEtcLocaltime() };
is( $@, '',
'fall back to _FindMatchZoneinfoFile if _Readlink finds nothing' );
is(
$tz->name(), 'America/Los_Angeles',
'FromEtchLocaltime() with _FindMatchingZoneinfoFile returning America/Los_Angeles'
);
}
SKIP:
{
skip "cannot read /etc/sysconfig/clock", 2
unless -r '/etc/sysconfig/clock' && -f _;
local *DateTime::TimeZone::Local::Unix::_ReadEtcSysconfigClock
= sub {'US/Eastern'};
my $tz;
eval { $tz = DateTime::TimeZone::Local::Unix->FromEtcSysconfigClock() };
is( $@, '',
'valid time zone name in /etc/sysconfig/clock should not die' );
is(
$tz->name(), 'America/New_York',
'FromEtcSysConfigClock() with _ReadEtcSysconfigClock returning US/Eastern'
);
}
SKIP:
{
skip "cannot read /etc/default/init", 2
unless -r '/etc/default/init' && -f _;
local *DateTime::TimeZone::Local::Unix::_ReadEtcDefaultInit
= sub {'Asia/Tokyo'};
my $tz;
eval { $tz = DateTime::TimeZone::Local::Unix->FromEtcDefaultInit() };
is( $@, '', 'valid time zone name in /etc/default/init should not die' );
is(
$tz->name(), 'Asia/Tokyo',
'FromEtcDefaultInit with _ReadEtcDefaultInit returning Asia/Tokyo'
);
}
SKIP:
{
skip
"Cannot run these tests without explicitly knowing local time zone first (only runs on developers' machine)",
4
unless $IsMaintainer && -l '/etc/localtime';
{
local $ENV{TZ} = '';
my $tz;
eval { $tz = DateTime::TimeZone::Local->TimeZone() };
is( $@, '', 'valid time zone name in /etc/localtime should not die' );
is(
$tz->name(), 'America/Chicago',
'/etc/localtime should link to America/Chicago'
);
}
{
local *DateTime::TimeZone::Local::Unix::FromEtcLocaltime
= sub {undef};
my $tz;
eval { $tz = DateTime::TimeZone::Local->TimeZone() };
is( $@, '', 'valid time zone name in /etc/timezone should not die' );
is(
$tz->name(), 'America/Chicago',
'/etc/timezone should contain America/Chicago'
);
}
}
SKIP:
{
skip
"Can only run this test on developers' machine", 2
unless $IsMaintainer && -f '/etc/default/init';
{
# requires that /etc/default/init contain
# TZ=Australia/Melbourne to work.
local *DateTime::TimeZone::Local::Unix::FromEtcLocaltime
= sub {undef};
local *DateTime::TimeZone::Local::Unix::FromEtcTimezone = sub {undef};
local *DateTime::TimeZone::Local::Unix::FromEtcTIMEZONE = sub {undef};
my $tz;
eval { $tz = DateTime::TimeZone::Local->TimeZone() };
is( $@, '', '/etc/default/init contains TZ=Australia/Melbourne' );
is(
$tz->name(), 'Australia/Melbourne',
'/etc/default/init should contain Australia/Melbourne'
);
}
}
SKIP:
{
my $file = '/etc/timezone';
skip "Cannot write this test unless we can write to /etc/timezone", 1
unless $IsMaintainer && -w $file;
open my $fh, '>', $file
or die "Cannot write to $file: $!";
print $fh 'Foo/Bar';
close $fh;
DateTime::TimeZone::Local::Unix->FromEtcTimezone();
is( $@, '',
'calling FromEtcTimezone when it contains a bad name should not leave $@ set'
);
open $fh, '>', $file
or die "Cannot write to $file: $!";
print $fh 'America/Chicago';
close $fh;
}
SKIP:
{
my $file = '/etc/TIMEZONE';
skip "Cannot write this test unless we can write to /etc/TIMEZONE", 1
unless $IsMaintainer && -w '/etc';
open my $fh, '>', $file
or die "Cannot write to $file: $!";
print $fh "TZ = Foo/Bar\n";
close $fh;
DateTime::TimeZone::Local::Unix->FromEtcTIMEZONE();
is( $@, '',
'calling FromEtcTIMEZONE when it contains a bad name should not leave $@ set'
);
unlink $file
or die "Cannot unlink $file: $!";
}
SKIP:
{
skip "These tests are too dangerous to run on someone else's machine ;)",
5
unless $IsMaintainer;
skip "These tests can only be run if we can overwrite /etc/localtime", 5
unless $CanWriteEtcLocaltime;
my $tz_file = readlink '/etc/localtime';
unlink '/etc/localtime' or die "Cannot unlink /etc/localtime: $!";
require File::Copy;
File::Copy::copy( '/usr/share/zoneinfo/Asia/Calcutta', '/etc/localtime' )
or die
"Cannot copy /usr/share/zoneinfo/Asia/Calcutta to '/etc/localtime': $!";
{
local $ENV{TZ} = '';
require Cwd;
my $cwd = Cwd::cwd();
my $tz;
eval { $tz = DateTime::TimeZone::Local->TimeZone() };
is( $@, '', 'copy of zoneinfo file at /etc/localtime' );
is(
$tz->name(), 'Asia/Calcutta',
'/etc/localtime should be a copy of Asia/Calcutta'
);
is( Cwd::cwd(), $cwd,
'cwd should not change after finding local time zone' );
$tz = DateTime::TimeZone::Local->TimeZone();
is( $@, '', 'calling _FindMatchZoneinfoFile does not leave $@ set' );
}
{
local $ENV{TZ} = '';
# Make sure that a die handler does not break our use of die
# to escape from File::Find::find()
local $SIG{__DIE__} = sub { die 'haha'; };
my $tz;
eval { $tz = DateTime::TimeZone::Local->TimeZone() };
is(
$tz->name(), 'Asia/Calcutta',
'a __DIE__ handler did not interfere with our use of File::Find'
);
}
unlink '/etc/localtime' or die "Cannot unlink /etc/localtime: $!";
symlink $tz_file, '/etc/localtime'
or die "Cannot symlink $tz_file to '/etc/localtime': $!";
}
{
local $ENV{TZ} = 'Australia/Melbourne';
my $tz = eval { DateTime::TimeZone->new( name => 'local' ) };
is(
$tz->name(), 'Australia/Melbourne',
q|DT::TZ->new( name => 'local' )|
);
}
SKIP:
{
skip "These tests require File::Temp", 1
unless require File::Temp;
skip "These tests require a filesystem which support symlinks", 1
unless eval { symlink '', ''; 1 };
my $tempdir = File::Temp::tempdir( CLEANUP => 1 );
my $first = File::Spec->catfile( $tempdir, 'first' );
open my $fh, '>', $first
or die "Cannot open $first: $!";
close $fh;
my $second = File::Spec->catfile( $tempdir, 'second' );
symlink $first => $second
or die "Cannot symlink $first => $second: $!";
my $third = File::Spec->catfile( $tempdir, 'third' );
symlink $second => $third
or die "Cannot symlink $first => $second: $!";
# It seems that on some systems (OSX, others?) the temp directory
# returned by File::Temp may be a symlink (/tmp is a link to
# /private/tmp), so when abs_path folows that link, we end up with
# a different path to the "first" file.
is(
basename( DateTime::TimeZone::Local::Unix->_Readlink($third) ),
basename($first),
'_Readlink follows multiple levels of symlinking'
);
}
done_testing();