Skip to content

use pathlib in tests: migrate most tests to pathlib - #6865

Open
snejus wants to merge 5 commits into
masterfrom
migrate-tests-to-pathlib
Open

use pathlib in tests: migrate most tests to pathlib#6865
snejus wants to merge 5 commits into
masterfrom
migrate-tests-to-pathlib

Conversation

@snejus

@snejus snejus commented Jul 21, 2026

Copy link
Copy Markdown
Member
  • This PR continues the test suite's move from mixed string/bytes path handling to pathlib.Path-based filesystem usage.

  • The main architectural change is that shared test helpers now produce Path objects directly, especially TestHelper.create_mediafile_fixture, so tests can use one consistent path model end-to-end.

  • Tests are updated to prefer higher-level path APIs like item.filepath and album.art_filepath instead of raw item.path bytes, reducing manual conversions and direct os.path usage.

  • Supporting utilities were adjusted where needed, such as allowing beets.util.mkdirall to accept Path inputs, so existing filesystem helpers still work during the transition.

  • High-level impact:

    • makes path handling in tests more consistent and readable
    • reduces bytes/string/path conversion noise
    • aligns tests with the newer object-level path interfaces already exposed by the app
    • lowers the chance of platform-specific path bugs, especially around encoding and separator handling
  • In practice, this is mostly a test infrastructure cleanup and API alignment change. It should not change product behavior, but it makes future path-related test updates simpler and safer.

Fixes #6807

The rest of tests require refactoring of associated source code.

Copilot AI review requested due to automatic review settings July 21, 2026 17:58
@snejus
snejus requested a review from a team as a code owner July 21, 2026 17:58
@github-actions

Copy link
Copy Markdown

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 snejus linked an issue Jul 21, 2026 that may be closed by this pull request
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

❌ 6 Tests Failed:

Tests completed Failed Passed Skipped
2696 6 2690 120
View the top 3 failed test(s) by shortest run time
test\plugins\test_thumbnails.py::plugins::test_thumbnails::ThumbnailsTest::test_add_tags
Stack Traces | 0.008s run time
self = <test.plugins.test_thumbnails.ThumbnailsTest testMethod=test_add_tags>
mock_stat = <MagicMock name='stat' id='2551916473920'>
mock_artresizer = <MagicMock name='ArtResizer' id='2551916469888'>

    #x1B[0m#x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.ArtResizer#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.ThumbnailsPlugin._check_local_ok#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, Mock())#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.os.stat#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_add_tags#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, mock_stat, mock_artresizer):#x1B[90m#x1B[39;49;00m
        plugin = ThumbnailsPlugin()#x1B[90m#x1B[39;49;00m
        plugin.get_uri = Mock(#x1B[90m#x1B[39;49;00m
            side_effect={#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[............/path/to/cover#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m: #x1B[33m"#x1B[39;49;00m#x1B[33mCOVER_URI#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m}.#x1B[92m__getitem__#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
        album = Mock(artpath=#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[............/path/to/cover#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        mock_stat.return_value.st_mtime = #x1B[94m12345#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        plugin.add_tags(album, #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[.../path/to/thumbnail#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        metadata = {#x1B[33m"#x1B[39;49;00m#x1B[33mThumb::URI#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m: #x1B[33m"#x1B[39;49;00m#x1B[33mCOVER_URI#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mThumb::MTime#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m: #x1B[33m"#x1B[39;49;00m#x1B[33m12345#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m
        mock_artresizer.shared.write_metadata.assert_called_once_with(#x1B[90m#x1B[39;49;00m
            #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[.../path/to/thumbnail#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, metadata#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
>       mock_stat.assert_called_once_with(os.fsdecode(album.artpath))#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_thumbnails.py#x1B[0m:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.6\x64\Lib\unittest\mock.py#x1B[0m:998: in assert_called_once_with
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.assert_called_with(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <MagicMock name='stat' id='2551916473920'>, args = ('........./path/to/cover',)
kwargs = {}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92massert_called_with#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""assert that the last call was made with the specified arguments.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Raises an AssertionError if the args and keyword args passed in are#x1B[39;49;00m
    #x1B[33m    different to the last call to the mock."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.call_args #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            expected = #x1B[96mself#x1B[39;49;00m._format_mock_call_signature(args, kwargs)#x1B[90m#x1B[39;49;00m
            actual = #x1B[33m'#x1B[39;49;00m#x1B[33mnot called.#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            error_message = (#x1B[33m'#x1B[39;49;00m#x1B[33mexpected call not found.#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33mExpected: #x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m  Actual: #x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                    % (expected, actual))#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mAssertionError#x1B[39;49;00m(error_message)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_error_message#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
            msg = #x1B[96mself#x1B[39;49;00m._format_mock_failure_message(args, kwargs)#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m msg#x1B[90m#x1B[39;49;00m
        expected = #x1B[96mself#x1B[39;49;00m._call_matcher(_Call((args, kwargs), two=#x1B[94mTrue#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        actual = #x1B[96mself#x1B[39;49;00m._call_matcher(#x1B[96mself#x1B[39;49;00m.call_args)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m actual != expected:#x1B[90m#x1B[39;49;00m
            cause = expected #x1B[94mif#x1B[39;49;00m #x1B[96misinstance#x1B[39;49;00m(expected, #x1B[96mException#x1B[39;49;00m) #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m #x1B[96mAssertionError#x1B[39;49;00m(_error_message()) #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96mcause#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           AssertionError: expected call not found.#x1B[0m
#x1B[1m#x1B[31mE           Expected: stat('........./path/to/cover')#x1B[0m
#x1B[1m#x1B[31mE             Actual: stat('\\\\?\\........./path/to/cover')#x1B[0m

#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.6\x64\Lib\unittest\mock.py#x1B[0m:986: AssertionError
test\plugins\test_thumbnails.py::plugins::test_thumbnails::ThumbnailsTest::test_check_local_ok
Stack Traces | 0.008s run time
self = <test.plugins.test_thumbnails.ThumbnailsTest testMethod=test_check_local_ok>
mock_giouri = <MagicMock name='GioURI' id='2551839583808'>
mock_artresizer = <MagicMock name='ArtResizer' id='2551839585152'>
mock_os = <MagicMock name='os' id='2551839586496'>

    #x1B[0m#x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.os#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.ArtResizer#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.GioURI#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_check_local_ok#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, mock_giouri, mock_artresizer, mock_os):#x1B[90m#x1B[39;49;00m
        #x1B[90m# test local resizing capability#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        mock_artresizer.shared.local = #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        mock_artresizer.shared.can_write_metadata = #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        plugin = ThumbnailsPlugin()#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m plugin._check_local_ok()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# test dirs creation#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        mock_artresizer.shared.local = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        mock_artresizer.shared.can_write_metadata = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mexists#x1B[39;49;00m(path):#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m path == os.fsdecode(NORMAL_DIR):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m path == os.fsdecode(LARGE_DIR):#x1B[90m#x1B[39;49;00m
                #x1B[94mreturn#x1B[39;49;00m #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mValueError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33munexpected path #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mpath#x1B[33m!r}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        mock_os.path.exists = exists#x1B[90m#x1B[39;49;00m
>       plugin = ThumbnailsPlugin()#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_thumbnails.py#x1B[0m:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mbeetsplug\thumbnails.py#x1B[0m:31: in __init__
    #x1B[0m#x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.config[#x1B[33m"#x1B[39;49;00m#x1B[33mauto#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] #x1B[95mand#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._check_local_ok():#x1B[90m#x1B[39;49;00m
                               ^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mbeetsplug\thumbnails.py#x1B[0m:79: in _check_local_ok
    #x1B[0m#x1B[94mif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m os.path.exists(syspath(dir_)):#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

path = '\\\\?\\C:\\Users\\runneradmin\\.cache\\thumbnails\\normal'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mexists#x1B[39;49;00m(path):#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m path == os.fsdecode(NORMAL_DIR):#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m path == os.fsdecode(LARGE_DIR):#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       #x1B[94mraise#x1B[39;49;00m #x1B[96mValueError#x1B[39;49;00m(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33munexpected path #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mpath#x1B[33m!r}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       ValueError: unexpected path '\\\\?\\C:\\Users\\runneradmin\\.cache\\thumbnails\\normal'#x1B[0m

#x1B[1m#x1B[31mtest\plugins\test_thumbnails.py#x1B[0m:55: ValueError
test\plugins\test_thumbnails.py::plugins::test_thumbnails::ThumbnailsTest::test_make_cover_thumbnail
Stack Traces | 0.008s run time
self = <test.plugins.test_thumbnails.ThumbnailsTest testMethod=test_make_cover_thumbnail>
mock_shutils = <MagicMock name='shutil' id='2551916464176'>
mock_os = <MagicMock name='os' id='2551916477952'>
mock_artresizer = <MagicMock name='ArtResizer' id='2551916471568'>

    #x1B[0m#x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.ThumbnailsPlugin._check_local_ok#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, Mock())#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.ArtResizer#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeets.util.syspath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, Mock(side_effect=#x1B[94mlambda#x1B[39;49;00m x: x))#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.os#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[37m@patch#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mbeetsplug.thumbnails.shutil#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_make_cover_thumbnail#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, mock_shutils, mock_os, mock_artresizer):#x1B[90m#x1B[39;49;00m
        thumbnail_dir = os.path.normpath(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/thumbnail/dir#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        md5_file = os.path.join(thumbnail_dir, #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mmd5#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        md5_file_str = os.fsdecode(md5_file)#x1B[90m#x1B[39;49;00m
        path_to_art = os.path.normpath(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[.../path/to/art#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        path_to_resized_art = os.path.normpath(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[.../to/resized/artwork#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        mock_os.path.join = os.path.join  #x1B[90m# don't mock that function#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        plugin = ThumbnailsPlugin()#x1B[90m#x1B[39;49;00m
        plugin.add_tags = Mock()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        album = Mock(artpath=path_to_art)#x1B[90m#x1B[39;49;00m
        plugin.thumbnail_file_name = Mock(return_value=#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mmd5#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        mock_os.path.exists.return_value = #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        mock_resize = mock_artresizer.shared.resize#x1B[90m#x1B[39;49;00m
        mock_resize.return_value = path_to_resized_art#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        plugin.make_cover_thumbnail(album, #x1B[94m12345#x1B[39;49;00m, thumbnail_dir)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       mock_os.path.exists.assert_called_once_with(md5_file_str)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_thumbnails.py#x1B[0m:107: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.6\x64\Lib\unittest\mock.py#x1B[0m:998: in assert_called_once_with
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.assert_called_with(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <MagicMock name='os.path.exists' id='2551757972080'>
args = ('\\thumbnail\\dir\\md5',), kwargs = {}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92massert_called_with#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""assert that the last call was made with the specified arguments.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Raises an AssertionError if the args and keyword args passed in are#x1B[39;49;00m
    #x1B[33m    different to the last call to the mock."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.call_args #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            expected = #x1B[96mself#x1B[39;49;00m._format_mock_call_signature(args, kwargs)#x1B[90m#x1B[39;49;00m
            actual = #x1B[33m'#x1B[39;49;00m#x1B[33mnot called.#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            error_message = (#x1B[33m'#x1B[39;49;00m#x1B[33mexpected call not found.#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33mExpected: #x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m  Actual: #x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                    % (expected, actual))#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mAssertionError#x1B[39;49;00m(error_message)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_error_message#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
            msg = #x1B[96mself#x1B[39;49;00m._format_mock_failure_message(args, kwargs)#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m msg#x1B[90m#x1B[39;49;00m
        expected = #x1B[96mself#x1B[39;49;00m._call_matcher(_Call((args, kwargs), two=#x1B[94mTrue#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        actual = #x1B[96mself#x1B[39;49;00m._call_matcher(#x1B[96mself#x1B[39;49;00m.call_args)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m actual != expected:#x1B[90m#x1B[39;49;00m
            cause = expected #x1B[94mif#x1B[39;49;00m #x1B[96misinstance#x1B[39;49;00m(expected, #x1B[96mException#x1B[39;49;00m) #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m #x1B[96mAssertionError#x1B[39;49;00m(_error_message()) #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96mcause#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           AssertionError: expected call not found.#x1B[0m
#x1B[1m#x1B[31mE           Expected: exists('\\thumbnail\\dir\\md5')#x1B[0m
#x1B[1m#x1B[31mE             Actual: exists('\\\\?\\\\thumbnail\\dir\\md5')#x1B[0m

#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.6\x64\Lib\unittest\mock.py#x1B[0m:986: AssertionError
test\plugins\test_fetchart.py::plugins::test_fetchart::TestFetchartCli::test_filesystem_does_not_pick_up_hidden_file
Stack Traces | 0.014s run time
self = <test.plugins.test_fetchart.TestFetchartCli object at 0x00000252037EFAD0>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_filesystem_does_not_pick_up_hidden_file#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        path = #x1B[96mself#x1B[39;49;00m.album.filepath / #x1B[33m"#x1B[39;49;00m#x1B[33m.cover.jpg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        path.write_text(#x1B[33m"#x1B[39;49;00m#x1B[33mIMAGE#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
>       #x1B[96mself#x1B[39;49;00m.hide_file_windows(path)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_fetchart.py#x1B[0m:119: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test.plugins.test_fetchart.TestFetchartCli object at 0x00000252037EFAD0>
path = WindowsPath('C:/Users/RUNNER~.../libdir/Non-Album/the ärtist/.cover.jpg')

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mhide_file_windows#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, path: #x1B[96mbytes#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m sys.platform == #x1B[33m"#x1B[39;49;00m#x1B[33mwin32#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            hidden_mask = #x1B[94m2#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           #x1B[94massert#x1B[39;49;00m ctypes.windll.kernel32.SetFileAttributesW(path, hidden_mask)#x1B[90m#x1B[39;49;00m
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           ctypes.ArgumentError: argument 1: TypeError: Don't know how to convert parameter 1#x1B[0m

#x1B[1m#x1B[31mtest\plugins\test_fetchart.py#x1B[0m:86: ArgumentError
test\plugins\test_fetchart.py::plugins::test_fetchart::TestFetchartCli::test_filesystem_picks_up_hidden_file
Stack Traces | 0.014s run time
self = <test.plugins.test_fetchart.TestFetchartCli object at 0x000002520E922F10>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_filesystem_picks_up_hidden_file#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        path = #x1B[96mself#x1B[39;49;00m.album.filepath / #x1B[33m"#x1B[39;49;00m#x1B[33m.cover.jpg#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        path.write_text(#x1B[33m"#x1B[39;49;00m#x1B[33mIMAGE#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
>       #x1B[96mself#x1B[39;49;00m.hide_file_windows(path)#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_fetchart.py#x1B[0m:136: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test.plugins.test_fetchart.TestFetchartCli object at 0x000002520E922F10>
path = WindowsPath('C:/Users/RUNNER~.../libdir/Non-Album/the ärtist/.cover.jpg')

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mhide_file_windows#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, path: #x1B[96mbytes#x1B[39;49;00m) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m sys.platform == #x1B[33m"#x1B[39;49;00m#x1B[33mwin32#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            hidden_mask = #x1B[94m2#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           #x1B[94massert#x1B[39;49;00m ctypes.windll.kernel32.SetFileAttributesW(path, hidden_mask)#x1B[90m#x1B[39;49;00m
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           ctypes.ArgumentError: argument 1: TypeError: Don't know how to convert parameter 1#x1B[0m

#x1B[1m#x1B[31mtest\plugins\test_fetchart.py#x1B[0m:86: ArgumentError
test\plugins\test_keyfinder.py::plugins::test_keyfinder::TestKeyFinder::test_add_key
Stack Traces | 0.032s run time
self = <test.plugins.test_keyfinder.TestKeyFinder object at 0x000002520E8F7890>
command_output = <MagicMock name='command_output' id='2551757977120'>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_add_key#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, command_output):#x1B[90m#x1B[39;49;00m
        item = Item(path=#x1B[33m"#x1B[39;49;00m#x1B[33m/file#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        item.add(#x1B[96mself#x1B[39;49;00m.lib)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        command_output.return_value = util.CommandOutput(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mdbm#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.run_command(#x1B[33m"#x1B[39;49;00m#x1B[33mkeyfinder#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        item.load()#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m item[#x1B[33m"#x1B[39;49;00m#x1B[33minitial_key#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] == #x1B[33m"#x1B[39;49;00m#x1B[33mC#m#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       command_output.assert_called_with(#x1B[90m#x1B[39;49;00m
            [#x1B[33m"#x1B[39;49;00m#x1B[33mKeyFinder#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m-f#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[96mstr#x1B[39;49;00m(item.filepath)]#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_keyfinder.py#x1B[0m:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <MagicMock name='command_output' id='2551757977120'>
args = (['KeyFinder', '-f', 'C:\\file'],), kwargs = {}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92massert_called_with#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""assert that the last call was made with the specified arguments.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    Raises an AssertionError if the args and keyword args passed in are#x1B[39;49;00m
    #x1B[33m    different to the last call to the mock."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.call_args #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            expected = #x1B[96mself#x1B[39;49;00m._format_mock_call_signature(args, kwargs)#x1B[90m#x1B[39;49;00m
            actual = #x1B[33m'#x1B[39;49;00m#x1B[33mnot called.#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            error_message = (#x1B[33m'#x1B[39;49;00m#x1B[33mexpected call not found.#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33mExpected: #x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m  Actual: #x1B[39;49;00m#x1B[33m%s#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                    % (expected, actual))#x1B[90m#x1B[39;49;00m
            #x1B[94mraise#x1B[39;49;00m #x1B[96mAssertionError#x1B[39;49;00m(error_message)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_error_message#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
            msg = #x1B[96mself#x1B[39;49;00m._format_mock_failure_message(args, kwargs)#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m msg#x1B[90m#x1B[39;49;00m
        expected = #x1B[96mself#x1B[39;49;00m._call_matcher(_Call((args, kwargs), two=#x1B[94mTrue#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        actual = #x1B[96mself#x1B[39;49;00m._call_matcher(#x1B[96mself#x1B[39;49;00m.call_args)#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m actual != expected:#x1B[90m#x1B[39;49;00m
            cause = expected #x1B[94mif#x1B[39;49;00m #x1B[96misinstance#x1B[39;49;00m(expected, #x1B[96mException#x1B[39;49;00m) #x1B[94melse#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m #x1B[96mAssertionError#x1B[39;49;00m(_error_message()) #x1B[94mfrom#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[04m#x1B[96mcause#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           AssertionError: expected call not found.#x1B[0m
#x1B[1m#x1B[31mE           Expected: command_output(['KeyFinder', '-f', 'C:\\file'])#x1B[0m
#x1B[1m#x1B[31mE             Actual: command_output(['KeyFinder', '-f', '\\\\?\\C:\\file'])#x1B[0m

#x1B[1m#x1B[31mC:\hostedtoolcache\windows\Python\3.14.6\x64\Lib\unittest\mock.py#x1B[0m:986: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

PR move big chunk of test suite from mixed bytes/str paths toward pathlib.Path, so tests use one path model and prefer item.filepath / album.art_filepath. grug like less path soup, but grug see few spots where test logic break or helper removal break other tests.

Changes:

  • Update many tests to build paths with Path ops, and use higher-level filepath/art_filepath APIs.
  • Update test helper(s) to return Path (not bytes) for media fixtures; remove some old “touch” helpers.
  • Small prod util tweak: beets.util.mkdirall now accepts Path.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/util/test_m3ufile.py Switch temp playlist paths to Path and PathsMixin.
test/util/test_hidden.py Stop forcing bytes paths when calling hidden.is_hidden.
test/ui/test_ui.py Use Path for config file paths; use Path.open/write_text.
test/ui/commands/test_update.py Use item.filepath.stat() for mtime check.
test/ui/commands/test_remove.py Use filepath.exists() and filepath vars instead of byte path.
test/ui/commands/test_move.py Use Path.unlink() and compare against filepath.
test/ui/commands/test_config.py Write YAML config via Path.write_text.
test/ui/commands/test_completion.py Use Path.exists() / Path.open() for completion script.
test/testall.py Remove old path-hacking test runner helper.
test/test_util.py Use Path for path construction in syspath tests.
test/test_library.py Use Path in a few path-oriented tests; switch to filepath.parent, etc.
test/test_importer.py Use Path.mkdir() / Path.write_bytes() in importer tests.
test/test_files.py Migrate many filesystem ops to Path methods and filepath/art_filepath.
test/rsrc/convert_stub.py Use Path.open() and os.fsencode for tag bytes.
test/plugins/test_web.py Try to migrate stored Item/Album paths to Path and simplify expected strings.
test/plugins/test_thumbnails.py Use temp_path and Path.read_text() for dolphin .directory check.
test/plugins/test_smartplaylist.py Use Path.unlink() for playlist cleanup.
test/plugins/test_scrub.py Pass item.filepath into MediaFile.
test/plugins/test_ipfs.py Build expected IPFS paths with Path and compare via filepath.
test/plugins/test_info.py Pass CLI file args as str(Path); assert output includes decoded path.
test/plugins/test_importsource.py Use Path.stat() for mtime checks.
test/plugins/test_importfeeds.py Build configured playlist names/paths via Path.
test/plugins/test_importadded.py Type annotate helper, use Path.stat() / filepath in assertions.
test/plugins/test_hook.py Use Path(...).is_file() for file existence assertion.
test/plugins/test_filefilter.py Compare expected item paths using Item.filepath.
test/plugins/test_fetchart.py Use album.filepath / album.art_filepath and Path.write_text() in tests.
test/plugins/test_embedart.py Use Path temp files + Path.read_bytes(); use item.filepath.stat() for mtimes.
test/plugins/test_duplicates.py Assert output contains str(item.filepath) instead of item.path.decode().
test/plugins/test_convert.py Use Path.write_text() / Path.read_text() for converted file check.
test/plugins/test_aura.py Use item.filepath.stat() for size.
test/plugins/test_art.py Small Path conversion in one test around cover art destination.
test/library/test_migrations.py Use Path for relative path construction and directory iteration.
test/dbcore/test_db.py Use Path for temp db file paths; use Path.unlink().
beets/util/init.py Allow mkdirall to accept a Path.
beets/test/helper.py create_mediafile_fixture now returns Path; remove old touch helper.
beets/test/_common.py Remove old _common.touch helper.
Comments suppressed due to low confidence (2)

test/test_files.py:502

  • grug see test end after self.i.remove(True) but then call util.mkdirall(self.i.filepath). that create dirs again, so test stop checking prune behavior. should assert album dir gone like other tests.
        self.ai.set_art(artfile)
        self.ai.store()

        self.i.remove(True)
        util.mkdirall(self.i.filepath)

test/plugins/test_art.py:937

  • grug see fetchart.Candidate wants path: bytes, but code pass Path artdest. this break (bytes contract). issue also say this file excluded from pathlib refactor. keep bytes here: pass os.fsencode(artdest) (or bytestring_path).
    def test_do_not_delete_original_if_already_in_place(self):
        artdest = self.i.filepath.parent / "cover.jpg"
        shutil.copyfile(self.art_file, syspath(artdest))
        self.afa_response = fetchart.Candidate(
            logger, source_name="test", path=artdest
        )

Comment thread test/plugins/test_web.py
Comment thread test/plugins/test_web.py Outdated
Comment thread test/test_files.py Outdated
Comment thread test/util/test_m3ufile.py
Comment thread beets/test/_common.py
@snejus
snejus force-pushed the make-rsrc-pathlib-path branch from bfd3b7e to 02a2c57 Compare July 24, 2026 16:04
Base automatically changed from make-rsrc-pathlib-path to master July 24, 2026 16:11
@snejus
snejus force-pushed the migrate-tests-to-pathlib branch 3 times, most recently from 06d9ca6 to 29e1096 Compare July 27, 2026 19:34
@snejus
snejus requested a review from Copilot July 27, 2026 19:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

test/plugins/test_art.py:937

  • grug see fetchart.Candidate want path: bytes (see fetchart.Candidate init). test pass Path, so later bytes ops blow up. keep bytes here (this file even in scope exception list).
        artdest = self.i.filepath.parent / "cover.jpg"
        shutil.copyfile(self.art_file, syspath(artdest))
        self.afa_response = fetchart.Candidate(
            logger, source_name="test", path=artdest
        )

Comment thread test/test_files.py

self.i.remove(True)
assert not self.i.filepath.parent.exists()
util.mkdirall(self.i.filepath)
self.config["create_backup_before_migrations"] = config_value
self.add_item(lyrics="some lyrics")
db_path = self.lib.path
db_path = self.lib_path
Comment thread test/rsrc/convert_stub.py
out_f.write(tag)
with Path(out_file).open("wb") as out_f, Path(in_file).open("rb") as in_f:
out_f.write(in_f.read())
out_f.write(os.fsencode(tag))
@snejus
snejus force-pushed the migrate-tests-to-pathlib branch from 29e1096 to aa6c306 Compare July 28, 2026 01:50
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.

Refactor tests to use pathlib.Path

2 participants