package tests::PluginManagerTest;

use strict;

use base qw/ Lire::Test::TestCase /;

use Lire::PluginManager;
use Lire::DlfAnalyser;
use Lire::Utils qw/tempdir create_file /;
use File::Path qw/mkpath rmtree/;
use File::Basename;
use Cwd qw/realpath/;
use Lire::Test::Mock;

sub set_up {
    my $self = $_[0];
    $self->SUPER::set_up();

    $self->{'_old_mgr'} = $Lire::PluginManager::instance;
    $Lire::PluginManager::instance = $self->{'mgr'}=new Lire::PluginManager();

    $self->{'tmpdir'} = tempdir( "PluginManager_XXXXXX", 'CLEANUP' => 1 );

    return;
}

sub tear_down {
    my $self = $_[0];
    $self->SUPER::tear_down();

    $Lire::PluginManager::instance = $self->{'_old_mgr'};
    rmtree( $self->{'tmpdir'} );

    return;
}

sub test_instance {
    my $self = $_[0];

    $self->assert_str_equals( $self->{'mgr'},
                              Lire::PluginManager->instance() );
}

sub test_check_type {
    my $self = $_[0];

    local %Lire::PluginManager::plugin_types =
      ( 'a_type' => 'Package', 'b_type' => 'AnotherPackage' );

    $self->assert( Lire::PluginManager::check_type( 'a_type' ) );
    $self->assert_dies( qr/missing 'type' parameter/,
                        sub { Lire::PluginManager::check_type( undef ) } );
    $self->assert_dies( qr/'type' parameter should be one of 'a_type' or 'b_type': 'bad'/,
                        sub { Lire::PluginManager::check_type( 'bad' ) } );
}

sub test_plugin_names {
    my $self = $_[0];

    my $mgr = $self->{'mgr'};
    %{$mgr->{'output_format'}} = ( 'test1' => undef, 'test2' => undef );
    $self->assert_deep_equals( [ 'test1', 'test2' ],
                               [ sort @{ $mgr->plugin_names( 'output_format' )} ] );
    $self->assert_deep_equals( [ 'test1', 'test2' ],
                               [ sort @{ Lire::PluginManager->plugin_names( 'output_format' )} ] );
}

sub test_plugins {
    my $self = $_[0];

    my $mgr = $self->{'mgr'};
    %{$mgr->{'output_format'}} = ( 'test1' => 1, 'test2' => 2 );
    $self->assert_deep_equals( [ 1, 2 ],
                               [ sort @{ $mgr->plugins( 'output_format' )} ] );

    $self->assert_deep_equals( [ 1, 2 ],
                               [ sort @{ Lire::PluginManager->plugins( 'output_format' )} ] );
}

sub test_has_plugin {
    my $self = $_[0];

    my $mgr = $self->{'mgr'};
    %{$mgr->{'output_format'}} = ( 'test1' => 1, 'test2' => 2 );
    $self->assert( ! $mgr->has_plugin( 'dlf_converter', 'not_there' ) );
    $self->assert( Lire::PluginManager->has_plugin( 'output_format', 'test1' ) );
}

sub test_get_plugin {
    my $self = $_[0];

    my $mgr = $self->{'mgr'};
    %{$mgr->{'output_format'}} = ( 'test1' => 1, 'test2' => 2 );
    $self->assert_num_equals( 1, $mgr->get_plugin( 'output_format', 'test1' ) );
    $self->assert_num_equals( 2, Lire::PluginManager->get_plugin( 'output_format', 'test2' ) );
    $self->assert_dies( qr/no 'dlf_converter' plugin 'test3' registered/,
                        sub { $mgr->get_plugin( 'dlf_converter', 'test3' ) } );
}

sub test_register_plugin {
    my $self = $_[0];

    my $mgr = $self->{'mgr'};
    $mgr->{'dlf_converter'}{'test'} = 1;

    my $plugin = new Lire::Test::Mock( 'Lire::Plugin', 'name' => 'test',
                                       'type' => 'dlf_converter' );
    $self->assert_dies( qr/'dlf_converter' plugin should be a 'Lire::DlfConverter' instance: 'Class::Inner/,
                        sub { $mgr->register_plugin( $plugin ) } );

    my $test = new Lire::Test::Mock( 'Lire::DlfConverter', 'name' => 'test',
                                     'type' => 'dlf_converter' );
    $self->assert_dies( qr/there is already a 'dlf_converter' plugin registered under name 'test'/,
                        sub { $mgr->register_plugin( $test ) } );

    my $unknown_type = new Lire::Test::Mock( 'Lire::DlfConverter',
                                             'name' => 'test1',
                                             'type' => 'bad_type' );
    $self->assert_dies( qr/plugin 'test1' has an unknown type: 'bad_type'/,
                        sub { $mgr->register_plugin( $unknown_type ) } );

    my $test2 = new Lire::Test::Mock( 'Lire::DlfConverter', 'name' => 'test2',
                                      'type' => 'dlf_converter' );
    Lire::PluginManager->register_plugin( $test2 );
    $self->assert_str_equals( $test2, $mgr->{'dlf_converter'}{'test2'} );
}

sub test_unregister_plugin {
    my $self = $_[0];

    my $mgr = $self->{'mgr'};
    %{$mgr->{'output_format'}} = ( 'test1' => 1, 'test2' => 2 );
    $mgr->unregister_plugin( 'output_format', 'test1' );
    $self->assert_null( $mgr->{'output_format'}{'test1'} );

    Lire::PluginManager->unregister_plugin( 'output_format', 'test2' );
    $self->assert_null( $mgr->{'output_format'}{'test2'} );

    $self->assert_dies( qr/no 'dlf_converter' plugin 'test3' registered/,
                        sub { $mgr->unregister_plugin( 'dlf_converter', 'test3' ) } );

}

sub test_register_default_plugins {
    my $self = $_[0];

    mkpath( [ $self->{'tmpdir'} . "/dir1",
              $self->{'tmpdir'} . "/dir2",
            ], 0, 0755 ) == 2
              or $self->error( "mkpath failed: $!\n" );
    create_file( "$self->{'tmpdir'}/dir1/failing", 'die "error here\n"' );
    create_file( "$self->{'tmpdir'}/dir2/a_plugin", <<EOF );
package converter;
use base qw/Lire::DlfConverter/;
use Lire::PluginManager;

sub name { 'test' }

sub type { 'dlf_converter' }

Lire::PluginManager->register_plugin( bless {}, 'converter' );

return;
EOF
    my @warn = ();
    local $SIG{'__WARN__'} = sub { push @warn, join( "", @_ )};

    $self->{'cfg'}{'plugins_init_path'} = [ "$self->{'tmpdir'}/dir1",
                                            "$self->{'tmpdir'}/dir2" ];

    no warnings 'redefine';
    local *Lire::PluginManager::_load_dlf_adapters =
        sub { $_[0]{'_load_dlf_adapters'} = 1 };
    local *Lire::PluginManager::_create_old_dlf_adapters =
        sub { $_[0]{'_create_old_dlf_adapters'} = 1 };

    Lire::PluginManager->register_default_plugins();

    $self->annotate( @warn );

    $self->assert_isa( 'Lire::DlfConverter',
                        $self->{'mgr'}{'dlf_converter'}{'test'} );

    $self->assert_num_equals( 1, scalar @warn );
    $self->assert_matches( qr!error in initialiser '$self->{'tmpdir'}/dir1/failing': error here!,
                           $warn[0] );
    $self->assert_num_equals( 1, $self->{'mgr'}{'_load_dlf_adapters'} );
    $self->assert_num_equals( 1, $self->{'mgr'}{'_create_old_dlf_adapters'} );

}

sub test_init_files {
    my $self = $_[0];

    my $dirs = [ $self->{'tmpdir'} . "/dir1",
                 $self->{'tmpdir'} . "/dir2",
                 $self->{'tmpdir'} . "/dir1",
               ];
    mkpath( $dirs, 0, 0755 ) == 2
      or $self->error( "mkpath failed: $!\n" );
    my @files = ( $self->{'tmpdir'} . "/dir1/test",
                  $self->{'tmpdir'} . "/dir1/test3",
                  $self->{'tmpdir'} . "/dir2/test2",
                );
    foreach my $f ( @files ) {
        create_file( $f );
    }

    $self->assert_deep_equals( \@files,
                               Lire::PluginManager->_init_files( $dirs ) );
}

sub test_load_dlf_adapters {
    my $self = $_[0];

    my $dirs = [ $self->{'tmpdir'} . "/dlf_converters1",
                 $self->{'tmpdir'} . "/dlf_converters2" ];
    mkpath( $dirs, 0, 0755 ) == 2
      or $self->error( "mkpath failed: $!\n" );
    $self->{'cfg'}{'lr_converters_init_path'} = $dirs;

    create_file( $self->{'tmpdir'} . "/dlf_converters1/test1" );
    chmod 0000, $self->{'tmpdir'} . "/dlf_converters1/test1"
      or $self->error( "chmod failed: $!\n" );
    create_file( $self->{'tmpdir'} . "/dlf_converters1/test2", <<'EOF' );
use strict;

$x = 1;
EOF

    create_file( $self->{'tmpdir'} . "/dlf_converters1/test3", <<'EOF' );
package Converter;

use base qw/Lire::DlfConverter/;

sub new {
    bless { 'name' => $_[1] }, $_[0];
}

sub name { $_[0]{'name'} };

return ( new Converter( 'test1' ), new Converter( 'test2' ) );
EOF
    create_file( $self->{'tmpdir'} . "/dlf_converters2/test4", <<'EOF' );
return bless {}, "Test";
EOF

    my @msg = ();
    local $SIG{'__WARN__'} = sub { push @msg, join "", @_;
                                   $self->annotate( $msg[-1] ); };
    $self->{'mgr'}->_load_dlf_adapters();

    # Reset the permission, so it can be deleted with rmtree()
    chmod 0644, $self->{'tmpdir'} . "/dlf_converters1/test1"
      or $self->error( "chmod failed: $!\n" );

    $self->assert( @msg == 3, "expected 3 warnings" );
    $self->assert_matches( qr/error reading DLF converter/, $msg[0] );
    $self->assert_matches( qr/error while running initializer/, $msg[1] );
    $self->assert_matches( qr/didn't return a Lire::DlfConverter/, $msg[2] );

    $self->assert_isa( 'Converter', $self->{'mgr'}{'dlf_converter'}{'test1'} );
    $self->assert_isa( 'Converter', $self->{'mgr'}{'dlf_converter'}{'test2'} );
}

sub test_parse_map_file {
    my $self = $_[0];

    my $address_file = $self->{'tmpdir'} . "/address.cf";
    create_file ( $address_file, <<EOF );
# A comment

service superservice # with comment
service1 schema1
line with an error
# More comment
EOF

    my @msg = ();
    local $SIG{'__WARN__'} = sub { push @msg, join "", @_ };
    my $services = $self->{'mgr'}->_parse_old_map_file( $address_file );
    $self->annotate( join "\n", @msg );
    $self->assert( @msg == 1, "expected one warning, got " . scalar @msg );
    $self->assert_matches( qr/can't parse line 5/, $msg[0] );

    $self->assert_deep_equals( { 'service' => 'superservice',
                                 'service1' => 'schema1',
                               }, $services );
}

sub test_create_old_dlf_adapters {
    my $self = $_[0];

    my $address_file = $self->{'tmpdir'} . "/address.cfg";
    $self->{'cfg'}{'lr_old_convertors_dir'} = $self->{'tmpdir'};
    $self->{'cfg'}{'lr_old_address_file'} = $address_file;


    create_file( "$self->{'tmpdir'}/service12dlf" );
    create_file( "$self->{'tmpdir'}/service22dlf" );
    create_file( "$self->{'tmpdir'}/service42dlf" );
    chmod( 0755, map { "$self->{'tmpdir'}/" . $_ . "2dlf" } qw/service1 service2/) == 2
      or $self->error( "chmod failed" );
    create_file( $address_file, <<EOF );
service1 test       # Good
service2 unknown    # Bad superservice
service3 test       # service32dlf doesn't exist
service4 test       # not executable
EOF

    $self->{'cfg'}{'lr_schemas_path'} = [ realpath(dirname( __FILE__ )) . "/schemas" ];
    my @msg = ();
    local $SIG{'__WARN__'} = sub { my $m = join "", @_;
                                   $self->annotate( $m);
                                   push @msg, $m; };
    $self->{'mgr'}->_create_old_dlf_adapters();

    @msg = sort @msg;
    my @warnings = ( qr/can't find executable service32dlf/,
                     qr/can't find executable service42dlf/,
                     qr/invalid superservice/
                   );
    $self->assert_num_equals( scalar @msg, scalar @warnings );
    for ( my $i=0; $i < @warnings; $i++ ) {
        $self->assert_matches( $warnings[$i], $msg[$i] );
    }

    my @converters = ( "service1" );
    $self->assert_isa( 'Lire::OldDlfAdapter',
                       $self->{'mgr'}{'dlf_converter'}{'service1'} );
}


sub set_up_fake_analysers {
    my $self = $_[0];

    $self->{'mgr'}{'dlf_analyser'}{'extended'} =
      new Lire::Test::Mock( 'Lire::DlfAnalyser', 'name' => 'extended',
                            'src_schema' => 'test',
                            'dst_schema' => 'test-extended' );

    $self->{'mgr'}{'dlf_analyser'}{'extended2'} =
      new Lire::Test::Mock( 'Lire::DlfAnalyser', 'name' => 'extended2',
                            'src_schema' => 'test',
                            'dst_schema' => 'test-extended', );

    $self->{'mgr'}{'dlf_analyser'}{'derived'} =
      new Lire::Test::Mock( 'Lire::DlfAnalyser',
                            'name' => 'derived',
                            'src_schema' => 'test-derived',
                            'dst_schema' => 'another-schema' );

    return;
}

sub test_analysers_by_src {
    my $self = $_[0];

    $self->set_up_fake_analysers();

    $self->assert_deep_equals( [], $self->{'mgr'}->analysers_by_src( 'wawa' ));
    my @analysers = sort { $a->name() cmp $b->name() }
      @{$self->{'mgr'}->analysers_by_src( 'test' )};
    $self->assert_deep_equals( [ $self->{'mgr'}{'dlf_analyser'}{'extended'},
                                 $self->{'mgr'}{'dlf_analyser'}{'extended2'}],
                               \@analysers );
}

sub test_analysers_by_dst {
    my $self = $_[0];

    $self->set_up_fake_analysers();
    $self->assert_deep_equals( [], $self->{'mgr'}->analysers_by_dst( 'wawa' ));
    my @analysers = sort { $a->name() cmp $b->name() }
      @{$self->{'mgr'}->analysers_by_dst( 'test-extended' )};
    $self->assert_deep_equals( [ $self->{'mgr'}{'dlf_analyser'}{'extended'},
                                 $self->{'mgr'}{'dlf_analyser'}{'extended2'}],
                               \@analysers );
}

1;


syntax highlighted by Code2HTML, v. 0.9.1