bliako has asked for the wisdom of the Perl Monks concerning the following question:

Often in test files I create temporary dirs with File::Temp's tempdir(). I want these tempdirs to be erased upon successful completion or be retained in case of test failures with a BAIL_OUT or 3rdparty die, for inspection. I am using Test::More's BAIL_OUT() because I see no point in keep testing when the input file is missing!

The solution (Re^4: File::Temp does not cleanup() on demand) is to set the global $File::Temp::KEEP_ALL = 1; and do manual cleanup only on successful completion with this at the end:

$File::Temp::KEEP_ALL = 0; File::Temp::cleanup;

All works OK except that it messes up the temp dirs handling of other modules I import. For example:

use strict; use warnings; use Capture::Tiny qw(capture); use File::Temp 'tempdir'; $File::Temp::KEEP_ALL = 1; my $tmpdir = File::Temp::tempdir(); print "at1: ";system("ls /tmp | wc -l"); my ($stdout, $stderr) = capture { # do some system command }; # now there are two more temp files in /tmp # they should have not been there! print "at2: ";system("ls /tmp | wc -l"); $File::Temp::KEEP_ALL = 0; File::Temp::cleanup; # they are still there print "at3: ";system("ls /tmp | wc -l");

Above, I monitor the /tmp dir for temp files and sure enough there are 2 more (for capture's saving the stderr, stdout) on completion.

The problem is fixed if I set $File::Temp::KEEP_ALL = 0; or don't use it at all.

And I have experimented with the order of importing the packages, but it has no effect. The global variable affects them anyway.

Ideally, my cleanup() would have cleaned capture's cleanup. But it doesn't. Although my $KEEP_ALL affects capture's behaviour.

I have thought about the OO interface of File::Temp but it still relies on the global $KEEP_ALL, from the doc:

... If the global variable $KEEP_ALL is true, the file or directory will n +ot be removed.

I wonder if there is a way around it. Perhaps a saner File::Temp which is not relying on global variables? One which encapsulates options to an object rather than relying on globals for such a sensitive issue? It looks to me there could be security implications in this too.

bw, bliako

  • Comment on "localise" package's our variables? They affect other packages (e.g. File::Temp::KEEP_ALL)
  • Select or Download Code

Replies are listed 'Best First'.
Re: "localise" package's our variables? They affect other packages (e.g. File::Temp::KEEP_ALL)
by ikegami (Patriarch) on Oct 10, 2024 at 16:20 UTC
    use File::Temp qw( ); my $tmpdir = File::Temp->newdir( CLEANUP => 0 ); ... $tmpdir->unlink_on_destroy( 1 ) if $success;

    Test:

    $ tt() { perl -e' use v5.14; use File::Temp qw( ); my $tmpdir = File::Temp->newdir( CLEANUP => 0 ); say $tmpdir; my $success = $ARGV[0]; $tmpdir->unlink_on_destroy( 1 ) if $success; ' -- "$@" } $ t() { local tmpdir=$( tt "$@" ) if [ -e "$tmpdir" ]; then echo Preserved rm -r -- "$tmpdir" else echo Cleaned fi } $ t 0 Preserved $ t 1 Cleaned

    Update: Needed to switch from File::Temp::tempdir to File::Temp->newdir to work.

      Fixed.

Re: "localise" package's our variables? They affect other packages (e.g. File::Temp::KEEP_ALL)
by swl (Prior) on Oct 10, 2024 at 21:38 UTC

    This does not answer the technical aspects of the question, but it might be worth considering Test::TempDir::Tiny as an alternative approach.

      Thank you, that is the simplest solution. Additionally it allows for env var PERL_TEST_TEMPDIR_TINY_NOCLEANUP to be set to 1 and keep the temp files at the end of the successful test anyway.

      I am tempted to fork and rename it to something which does not contain "Test::" as I intend to use this in code other than testing :)

Re: "localise" package's our variables? They affect other packages (e.g. File::Temp::KEEP_ALL)
by choroba (Cardinal) on Oct 10, 2024 at 14:59 UTC
    Does it really look at the value of the global variable at the moment of deletion, or at the moment of creation?

    If the latter, the following should work:

    $File::Temp::KEEP_ALL = 1; my $tmpdir = File::Temp::tempdir(); $File::Temp::KEEP_ALL = 0;

    Sorry, too busy to properly test at the moment.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Both.

Re: "localise" package's our variables? They affect other packages (e.g. File::Temp::KEEP_ALL)
by ikegami (Patriarch) on Oct 10, 2024 at 16:37 UTC
    use File::Path qw( remove_tree ); use File::Temp qw( tempdir ); my $tmpdir = tempdir( CLEANUP => 0 ); ... remove_tree( $tmpdir ) if $success;

    Test:

    $ tt() { perl -e' use v5.14; use File::Path qw( remove_tree ); use File::Temp qw( tempdir ); my $tmpdir = tempdir( CLEANUP => 0 ); say $tmpdir; my $success = $ARGV[0]; remove_tree( $tmpdir ) if $success; ' -- "$@" } $ t() { local tmpdir=$( tt "$@" ) if [ -e "$tmpdir" ]; then echo Preserved rm -r -- "$tmpdir" else echo Cleaned fi } $ t 0 Preserved $ t 1 Cleaned

      Thank you. It is far from elegant (not your fault:)) but perhaps my test files will be templated on the below from now on. The question still remains though re: global variables in packages ...

      ################################################################### #### NOTE env-var TEMP_DIRS_KEEP=1 will stop erasing tmp files ################################################################### use strict; use warnings; our $VERSION = '1.01'; use Test::More; use Test::More::UTF8; use FindBin; use File::Temp 'tempdir'; use File::Path qw( remove_tree ); my $curdir = $FindBin::Bin; my $tmpdir = File::Temp::tempdir( CLEANUP=>0, TEMPLATE => 'ooooooooXXXX' ); ok(-d $tmpdir, "tmpdir exists $tmpdir") or BAIL_OUT; #die 123; # this should keep the tmpdir intact for inspection #BAIL_OUT('bailing out'); # this should keep the tmpdir intact for ins +pection # if you set env var TEMP_DIRS_KEEP=1 when running # the temp files WILL NOT BE DELETED otherwise # they are deleted automatically diag "temp dir: $tmpdir ..."; do { remove_tree( $tmpdir ); diag "temp files cleaned!"; } unless exists($ENV{'TEMP_DIRS_KEEP'}) && $ENV{'TEMP_DIRS_KEEP'}>0; # END done_testing;
        This shows nicely why I hate state variables in named subroutines. You can't call the subroutine from anywhere else without touching the same value.

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

        It is far from elegant

        huh? It's the very definition of elegance. It's trivially simple, and very clearly says what it does.

      The parent is NOT a dupe. It is a different solution than the other similar post.

        sorry; the posts looked rather similar to me, and I thought the code differences could be explained by the "update: needed to switch..." addenda to the first post -- I thought two originally got posted, then you edited the first; I was apparently wrong. I'll be more careful in DUP-consideration in the future.