The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package TestCommon;
#
#	Thread::Apartment tests common functions
#
#	tests:
#	1. load OK
#	2. Create a wrapped object wo/ providing TQD/thread
#		(also creates a 2nd T::A object for async/closure testing)
#	3. Test simple method call
#	4. Test fully qualified method call
#	5. Test array-returning method call
#	6. Test attempt to access private method
#	7. Test for nonexistant method name
#	8. Test for AUTOLOADing method name
#	9. Test simplex method call
#	10. Test urgent method call
#	11. Test urgent, simplex method call
#	12. Test passing multiple, complex parameters
#	13. Test calling encapsulated TAS object
#	14. Test method call returning an error
#	15. Test method call returning an object
#	16. Test async method calls between objects
#		(also tests passing closures)
#	17. Test various closure calls between objects
#		(also tests returning closures)
#	18. Test timed method calls for timeout
#	19. Pass object to another thread and repeat tests (3-15)
#	20. Create TQD/thread externally and repeat tests (3-15)
#	21. Create an I/O object and repeat tests (3-15)
#	22. test ref counting
#
use Thread::Queue::Duplex;
use Thread::Apartment qw(start rendezvous
	rendezvous_any rendezvous_until rendezvous_any_until set_ta_debug);

use strict;
use warnings;

our $testtype;

sub report_result {
	my ($testno, $result, $testmsg, $okmsg, $notokmsg) = @_;

	if ($result) {

		$okmsg = '' unless $okmsg;
		print STDOUT (($result eq 'skip') ?
			"ok $$testno # skip $testmsg for $testtype\n" :
			"ok $$testno # $testmsg $okmsg for $testtype\n");
	}
	else {
		$notokmsg = '' unless $notokmsg;
		print STDOUT
			"not ok $$testno # $testmsg $notokmsg for $testtype\n";
	}
	$$testno++;
}
#
#	threaded version
#
sub run_thread {
	my ($tqd, $testno) = @_;
	$tqd->listen();
	my $req = $tqd->dequeue();
	my $id = shift @$req;
	my $obj = shift @$req;
	my $batter = shift @$req;
	my $installed = shift @$req;
	run($obj, $batter, $testno, $installed);
#
#	if in thread, respond
#
#print STDERR "run_thread responding\n";
	$tqd->respond($id, 'OK');
	return 1;
}
#
#	tests encapsulated in subroutine for reuse
#
sub run {
	my ($obj, $batter, $testno, $installed) = @_;
#
#	get the basics out of the way
#
	$installed = 'ThirdBase' unless $installed;
	report_result($testno, (ref $obj eq 'Thread::Apartment::Client'), 'ref');

	report_result($testno, $obj->isa($installed), 'isa()');

	report_result($testno, $obj->isa('FirstBase'), 'isa(base)');

	report_result($testno, (!$obj->isa('BadBase')), 'isa(bad base)');

	report_result($testno, $obj->can('walk'), 'can(good)');

	report_result($testno, (!$obj->can('punt')), 'can(bad)');

	report_result($testno, $obj->can('secondBase'), 'can(implicit inherited)');

#	report_result($testno, $obj->can('FirstBase::firstBase'), 'can(explicit inherited)');
#
#	basic calls
#
	my $result = $obj->thirdBase();
	report_result($testno, defined($result) && ($result eq 'thirdbase'), 'simple method', '', $@);

	$result = $obj->firstBase();
	report_result($testno, defined($result) && ($result eq 'triple'), 'overridden method', '', $@);

	$result = $obj->secondBase();
	report_result($testno, defined($result) && ($result eq 'secondbase'), 'inherited method', '', $@);
#
#	we can't yet support superclass methods...shouldn't be a huge
#	issue...
#
#	$result = $obj->FirstBase::firstBase();
#	report_result($testno, defined($result) && ($result eq 'firstbase'), 'explicit inherited method', '', $@);
#
#	array results
#
	my @results = $obj->homeRun();
	report_result($testno,
		((scalar @results == 4) &&
		($results[0] eq 'first') &&
		($results[1] eq 'second') &&
		($results[2] eq 'third') &&
		($results[3] eq 'home')),
		 'array returning method');
#
#	private method
#
	eval {
		$result = $obj->_bunt();
	};
	report_result($testno, defined($@), 'private method', $@, 'private method accessed');
#
#	bogus method
#
	eval {
		$result = $obj->touchDown();
	};
	report_result($testno, defined($@), 'bogus method', $@, 'bogus method accessed');
#
#	autoloaded method: we need to get an object that can autoload
#
	eval {
		$result = $obj->touchDown();
	};
	report_result($testno, defined($@), 'AUTOLOADed method', $@, 'AUTOLOADed method accessed');
#
#	try simplex
#
	eval {
		$obj->balk();
	};
	if ($@) {
		report_result($testno, undef, 'simplex method', undef, $@);
	}
	else {
#
#	verify the case changed
#
		$result = $obj->thirdBase();
		report_result($testno, ($result eq 'THIRDBASE'), 'simplex method', undef, 'case did not change!');
	}
#
#	try urgent:
#		call a wait to hold queue on other end
#		call a simplex
#		call a urgent
#		verify case of urgent response (should be upper)
#	NOTE: can only
#
	$obj->timeOut(3);	# wait 3 secs
	$obj->balk();		# would change case back to lower
	sleep 1;			# wait for thread to pick up the timeout...
#
#	should still be upper case
#
	$result = $obj->steal();
	report_result($testno, ($result eq 'STEAL'), 'urgent method', undef, 'case changed!');
#
#	now verify the case changed due to other simplex
#
	$result = $obj->thirdBase();
	report_result($testno, ($result eq 'thirdbase'), 'simplex method', undef, 'case did not change!');
#
#	try urgent simplex:
#		call a wait to hold queue on other end
#		call a simplex
#		call a urgent simplex => forces case to lower
#		verify case of urgent response (should be upper)
#
	$obj->balk();		# change case back to upper
	$obj->timeOut(3);	# wait 3 secs
	$obj->balk();		# would change case back to lower
	sleep 2;			# wait for thread to pick up the timeout...
	$obj->walk();		# but this forces lower, so prior balk should make upper
#
#	should still be upper case
#
	$result = $obj->thirdBase();
	report_result($testno, ($result eq 'THIRDBASE'), 'urgent simplex method', undef, 'case changed!');
	$obj->walk();		# restore lower case
#
#	complex params
#
	$result = $obj->triplePlay('Cabrerra',
		{
			Molina => {
				Erstad => {
					Molina => 23,
					Cabrerra => 14,
				},
				Cabrerra => {
					Molina => 23,
					Erstad => 22,
				},
			},
			Erstad => {
				Molina => {
					Erstad => 22,
					Cabrerra => 14,
				},
				Cabrerra => {
					Molina => 23,
					Erstad => 22,
				},
			},
			Cabrerra => {
				Erstad => {
					Molina => 23,
					Cabrerra => 14,
				},
				Molina => {
					Cabrerra => 14,
					Erstad => 22,
				},
			},
		});
	report_result($testno, ($result == 22), 'complex params');
#
#	encapsulated/reentrant TAS call:
#	NOTE 2 calls required in order to avoid deadlock
#
	my $val = $obj->onDeck();		# simplex to set on deck in encapsulated TAS
	$val = 'undef' unless $val;
	$result = $obj->batterUp();		# fetches the results w/ reentrant method
	report_result($testno, ($result eq 'batter up'), 'encapsulated/reentrant TAS');
#	$Thread::Queue::Duplex::tqd_debug = 0;
#
#	error test
#
	$result = $obj->error();
	report_result($testno, ((!defined($result)) && ($@ eq 'booted ball!')), 'error result');
#
#	object returning test
#
	$result = $obj->suicideSqueeze('uc');
	unless ($result) {
		report_result($testno, undef, 'object returning result', undef, $@);
	}
	else {
		$result = $result->homeBase();
		report_result($testno, (defined($result) && ($result eq 'HOMEBASE')),
			'object returning result');
	}
#
#	async tests
#
#	$Thread::Queue::Duplex::tqd_debug = 1;
#	set_ta_debug();
	$batter->set_test_object($obj);
	$batter->run_simple_async($testno, $testtype);
	my $count = 0;
	sleep 1,
	$count++
		until $batter->async_ready || ($count >= 10);

	report_result($testno, undef, 'async closure') unless $batter->async_ready;

	$batter->run_override_async($testno, $testtype);
	$count = 0;
	sleep 1,
	$count++
		until $batter->async_ready || ($count >= 10);

	report_result($testno, undef, 'async override closure') unless $batter->async_ready;

	$batter->run_inherited_async($testno, $testtype);
	$count = 0;
	sleep 1,
	$count++
		until $batter->async_ready || ($count >= 10);

	report_result($testno, undef, 'async inherited closure') unless $batter->async_ready;
#
#	closure argument test
#
	$batter->run_closure_args($testno, $testtype);
	$count = 0;
	sleep 1,
	$count++
		until $batter->async_ready || ($count >= 10);

	report_result($testno, undef, 'closure args') unless $batter->async_ready;

	$batter->remove_test_object();
#
#	response timeout tests
#
	$obj->timeOut(20);	# sleep 20 secs, which is > the AptTimeout of 10 secs
	$result = $obj->thirdBase();	# and execute something
	report_result($testno, (!defined($result)), 'AptTimeout timeout');
#
#	autoload, re-entrancy, and closure behavior tests
#	NOTE: reentrant/autoload get overridden internally
#
	my $ump = Thread::Apartment->new(
		AptClass => 'Umpire',
		AptTimeout => 10,
		AptReentrant => 0,
		AptAutoload => 0,
		AptClosureCalls => [ 'Simplex' ],
		AptParams => [ 'lc' ]
	);
	report_result($testno, $ump, 'create autoloading/reentrant/simplex closure\'ing object', '', $@);
#
#	test autoload
#
	$result = $ump->random_method();
	report_result($testno, ($result && ($result eq 'Method is random_method')),
		'autoloaded method call');
#
#	test re-entrancy
#
	$val = $ump->onDeck();		# simplex to set on deck in encapsulated TAS
	$val = 'undef' unless $val;
	$result = $ump->batterUp();		# fetches the results w/ reentrant method
	report_result($testno, ($result eq 'batter up'),
		'encapsulated/reentrant TAS for autoloading/reentrant object');
#
#	TO DO: test simplex closure
#
#	test async w/ rendezvous
#
	my @tacs = ();
	foreach (1..3) {
		push @tacs, Thread::Apartment->new(
			AptClass => 'ThirdBase',
			AptParams => ['lc']);
		die "Can't create: $@" unless $tacs[-1];
	}
#
#	default rendezvous
#
	my $async_closure = $tacs[2]->get_delay_closure();
	scalar start($tacs[$_])->delay(1 + $_)
		foreach (0..1);
	scalar start($async_closure)->(3);

	my @rdvus = rendezvous();
	if (scalar @rdvus != 3) {
		report_result($testno, undef, 'default rendezvous', '', 'didn\'t get all pending');
	}
	else {
		$_->get_pending_results()
			foreach (@rdvus);
		report_result($testno, 1, 'default rendezvous');
	}
#
#	default rendezvous w/ none active
#
	@rdvus = rendezvous();
	report_result($testno, (scalar @rdvus == 0), 'default rendezvous, all idle');
#
#	explicit rendezvous
#
	scalar start($tacs[$_])->delay(2 + $_)
		foreach (0..1);
	scalar start($async_closure)->(4);

	@rdvus = rendezvous($async_closure);
	if (scalar @rdvus != 1) {
		report_result($testno, undef, 'explicit rendezvous', '', 'should be 1 rdvu');
	}
	else {
		$_->get_pending_results()
			foreach (@rdvus);

		@rdvus = rendezvous($tacs[0], $tacs[1]);
		if (scalar @rdvus != 2) {
			report_result($testno, undef, 'explicit rendezvous', '', 'should be 2 rdvus');
		}
		else {
			$_->get_pending_results()
				foreach (@rdvus);
			report_result($testno, 1, 'explicit rendezvous');
		}
	}
#
#	default rendezvous_any
#
	scalar start($tacs[$_])->delay(2 + $_)
		foreach (0..2);

	@rdvus = rendezvous_any()
		while (scalar @rdvus < 3);

	$_->get_pending_results()
		foreach (@rdvus);
	report_result($testno, (scalar @rdvus == 3), 'default rendezvous_any');
#
#	explicit rendezvous_any; also tests attempted rendezvous
#	with idle thread
#
	scalar start($tacs[$_])->delay(2 + $_)
		foreach (0..2);

	@rdvus = rendezvous_any(@tacs)
		while (scalar @rdvus < 3);
	$_->get_pending_results()
		foreach (@rdvus);
	report_result($testno, (scalar @rdvus == 3), 'explicit rendezvous_any');
#
#	default rendezvous_until
#
	scalar start($tacs[$_])->delay(2 + $_)
		foreach (0..2);

	@rdvus = rendezvous_until(2);
	if (scalar @rdvus != 0) {
		report_result($testno, undef, 'default rendezvous_until',
			'', 'should have timed out');
	}
	else {
		@rdvus = rendezvous_until(6);
		$_->get_pending_results()
			foreach (@rdvus);
		report_result($testno, (scalar @rdvus == 3), 'default rendezvous_until');
	}
#
#	explicit rendezvous_until
#
	scalar start($tacs[$_])->delay(2 + $_)
		foreach (0..2);

	@rdvus = rendezvous_until(2, @tacs);
	if (scalar @rdvus != 0) {
		report_result($testno, undef, 'explicit rendezvous_until',
			'', 'should have timed out');
	}
	else {
		@rdvus = rendezvous_until(6, @tacs);
		$_->get_pending_results()
			foreach (@rdvus);
		report_result($testno, (scalar @rdvus == 3), 'explicit rendezvous_until');
	}
#
#	default rendezvous_any_until
#
	scalar start($tacs[$_])->delay(4 + $_)
		foreach (0..2);

	@rdvus = rendezvous_any_until(2);
	if (scalar @rdvus != 0) {
		report_result($testno, undef, 'default rendezvous_any_until',
			'', 'should have timed out');
	}
	else {
		@rdvus = rendezvous_any_until(8)
			while (scalar @rdvus < 3);
		$_->get_pending_results()
			foreach (@rdvus);
		report_result($testno, (scalar @rdvus == 3), 'default rendezvous_any_until');
	}
#
#	explicit rendezvous_any_until
#
	scalar start($tacs[$_])->delay(4 + $_)
		foreach (0..2);

	@rdvus = ();
	@rdvus = rendezvous_any_until(2, @tacs);
	if (scalar @rdvus != 0) {
		report_result($testno, undef, 'default rendezvous_until',
			'', 'should have timed out');
	}
	else {
		@rdvus = rendezvous_any_until(8, @tacs)
			while (scalar @rdvus < 3);
		$_->get_pending_results()
			foreach (@rdvus);
		report_result($testno, (scalar @rdvus == 3), 'explicit rendezvous_until');
	}
#
#	stop()/join() test
#
	$_->cleanUp(),
	$_->stop(),
	$_->join()
		foreach (@tacs);

#print STDERR "Cleaning up Umpire\n";
	$ump->cleanUp();
#print STDERR "Stopping Umpire\n";
	$ump->stop;
#print STDERR "Joining Umpire\n";
	$ump->join;

#print STDERR "Cleaning up ThirdBase\n";

	$obj->cleanUp();
#print STDERR "Stopping THirdBase\n";
	$obj->stop;
#print STDERR "Joining THirdBase\n";
	$obj->join;
#	print STDERR "Join complete\n";
	report_result($testno, 1, 'stop/join');
#
#	clean out the pool
#
	Thread::Apartment->destroy_pool();
	report_result($testno, 1, 'destroy_pool');
#
#	need to run an eviction test!!!
#	need a refcount test
#
	return 1;
}

1;