DBIx-Loop

 view release on metacpan or  search on metacpan

t/lib/AdapterConformance.pm  view on Meta::CPAN

package AdapterConformance;

use 5.008003;
use strict;
use warnings;
use Test::More;
use File::Temp ();

# One parametric test body every loop adapter must pass identically: given an
# adapter instance, drive Backend B (the worker pool, over SQLite) through it
# and assert the seam behaves - queries resolve, concurrency works, timers
# fire while a query is in flight, errors isolate, the native-future bridge
# round-trips, and disconnect tears down cleanly.
#
#   use AdapterConformance;
#   AdapterConformance::run($adapter, name => 'IOAsync');

sub run {
    my ($ad, %opt) = @_;
    my $name = $opt{name} || ref $ad;

    require DBIx::Loop;

    my $dir  = File::Temp->newdir;
    my $file = "$dir/conf.db";
    my $db   = DBIx::Loop->connect(
        "dbi:SQLite:dbname=$file", '', '',
        { RaiseError => 1, PrintError => 0 },
        loop => $ad, workers => 2,
    );

    subtest "$name: adapter conformance" => sub {

        # -- single round trip -----------------------------------------------
        my $c = $db->do("CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)");
        $ad->await($c);
        ok($c->is_done, 'create resolves');

        for my $i (1 .. 4) {
            my $w = $db->do("INSERT INTO t (id,v) VALUES (?,?)", $i, $i * 10);
            $ad->await($w);
        }
        my $q = $db->query("SELECT id,v FROM t ORDER BY id");
        $ad->await($q);
        my $res = ($q->get)[0];
        is(scalar @{ $res->{rows} }, 4, 'four rows back');
        is_deeply($res->{columns}, ['id', 'v'], 'columns named');

        # -- concurrency: more in flight than workers -------------------------
        my @f = map { $db->query("SELECT COUNT(*) FROM t") } 1 .. 5;
        $ad->await($_) for @f;
        is(scalar(grep { $_->is_done && ($_->get)[0]{rows}[0][0] == 4 } @f), 5,
            '5 concurrent queries across 2 workers all resolve');

        # -- a timer fires while a query is in flight (loop not blocked) ------
        SKIP: {
            skip 'adapter has no timer', 1 unless $ad->can('timer');
            my $fired = 0;
            $ad->timer(0.02, sub { $fired = 1 });
            my $slow = $db->query(
                "WITH RECURSIVE c(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM c WHERE x < 200000) SELECT COUNT(*) FROM c");
            $ad->await($slow);
            # give the timer a chance if the query won the race
            $ad->await(do { my $f = DBIx::Loop::Future->new;
                            $ad->timer(0.03, sub { $f->done(1) }); $f });
            ok($fired, 'timer fired while a query was in flight');
        }

        # -- error isolation ---------------------------------------------------
        my $bad  = $db->query("SELECT * FROM missing_table");
        my $good = $db->query("SELECT 1");
        $ad->await($bad); $ad->await($good);
        ok($bad->is_failed, 'bad query fails');
        ok($good->is_done,  'good query unaffected');

        # -- native-future bridge ----------------------------------------------
        SKIP: {
            skip 'adapter has no to_native bridge', 1 unless $ad->can('to_native');
            my $f  = $db->query("SELECT 7");
            my $nf = $ad->to_native($f);
            $ad->await($f);
            ok(defined $nf, 'to_native returns a native future/promise');
        }

        # -- teardown -----------------------------------------------------------
        $db->disconnect;
        pass('disconnect clean');
    };
}

1;



( run in 0.913 second using v1.01-cache-2.11-cpan-800906f7e73 )