Re: Proposal: Test::MakeVariantTestFiles - take 2

Jens Rehsack <[email protected]>
Newsgroups gmane.comp.lang.perl.modules.dbi.sybase.devel
Message-ID <[email protected]>
Am 07.10.2013 um 23:51 schrieb Tim Bunce <[email protected]>:

My apologizes for later reply - very, very busy and trouble'ish
weeks … :/

I try to stay short *gg*

> [sorry for the delay] Here's a more fleshed-out example...
> 
> This example is talking about two modules:
> 
> One that provides the write_variants sub and some related classes.
> It's dumb, with no DBI or DBIT knowledge at all. Let's call it
> WriteVariants for now.
> 
> The other is closer to the original DBIT 'DSN provider'. Let's call it
> DBIT::Provider for now. It calls WriteVariants with the appropriate
> arguments to create the variant test files needed for DBIT.
> That's a set of tests, plus a list of callbacks.
> 
> 
> Rough terminology:
> 
> A 'setting' is an instance of a trivial class that can provide a string
> of code for setup and teardown of a setting.
> 
> new_env_setting is a function that returns an instance of 'setting'
> object that represents an environment variable.
> 
> new_multi_setting is a function that returns an instance of 'setting'
> object that represents an ordered list of other settings.
> 
> A 'setting provider' callback returns a hash of name-settings pairs.
> 
> 
> Example:
> 
> write_variants(
>    providers => [  # the order is important
>        \&dbi_settings_provider,
>        \&driver_settings_provider,
>        \&dbd_settings_provider,
>    ],
>    test_cases => { # details not important here
>        # 
>        01basics => TC->new(...),
>        02dbidrv => TC->new(...),
>        ...
>    },
> );

Probably that test_cases part should be discussed again at weekly
DBIT meeting. Until that (and because of it looks not important
for following details), I ignore that they're mentioned here ^^

> sub dbi_settings_provider {
> 
>    my %settings = (
>        pureperl => new_env_setting(DBI_PUREPERL => 2),
>        gofer    => new_env_setting(DBI_AUTOPROXY => 'dbi:Gofer:transport=null;policy=pedantic'),
>    );
> 
>    # Add combinations:
>    # Returns the original settings plus extras created by combining.
>    # In this case returns one extra key-value pair, i.e.:
>    # $settings{pureperl_gofer} = new_multi_setting( $settings{pureperl}, $settings{gofer} );
>    %settings = add_combinations(%settings);
> 
>    # add a 'null setting' that tests plain DBI with default environment
>    $settings{plain} = undef;
> 
>    return %settings;
> }

Beside the unlucky example, that looks suitable. Initially I was a bit against
putting the "add_combinations" into this sub - but thinking over it for a while
and suddenly it makes sense …

> This dbi_settings_provider returns four sets of name-settings pairs.
> One has no settings, two have a single setting, and one has two settings.
> 
> 
> sub driver_settings_provider {
>    my ($settings_context) = @_;
> 
>    my @drivers = Test::Database->list_drivers("available");
> 
>    # filter out non-pureperl drivers if testing with DBI_PUREPERL
>    @drivers = grep { driver_is_pureperl($_) } @drivers
>        if $settings_context->env('DBI_PUREPERL');
> 
>    return map { $_ => new_env_setting(DBI_DRIVER => $_) } @drivers;
> }
> 
> This driver_settings_provider is called once for each of the
> name-settings pairs returned by dbi_settings_provider.
> 
> The $settings_context argument is an object representing the
> settings that are 'in effect' for this call.
> 
> It returns a name-settings pair for each driver that's
> testable with the current settings (e.g. DBI_PUREPERL).
> 
> 
> sub dbd_settings_provider {
>    my ($settings_context) = @_;
> 
>    # this would dispatch to plug-ins based on the value of
>    # $settings_context->env('DBI_DRIVER')
> 
>    return %settings;
> }
> 
> This dbd_settings_provider is called once for each of the
> name-settings pairs returned by driver_settings_provider
> for each of name-settings pairs returned by dbi_settings_provider.
> 
> For example, the plug-in for the DBM driver would return settings for
> combinations of dbm_mldbm and dbm_type. The plug-in for the CSV driver
> would return settings for csv_class etc. Both would have variants for
> SQL::Nano vs SQL::Statement.

Well, those variants are in dbi_settings (because of API history …) and
cannot simply move to a dbd_settings_provider. But this one could
(following you API proposal) filter or inject such settings into the
dbi tree and force a re-combination …

This is - however - a part which has to be rethought together.

> write_variants(
>    providers => [  # the order is important
>        \&dbi_settings_provider,
>        \&driver_settings_provider,
>        \&dbd_settings_provider,
>    ],
>    test_cases => { # details not important here
>        # 
>        foo => TC->new(...),
>        bar => TC->new(...),
>        ...
>    },
> );
> 
> When write_variants is called, as shown above, it would call dbi_settings_provider,
> for each setting returned by that sub it would call driver_settings_provider,
> for each setting returned by that sub it would call dbd_settings_provider
> for each setting returned by that sub it would write the test_cases
> using the current settings from each provider being iterated over.
> 
> As each test case it written it would include the setup and teardown
> code for each of the current settings. Each test case could be an
> instance of a trivial object with a simple API so people could use their
> favorite template system if they want.
> 
> When write_variants returns we'd have a directory tree like this:
> 
>    t/
>      plain/
>        csv/
>          sql_nano/
>            foo.t
>            bar.t
>          sql_statement/
>            foo.t
>            bar.t
>        SQLite/
>          foo.t
>          bar.t
>      gofer/
>        csv/
>          sql_nano/
>            ...
>          sql_statement/
>            ...
>        SQLite/
>          ...
>      pureperl_gofer/
>        .../

You suggest to go deep down into directory structure instead of doing
t/dbit/zvgn_cvp_foo.t
t/dbit/zvgn_cvx_foo.t
t/dbit/zvg_cvp_foo.t
t/dbit/zvg_cvx_foo.t
t/dbit/zvgn_cvp_bar.t
t/dbit/zvgn_cvx_bar.t
t/dbit/zvgn_cvp_bar.t
t/dbit/zvgn_cvx_bar.t

It's nicer - but you currently ignore namespace prefixes of foo.t coming
from DBI and another foo.t coming from SQL::Statement (well - we can
coordinate - but I think you get my example).

> etc.
> 
> Clearly the example above is rough but I hope it's enough to give a good
> sense of the approach I'm suggesting.
> 
> I think this gives us good separation of concerns. There's a clear
> division of responsibility between WriteVariants on one hand, plug-in
> modules providing variants on the other, and DBIT::Provider glueing them
> together in the middle. All with clear responsibilities and interfaces.

You improved the internal API of DBI::Test::Conf - that's what currently
happens there ;)

> WriteVariants (or whatever it gets called) is clearly not dependant on DBI
> and I can't see any reason DBIT::Provider (or whatever it gets called)
> should be either.
> 
> Any thouhts?


Just for clarification how it's currently (it needs API design, no question!):

And finally - current DBIT (ignoring already outsourced DSN::Provider ^^) is
70% test variant generator
20% DBI::Mock
8% test support (connect_ok …)
2% test cases for DB[ID] 

What is that DBI::Mock for? It makes it much easier to write same test for
Database engine than for a DBD by using a suitable (RootClass => ) in connect.

Cheers
-- 
Jens Rehsack
[email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.