commit local changes.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
"""Tests for the search engine: ranking, fuzzy matching, and @maintainer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sjaur.query import parse_query
|
||||
from sjaur.search import MAINTAINER_MATCH_THRESHOLD, SearchEngine
|
||||
|
||||
|
||||
def names(results):
|
||||
return [result.package.name for result in results]
|
||||
|
||||
|
||||
def test_exact_name_match_first(engine):
|
||||
results = engine.search(parse_query("firefox"))
|
||||
assert names(results)[0] == "firefox"
|
||||
# All firefox variants are found.
|
||||
assert {"firefox", "firefox-nightly", "firefox-wayland"} <= set(names(results))
|
||||
|
||||
|
||||
def test_name_prefix_ranks_above_substring(engine):
|
||||
results = engine.search(parse_query("fire"))
|
||||
top = names(results)
|
||||
assert "firefox" in top[:3] # prefix match
|
||||
# The substring-only "firefox-extension-uorigin" must not outrank the
|
||||
# prefix matches.
|
||||
assert top.index("firefox") < top.index("firefox-extension-uorigin")
|
||||
|
||||
|
||||
def test_typo_still_finds_package(engine):
|
||||
results = engine.search(parse_query("firfox"))
|
||||
assert "firefox" in names(results)
|
||||
|
||||
|
||||
def test_description_match(engine):
|
||||
results = engine.search(parse_query("office productivity"))
|
||||
assert "libreoffice-fresh" in names(results)
|
||||
|
||||
|
||||
def test_description_tokens_all_must_appear(engine):
|
||||
results = engine.search(parse_query("productivity suite"))
|
||||
assert names(results)[0] == "libreoffice-fresh"
|
||||
|
||||
|
||||
def test_maintainer_field_contributes_to_normal_search(engine):
|
||||
results = engine.search(parse_query("alice"))
|
||||
assert "fzf" in names(results)
|
||||
|
||||
|
||||
def test_maintainer_operator_fuzzy(engine):
|
||||
results = engine.search(parse_query("@max_musterman"))
|
||||
found = names(results)
|
||||
# Fuzzy: similar maintainers match despite the typo.
|
||||
assert "python-pytest" in found # max_musterman123
|
||||
assert "firefox-wayland" in found # max_mustermann
|
||||
# Unrelated maintainers must not appear.
|
||||
assert "firefox" not in found
|
||||
assert "chromium" not in found
|
||||
|
||||
|
||||
def test_maintainer_operator_with_package_query(engine):
|
||||
results = engine.search(parse_query("@jonas browser"))
|
||||
found = names(results)
|
||||
assert {"firefox", "chromium"} <= set(found)
|
||||
# Maintained by others.
|
||||
assert "python-pytest" not in found
|
||||
|
||||
|
||||
def test_maintainer_query_boosts_maintainer_matches(engine):
|
||||
# "jonas" maintains firefox and chromium; both rank top for the plain
|
||||
# text search, and the @-combined search scores at least as high overall.
|
||||
plain = engine.search(parse_query("jonas"))
|
||||
combined = engine.search(parse_query("@jonas jonas"))
|
||||
assert names(plain)[0] == "firefox" # shorter name wins the prefix tie
|
||||
assert names(combined)[0] in ("firefox", "chromium")
|
||||
assert combined[0].score >= plain[0].score
|
||||
|
||||
|
||||
def test_maintainer_below_threshold_is_excluded(engine):
|
||||
results = engine.search(parse_query("@zzzzzzzzzz"))
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_orphan_packages_have_no_maintainer_matches(engine):
|
||||
# An empty (orphaned) maintainer cannot fuzzy-match an @query, but the
|
||||
# package itself is still reachable through a normal search.
|
||||
results = engine.search(parse_query("@orph"))
|
||||
found = names(results)
|
||||
assert "python-editor" in found # maintainer "orphan"
|
||||
assert "libreoffice-fresh" not in found
|
||||
assert "libreoffice-fresh" in names(engine.search(parse_query("office")))
|
||||
|
||||
|
||||
def test_combined_scoring_formula(engine):
|
||||
# With a maintainer query, maintainer similarity carries most of the
|
||||
# weight but the package score still orders results.
|
||||
results = engine.search(parse_query("@jonas standalone"))
|
||||
firefox = next(r for r in results if r.package.name == "firefox")
|
||||
chromium = next(r for r in results if r.package.name == "chromium")
|
||||
assert firefox.score > 0 and chromium.score > 0
|
||||
# firefox's description contains "standalone", chromium's does not.
|
||||
assert firefox.score > chromium.score
|
||||
|
||||
|
||||
def test_provides_field_is_searched(engine):
|
||||
results = engine.search(parse_query("chromium-browser"))
|
||||
assert names(results)[0] == "chromium"
|
||||
|
||||
|
||||
def test_dependencies_are_searched_with_lower_priority(engine):
|
||||
results = engine.search(parse_query("pytest"))
|
||||
# python-pytest matches by name; anything matching only via depends must
|
||||
# not outrank it.
|
||||
assert names(results)[0] == "python-pytest"
|
||||
|
||||
|
||||
def test_empty_results(engine):
|
||||
assert engine.search(parse_query("zzzznotapackagezzzz")) == []
|
||||
|
||||
|
||||
def test_empty_query_returns_popular_packages(engine):
|
||||
results = engine.search(parse_query(""))
|
||||
top = names(results)
|
||||
assert len(top) > 0
|
||||
# Most-voted package first.
|
||||
votes = [r.package.votes for r in results]
|
||||
assert votes == sorted(votes, reverse=True)
|
||||
|
||||
|
||||
def test_results_are_deterministic(engine):
|
||||
first = engine.search(parse_query("web browser"))
|
||||
second = engine.search(parse_query("web browser"))
|
||||
assert names(first) == names(second)
|
||||
assert [r.score for r in first] == [r.score for r in second]
|
||||
|
||||
|
||||
def test_limit(engine):
|
||||
results = engine.search(parse_query("firefox"), limit=2)
|
||||
assert len(results) == 2
|
||||
|
||||
|
||||
def test_whitespace_only_query_behaves_like_empty(engine):
|
||||
results = engine.search(parse_query(" "))
|
||||
assert results == engine.search(parse_query(""))
|
||||
|
||||
|
||||
def test_maintainer_threshold_is_fuzzy_not_exact():
|
||||
# The threshold must be low enough that small typos still match.
|
||||
assert MAINTAINER_MATCH_THRESHOLD < 60.0
|
||||
|
||||
|
||||
def test_index_is_built_once_and_reused(engine):
|
||||
# Repeated searches must not rebuild the index (same engine object).
|
||||
first = engine.search(parse_query("firefox"))
|
||||
assert engine._names is not None
|
||||
second = engine.search(parse_query("firefox"))
|
||||
assert names(first) == names(second)
|
||||
|
||||
|
||||
def test_repo_and_aur_packages_of_same_name_are_both_kept():
|
||||
from conftest import make_package
|
||||
|
||||
aur = make_package("python-editor", votes=42)
|
||||
repo = make_package("python-editor", votes=0)
|
||||
object.__setattr__(repo, "repository", "extra")
|
||||
|
||||
combined = SearchEngine([aur, repo])
|
||||
assert len(combined.packages) == 2
|
||||
# Both survive a search and rank identically (votes tie-break first).
|
||||
results = combined.search(parse_query("python-editor"))
|
||||
assert [r.package.repository for r in results if r.package.name == "python-editor"] == [
|
||||
"aur",
|
||||
"extra",
|
||||
]
|
||||
|
||||
|
||||
def test_quick_filter_returns_matching_subset(engine):
|
||||
from conftest import make_package
|
||||
from sjaur.search import SearchResult
|
||||
|
||||
base = engine.search(parse_query("firefox"))
|
||||
narrowed = engine.quick_filter(base, "firefo")
|
||||
assert {r.package.name for r in narrowed} <= {r.package.name for r in base}
|
||||
assert "firefox" in {r.package.name for r in narrowed}
|
||||
|
||||
# Fuzzy-only hits survive filtering by the same text (no flicker),
|
||||
# and correcting the typo keeps real name matches while dropping
|
||||
# unrelated fuzzy noise.
|
||||
typo = engine.search(parse_query("firfox"))
|
||||
assert engine.quick_filter(typo, "firfox") != []
|
||||
corrected = engine.quick_filter(typo, "firefox")
|
||||
assert "firefox" in {r.package.name for r in corrected}
|
||||
assert "ffbox" not in {r.package.name for r in corrected}
|
||||
|
||||
# Multi-word queries require every token to be present.
|
||||
web = engine.search(parse_query("web"))
|
||||
assert engine.quick_filter(web, "web browser") != []
|
||||
|
||||
# Empty text keeps everything; unknown packages are dropped.
|
||||
assert engine.quick_filter(base, "") == base
|
||||
stranger = SearchResult(package=make_package("not-in-engine"), score=1.0)
|
||||
assert engine.quick_filter([stranger], "firefox") == []
|
||||
Reference in New Issue
Block a user