http://www.perlmonks.org?node_id=1228760


in reply to Error: Attempt to reload module.. while testing failing require

Note the pseudocode documented in require: If a module fails to compile (do returns undef), it will still get an %INC entry of undef, meaning that the entry still exists, but it indicates a failed load.

$ echo "die" > Whatever.pm $ perl -wMstrict -MData::Dumper -I. -e 'eval {require Whatever}; print + Dumper({%INC{"Whatever.pm"}})' $VAR1 = { 'Whatever.pm' => undef };

perldiag says about that error message: "Attempt to reload %s aborted. (F) You tried to load a file with use or require that failed to compile once already. Perl will not try to compile this file again unless you delete its entry from %INC. See require and %INC in perlvar." So deleteing the %INC entry is the way to go, if you want to structure your code this way.

By the way, is the source of Win32::Backup::Robocopy this? I don't see it currently making use of $ENV{PERL_ROBOCOPY_EXE}. It would be good to see that source.

how can I return 0 from the BEGIN block?

I don't think you can... I'm not sure that you need BEGIN blocks in the test code in the first place (as you noted in your update).

There are better ways to do this?

My suggestion would be to not do the checks on $ENV{PERL_ROBOCOPY_EXE} in the main code of the module. It seems to be an OO module, so why not just check for robocopy.exe in new? If you want to give a default that should be modifiable globally, then you could put an our $DEFAULT_ROBOCOPY_EXE = length $ENV{PERL_ROBOCOPY_EXE} ? $ENV{PERL_ROBOCOPY_EXE} : 'C:\\Windows\\System32\\robocopy.exe'; in the main code of Win32::Backup::Robocopy, and then in new, do something along the lines of $self->{robocopy_exe} = $arg{robocopy_exe}//$DEFAULT_ROBOCOPY_EXE;, and then check for the existence of the file $self->{robocopy_exe}.

If this were a non-OO module that has to do some initialization in the main code of the module, then I might abstract out that initialization into a sub, such as sub _init { ... } _init();, as this would allow me to call My::Module::_init() as many times as I want without having to reload the module.

Replies are listed 'Best First'.
Re^2: Error: Attempt to reload module.. while testing failing require
by Discipulus (Canon) on Jan 21, 2019 at 09:39 UTC
    Thanks haukex for your crystalline suggestions,

    now I see: a module returning 0 will be automatically removed from %INC while a module that explicitly die will be not:

    echo die > Whatever.pm perl -wMstrict -MData::Dump -I. -e "my $ret;eval {$ret=require Whatev +er};dd $ret; dd %$INC{$_} for grep{/Whatever/} keys %INC;" undef # <--- require returned value ("Whatever.pm", undef) # die in the module will leaves the entry in % +INC echo 0 > Whatever.pm perl -wMstrict -MData::Dump -I. -e "my $ret;eval {$ret=require Whatev +er};dd $ret; dd %$INC{$_} for grep{/Whatever/} keys %INC;" undef # <--- require returned value

    So i have no chanche at all to make the require or use to fail (in the right, intended way, aka leaving %INC clean)?

    Yes you pointed to the right source of Win32::Backup::Robocopy (thanks!) but as I'm now git aware ( ;) the env branch was not online: now is there

    Definitevely put the check in the new is better: but I'd avoid the our solution: are not to avoid these in modules? In the new call I can put a _check_robocopy but definetively I have to die or croack if what is set in %ENV is invalid.

    In the test I have to delete from %INC anyway..

    Thanks!

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      So i have no chanche at all to make the require or use to fail (in the right, intended way, aka leaving %INC clean)?

      That depends on what you're testing. You can override require (e.g. BEGIN { *CORE::GLOBAL::require = sub { ... } }, newer versions of Perl required), point require at a different file that returns a false value, or, if you really wanted to, you could put a return; in Win32::Backup::Robocopy (because simplifying a lot, use is require is do is eval, and you can return from the latter two). However, I wouldn't recommend that, because having a bare return in the main code of module is IMO quite unusual, and in a normal Perl script would cause an "Can't return outside a subroutine" error. If what you are testing is the behavior of Win32::Backup::Robocopy, then I'd only consider two options realistic: it dieing, or it succeeding. To test that, yes, you will have to delete from %INC to get require to reload the module - but actually, I'd recommend the solutions I suggested above instead.

      the our solution: are not to avoid these in modules?

      I don't see why not? IMHO it's fine to use them in the way I showed, it allows setting a global default while still giving the user per-object control via a new argument. If a package variable was the only way to set a module's option, then yes, I'd agree that's not the best design choice, since users would have to use local or risk action-at-a-distance. But in some cases, some defaults really are global - just one example, in my File::Replace, I have a $File::Replace::DISABLE_CHMOD global, which can be set in the case that the system doesn't support chmod (but it can still be set on a per-object basis).

      definetively I have to die or croack if what is set in %ENV is invalid.

      Hm, I think I understand, you want to let the user know if what they set in %ENV is invalid? I think it's a design choice at which point you do that and whyhow. For example, if $ENV{PERL_ROBOCOPY_EXE} contains an invalid location, but a valid location is passed to new, is that really a fatal error? TIMTOWTDI, but I might consider just issuing a warning when Win32::Backup::Robocopy is loaded with a bad $ENV{PERL_ROBOCOPY_EXE} setting.