Skip to content

Commit e8ba36c

Browse files
committed
Rewrite path query tests using pytest.mark.parametrize
1 parent 952a879 commit e8ba36c

File tree

1 file changed

+105
-246
lines changed

1 file changed

+105
-246
lines changed

test/test_query.py

Lines changed: 105 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,22 @@
1414

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

17-
import os
1817
import sys
19-
import unittest
20-
from contextlib import contextmanager
21-
from functools import partial
18+
from pathlib import Path
2219

2320
import pytest
2421
from mock import patch
2522

26-
import beets.library
27-
from beets import dbcore, util
23+
from beets import dbcore
2824
from beets.dbcore import types
2925
from beets.dbcore.query import (
3026
InvalidQueryArgumentValueError,
3127
NoneQuery,
3228
ParsingError,
29+
PathQuery,
3330
)
3431
from beets.test import _common
35-
from beets.test.helper import BeetsTestCase, ItemInDBTestCase
36-
from beets.util import syspath
32+
from beets.test.helper import BeetsTestCase, TestHelper
3733

3834
# Because the absolute path begins with something like C:, we
3935
# can't disambiguate it from an ordinary query.
@@ -442,244 +438,6 @@ def test_eq(self):
442438
assert q3 != q4
443439

444440

445-
class PathQueryTest(ItemInDBTestCase, AssertsMixin):
446-
def setUp(self):
447-
super().setUp()
448-
449-
# This is the item we'll try to match.
450-
self.i.path = util.normpath("/a/b/c.mp3")
451-
self.i.title = "path item"
452-
self.i.album = "path album"
453-
self.i.store()
454-
self.lib.add_album([self.i])
455-
456-
# A second item for testing exclusion.
457-
i2 = _common.item()
458-
i2.path = util.normpath("/x/y/z.mp3")
459-
i2.title = "another item"
460-
i2.album = "another album"
461-
self.lib.add(i2)
462-
self.lib.add_album([i2])
463-
464-
@contextmanager
465-
def force_implicit_query_detection(self):
466-
# Unadorned path queries with path separators in them are considered
467-
# path queries only when the path in question actually exists. So we
468-
# mock the existence check to return true.
469-
beets.dbcore.query.PathQuery.force_implicit_query_detection = True
470-
yield
471-
beets.dbcore.query.PathQuery.force_implicit_query_detection = False
472-
473-
def test_path_exact_match(self):
474-
q = "path:/a/b/c.mp3"
475-
results = self.lib.items(q)
476-
self.assert_items_matched(results, ["path item"])
477-
478-
results = self.lib.albums(q)
479-
self.assert_albums_matched(results, ["path album"])
480-
481-
# FIXME: fails on windows
482-
@unittest.skipIf(sys.platform == "win32", "win32")
483-
def test_parent_directory_no_slash(self):
484-
q = "path:/a"
485-
results = self.lib.items(q)
486-
self.assert_items_matched(results, ["path item"])
487-
488-
results = self.lib.albums(q)
489-
self.assert_albums_matched(results, ["path album"])
490-
491-
# FIXME: fails on windows
492-
@unittest.skipIf(sys.platform == "win32", "win32")
493-
def test_parent_directory_with_slash(self):
494-
q = "path:/a/"
495-
results = self.lib.items(q)
496-
self.assert_items_matched(results, ["path item"])
497-
498-
results = self.lib.albums(q)
499-
self.assert_albums_matched(results, ["path album"])
500-
501-
def test_no_match(self):
502-
q = "path:/xyzzy/"
503-
results = self.lib.items(q)
504-
self.assert_items_matched(results, [])
505-
506-
results = self.lib.albums(q)
507-
self.assert_albums_matched(results, [])
508-
509-
def test_fragment_no_match(self):
510-
q = "path:/b/"
511-
results = self.lib.items(q)
512-
self.assert_items_matched(results, [])
513-
514-
results = self.lib.albums(q)
515-
self.assert_albums_matched(results, [])
516-
517-
def test_nonnorm_path(self):
518-
q = "path:/x/../a/b"
519-
results = self.lib.items(q)
520-
self.assert_items_matched(results, ["path item"])
521-
522-
results = self.lib.albums(q)
523-
self.assert_albums_matched(results, ["path album"])
524-
525-
@unittest.skipIf(sys.platform == "win32", WIN32_NO_IMPLICIT_PATHS)
526-
def test_slashed_query_matches_path(self):
527-
with self.force_implicit_query_detection():
528-
q = "/a/b"
529-
results = self.lib.items(q)
530-
self.assert_items_matched(results, ["path item"])
531-
532-
results = self.lib.albums(q)
533-
self.assert_albums_matched(results, ["path album"])
534-
535-
@unittest.skipIf(sys.platform == "win32", WIN32_NO_IMPLICIT_PATHS)
536-
def test_path_query_in_or_query(self):
537-
with self.force_implicit_query_detection():
538-
q = "/a/b , /a/b"
539-
results = self.lib.items(q)
540-
self.assert_items_matched(results, ["path item"])
541-
542-
def test_non_slashed_does_not_match_path(self):
543-
with self.force_implicit_query_detection():
544-
q = "c.mp3"
545-
results = self.lib.items(q)
546-
self.assert_items_matched(results, [])
547-
548-
results = self.lib.albums(q)
549-
self.assert_albums_matched(results, [])
550-
551-
def test_slashes_in_explicit_field_does_not_match_path(self):
552-
with self.force_implicit_query_detection():
553-
q = "title:/a/b"
554-
results = self.lib.items(q)
555-
self.assert_items_matched(results, [])
556-
557-
def test_path_item_regex(self):
558-
q = "path::c\\.mp3$"
559-
results = self.lib.items(q)
560-
self.assert_items_matched(results, ["path item"])
561-
562-
results = self.lib.albums(q)
563-
self.assert_albums_matched(results, ["path album"])
564-
565-
def test_path_album_regex(self):
566-
q = "path::b"
567-
results = self.lib.albums(q)
568-
self.assert_albums_matched(results, ["path album"])
569-
570-
def test_escape_underscore(self):
571-
self.add_album(
572-
path=b"/a/_/title.mp3",
573-
title="with underscore",
574-
album="album with underscore",
575-
)
576-
q = "path:/a/_"
577-
results = self.lib.items(q)
578-
self.assert_items_matched(results, ["with underscore"])
579-
580-
results = self.lib.albums(q)
581-
self.assert_albums_matched(results, ["album with underscore"])
582-
583-
def test_escape_percent(self):
584-
self.add_album(
585-
path=b"/a/%/title.mp3",
586-
title="with percent",
587-
album="album with percent",
588-
)
589-
q = "path:/a/%"
590-
results = self.lib.items(q)
591-
self.assert_items_matched(results, ["with percent"])
592-
593-
results = self.lib.albums(q)
594-
self.assert_albums_matched(results, ["album with percent"])
595-
596-
def test_escape_backslash(self):
597-
self.add_album(
598-
path=rb"/a/\x/title.mp3",
599-
title="with backslash",
600-
album="album with backslash",
601-
)
602-
q = "path:/a/\\\\x"
603-
results = self.lib.items(q)
604-
self.assert_items_matched(results, ["with backslash"])
605-
606-
results = self.lib.albums(q)
607-
self.assert_albums_matched(results, ["album with backslash"])
608-
609-
def test_case_sensitivity(self):
610-
self.add_album(path=b"/A/B/C2.mp3", title="caps path")
611-
612-
makeq = partial(beets.dbcore.query.PathQuery, "path", "/A/B")
613-
614-
results = self.lib.items(makeq(case_sensitive=True))
615-
self.assert_items_matched(results, ["caps path"])
616-
617-
results = self.lib.items(makeq(case_sensitive=False))
618-
self.assert_items_matched(results, ["path item", "caps path"])
619-
620-
# FIXME: Also create a variant of this test for windows, which tests
621-
# both os.sep and os.altsep
622-
@unittest.skipIf(sys.platform == "win32", "win32")
623-
def test_path_sep_detection(self):
624-
is_path_query = beets.dbcore.query.PathQuery.is_path_query
625-
626-
with self.force_implicit_query_detection():
627-
assert is_path_query("/foo/bar")
628-
assert is_path_query("foo/bar")
629-
assert is_path_query("foo/")
630-
assert not is_path_query("foo")
631-
assert is_path_query("foo/:bar")
632-
assert not is_path_query("foo:bar/")
633-
assert not is_path_query("foo:/bar")
634-
635-
# FIXME: shouldn't this also work on windows?
636-
@unittest.skipIf(sys.platform == "win32", WIN32_NO_IMPLICIT_PATHS)
637-
def test_detect_absolute_path(self):
638-
"""Test detection of implicit path queries based on whether or
639-
not the path actually exists, when using an absolute path query.
640-
641-
Thus, don't use the `force_implicit_query_detection()`
642-
contextmanager which would disable the existence check.
643-
"""
644-
is_path_query = beets.dbcore.query.PathQuery.is_path_query
645-
646-
path = self.touch(os.path.join(b"foo", b"bar"))
647-
assert os.path.isabs(util.syspath(path))
648-
path_str = path.decode("utf-8")
649-
650-
# The file itself.
651-
assert is_path_query(path_str)
652-
653-
# The parent directory.
654-
parent = os.path.dirname(path_str)
655-
assert is_path_query(parent)
656-
657-
# Some non-existent path.
658-
assert not is_path_query(f"{path_str}baz")
659-
660-
def test_detect_relative_path(self):
661-
"""Test detection of implicit path queries based on whether or
662-
not the path actually exists, when using a relative path query.
663-
664-
Thus, don't use the `force_implicit_query_detection()`
665-
contextmanager which would disable the existence check.
666-
"""
667-
is_path_query = beets.dbcore.query.PathQuery.is_path_query
668-
669-
self.touch(os.path.join(b"foo", b"bar"))
670-
671-
# Temporarily change directory so relative paths work.
672-
cur_dir = os.getcwd()
673-
try:
674-
os.chdir(syspath(self.temp_dir))
675-
assert is_path_query("foo/")
676-
assert is_path_query("foo/bar")
677-
assert is_path_query("foo/bar:tagada")
678-
assert not is_path_query("bar")
679-
finally:
680-
os.chdir(cur_dir)
681-
682-
683441
class IntQueryTest(BeetsTestCase):
684442
def test_exact_value_match(self):
685443
item = self.add_item(bpm=120)
@@ -1104,3 +862,104 @@ def test_filter_items_by_common_field(self):
1104862
q = "artpath::A Album1"
1105863
results = self.lib.items(q)
1106864
self.assert_items_matched(results, ["Album1 Item1", "Album1 Item2"])
865+
866+
867+
@pytest.fixture(scope="class")
868+
def helper():
869+
helper = TestHelper()
870+
helper.setup_beets()
871+
872+
yield helper
873+
874+
helper.teardown_beets()
875+
876+
877+
class TestPathQuery:
878+
_p = pytest.param
879+
880+
@pytest.fixture(scope="class")
881+
def lib(self, helper):
882+
helper.add_item(path="/a/b/c.mp3", title="path item")
883+
helper.add_item(path="/x/y/z.mp3", title="another item")
884+
helper.add_item(path=b"/c/_/title.mp3", title="with underscore")
885+
helper.add_item(path=b"/c/%/title.mp3", title="with percent")
886+
helper.add_item(path=rb"/c/\x/title.mp3", title="with backslash")
887+
helper.add_item(path=b"/A/B/C2.mp3", title="caps path")
888+
889+
return helper.lib
890+
891+
@pytest.mark.parametrize(
892+
"q, expected_titles",
893+
[
894+
_p("path:/a/b/c.mp3", ["path item"], id="exact-match"),
895+
_p("path:/a", ["path item"], id="parent-dir-no-slash"),
896+
_p("path:/a/", ["path item"], id="parent-dir-with-slash"),
897+
_p("path:/xyzzy/", [], id="no-match"),
898+
_p("path:/b/", [], id="fragment-no-match"),
899+
_p("path:/x/../a/b", ["path item"], id="non-normalized"),
900+
_p("path::c\\.mp3$", ["path item"], id="regex"),
901+
_p("path:/c/_", ["with underscore"], id="underscore-escaped"),
902+
_p("path:/c/%", ["with percent"], id="percent-escaped"),
903+
_p("path:/c/\\\\x", ["with backslash"], id="backslash-escaped"),
904+
],
905+
)
906+
def test_explicit(self, lib, q, expected_titles):
907+
assert {i.title for i in lib.items(q)} == set(expected_titles)
908+
909+
@pytest.mark.skipif(sys.platform == "win32", reason=WIN32_NO_IMPLICIT_PATHS)
910+
@pytest.mark.parametrize(
911+
"q, expected_titles",
912+
[
913+
_p("/a/b", ["path item"], id="slashed-query"),
914+
_p("/a/b , /a/b", ["path item"], id="path-in-or-query"),
915+
_p("c.mp3", [], id="no-slash-no-match"),
916+
_p("title:/a/b", [], id="slash-with-explicit-field-no-match"),
917+
],
918+
)
919+
def test_implicit(self, monkeypatch, lib, q, expected_titles):
920+
monkeypatch.setattr(
921+
"beets.dbcore.query.PathQuery.is_path_query", lambda path: True
922+
)
923+
924+
assert {i.title for i in lib.items(q)} == set(expected_titles)
925+
926+
@pytest.mark.parametrize(
927+
"case_sensitive, expected_titles",
928+
[
929+
_p(True, [], id="non-caps-dont-match-caps"),
930+
_p(False, ["caps path"], id="non-caps-match-caps"),
931+
],
932+
)
933+
def test_case_sensitivity(
934+
self, lib, monkeypatch, case_sensitive, expected_titles
935+
):
936+
q = "path:/a/b/c2.mp3"
937+
monkeypatch.setattr(
938+
"beets.util.case_sensitive", lambda *_: case_sensitive
939+
)
940+
941+
assert {i.title for i in lib.items(q)} == set(expected_titles)
942+
943+
# FIXME: Also create a variant of this test for windows, which tests
944+
# both os.sep and os.altsep
945+
@pytest.mark.skipif(sys.platform == "win32", reason=WIN32_NO_IMPLICIT_PATHS)
946+
@pytest.mark.parametrize(
947+
"q, is_path_query",
948+
[
949+
("/foo/bar", True),
950+
("foo/bar", True),
951+
("foo/", True),
952+
("foo", False),
953+
("foo/:bar", True),
954+
("foo:bar/", False),
955+
("foo:/bar", False),
956+
],
957+
)
958+
def test_path_sep_detection(self, monkeypatch, tmp_path, q, is_path_query):
959+
monkeypatch.chdir(tmp_path)
960+
(tmp_path / "foo").mkdir()
961+
(tmp_path / "foo" / "bar").touch()
962+
if Path(q).is_absolute():
963+
q = str(tmp_path / q[1:])
964+
965+
assert PathQuery.is_path_query(q) == is_path_query

0 commit comments

Comments
 (0)