On Wednesday, November 13, 2002, at 08:54  pm, Michael G Schwern wrote:
[snip]
I think I'll merge your $Test::Builder::default idea with this.  Have a
stack of Test::Builder objects rather than just the states.  This way you
can do something like the above.  If you add a $tb->copy method which
returns a carbon-copy of $tb then...

    $tb->push_state;  # calls $tb->copy and pushes it into the stack
    		      # the copy will now be used by default
    $tb->pop_state;   # pops the stack and tosses it, restoring the
    		      # original.
So what you're proposing is a class-wide "default object" stack,
rather than an object-based state stack - yes?

I have a hinky feeling about these being object methods. Changing the
default object would seem read more naturally to me as a class method:

	Test::Builder->push_default($tb);
	Test::Builder->pop_default

but what if you want to use your own subclass? Well if you also add a
$tb->state method which return the full internal state it makes it possible
for subclasses to copy each other. So...

$tb->push_state(My::Test::Builder->copy($tb));
...my test code...
$tb->pop_state;

The problem there is the case where you want to override behaviors but still
keep state between the two objects. So things like the test counter and
test details would have to be preserved. I guess this is what chromatic was
talking about when he suggested having only certain parts of the internal
state be a singleton.
Ahhh... that makes sense now.

A specific example:

In Test::Class I would like to override ok() so that it reports
Test::Class->current_method as the default test name. The 'nice' way
of doing this would be to create my own subclass of T::B with an
appropriately overridden ok() method.

However, I still want to allow the user to run non-T::C based tests
before and after the T::C tests.

This means that my custom T::B class will need to share the state of
the T::B object used before and after the test class was run.

So looking at the stuff in reset():
[snip]
We can seperate them out into Test::Builder configuration and test state.

This is test state and would be shared amongst the T::B objects in the
default stack

$Test_Died
$Have_Plan
$No_Plan
$Curr_Test
$Original_Pid
@Test_Results
@Test_Details
$Expected_Tests
$Skip_All

and these are T::B configuration values and wouldn't be shared, though they
would be initially copied.

$Level
$Exported_To
$Use_Nums
$No_Header
$No_Ending
the output filehandles
I'm not convinced about the separation between test state and T::B
configuration - what advantage does it give you?

I can see situations where you would want to share all the state, and
situations where you want completely different state - but can't think
of any where you would want to share part of the state.

This is may be lack of imagination on my part :-)

If we have a Test::Builder state() method that returns a
Test::Builder::State object, then we could just supply it as a
constructor argument if we wanted to share state:

So, for my Test::Class example I would do something like this:

	package My::Test::Builder;
	use base qw(Test::Builder);
	sub ok {
		my ($self, $test, $name);
		local $Test::Builder::Level = $Test::Builder::Level+1;
		$name = Test::Class->current_method unless defined($name);
		$self->SUPER::ok($test, $name);
	};

	... later on in Test::Class ...

	sub runtests {
		local $Test::Class::Default = My::Test::Builder->create(
			state => Test::Builder->default->state
		);
		.... all tests in method will now use my T::B class ...
	};

So I guess we need several constructors.
If you have explicit access to the state I think you can get away with one (and the mangled new(), which is really just there for backwards compatibility).

real constructor      - a "normal" constructor
my $new = Test::Builder->create();

singleton constructor - returns a single, default object.  This object
	  	        is actually a wrapper around the default object
			stack.
	my $default = Test::Builder->new();		# or ...
	my $default = $Test::Builder::Default;	# or ...
	my $default = Test::Builder->default;

depending on how you want to play with the API. Not really a
constructor - just provides access to the default object created by
Test::Builder.

copy	  	      - copies the state of one object to a new one
use Storable qw(dclone);
my $clone_tb = Test::Builder->create(state => dclone($tb->state));

copy & share state    - like copy, but test state is shared between the
       	     	      	original and the copy
my $shared_tb = Test::Builder->create(state => $tb->state);

with those plus the push/pop_stack methods you can pick and choose what sort
of state sharing you want.
If we have explicit access to the state - is there any advantage in
having a stack of objects?

Localising $Test::Builder::Default, or saving/restoring T::B->default
would seem to give you all the functionality necessary. (You could
always subclass T::B if you really wanted it :-)

I really don't want to think about how all this will interact with threading :-/

Cheers,

Adrian

Reply via email to