Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

file testing issue - tmp file cleanup #5285

Closed
wants to merge 9 commits into from

Conversation

khrystianc
Copy link

Description

Fixes #5229

(#5229)

To Do

  • Documentation. (If you've added a new command-line flag, for example, find the appropriate page under docs/ to describe it.)
  • Changelog. (Add an entry to docs/changelog.rst to the bottom of one of the lists near the top of the document.)
  • Tests. (Very much encouraged but not strictly required.)

Added cleanup function to address the creation of unnecessary tmp files during testing
Add cleanup function to test files to remove temporary files
Add cleanup function to test files to remove temporary files
Add cleanup function to test files to remove temporary files
Add cleanup function to test files to remove temporary files
Add cleanup function to test files to remove temporary files
@snejus
Copy link
Member

snejus commented Jun 5, 2024

Hi @khrystianc , thanks for this!

I did a quick test using the same shell command as I mentioned in the issue

for fi in test/plugins/*.py; do
  print Testing $fi && pytest $fi --no-cov &>/dev/null
  drs=(/tmp/tmp*)
  if (( $#drs )); then
    print -l $drs && rm -r $drs
  fi
done
Testing test/plugins/__init__.py
Testing test/plugins/lyrics_download_samples.py
Testing test/plugins/test_acousticbrainz.py
Testing test/plugins/test_advancedrewrite.py
Testing test/plugins/test_albumtypes.py
Testing test/plugins/test_art.py
/tmp/tmp2nh4cgqg.png
/tmp/tmpb0b2215s.jpg
/tmp/tmpflpki4b3.jpg
/tmp/tmpk_viivu1.jpg
/tmp/tmpoad9cbic.jpg
/tmp/tmps13osk82.png
/tmp/tmps_prgygd.jpg
/usr/bin/rm: remove 7 arguments recursively? y
removed '/tmp/tmp2nh4cgqg.png'
removed '/tmp/tmpb0b2215s.jpg'
removed '/tmp/tmpflpki4b3.jpg'
removed '/tmp/tmpk_viivu1.jpg'
removed '/tmp/tmpoad9cbic.jpg'
removed '/tmp/tmps13osk82.png'
removed '/tmp/tmps_prgygd.jpg'
Testing test/plugins/test_bareasc.py
/tmp/tmp08cv_39g
/tmp/tmp69gvrdko
/tmp/tmpukgzr05o
/usr/bin/rm: remove 3 arguments recursively? y
removed directory '/tmp/tmp08cv_39g/libdir'
removed directory '/tmp/tmp08cv_39g'
removed directory '/tmp/tmp69gvrdko/libdir'
removed directory '/tmp/tmp69gvrdko'
removed directory '/tmp/tmpukgzr05o/libdir'
removed directory '/tmp/tmpukgzr05o'
Testing test/plugins/test_beatport.py
Testing test/plugins/test_bucket.py
Testing test/plugins/test_convert.py
Testing test/plugins/test_discogs.py
Testing test/plugins/test_edit.py
Testing test/plugins/test_embedart.py
/tmp/tmp3uz5wqip
/tmp/tmp5cb9i01a
/tmp/tmp6htzlrvh
/tmp/tmpcasn0w7p
/tmp/tmpdswl3q6c
/tmp/tmpf0zeqn3s
/tmp/tmph46_2pl6
/tmp/tmphxyp5but
/tmp/tmpjbab_5fw
/tmp/tmpk6omw8ea
/tmp/tmpq0qzf1dm
/tmp/tmpquflonrj
/tmp/tmp_rnoob49
/tmp/tmps0vvpoh7.jpg
/tmp/tmpu3ay62wr
/tmp/tmpuallp51s.jpg
/tmp/tmpw3d7rxdp
/tmp/tmpzgtf8mu2
/usr/bin/rm: remove 18 arguments recursively? y
removed directory '/tmp/tmp3uz5wqip'
removed directory '/tmp/tmp5cb9i01a'
removed directory '/tmp/tmp6htzlrvh'
removed directory '/tmp/tmpcasn0w7p'
removed directory '/tmp/tmpdswl3q6c'
removed directory '/tmp/tmpf0zeqn3s'
removed directory '/tmp/tmph46_2pl6'
removed directory '/tmp/tmphxyp5but'
removed directory '/tmp/tmpjbab_5fw'
removed directory '/tmp/tmpk6omw8ea'
removed directory '/tmp/tmpq0qzf1dm'
removed directory '/tmp/tmpquflonrj'
removed directory '/tmp/tmp_rnoob49'
removed '/tmp/tmps0vvpoh7.jpg'
removed directory '/tmp/tmpu3ay62wr'
removed '/tmp/tmpuallp51s.jpg'
removed directory '/tmp/tmpw3d7rxdp'
removed directory '/tmp/tmpzgtf8mu2'
Testing test/plugins/test_embyupdate.py
Testing test/plugins/test_export.py
Testing test/plugins/test_fetchart.py
Testing test/plugins/test_filefilter.py
Testing test/plugins/test_ftintitle.py
Testing test/plugins/test_hook.py
Testing test/plugins/test_ihate.py
Testing test/plugins/test_importadded.py
Testing test/plugins/test_importfeeds.py
Testing test/plugins/test_info.py
Testing test/plugins/test_ipfs.py
Testing test/plugins/test_keyfinder.py
Testing test/plugins/test_lastgenre.py
Testing test/plugins/test_limit.py
Testing test/plugins/test_lyrics.py
Testing test/plugins/test_mbsubmit.py
Testing test/plugins/test_mbsync.py
Testing test/plugins/test_mpdstats.py
Testing test/plugins/test_parentwork.py
Testing test/plugins/test_permissions.py
Testing test/plugins/test_player.py
Testing test/plugins/test_playlist.py
Testing test/plugins/test_play.py
/tmp/tmp0wsi_ndn.m3u
/tmp/tmp7jsdhdhu.m3u
/tmp/tmp87a0xpnn.m3u
/tmp/tmpc3xz17ls.m3u
/tmp/tmpcors6_n1.m3u
/tmp/tmpikokxhu3.m3u
/tmp/tmpipeusix7.m3u
/tmp/tmpno9u_kfy.m3u
/tmp/tmpw3k0pbc9.m3u
/tmp/tmpzx73p2os.m3u
/usr/bin/rm: remove 10 arguments recursively? y
removed '/tmp/tmp0wsi_ndn.m3u'
removed '/tmp/tmp7jsdhdhu.m3u'
removed '/tmp/tmp87a0xpnn.m3u'
removed '/tmp/tmpc3xz17ls.m3u'
removed '/tmp/tmpcors6_n1.m3u'
removed '/tmp/tmpikokxhu3.m3u'
removed '/tmp/tmpipeusix7.m3u'
removed '/tmp/tmpno9u_kfy.m3u'
removed '/tmp/tmpw3k0pbc9.m3u'
removed '/tmp/tmpzx73p2os.m3u'
Testing test/plugins/test_plexupdate.py
Testing test/plugins/test_plugin_mediafield.py
Testing test/plugins/test_random.py
Testing test/plugins/test_replaygain.py
Testing test/plugins/test_smartplaylist.py
Testing test/plugins/test_spotify.py
Testing test/plugins/test_subsonicupdate.py
Testing test/plugins/test_the.py
Testing test/plugins/test_thumbnails.py
Testing test/plugins/test_types_plugin.py
Testing test/plugins/test_web.py
Testing test/plugins/test_zero.py
/tmp/tmpsb3u4lj7
/usr/bin/rm: remove 1 argument recursively? y
removed '/tmp/tmpsb3u4lj7'

it seems like the situation stays the same?


if __name__ == "__main__":
unittest.main(defaultTest="suite")

# Cleanup temporary files
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this cleanup only runs when this script is called directly

if __name__ == "__main__":

Since we use pytest to run tests, I think this logic may not run. Have you tried running, say, pytest test/plugins/test_art.py and checking whether the files have been removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the quick response. Let me review and get back to you.

@snejus
Copy link
Member

snejus commented Jun 5, 2024

@khrystianc from what I've seen most of the offending tests use inherit from the TestHelper class in beets/test/helpers.py. If you have a look at its setup_beets method I think it gives some clues about what's going on here

    def setup_beets(self, disk=False):
        """Setup pristine global configuration and library for testing.

        Sets ``beets.config`` so we can safely use any functionality
        that uses the global configuration.  All paths used are
        contained in a temporary directory

        Sets the following properties on itself.

        - ``temp_dir`` Path to a temporary directory containing all
          files specific to beets

        - ``libdir`` Path to a subfolder of ``temp_dir``, containing the
          library's media files. Same as ``config['directory']``.

        - ``config`` The global configuration used by beets.

        - ``lib`` Library instance created with the settings from
          ``config``.

        Make sure you call ``teardown_beets()`` afterwards.
        """
        self.create_temp_dir()
        os.environ["BEETSDIR"] = util.py3_path(self.temp_dir)

        self.config = beets.config
        self.config.clear()
        self.config.read()

        self.config["plugins"] = []
        self.config["verbose"] = 1
        self.config["ui"]["color"] = False
        self.config["threaded"] = False

        self.libdir = os.path.join(self.temp_dir, b"libdir")
        os.mkdir(syspath(self.libdir))
        self.config["directory"] = util.py3_path(self.libdir)

        if disk:
            dbpath = util.bytestring_path(self.config["library"].as_filename())
        else:
            dbpath = ":memory:"
        self.lib = Library(dbpath, self.libdir)

Note the last sentence in the docstring with the following warning:

    Make sure you call ``teardown_beets()`` afterwards.

I have a feeling that these tests may be misusing this functionality, where teardown_beets method may not get called?

Copy link

@bal-e bal-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really worried about this implementation. If the user has other files named /tmp/tmp*, which is entirely possible, they will get wiped out completely by this. We need a much more sophisticated solution to solve this -- or we should use /tmp/beets-test/* for all our temporary files. Less importantly, the cleanup_tmp_files() function has been duplicated to a bunch of test modules -- why not define it once and import it everywhere else?

@khrystianc
Copy link
Author

@bal-e I agree. I see what you mean. If you've got an idea on how to implement this, please give it a shot. If not, I will continue to work this issue and definitely take this observation to into account. Thank you so much

@bal-e
Copy link

bal-e commented Jul 18, 2024

Shall we close this in favor of #5362 and #5345?

snejus added a commit that referenced this pull request Jul 18, 2024
Fixes #5229, is part of #5361 and relates to #5285.

I have to admit thsi was a fairly tough task - I initially assumed that
the problem lies
with how the tests are setup, and that we're probably missing some
`teardown_beets` calls
here and there.

Unfortunately, it was not so simple. I came across several issues that
gave rise to
leftover temporary files:

1. `fetchart`, `artresizer` and `play` handling of temporary files.
These plugins created
isolated temporary files outside of the directories that tests clean up.
You will find
I added a couple of functions (namely `get_module_tempdir`) that force
these plugins to
create files in directories determined by their module names. This way
we can clean up
   after them using the new `CleanupModulesMixin`.

2. Tests that ran temporary directories setup twice, running
`_common.TestCase.setUp` and
`test.helper.TestHelper.setup_beets`. Both of these ran `self.temp_dir =
mkdtemp()`,
therefore the directories created by the initial setup persisted since
those have been
overridden and thus unreachable in the teardown. Here, I removed the
`setUp` calls, see

   - `test/plugins/test_embedart.py`
   - `test/test_importer.py`
   - and `test/test_plugins.py` where `setup_beets` was called twice

3. `test/test_config_command.py` attempted to manage the temporary
directory by itself,
where I found that `tearDown` failed to remove the directory for four
tests. Could not
figure out the cause, and found that delegating this task to
`TestHelper` fixed the
   issue.

4. Mediafile fixture removal depended on calling
`remove_mediafile_fixtures` method, which
`test/plugins/test_zero.py` failed to do. I made the fixtures to be
created within the
same `temp_dir` directory that gets removed in the teardown, so now they
are taken care
   of automatically.

In summary, see the test modules that left files behind:

```
Temp files created by test/__init__.py
Temp files created by test/plugins/__init__.py
Temp files created by test/plugins/lyrics_download_samples.py
Temp files created by test/plugins/test_acousticbrainz.py
Temp files created by test/plugins/test_advancedrewrite.py
Temp files created by test/plugins/test_albumtypes.py
Temp files created by test/plugins/test_art.py
	/tmp/tmp11nicahe.jpg
	/tmp/tmp1bjmodum.png
	/tmp/tmped7nhls4.jpg
	/tmp/tmpflnzr9wz.jpg
	/tmp/tmpjngkauqs.png
	/tmp/tmpkzy9mn6t.jpg
	/tmp/tmpph_wmuea.jpg
	/tmp/tmps6gk58i_.jpg
	/tmp/tmpz2eji_o4.jpg
Temp files created by test/plugins/test_aura.py
Temp files created by test/plugins/test_bareasc.py
	/tmp/tmphl3kzhug
	/tmp/tmpnh2q6v02
	/tmp/tmpppw5qrhz
Temp files created by test/plugins/test_beatport.py
Temp files created by test/plugins/test_bucket.py
Temp files created by test/plugins/test_convert.py
Temp files created by test/plugins/test_discogs.py
Temp files created by test/plugins/test_edit.py
Temp files created by test/plugins/test_embedart.py
	/tmp/tmp1ayvqzhx
	/tmp/tmp58k6mdfx.jpg
	/tmp/tmp64c2lqiv
	/tmp/tmp6nar4kr5
	/tmp/tmp6u0d5dex
	/tmp/tmpacoq7w_f
	/tmp/tmpajnr_sxr
	/tmp/tmpasj16beh
	/tmp/tmpboyaixb5
	/tmp/tmpcrmcyt5r
	/tmp/tmpdomje5g3
	/tmp/tmplu3o6t6g
	/tmp/tmpns_xvkns
	/tmp/tmpo87o1h6o.jpg
	/tmp/tmpqem39h_j
	/tmp/tmprlzm18pb
	/tmp/tmpt22v4u6x
	/tmp/tmptp3rxdgv
Temp files created by test/plugins/test_embyupdate.py
Temp files created by test/plugins/test_export.py
Temp files created by test/plugins/test_fetchart.py
Temp files created by test/plugins/test_filefilter.py
Temp files created by test/plugins/test_ftintitle.py
Temp files created by test/plugins/test_hook.py
Temp files created by test/plugins/test_ihate.py
Temp files created by test/plugins/test_importadded.py
Temp files created by test/plugins/test_importfeeds.py
Temp files created by test/plugins/test_info.py
Temp files created by test/plugins/test_ipfs.py
Temp files created by test/plugins/test_keyfinder.py
Temp files created by test/plugins/test_lastgenre.py
Temp files created by test/plugins/test_limit.py
Temp files created by test/plugins/test_lyrics.py
Temp files created by test/plugins/test_mbsubmit.py
Temp files created by test/plugins/test_mbsync.py
Temp files created by test/plugins/test_mpdstats.py
Temp files created by test/plugins/test_parentwork.py
Temp files created by test/plugins/test_permissions.py
Temp files created by test/plugins/test_player.py
Temp files created by test/plugins/test_playlist.py
Temp files created by test/plugins/test_play.py
	/tmp/tmp6ohknmve.m3u
	/tmp/tmp8rw2z_j4.m3u
	/tmp/tmp9vi27ypx.m3u
	/tmp/tmpa_s66jh8.m3u
	/tmp/tmpb7h3cn3n.m3u
	/tmp/tmpexbmqvry.m3u
	/tmp/tmpinbqrt80.m3u
	/tmp/tmpql02hax5.m3u
	/tmp/tmpvbdzprsf.m3u
	/tmp/tmpzipim36x.m3u
Temp files created by test/plugins/test_plexupdate.py
Temp files created by test/plugins/test_plugin_mediafield.py
Temp files created by test/plugins/test_random.py
Temp files created by test/plugins/test_replaygain.py
Temp files created by test/plugins/test_smartplaylist.py
Temp files created by test/plugins/test_spotify.py
Temp files created by test/plugins/test_subsonicupdate.py
Temp files created by test/plugins/test_the.py
Temp files created by test/plugins/test_thumbnails.py
Temp files created by test/plugins/test_types_plugin.py
Temp files created by test/plugins/test_web.py
Temp files created by test/plugins/test_zero.py
	/tmp/tmp3ub9xmzy
Temp files created by test/rsrc/beetsplug/test.py
Temp files created by test/rsrc/convert_stub.py
Temp files created by test/testall.py
Temp files created by test/test_art_resize.py
	/tmp/tmp3p7p60ih.jpg
	/tmp/tmp8exclgit.jpg
	/tmp/tmpkrrjsitl.jpg
	/tmp/tmpw6n8ee8e.jpg
	/tmp/tmpygws_0aw.jpg
Temp files created by test/test_autotag.py
Temp files created by test/test_config_command.py
	/tmp/tmp333f0r2j
	/tmp/tmphr356z5r
	/tmp/tmporp4rag2
	/tmp/tmpy7sjqdsw
Temp files created by test/test_datequery.py
Temp files created by test/test_dbcore.py
Temp files created by test/test_files.py
Temp files created by test/test_hidden.py
Temp files created by test/test_importer.py
	/tmp/tmp0m363gfb
	/tmp/tmp2n3i13mc
	/tmp/tmpxk3v304s
Temp files created by test/test_library.py
Temp files created by test/test_logging.py
Temp files created by test/test_m3ufile.py
Temp files created by test/test_mb.py
Temp files created by test/test_metasync.py
Temp files created by test/test_pipeline.py
Temp files created by test/test_plugins.py
	/tmp/tmp6pxhx67u
	/tmp/tmpb8pqi9ui
	/tmp/tmpcx_658g7
	/tmp/tmp_giqb9jz
	/tmp/tmpgm9xk94_
	/tmp/tmpk60l6bt3
	/tmp/tmpqoj4la68
	/tmp/tmptcdu20rp
	/tmp/tmpvr7k5shn
	/tmp/tmpwnfnzs91
Temp files created by test/test_query.py
Temp files created by test/test_sort.py
Temp files created by test/test_template.py
Temp files created by test/test_ui_commands.py
	/tmp/tmpns2u94w6
Temp files created by test/test_ui_importer.py
Temp files created by test/test_ui_init.py
Temp files created by test/test_ui.py
Temp files created by test/test_util.py
Temp files created by test/test_vfs.py
```

And that's what we have right now:

```
Temp files created by test/__init__.py
Temp files created by test/plugins/__init__.py
Temp files created by test/plugins/lyrics_download_samples.py
Temp files created by test/plugins/test_acousticbrainz.py
Temp files created by test/plugins/test_advancedrewrite.py
Temp files created by test/plugins/test_albumtypes.py
Temp files created by test/plugins/test_art.py
Temp files created by test/plugins/test_aura.py
Temp files created by test/plugins/test_bareasc.py
Temp files created by test/plugins/test_beatport.py
Temp files created by test/plugins/test_bucket.py
Temp files created by test/plugins/test_convert.py
Temp files created by test/plugins/test_discogs.py
Temp files created by test/plugins/test_edit.py
Temp files created by test/plugins/test_embedart.py
Temp files created by test/plugins/test_embyupdate.py
Temp files created by test/plugins/test_export.py
Temp files created by test/plugins/test_fetchart.py
Temp files created by test/plugins/test_filefilter.py
Temp files created by test/plugins/test_ftintitle.py
Temp files created by test/plugins/test_hook.py
Temp files created by test/plugins/test_ihate.py
Temp files created by test/plugins/test_importadded.py
Temp files created by test/plugins/test_importfeeds.py
Temp files created by test/plugins/test_info.py
Temp files created by test/plugins/test_ipfs.py
Temp files created by test/plugins/test_keyfinder.py
Temp files created by test/plugins/test_lastgenre.py
Temp files created by test/plugins/test_limit.py
Temp files created by test/plugins/test_lyrics.py
Temp files created by test/plugins/test_mbsubmit.py
Temp files created by test/plugins/test_mbsync.py
Temp files created by test/plugins/test_mpdstats.py
Temp files created by test/plugins/test_parentwork.py
Temp files created by test/plugins/test_permissions.py
Temp files created by test/plugins/test_player.py
Temp files created by test/plugins/test_playlist.py
Temp files created by test/plugins/test_play.py
Temp files created by test/plugins/test_plexupdate.py
Temp files created by test/plugins/test_plugin_mediafield.py
Temp files created by test/plugins/test_random.py
Temp files created by test/plugins/test_replaygain.py
Temp files created by test/plugins/test_smartplaylist.py
Temp files created by test/plugins/test_spotify.py
Temp files created by test/plugins/test_subsonicupdate.py
Temp files created by test/plugins/test_the.py
Temp files created by test/plugins/test_thumbnails.py
Temp files created by test/plugins/test_types_plugin.py
Temp files created by test/plugins/test_web.py
Temp files created by test/plugins/test_zero.py
Temp files created by test/rsrc/beetsplug/test.py
Temp files created by test/rsrc/convert_stub.py
Temp files created by test/testall.py
Temp files created by test/test_art_resize.py
Temp files created by test/test_autotag.py
Temp files created by test/test_config_command.py
Temp files created by test/test_datequery.py
Temp files created by test/test_dbcore.py
Temp files created by test/test_files.py
Temp files created by test/test_hidden.py
Temp files created by test/test_importer.py
Temp files created by test/test_library.py
Temp files created by test/test_logging.py
Temp files created by test/test_m3ufile.py
Temp files created by test/test_mb.py
Temp files created by test/test_metasync.py
Temp files created by test/test_pipeline.py
Temp files created by test/test_plugins.py
Temp files created by test/test_query.py
Temp files created by test/test_sort.py
Temp files created by test/test_template.py
Temp files created by test/test_ui_commands.py
Temp files created by test/test_ui_importer.py
Temp files created by test/test_ui_init.py
Temp files created by test/test_ui.py
Temp files created by test/test_util.py
Temp files created by test/test_vfs.py
```

Note that the command which provides the output is now available through
`poe`.
@snejus
Copy link
Member

snejus commented Jul 18, 2024

This got superseded by #5345

@snejus snejus closed this Jul 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ensure that tests do not leave any files behind
3 participants