The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

use strict;
use warnings;
use Test::More;
use File::Spec;


eval { require Storable; };
unless($INC{'Storable.pm'}) {
  plan skip_all => 'no Storable.pm';
}
unless(UNIVERSAL::can(Storable => 'lock_nstore')) {
  plan skip_all => 'Storable.pm is too old - no file locking support';
}


# Initialise filenames and check they're there

my $SrcFile   = File::Spec->catfile('t', 'desertnet.src');
my $XMLFile   = File::Spec->catfile('t', 'desertnet3.xml');
my $CacheFile = File::Spec->catfile('t', 'desertnet3.stor');

unless(-e $SrcFile) {
  plan skip_all => 'test data missing';
}

# Make sure we can write to the filesystem and check it uses the same
# clock as the machine we're running on.

my $t0 = time();
unless(open(XML, '>', $XMLFile)) {
  plan skip_all => "can't create test file '$XMLFile': $!";
}
close(XML);
my $t1 = (stat($XMLFile))[9];
my $t2 = time();

if($t1 < $t0  or  $t2 < $t1) {
  plan skip_all => 'time moved backwards!'
}


plan tests => 23;

##############################################################################
#                   S U P P O R T   R O U T I N E S
##############################################################################

##############################################################################
# Copy a file
#

sub CopyFile {
  my($src, $dst) = @_;

  open(my $in, $src) or die "open(<$src): $!";
  local($/) = undef;
  my $data = <$in>;
  close($in);

  open(my $out, '>', $dst) or die "open(>$dst): $!";
  print $out $data;
  close($out);

  return(1);
}


##############################################################################
# Delete a file - portably
#

sub DeleteFile {
  my($Filename) = @_;

  if ('VMS' eq $^O) {
    1 while (unlink($Filename));
  } else {
    unlink($Filename);
  }
}


##############################################################################
# Create a file, making sure that its timestamp is newer than another
# existing file.
#

sub MakeNewerFile {
  my($File1, $File2, $CodeRef) = @_;

  my $t0 = (stat($File1))[9];
  while(1) {
    unlink($File2);
    $CodeRef->();
    return if (stat($File2))[9] > $t0;
    sleep(1);
  }
}


##############################################################################
# Wait until the current time is greater than the supplied value
#

sub PassTime {
  my($Target) = @_;

  while(time <= $Target) {
    sleep 1;
  }
}


##############################################################################
#                      T E S T   R O U T I N E S
##############################################################################

use XML::Simple;

# Initialise test data

my $Expected  = {
          'server' => {
                        'sahara' => {
                                      'osversion' => '2.6',
                                      'osname' => 'solaris',
                                      'address' => [
                                                     '10.0.0.101',
                                                     '10.0.1.101'
                                                   ]
                                    },
                        'gobi' => {
                                    'osversion' => '6.5',
                                    'osname' => 'irix',
                                    'address' => '10.0.0.102'
                                  },
                        'kalahari' => {
                                        'osversion' => '2.0.34',
                                        'osname' => 'linux',
                                        'address' => [
                                                       '10.0.0.103',
                                                       '10.0.1.103'
                                                     ]
                                      }
                      }
        };

ok(CopyFile($SrcFile, $XMLFile), 'copied known good source file');
unlink($CacheFile);
ok(! -e $CacheFile, 'no cache files lying around');

my $opt = XMLin($XMLFile);
is_deeply($opt, $Expected, 'parsed expected data from file');
ok(! -e $CacheFile, 'and no cache file was created');
PassTime(time());                     # Ensure cache file will be newer

$opt = XMLin($XMLFile, cache => 'storable');
is_deeply($opt, $Expected, 'parsed expected data from file (again)');
ok(-e $CacheFile, 'but this time a cache file was created');
$t0 = (stat($CacheFile))[9];       # Remember cache timestamp
PassTime($t0);

$opt = XMLin($XMLFile, cache => ['storable']);
is_deeply($opt, $Expected, 'got expected data from cache');
$t1 = (stat($CacheFile))[9];
is($t0, $t1, 'and cache timestamp has not changed');

PassTime(time());
$t0 = time();
open(FILE, ">>$XMLFile");             # Touch the XML file
print FILE "\n";
close(FILE);
$opt = XMLin($XMLFile, cache => 'storable');
is_deeply($opt, $Expected, 'parsed in expected value again');
$t2 = (stat($CacheFile))[9];
isnt($t1, $t2, 'and this time the cache timestamp has changed');

DeleteFile($XMLFile);
ok(! -e $XMLFile, 'deleted the source file');
open(FILE, ">$XMLFile");              # Re-create it (empty)
close(FILE);
ok(-e $XMLFile, 'recreated the source file');
is(-s $XMLFile, 0, 'but with nothing in it');
MakeNewerFile($XMLFile, $CacheFile, sub { # Make sure cache file is newer
  Storable::nstore($Expected, $CacheFile);
});
$opt = XMLin($XMLFile, cache => 'storable');
is_deeply($opt, $Expected, 'got the expected data from the cache');
$t2 = (stat($CacheFile))[9];
PassTime($t2);
open(FILE, ">$XMLFile") ||            # Write some new data to the XML file
  die "open(>$XMLFile): $!\n";
print FILE qq(<opt one="1" two="2"></opt>\n);
close(FILE);

$opt = XMLin($XMLFile);               # Parse with no caching
is_deeply($opt, { one => 1, two => 2}, 'parsed in expected data from file');
$t0 = (stat($CacheFile))[9];          # And timestamp on cache file
my $s0 = (-s $CacheFile);
is($t0, $t2, 'and the cache file was not touched');

                                      # Parse again with caching enabled
$opt = XMLin($XMLFile, cache => 'storable');
is_deeply($opt, { one => 1, two => 2}, 'parsed expected data through cache');
$t1 = (stat($CacheFile))[9];
my $s1 = (-s $CacheFile);
ok(($t0 != $t1) || ($s0 != $s1),
'and the cache was updated'); # Content changes but date may not on Win32

ok(CopyFile($SrcFile, $XMLFile), 'copied back the original file');
PassTime($t1);
$opt = XMLin($XMLFile, cache => 'storable');
is_deeply($opt, $Expected, 'parsed expected data in through cache');

# Make sure scheme name is case-insensitive

$opt = XMLin($XMLFile, cache => 'Storable');
is_deeply($opt, $Expected, 'scheme name is case-insensitive');

# Make sure bad scheme names are trapped

$@='';
$_ = eval { XMLin($XMLFile, cache => 'Storubble'); };
is($_, undef, 'bad cache scheme names are trapped');
like($@, qr/Unsupported caching scheme: storubble/,
'with correct error message');


# Clean up and go

unlink($CacheFile);
unlink($XMLFile);
exit(0);