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 ... };
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).So I guess we need several constructors.
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 sortIf we have explicit access to the state - is there any advantage in
of state sharing you want.
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