diff options
Diffstat (limited to 'tests/test_search.py')
-rw-r--r-- | tests/test_search.py | 60 |
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<T, d>': # 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}]') |