Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/pcapkit/corekit/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ inherits from.
module
multidict
protochain
sentinels
version
27 changes: 27 additions & 0 deletions docs/source/pcapkit/corekit/sentinels.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Sentinel Objects
================

.. module:: pcapkit.corekit.sentinels

:mod:`pcapkit.corekit.sentinels` is the single, shared home for every
module-level singleton sentinel this package defines for itself -- a value
whose only job is to be recognised by identity (``value is SENTINEL``), so
that it can never be confused with a value a caller might legitimately pass.

Each of the three below used to live beside the one class that consumed it --
:class:`NullType` in :mod:`pcapkit.corekit.module`, :class:`NoValueType` in
:mod:`pcapkit.corekit.fields.field` and :class:`NoDefaultType` in
:mod:`pcapkit.corekit.enum` -- until GitHub issue #911 moved all four
definitions here, the private ``_AbsentType``/``_Absent`` included. Each
original module keeps a re-export, so every existing ``from <module> import
<name>`` keeps working unchanged.

.. autoclass:: pcapkit.corekit.sentinels.NullType
.. autodata:: pcapkit.corekit.sentinels.NULL

.. autoclass:: pcapkit.corekit.sentinels.NoValueType
.. autodata:: pcapkit.corekit.sentinels.NoValue
:no-value:

.. autoclass:: pcapkit.corekit.sentinels.NoDefaultType
.. autodata:: pcapkit.corekit.sentinels.NO_DEFAULT
113 changes: 113 additions & 0 deletions tests/project/test_sentinels_doc_page_934_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
"""Pins the API page for :mod:`pcapkit.corekit.sentinels` existing and reachable.

GitHub issue #934: :file:`docs/source/contributing/conventions.rst` cross-references
:mod:`pcapkit.corekit.sentinels` five times (the ``:mod:`` role, at lines 165, 168, 171,
174 and 261), and none of them used to resolve, because no page under
:file:`docs/source/` documented that module -- confirmed by an explicit nitpicky
``sphinx-build`` before this page existed, and again after, to confirm the fix.

This does **not** re-test Sphinx's cross-reference resolution itself -- as
:file:`tests/project/test_documentation_claims.py` explains at length, whether a
reference resolves is a property of the built inventory rather than of the source, and
the honest check is the rendered HTML from an actual ``sphinx-build`` run, recorded in
the pull request rather than reimplemented here. What this pins instead is the two
purely textual preconditions a later edit could silently break even though a plain
(non-nitpicky) build would keep passing either way: the page has to keep existing and
keep declaring the module it documents, and the ``corekit`` index has to keep listing it
in its toctree so it is not an orphan page -- Sphinx does not fail a plain build over an
orphan page either, so nothing else would catch that regression.

"""

from __future__ import annotations

import pathlib
import unittest

ROOT = pathlib.Path(__file__).resolve().parents[2]

#: The page GitHub issue #934 added.
PAGE = ROOT / 'docs' / 'source' / 'pcapkit' / 'corekit' / 'sentinels.rst'

#: The ``corekit`` API index, whose toctree has to list the page above.
INDEX = ROOT / 'docs' / 'source' / 'pcapkit' / 'corekit' / 'index.rst'


def _toctree_entries(text: 'str') -> 'list[str]':
"""Return the entries of the first ``.. toctree::`` directive in ``text``.

Parsed structurally -- skip the directive's own options (``:maxdepth:`` and
the like), then collect non-blank lines until the entry block ends -- rather
than searched for a literal substring, so a reordering or an added option
does not make this misreport what the toctree actually names.

"""
lines = text.splitlines()
for index, line in enumerate(lines):
if line.strip() == '.. toctree::':
break
else:
raise AssertionError('no ".. toctree::" directive found')

entries = [] # type: list[str]
started = False
for line in lines[index + 1:]:
stripped = line.strip()
if not stripped:
if started:
break
continue
if stripped.startswith(':'):
continue
started = True
entries.append(stripped)
return entries


class SentinelsAPIPageTests(unittest.TestCase):
"""The page GitHub issue #934 added, and its registration in the toctree."""

def test_page_exists(self) -> 'None':
"""The API page itself is present on disk."""
self.assertTrue(PAGE.is_file(), f'{PAGE} does not exist')

def test_page_documents_the_sentinels_module(self) -> 'None':
"""The page declares the module it is meant to document.

Without this directive, ``autoclass``/``autodata`` targets on the page
would still resolve by their own fully-qualified names, but the plain
:mod:`pcapkit.corekit.sentinels` reference itself -- the exact role
GitHub issue #934 is about -- would not.

Matches either ``.. module::`` or ``.. automodule::``: both register the
same ``py:module`` target Sphinx resolves ``:mod:`` roles against, so a
later switch between the two keeps the contract this test pins rather
than failing over a directive choice that was never the point.

"""
text = PAGE.read_text(encoding='utf-8')
self.assertRegex(
text, r'\.\.\s+(?:auto)?module::\s+pcapkit\.corekit\.sentinels\b',
f'{PAGE} does not declare "pcapkit.corekit.sentinels" as its module',
)

def test_page_is_registered_in_the_corekit_toctree(self) -> 'None':
"""The ``corekit`` index lists the page, so it is not an orphan.

An orphan page still resolves cross-references -- Sphinx's reference
inventory does not care whether a page is reachable from a toctree --
but it produces a distinct "document isn't included in any toctree"
warning, which is a different defect from the one GitHub issue #934
reports. Pinning both closes the two ways this fix can go wrong.

"""
entries = _toctree_entries(INDEX.read_text(encoding='utf-8'))
self.assertIn(
'sentinels', entries,
f'{INDEX} toctree does not list "sentinels": {entries!r}',
)


if __name__ == '__main__':
unittest.main()
Loading