summaryrefslogtreecommitdiffstats
path: root/tests/test_search.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_search.py')
-rw-r--r--tests/test_search.py60
1 files changed, 51 insertions, 9 deletions
diff --git a/tests/test_search.py b/tests/test_search.py
index 68a7b01..63443a8 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -1,4 +1,5 @@
"""Test the search index builder."""
+from __future__ import annotations
import json
import warnings
@@ -72,7 +73,7 @@ test that non-comments are indexed: fermion
@pytest.mark.sphinx(testroot='ext-viewcode')
def test_objects_are_escaped(app):
- app.builder.build_all()
+ app.build(force_all=True)
index = load_searchindex(app.outdir / 'searchindex.js')
for item in index.get('objects').get(''):
if item[-1] == 'n::Array&lt;T, d&gt;': # n::Array<T,d> is escaped
@@ -83,7 +84,7 @@ def test_objects_are_escaped(app):
@pytest.mark.sphinx(testroot='search')
def test_meta_keys_are_handled_for_language_en(app):
- app.builder.build_all()
+ app.build(force_all=True)
searchindex = load_searchindex(app.outdir / 'searchindex.js')
assert not is_registered_term(searchindex, 'thisnoteith')
assert is_registered_term(searchindex, 'thisonetoo')
@@ -96,7 +97,7 @@ def test_meta_keys_are_handled_for_language_en(app):
@pytest.mark.sphinx(testroot='search', confoverrides={'html_search_language': 'de'}, freshenv=True)
def test_meta_keys_are_handled_for_language_de(app):
- app.builder.build_all()
+ app.build(force_all=True)
searchindex = load_searchindex(app.outdir / 'searchindex.js')
assert not is_registered_term(searchindex, 'thisnoteith')
assert is_registered_term(searchindex, 'thisonetoo')
@@ -109,14 +110,14 @@ def test_meta_keys_are_handled_for_language_de(app):
@pytest.mark.sphinx(testroot='search')
def test_stemmer_does_not_remove_short_words(app):
- app.builder.build_all()
+ app.build(force_all=True)
searchindex = (app.outdir / 'searchindex.js').read_text(encoding='utf8')
assert 'bat' in searchindex
@pytest.mark.sphinx(testroot='search')
def test_stemmer(app):
- app.builder.build_all()
+ app.build(force_all=True)
searchindex = load_searchindex(app.outdir / 'searchindex.js')
print(searchindex)
assert is_registered_term(searchindex, 'findthisstemmedkei')
@@ -125,7 +126,7 @@ def test_stemmer(app):
@pytest.mark.sphinx(testroot='search')
def test_term_in_heading_and_section(app):
- app.builder.build_all()
+ app.build(force_all=True)
searchindex = (app.outdir / 'searchindex.js').read_text(encoding='utf8')
# if search term is in the title of one doc and in the text of another
# both documents should be a hit in the search index as a title,
@@ -136,7 +137,7 @@ def test_term_in_heading_and_section(app):
@pytest.mark.sphinx(testroot='search')
def test_term_in_raw_directive(app):
- app.builder.build_all()
+ app.build(force_all=True)
searchindex = load_searchindex(app.outdir / 'searchindex.js')
assert not is_registered_term(searchindex, 'raw')
assert is_registered_term(searchindex, 'rawword')
@@ -157,8 +158,8 @@ def test_IndexBuilder():
index = IndexBuilder(env, 'en', {}, None)
index.feed('docname1_1', 'filename1_1', 'title1_1', doc)
index.feed('docname1_2', 'filename1_2', 'title1_2', doc)
- index.feed('docname2_1', 'filename2_1', 'title2_1', doc)
index.feed('docname2_2', 'filename2_2', 'title2_2', doc)
+ index.feed('docname2_1', 'filename2_1', 'title2_1', doc)
assert index._titles == {'docname1_1': 'title1_1', 'docname1_2': 'title1_2',
'docname2_1': 'title2_1', 'docname2_2': 'title2_2'}
assert index._filenames == {'docname1_1': 'filename1_1', 'docname1_2': 'filename1_2',
@@ -279,7 +280,7 @@ def test_IndexBuilder_lookup():
srcdir='search_zh',
)
def test_search_index_gen_zh(app):
- app.builder.build_all()
+ app.build(force_all=True)
index = load_searchindex(app.outdir / 'searchindex.js')
assert 'chinesetest ' not in index['terms']
assert 'chinesetest' in index['terms']
@@ -304,3 +305,44 @@ def test_parallel(app):
app.build()
index = load_searchindex(app.outdir / 'searchindex.js')
assert index['docnames'] == ['index', 'nosearch', 'tocitem']
+
+
+@pytest.mark.sphinx(testroot='search')
+def test_search_index_is_deterministic(app):
+ app.build(force_all=True)
+ index = load_searchindex(app.outdir / 'searchindex.js')
+ # Pretty print the index. Only shown by pytest on failure.
+ print(f'searchindex.js contents:\n\n{json.dumps(index, indent=2)}')
+ assert_is_sorted(index, '')
+
+
+def is_title_tuple_type(item: list[int | str]):
+ """
+ In the search index, titles inside .alltitles are stored as a tuple of
+ (document_idx, title_anchor). Tuples are represented as lists in JSON,
+ but their contents must not be sorted. We cannot sort them anyway, as
+ document_idx is an int and title_anchor is a str.
+ """
+ return len(item) == 2 and isinstance(item[0], int) and isinstance(item[1], str)
+
+
+def assert_is_sorted(item, path: str):
+ lists_not_to_sort = {
+ # Each element of .titles is related to the element of .docnames in the same position.
+ # The ordering is deterministic because .docnames is sorted.
+ '.titles',
+ # Each element of .filenames is related to the element of .docnames in the same position.
+ # The ordering is deterministic because .docnames is sorted.
+ '.filenames',
+ }
+
+ err_path = path or '<root>'
+ if isinstance(item, dict):
+ assert list(item.keys()) == sorted(item.keys()), f'{err_path} is not sorted'
+ for key, value in item.items():
+ assert_is_sorted(value, f'{path}.{key}')
+ elif isinstance(item, list):
+ if not is_title_tuple_type(item) and path not in lists_not_to_sort:
+ assert item == sorted(item), f'{err_path} is not sorted'
+ for i, child in enumerate(item):
+ assert_is_sorted(child, f'{path}[{i}]')