#!/usr/local/bin/perl -w

###############################################################################
# Purpose : Unit test for Sub::Slice
# Author  : Simon Flack, John Alden and Tim Sweetman
# Created : Jan 2003
# CVS     : $Header: /home/cvs/software/cvsroot/sub_slice/t/slice.t,v 1.17 2005/01/12 16:51:19 simonf Exp $
###############################################################################
# -t : trace
# -T : deep trace
# -s : save output
###############################################################################

use strict;
use Test::Assertions 'test';
use Log::Trace;
use Getopt::Std;
use File::Path;

$|++;
use lib './lib', '../lib';
plan tests => 57;

my $path = 'test_output';
my %default_options = (storage_options => { path => $path });
rmtree($path,1); #ensure clean even if last run was -s

getopts('tTs', \my %opt);

#
# START OF TESTS
#

my $Ends = 0;

eval { require Sub::Slice; };
ASSERT(!$@ && $INC{'Sub/Slice.pm'}, "Sub::Slice compiles");
TRACE($INC{'Sub/Slice.pm'});

import Log::Trace 'print' if $opt{t};
deep_import Log::Trace 'print' if $opt{T};

my $token = make_token();
ASSERT( real_token($token),  "Create new token");
DUMP($token);
ASSERT($token->{estimate} == 10, "Set estimate");
ASSERT($token->{status} eq 'not run', "Set status");

my $last_total = 0;
for (1 .. 30) {
	my $total = eval { keep_going( $token ) };
	if (my $e = $@) {
		print STDERR 'Error --> ', $e, $/;
	}
	ASSERT( real_token($token)  && $total && $total ne $last_total,
			'running slice #' . $token->count );
	$last_total = $total;
	last if $token->done;
}

# test iterate=0
$token = make_token(0);
eval {keep_going($token)};
ASSERT($token->done() && $token->count() > 1, 'unlimited iterations - pass through in one op');
DUMP($token);

# check tampering is trapped
$token = make_token();
$token->{id} = 0;
ASSERT( DIED(sub { keep_going($token) } ), "dies when ID is tampered with");

$token = make_token();
$token->{pin} = 0;
ASSERT( DIED(sub { keep_going($token) } ), "dies when pin is tampered with");

ASSERT($Ends == 2, "right number of cleanup steps done");

#
# Some tests on the $job itself
#

my $job = new Sub::Slice( 
	%default_options, 
	iterations => 1, 
	backend => 'Sub::Slice::Backend::Filesystem',
	pin_length => 1e7,
	auto_blob_threshold => 5,
);
DUMP($job);

#Pin length
ASSERT(length $job->token->{pin} == 7, "pin length");

#Auto blob storage
$job->store('non-blob', '12345'); #At BLOB threshold
ASSERT(! defined ($job->fetch_blob('non-blob')), "short value not stored as blob");
$job->store('is-blob', '123456'); #Over BLOB threshold
ASSERT($job->fetch_blob('is-blob') eq '123456' && $job->fetch('is-blob') eq '123456', "long value auto stored as blob");

# Test job accessors
$job->set_estimate(10);
ASSERT($job->estimate() == 10, "estimate accessor");
ASSERT($job->count() eq '0', "count accessor");

# Input checking
ASSERT(DIED(sub{ $job->id(1) }), "id mutator check");
ASSERT(DIED(sub{ $job->token(1) }), "token mutator check");
ASSERT(DIED(sub{ $job->estimate(1) }), "estimate mutator check");
ASSERT(DIED(sub{ $job->count(1) }), "count mutator check");
ASSERT(DIED(sub{ $job->is_done(1) }), "is_done mutator check");
ASSERT(DIED(sub{ $job->stage(1) }), "stage mutator check");
ASSERT(DIED(sub{ $job->done(1) }), "done() args check");

ASSERT(DIED(sub{ $job->store(undef,1) }) && DIED(sub{ $job->store([],1) }), "store input checks");
ASSERT(DIED(sub{ $job->fetch() }) && DIED(sub{ $job->fetch([]) }), "fetch input checks");
ASSERT(DIED(sub{ $job->next_stage() }) && DIED(sub{ $job->next_stage([]) }), "next_stage input checks");

# Constructor validation
ASSERT(DIED(sub{
	new Sub::Slice( %default_options, iterations => 1, backend => 'Sub::Slice::Backend::Missing' );
}), "Non-existant backend raises an exception");

ASSERT(DIED(sub{
	new Sub::Slice( %default_options, iterations => 1, backend => 'Sub::Slice::Backend::..' );
}), "Illegal backend name raises an exception");

ASSERT(DIED(sub{
	new Sub::Slice( %default_options, token => "fribble" );
}), "Garbage for token #1");

ASSERT(DIED(sub{
	new Sub::Slice( %default_options, token => [] );
}), "Garbage for token #2");

ASSERT(DIED(sub{
	new Sub::Slice( "fribble" );
}), "odd number of arguments");

#
# END OF TESTS
#

#Cleanup
undef $job; #Needed to release any open files on win32
if($opt{s}){
	warn("output files saved in $path\n");
} else {
	rmtree($path);
}

###################################################################################

sub real_token {
	return $_[0] &&  UNIVERSAL::isa(shift, 'Sub::Slice::Token');
}

sub make_token {
	my $iterate = shift;
	$iterate = 1 unless defined $iterate;
	my $job = new Sub::Slice( %default_options, iterations => $iterate, backend => 'Filesystem' );
	$job->set_estimate(10);
	$job->status("not run");
	DUMP($job);
	return $job->token
}

sub keep_going {
	# rather bloated example...
	my $token = shift;

	# Check that this all works the same if the token has lost its
	# blessedness (which happens if you serialise using XML::Simple,
	# for example)
	# However, if we were to actually USE this $job, the counter
	# ends up stuck at 0 because we're relying on the blessed hash
	# that's kept in $token. That's probably not ideal,
	# because, as I understand it, no transport mechanism emulates
	# call-by-reference.
	my $job = new Sub::Slice (%default_options, token => {%$token});
	my $id_again = $job->token->{id};
	ASSERT($id_again, "token has ID");

	undef $job;
	$job = new Sub::Slice (%default_options, token => $token);
	my $id = $job->token->{id};
	ASSERT($id, "token has ID");
	ASSERT($id_again eq $id, "reblessing fetch works identically");


	at_start $job sub {
			$job->store('CODE', __PACKAGE__);
			$job->store('name', 'Simon Flack');
			$job->store('count', 0);
			$job->store('total', 0.1);
			$job->store_blob('data/foo.txt', "data");
		};
	at_stage $job 'first_stage',
		sub {
			$job->next_stage('second_stage');
			ASSERT($job->fetch('count') == 0, "check 0/undef distinction");
			$job->store('count', 1);
			my $file_data = $job->fetch_blob('data/foo.txt');
			die "file data missing or incorrect" unless $file_data eq 'data';
		};
	at_stage $job 'second_stage',
		sub {
			my $count = $job->fetch('count');
			$job->store('count', $count + 1);
			$job->next_stage('final') if $job->fetch('count') == 5;
		};
	at_stage $job 'never_happens',
		sub {
			die("Should never happen");
		};
	at_stage $job 'final',
		sub {
			my $name = $job->fetch('name');
			$job->done;
		};
	at_end $job
		sub {
			TRACE ("at_end");
			$job->status("ended");
			$Ends++;
		};

	$job->store('total', $job->fetch('count') + $job->fetch('total'));
	return $job->fetch('total');
}
