The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

use Test::More;
use Test::Fatal;
use Class::MOP::Mixin::HasMethods;

# When the Perl debugger is enabled, %DB::sub tracks method information
# (line numbers and originating file).  However, the reinitialize()
# functionality for classes and roles can sometimes clobber this information,
# causing to reference internal MOP files/lines instead.
# These tests check to make sure the the reinitialize() functionality
# preserves the correct debugging information when it (re)adds methods
# back into a class or role.

BEGIN {
     $^P = 831; # Enable debug mode
}

# Empty debugger
sub DB::DB {}

my ($foo_role_start, $foo_role_end, $foo_start_1, $foo_end_1, $foo_start_2, $foo_end_2);

# Simple Moose Role
{
    package FooRole;
    use Moose::Role;

    $foo_role_start = __LINE__ + 1;
    sub foo_role {
        return 'FooRole::foo_role';
    }
    $foo_role_end = __LINE__ - 1;
}

# Simple Moose package
{
    package Foo;
    use Moose;

    with 'FooRole';

    # Track the start/end line numbers of method foo(), for comparison later
    $foo_start_1 = __LINE__ + 1;
    sub foo {
        return 'foo';
    }
    $foo_end_1 = __LINE__ - 1;

    no Moose;
}

# Extend our simple Moose package, with overriding method
{
    package Bar;
    use Moose;

    extends 'Foo';

    # Track the start/end line numbers of method foo(), for comparison later
    $foo_start_2 = __LINE__ + 1;
    sub foo {
        return 'bar';
    }
    $foo_end_2 = __LINE__ - 1;

    no Moose;
}

# Check that Foo and Bar classes were set up correctly
my $bar_object = Bar->new();
isa_ok(Foo->meta->get_method('foo'), 'Moose::Meta::Method');
isa_ok(Bar->meta->get_method('foo'), 'Moose::Meta::Method');
isa_ok(Foo->meta->get_method('foo_role'), 'Moose::Meta::Method');
is($bar_object->foo_role(), 'FooRole::foo_role', 'Bar object has access to foo_role method');

# Run tests against Bar meta class...

my $bar_meta = Bar->meta;
like($DB::sub{"Bar::foo"}, qr/add_method_debugmode\.t:($foo_start_2)-($foo_end_2)/, "Check line numbers for  Bar::foo (initial)");

# Run _restore_metamethods_from directly (part of the reinitialize() process)
$bar_meta->_restore_metamethods_from($bar_meta);
like($DB::sub{"Foo::foo"}, qr/add_method_debugmode\.t:($foo_start_1)-($foo_end_1)/, "Check line numbers for Foo::foo (after _restore)");
like($DB::sub{"Bar::foo"}, qr/add_method_debugmode\.t:($foo_start_2)-($foo_end_2)/, "Check line numbers for Bar::foo (after _restore)");

# Call reinitialize explicitly, which triggers HasMethods::add_method
is( exception {
    $bar_meta = $bar_meta->reinitialize('Bar');
}, undef );
isa_ok(Bar->meta->get_method('foo'), 'Moose::Meta::Method');
like($DB::sub{"Foo::foo"}, qr/add_method_debugmode\.t:($foo_start_1)-($foo_end_1)/, "Check line numbers for Foo::foo (after reinitialize)");
like($DB::sub{"Bar::foo"}, qr/add_method_debugmode\.t:($foo_start_2)-($foo_end_2)/, "Check line numbers for Bar::foo (after reinitialize)");

# Add a method to Bar; this triggers reinitialize as well
# Check that method line numbers are still listed as part of this file, and not a MOP file
$bar_meta->add_method('foo2' => sub { return 'new method foo2'; });
like($DB::sub{"Foo::foo"}, qr/add_method_debugmode\.t:($foo_start_1)-($foo_end_1)/, "Check line numbers for Foo::foo (after add_method)");
like($DB::sub{"Bar::foo"}, qr/add_method_debugmode\.t:($foo_start_2)-($foo_end_2)/, "Check line numbers for Bar::foo (after add_method)");
like($DB::sub{"Bar::foo2"}, qr/(.*):(\d+)-(\d+)/, "Check for existence of Bar::foo2");

# Clobber Bar::foo by adding a method with the same name
$bar_meta->add_method(
    'foo' => $bar_meta->method_metaclass->wrap(
        package_name => $bar_meta->name,
        name => 'foo',
        body => sub { return 'clobbered Bar::foo'; }
    )
);
unlike($DB::sub{"Bar::foo"}, qr/add_method_debugmode\.t/, "Check that source file for Bar::foo has changed");

# Run tests against FooRole meta role ...

my $foorole_meta = FooRole->meta;
like($DB::sub{"FooRole::foo_role"}, qr/add_method_debugmode\.t:($foo_role_start)-($foo_role_end)/, "Check line numbers for FooRole::foo_role (initial)");

# Call _restore_metamethods_from directly
$foorole_meta->_restore_metamethods_from($foorole_meta);
like($DB::sub{"FooRole::foo_role"}, qr/add_method_debugmode\.t:($foo_role_start)-($foo_role_end)/, "Check line numbers for FooRole::foo_role (after _restore)");

# Call reinitialize
# Check that method line numbers are still listed as part of this file
is( exception {
    $foorole_meta->reinitialize('FooRole');
}, undef );
isa_ok(FooRole->meta->get_method('foo_role'), 'Moose::Meta::Method');
like($DB::sub{"FooRole::foo_role"}, qr/add_method_debugmode\.t:($foo_role_start)-($foo_role_end)/, "Check line numbers for FooRole::foo_role (after reinitialize)");

# Clobber foo_role method
$foorole_meta->add_method(
    'foo_role' => $foorole_meta->method_metaclass->wrap(
        package_name => $foorole_meta->name,
        name => 'foo_role',
        body => sub { return 'clobbered FooRole::foo_role'; }
    )
);
unlike($DB::sub{"FooRole::foo_role"}, qr/add_method_debugmode\.t/, "Check that source file for FooRole::foo_role has changed");

done_testing;