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

Make sure tests do not leave any temp files behind #5345

Merged
merged 11 commits into from
Jul 18, 2024
Merged

Conversation

snejus
Copy link
Member

@snejus snejus commented Jul 2, 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 snejus self-assigned this Jul 2, 2024
@snejus snejus requested a review from Serene-Arc July 2, 2024 14:26
Copy link

github-actions bot commented Jul 2, 2024

Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.

snejus added 5 commits July 2, 2024 15:35
…Case

And move the definition to a shared module.

The problem was that EmbedartCliTest ran `_common.TestCase.setUp`
method which initialised temporary directory for the tests AND ran
`helper.TestHelper.setup_beets` method which initialised another set of
temporary directories. This meant that the first set of directories
could not be tracked down for the cleanup.
This allows to clean them up in art (1) fetching, (2) resizing and (3)
deinterlace tests.
This way they get automatically removed with removal of temp_dir.

After this change `test/plugins/test_zero.py` does not any more leave
any mediafield fixtures hanging around.
Due to some weird race conditions the temporary directories were not
getting torn down for four of the tests. I failed to figure out why, and
I found that using TestHelper to manage the temporary directory somehow
fixes this.
@Serene-Arc
Copy link
Contributor

Another reason why the switch to pytest would be a great thing. I'll give a review but what is the relation to the other PR, #5285? Do they conflict or are they about different classes?

@snejus
Copy link
Member Author

snejus commented Jul 3, 2024

I think this supersedes #5285

@Serene-Arc
Copy link
Contributor

Double checking again, sorry, what are the conflicts with this and #5362 ?

@bal-e bal-e mentioned this pull request Jul 18, 2024
3 tasks
@snejus
Copy link
Member Author

snejus commented Jul 18, 2024

#5362 is branched off the tip of this PR, so there are no conflicts 🙂
image

Once this PR is merged, the base in #5362 will change to master.

Comment on lines +233 to +247
[tool.poe.tasks.check-temp-files]
help = "Run each test module one by one and check for leftover temp files"
shell = """
setopt nullglob
for file in test/**/*.py; do
print Temp files created by $file && poe test $file &>/dev/null
tempfiles=(/tmp/**/tmp* /tmp/beets/**/*)
if (( $#tempfiles )); then
print -l $'\t'$^tempfiles
rm -r --interactive=never $tempfiles &>/dev/null
fi
done
"""
interpreter = "zsh"

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add this to our testing CI? It seems like there's no downside.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the downside here is how long it takes to run - I have done it now and it took longer than 4 minutes.

In any case, there's probably little value for it once these issues are fixed - but it's good to have it around in case this issue comes up again.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's interesting, why does it take longer to run than the normal test suite?

I suppose it doesn't actually matter, once we switch to pytest, it'll handle all of the temp files and directories and fix issues.

Additionally this issue is more of a nice to have and to stop any possible dependence between test runs because the OS should clear out temp files, at the very least every reboot. That's what the temp files are for.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's interesting, why does it take longer to run than the normal test suite?

Because it runs pytest for each of the test files :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mean that it runs the entire test suite for each of the files?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, each invocation runs tests found in each of these files

$ centralize-test-setup print -l test/**/*.py                                                                                                  beets 2.0.0 │ 🐍 3.8 🐶
test/__init__.py
test/plugins/__init__.py
test/plugins/lyrics_download_samples.py
test/plugins/test_acousticbrainz.py
test/plugins/test_advancedrewrite.py
test/plugins/test_albumtypes.py
test/plugins/test_art.py
test/plugins/test_aura.py
test/plugins/test_bareasc.py
test/plugins/test_beatport.py

@snejus snejus merged commit 1163645 into master Jul 18, 2024
12 checks passed
@snejus snejus deleted the cleanup-tests branch July 18, 2024 11:25
snejus added a commit that referenced this pull request Jul 31, 2024
This PR deduplicates shared tests setup that was used across test files.
It attempts to centralise the setup into a single source of truth
implementation in `beets.test.helper`.

This works towards the eventual migration to `pytest` (#5361) making it
easier to replace the tests setup with `pytest` fixtures.

It builds upon the temporary files cleanup in #5345.

## Details

### Test setup

* Mostly duplicate test setup implementations in
`beets.test._common.TestCase` and `beets.test.helper.TestHelper` were
merged into a single implementation as
`beets.test.helper.BeetsTestCase`.
* Replaced `unittest.TestCase` and `beets.test.helper.TestHelper`
combination with `beets.test.helper.ImportTestCase`
* Refactored `test/test_plugins.py` removing duplicate import files
setup.
* Centralised setting up the database on disk.
* Centralised plugin test setup into `beets.test.helpers.PluginMixin`.

### Removals

* Around 1/3 of the removed lines correspond to the removal of `def
suite()` functions that were previously used for tests collection.
* Removed redundant `setUp` and `tearDown` methods from many test files
given that the functions that they performed had been centralised in
`beets.test.helper`.
* Removed redundant library initialisations


### Importing

* Centralised import directory creation (only gets created on the first
access).
* Deduplicated `_setup_import_session` implementations in
`TerminalImportSessionSetup` and `ImportHelper`.
* Merged `ImportHelper._create_import_dir` and
`TestHelper.create_importer` implementations into
`ImportHelper.setup_importer`.
* Merged various takes on import files prep into
`ImportHelper.prepare_albums_for_import` and
`ImportHelper.prepare_album_for_import`.
* Seeing that many tests used it, defined `AsIsImporterMixin` which
provides a method `run_asis_importer` to setup _asis_ (non-autotag)
importer and run it
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
2 participants