Skip to content

Speed up a couple of tests #5799

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

Merged
merged 2 commits into from
May 26, 2025
Merged

Speed up a couple of tests #5799

merged 2 commits into from
May 26, 2025

Conversation

snejus
Copy link
Member

@snejus snejus commented May 25, 2025

Speed up tests by using unittest.TestCase for tests that don't require directory setup

This PR improves test performance by switching several test classes from BeetsTestCase to standard unittest.TestCase when they don't require the directory setup and teardown overhead. The changes focus on test classes that:

  1. Only test utility functions
  2. Don't need temporary directories
  3. Don't interact with the filesystem

The PR removes unnecessary imports of BeetsTestCase and adds direct imports of unittest where needed. This change reduces the test execution time by avoiding the expensive setup/teardown steps for tests that don't require them.

@snejus snejus requested review from Copilot and a team May 25, 2025 13:40
Copy link

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.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR aims to speed up tests by switching several test classes from using BeetsTestCase to the lighter weight unittest.TestCase in cases where the filesystem setup/teardown is unnecessary.

  • Remove unnecessary BeetsTestCase imports from multiple test files
  • Update test class inheritance to use unittest.TestCase
  • Adjust test configuration in pyproject.toml for coverage collection

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/test_util.py Replaced BeetsTestCase with unittest.TestCase for utility tests
test/test_ui.py Updated test classes to inherit from unittest.TestCase
test/test_sort.py Changed CaseSensitivityTest to inherit from DummyDataTestCase and unittest.TestCase
test/test_query.py Replaced BeetsTestCase with unittest.TestCase in query-related tests
test/test_logging.py Modified LoggingTest to inherit from unittest.TestCase, removing extra BeetsTestCase import
test/test_importer.py Updated test classes to use unittest.TestCase
test/test_files.py Changed HelperTest to use unittest.TestCase
test/plugins/* Updated various plugin tests to replace BeetsTestCase with unittest.TestCase
pyproject.toml Adjusted the coverage flags configuration
Comments suppressed due to low confidence (2)

pyproject.toml:223

  • Verify that the removal of duplicate coverage flags (for both beets and beetsplug) is intentional and that the current setup still collects coverage for all required modules.
---cov=beets

test/test_sort.py:383

  • Double-check that combining DummyDataTestCase with unittest.TestCase provides all necessary initialization, since the removal of BeetsTestCase might remove expected setup steps.
class CaseSensitivityTest(DummyDataTestCase, unittest.TestCase):

@snejus snejus force-pushed the speed-up-some-tests branch from 94d60b7 to 6fa0676 Compare May 25, 2025 14:02
Copy link
Contributor

@semohr semohr left a comment

Choose a reason for hiding this comment

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

That is a nice find!

Just out of interest, do you have any simple benchmarks with before and after timings?

@semohr semohr added the testing Relates to the testing/CI infrastructure label May 26, 2025
@snejus
Copy link
Member Author

snejus commented May 26, 2025

Just out of interest, do you have any simple benchmarks with before and after timings?

Of course! Using zsh shell:

$ git checkout speed-up-some-tests
$ classes=($(git diff master.. -U0 | grep -E '[A-Za-z]+Test\b' -o | sort -u))
$ poe test -k ${(j: or :)classes} | grep -e Poe -e passed 
$ Poe => pytest -p no:cov -k 'ArtistFlatteningTest or BeatportResponseEmptyTest or CaseSensitivityTest or CommonOptionsParserTest or DeprecatedConfigTest or EncodingTest or EnforceRatioConfigTest or HelperTest or InferAlbumDataTest or LastGenrePluginTest or LoggingTest or MatchTest or NotQueryMatchTest or PathConversionTest or PathFormatTest or SubsonicPluginTest or SummarizeItemsTest or TagLogTest or ThePluginTest'
==== 102 passed, 1860 deselected in 18.56s ====
$ git checkout master
$ poe test -k ${(j: or :)classes} | grep -e Poe -e passed
$ Poe => pytest -p no:cov -k 'ArtistFlatteningTest or BeatportResponseEmptyTest or CaseSensitivityTest or CommonOptionsParserTest or DeprecatedConfigTest or EncodingTest or EnforceRatioConfigTest or HelperTest or InferAlbumDataTest or LastGenrePluginTest or LoggingTest or MatchTest or NotQueryMatchTest or PathConversionTest or PathFormatTest or SubsonicPluginTest or SummarizeItemsTest or TagLogTest or ThePluginTest'
==== 102 passed, 1860 deselected in 22.96s ====

Not a huge difference, actually, since this only affects 102 tests.

Just had a look at specific test durations:

this branch

0.42s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_whitelist_custom
0.42s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_whitelist_c14n
0.41s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_count_c14n
0.41s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_prefer_specific_without_canonical
0.41s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_c14n_whitelist
0.41s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_sort_by_depth
0.41s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_tags_for
0.27s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_empty_string_enables_canonical
0.22s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_whitelist_only
0.21s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_c14n_only
0.21s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_empty_string_enables_whitelist
0.21s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_prefer_specific_loads_tree
0.21s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_default
0.12s call     test/test_sort.py::CaseSensitivityTest::test_case_sensitive_only_affects_text
0.12s call     test/test_sort.py::CaseSensitivityTest::test_flex_field_case_sensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_flex_field_case_insensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_smart_artist_case_sensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_fixed_field_case_sensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_fixed_field_case_insensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_smart_artist_case_insensitive
0.02s call     test/plugins/test_the.py::ThePluginTest::test_unthe_with_default_patterns
0.01s call     test/plugins/test_art.py::EnforceRatioConfigTest::test_percent
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_missing_port
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan_failed_unreachable
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan_failed_not_found
0.01s call     test/plugins/test_art.py::EnforceRatioConfigTest::test_px
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_context_path
0.01s call     test/test_importer.py::InferAlbumDataTest::test_apply_lets_album_values_override
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan_failed_bad_credentials
0.01s call     test/test_importer.py::InferAlbumDataTest::test_apply_gets_artist_and_id
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_trailing_forward_slash_url
0.01s call     test/test_importer.py::InferAlbumDataTest::test_small_single_artist_album
0.01s call     test/test_importer.py::InferAlbumDataTest::test_asis_comp_applied_to_all_items
0.01s call     test/test_importer.py::InferAlbumDataTest::test_asis_heterogenous_va
0.01s call     test/test_importer.py::InferAlbumDataTest::test_asis_track_albumartist_override
0.01s call     test/test_importer.py::InferAlbumDataTest::test_asis_majority_artist_single_artist
0.01s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_missing_schema
0.01s call     test/test_importer.py::InferAlbumDataTest::test_asis_homogenous_single_artist
0.01s call     test/plugins/test_the.py::ThePluginTest::test_template_function_with_defaults
0.01s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_format_and_stringify
0.01s call     test/plugins/test_the.py::ThePluginTest::test_custom_pattern

master

0.35s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_prefer_specific_loads_tree
0.29s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_count_c14n
0.29s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_whitelist_custom
0.28s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_sort_by_depth
0.28s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_c14n_whitelist
0.28s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_c14n_only
0.28s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_empty_string_enables_canonical
0.28s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_whitelist_c14n
0.27s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_prefer_specific_without_canonical
0.12s call     test/test_sort.py::CaseSensitivityTest::test_flex_field_case_sensitive
0.12s call     test/test_sort.py::CaseSensitivityTest::test_case_sensitive_only_affects_text
0.11s call     test/test_sort.py::CaseSensitivityTest::test_flex_field_case_insensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_smart_artist_case_insensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_smart_artist_case_sensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_fixed_field_case_sensitive
0.11s call     test/test_sort.py::CaseSensitivityTest::test_fixed_field_case_insensitive
0.08s call     test/plugins/test_art.py::EnforceRatioConfigTest::test_percent
0.08s call     test/test_query.py::MatchTest::test_substring_match_non_string_value
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan
0.08s call     test/test_importer.py::InferAlbumDataTest::test_asis_majority_artist_single_artist
0.08s call     test/plugins/test_art.py::EnforceRatioConfigTest::test_px
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan_failed_not_found
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan_failed_unreachable
0.08s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_empty_string_enables_whitelist
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_context_path
0.08s call     test/test_importer.py::InferAlbumDataTest::test_apply_gets_artist_and_id
0.08s call     test/test_importer.py::InferAlbumDataTest::test_asis_heterogenous_va
0.08s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_whitelist_only
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_start_scan_failed_bad_credentials
0.08s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_tags_for
0.08s call     test/test_importer.py::InferAlbumDataTest::test_asis_comp_applied_to_all_items
0.08s call     test/test_importer.py::InferAlbumDataTest::test_asis_homogenous_single_artist
0.08s call     test/test_importer.py::InferAlbumDataTest::test_apply_lets_album_values_override
0.08s call     test/test_importer.py::InferAlbumDataTest::test_small_single_artist_album
0.08s call     test/test_importer.py::InferAlbumDataTest::test_asis_track_albumartist_override
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_missing_port
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_trailing_forward_slash_url
0.08s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_format_and_stringify
0.08s call     test/test_query.py::MatchTest::test_year_match_negative
0.08s call     test/test_query.py::MatchTest::test_regex_match_negative
0.08s call     test/plugins/test_art.py::DeprecatedConfigTest::test_moves_filesystem_to_end
0.08s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_no_duplicate
0.08s call     test/test_query.py::MatchTest::test_substring_match_positive
0.08s call     test/plugins/test_lastgenre.py::LastGenrePluginTest::test_default
0.08s call     test/plugins/test_subsonicupdate.py::SubsonicPluginTest::test_url_with_missing_schema
0.08s call     test/plugins/test_the.py::ThePluginTest::test_unthe_with_default_patterns
0.08s call     test/test_query.py::MatchTest::test_regex_match_positive
0.08s call     test/plugins/test_the.py::ThePluginTest::test_unthe_with_strip
0.08s call     test/test_util.py::PathConversionTest::test_bytesting_path_windows_removes_magic_prefix
0.08s call     test/test_ui.py::PathFormatTest::test_custom_paths_prepend

Note tests that take 0.01s instead of 0.08s - that's the effect of my change. On the other hand, there's something fishy going on with LastGenrePluginTest and CaseSensitivityTest since their durations increased, so I'm going to have a look at them before this is merged.

@snejus snejus force-pushed the speed-up-some-tests branch from 6fa0676 to 5900282 Compare May 26, 2025 12:06
@snejus
Copy link
Member Author

snejus commented May 26, 2025

Lastgenre tests do depend on the configuration made available by beets.test.helper.ConfigMixin but they used global config instead. I removed the global config and made it to use the config exposed by the helper.

@snejus snejus merged commit 60f24cd into master May 26, 2025
20 checks passed
@snejus snejus deleted the speed-up-some-tests branch May 26, 2025 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
testing Relates to the testing/CI infrastructure
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants