Skip to content

Commit d7ccf01

Browse files
committed
Fix path tests on Windows
1 parent 93129d2 commit d7ccf01

3 files changed

Lines changed: 56 additions & 26 deletions

File tree

test/test_library.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -925,10 +925,10 @@ def test_albuminfo_reflects_metadata(self):
925925

926926
def test_albuminfo_stores_art(self):
927927
ai = self.lib.get_album(self.i)
928-
ai.artpath = "/my/great/art"
928+
ai.artpath = os.fsdecode(np("/my/great/art"))
929929
ai.store()
930930
new_ai = self.lib.get_album(self.i)
931-
assert new_ai.artpath == b"/my/great/art"
931+
assert new_ai.artpath == np("/my/great/art")
932932

933933
def test_albuminfo_for_two_items_doesnt_duplicate_row(self):
934934
i2 = item(self.lib)

test/test_query.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import pytest
2323

24+
from beets import util
2425
from beets.dbcore import types
2526
from beets.dbcore.query import (
2627
AndQuery,
@@ -278,6 +279,23 @@ class TestPathQuery:
278279
and path separator detection across different platforms.
279280
"""
280281

282+
@staticmethod
283+
def abs_query_path(path: str, trailing_sep: bool = False) -> str:
284+
"""Build a platform-correct absolute query path without normalizing it.
285+
286+
On Windows, leading-slash paths are drive-rooted but Python 3.13 no
287+
longer treats them as absolute. Prefix the current drive so explicit
288+
path queries stay absolute while preserving raw segments such as ``..``.
289+
"""
290+
if os.path.__name__ == "ntpath" and path.startswith("/"):
291+
drive, _ = os.path.splitdrive(os.fsdecode(util.normpath(os.sep)))
292+
path = drive + path
293+
294+
path = path.replace("/", os.sep)
295+
if trailing_sep:
296+
path = os.path.join(path, "")
297+
return path.replace("\\", "\\\\")
298+
281299
@pytest.fixture(scope="class")
282300
def lib(self, helper):
283301
helper.add_item(path=b"/aaa/bb/c.mp3", title="path item")
@@ -290,24 +308,32 @@ def lib(self, helper):
290308
return helper.lib
291309

292310
@pytest.mark.parametrize(
293-
"q, expected_titles",
311+
"path, expected_titles, trailing_sep",
294312
[
295-
_p("path:/aaa/bb/c.mp3", ["path item"], id="exact-match"),
296-
_p("path:/aaa", ["path item"], id="parent-dir-no-slash"),
297-
_p("path:/aaa/", ["path item"], id="parent-dir-with-slash"),
298-
_p("path:/aa", [], id="no-match-does-not-match-parent-dir"),
299-
_p("path:/xyzzy/", [], id="no-match"),
300-
_p("path:/b/", [], id="fragment-no-match"),
301-
_p("path:/x/../aaa/bb", ["path item"], id="non-normalized"),
302-
_p("path::c\\.mp3$", ["path item"], id="regex"),
303-
_p("path:/c/_", ["with underscore"], id="underscore-escaped"),
304-
_p("path:/c/%", ["with percent"], id="percent-escaped"),
305-
_p("path:/c/\\\\x", ["with backslash"], id="backslash-escaped"),
313+
_p("/aaa/bb/c.mp3", ["path item"], False, id="exact-match"),
314+
_p("/aaa", ["path item"], False, id="parent-dir-no-slash"),
315+
_p("/aaa", ["path item"], True, id="parent-dir-with-slash"),
316+
_p("/aa", [], False, id="no-match-does-not-match-parent-dir"),
317+
_p("/xyzzy", [], True, id="no-match"),
318+
_p("/b", [], True, id="fragment-no-match"),
319+
_p("/x/../aaa/bb", ["path item"], False, id="non-normalized"),
320+
_p(r"c\.mp3$", ["path item"], False, id="regex"),
321+
_p("/c/_", ["with underscore"], False, id="underscore-escaped"),
322+
_p("/c/%", ["with percent"], False, id="percent-escaped"),
323+
_p(r"/c/\x", ["with backslash"], False, id="backslash-escaped"),
306324
],
307325
)
308-
def test_explicit(self, monkeypatch, lib, q, expected_titles):
326+
def test_explicit(
327+
self, monkeypatch, lib, path, expected_titles, trailing_sep
328+
):
309329
"""Test explicit path queries with different path specifications."""
310330
monkeypatch.setattr("beets.util.case_sensitive", lambda *_: True)
331+
if path == r"c\.mp3$":
332+
q = f"path::{path}"
333+
elif path == r"/c/\x" and os.path.__name__ != "ntpath":
334+
q = r"path:/c/\\x"
335+
else:
336+
q = f"path:{self.abs_query_path(path, trailing_sep=trailing_sep)}"
311337

312338
assert {i.title for i in lib.items(q)} == set(expected_titles)
313339

@@ -318,7 +344,6 @@ def test_absolute(self, lib, helper, query):
318344
item_path = helper.lib_path / "item.mp3"
319345
bytes_path = os.fsencode(item_path)
320346
helper.add_item(path=bytes_path, title="absolute item")
321-
# Escape backslashes for Windows paths
322347
q = f"{query}{item_path}".replace("\\", "\\\\")
323348

324349
assert {i.title for i in lib.items(q)} == {"absolute item"}
@@ -360,7 +385,7 @@ def test_case_sensitivity(
360385
self, lib, monkeypatch, case_sensitive, expected_titles
361386
):
362387
"""Test path matching with different case sensitivity settings."""
363-
q = "path:/a/b/c2.mp3"
388+
q = f"path:{self.abs_query_path('/a/b/c2.mp3')}"
364389
monkeypatch.setattr(
365390
"beets.util.case_sensitive", lambda *_: case_sensitive
366391
)

test/test_sort.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@
1414

1515
"""Various tests for querying the library database."""
1616

17+
import os
1718
from unittest.mock import patch
1819

1920
import beets.library
20-
from beets import config, dbcore
21+
from beets import config, dbcore, util
2122
from beets.dbcore import types
2223
from beets.library import Album
2324
from beets.test import _common
2425
from beets.test.helper import BeetsTestCase
2526

2627

28+
def abs_test_path(path: str) -> str:
29+
return os.fsdecode(util.normpath(path))
30+
31+
2732
# A test case class providing a library with some dummy data and some
2833
# assertions involving that data.
2934
class DummyDataTestCase(BeetsTestCase):
@@ -69,7 +74,7 @@ def setUp(self):
6974
items[0].flex2 = "Flex2-A"
7075
items[0].album_id = albums[0].id
7176
items[0].artist_sort = None
72-
items[0].path = "/path0.mp3"
77+
items[0].path = abs_test_path("/path0.mp3")
7378
items[0].track = 1
7479
items[1].title = "Baz qux"
7580
items[1].artist = "Two"
@@ -80,7 +85,7 @@ def setUp(self):
8085
items[1].flex2 = "Flex2-A"
8186
items[1].album_id = albums[0].id
8287
items[1].artist_sort = None
83-
items[1].path = "/patH1.mp3"
88+
items[1].path = abs_test_path("/patH1.mp3")
8489
items[1].track = 2
8590
items[2].title = "Beets 4 eva"
8691
items[2].artist = "Three"
@@ -91,7 +96,7 @@ def setUp(self):
9196
items[2].flex2 = "Flex1-B"
9297
items[2].album_id = albums[1].id
9398
items[2].artist_sort = None
94-
items[2].path = "/paTH2.mp3"
99+
items[2].path = abs_test_path("/paTH2.mp3")
95100
items[2].track = 3
96101
items[3].title = "Beets 4 eva"
97102
items[3].artist = "Three"
@@ -102,7 +107,7 @@ def setUp(self):
102107
items[3].flex2 = "Flex1-C"
103108
items[3].album_id = albums[2].id
104109
items[3].artist_sort = None
105-
items[3].path = "/PATH3.mp3"
110+
items[3].path = abs_test_path("/PATH3.mp3")
106111
items[3].track = 4
107112
for item in items:
108113
self.lib.add(item)
@@ -156,10 +161,10 @@ def test_sort_path_field(self):
156161
q = ""
157162
sort = dbcore.query.FixedFieldSort("path", True)
158163
results = self.lib.items(q, sort)
159-
assert results[0]["path"] == b"/path0.mp3"
160-
assert results[1]["path"] == b"/patH1.mp3"
161-
assert results[2]["path"] == b"/paTH2.mp3"
162-
assert results[3]["path"] == b"/PATH3.mp3"
164+
assert results[0]["path"] == util.normpath("/path0.mp3")
165+
assert results[1]["path"] == util.normpath("/patH1.mp3")
166+
assert results[2]["path"] == util.normpath("/paTH2.mp3")
167+
assert results[3]["path"] == util.normpath("/PATH3.mp3")
163168

164169

165170
class SortFlexFieldTest(DummyDataTestCase):

0 commit comments

Comments
 (0)