Skip to content
Closed
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
170 changes: 170 additions & 0 deletions tests/test_navigation_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,175 @@ def test_header_row_quotation_marks_are_ignored(self):
self.assertEqual((report['quotes'], report['summaries']), (0, 1))


class VerdictVocabulary(unittest.TestCase):
"""Lexical family checks, not semantic entailment or scope verification."""

def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix='verdict-vocabulary-')
self.addCleanup(self.tmp.cleanup)
self.root = pathlib.Path(self.tmp.name)
self.source = self.root / 'record.md'
self.doc = self.root / 'nav.md'

def _report(self, body, summary):
self.source.write_text(body, encoding='utf-8')
self.doc.write_text(HEADER + '| #1 | [r](record.md) — summary: '
+ summary + ' |\n', encoding='utf-8')
return check_doc_quotes.check_document(str(self.doc), str(self.root))

def _rejected(self, body, word):
report = self._report(body, word)
self.assertEqual([f[0] for f in report['failures']],
['verdict-word-not-in-source'])
self.assertTrue(report['failures'][0][2].startswith(word + ': '))
self.assertEqual((report['checked'], report['summaries']), (1, 1))

def test_all_bare_verdicts_remain_valid(self):
for word in check_doc_quotes.VERDICT:
with self.subTest(word=word):
self.assertEqual(self._report(word, word)['failures'], [])

def test_inflected_verdicts_share_the_explicit_family(self):
for root in ('ACCEPT', 'AMEND', 'REJECT'):
for body in (root, root + 'ED'):
for summary in (root, root + 'ED'):
with self.subTest(body=body, summary=summary):
self.assertEqual(self._report(body, summary)['failures'], [])

def test_uppercase_scope_tags_remain_valid(self):
for word in check_doc_quotes.VERDICT:
for suffix in ('_SCOPED', '_ENGINEERING_SCOPED', '_R1', '_A_23'):
with self.subTest(word=word, suffix=suffix):
self.assertEqual(self._report(word + suffix, word)['failures'], [])
self.assertEqual(self._report(word, word + suffix)['failures'], [])

def test_embedded_word_prefixes_cannot_supply_a_verdict(self):
for word in check_doc_quotes.VERDICT:
for prefix in ('UN', 'PRE', 'x'):
with self.subTest(word=word, prefix=prefix):
self._rejected(prefix + word, word)

def test_embedded_word_suffixes_cannot_supply_a_verdict(self):
for word in check_doc_quotes.VERDICT:
for suffix in ('NESS', 'LIKE', 'x'):
with self.subTest(word=word, suffix=suffix):
self._rejected(word + suffix, word)

def test_underscore_prefixes_cannot_supply_a_verdict(self):
for word in check_doc_quotes.VERDICT:
for prefix in ('NOT_', 'OTHER_', '_'):
with self.subTest(word=word, prefix=prefix):
self._rejected(prefix + word, word)

def test_unicode_word_boundaries_are_preserved(self):
for word in check_doc_quotes.VERDICT:
for body in ('α' + word, word + 'β', '9' + word):
with self.subTest(body=body):
self._rejected(body, word)

def test_malformed_scope_tags_cannot_supply_a_verdict(self):
for word in check_doc_quotes.VERDICT:
for suffix in ('_', '__SCOPED', '_scoped'):
with self.subTest(word=word, suffix=suffix):
self._rejected(word + suffix, word)

def test_nonverdict_summary_words_do_not_invent_status_claims(self):
for word in check_doc_quotes.VERDICT:
for summary in ('UN' + word, word + 'NESS', 'NOT_' + word):
with self.subTest(summary=summary):
self.assertEqual(self._report('No status supplied.', summary)['failures'], [])

def test_different_verdict_families_still_reject(self):
for body in check_doc_quotes.VERDICT:
for summary in check_doc_quotes.VERDICT:
if body != summary:
with self.subTest(body=body, summary=summary):
self._rejected(body, summary)

def test_each_claimed_family_is_checked(self):
report = self._report('ACCEPT_SCOPED', 'ACCEPT; HOLD; VERIFIED')
self.assertEqual([f[2].split(':', 1)[0] for f in report['failures']],
['HOLD', 'VERIFIED'])

def test_markdown_and_punctuation_do_not_hide_real_tokens(self):
for word in check_doc_quotes.VERDICT:
for wrapped in ('**' + word + '**', '`' + word + '`', '(' + word + ')'):
with self.subTest(wrapped=wrapped):
self.assertEqual(self._report(wrapped, wrapped)['failures'], [])

def test_quotation_matching_is_unchanged(self):
self.source.write_text('Recorded word UNVERIFIED.', encoding='utf-8')
self.doc.write_text(HEADER + '| #1 | [r](record.md) — “UNVERIFIED.” |\n',
encoding='utf-8')
report = check_doc_quotes.check_document(str(self.doc), str(self.root))
self.assertEqual(report['failures'], [])
self.assertEqual((report['quotes'], report['summaries']), (1, 0))

def test_offline_links_remain_explicitly_unchecked(self):
self.doc.write_text(HEADER + '| #1 | [r](https://github.com/o/r/pull/1'
'#issuecomment-2) — summary: VERIFIED |\n', encoding='utf-8')
report = check_doc_quotes.check_document(str(self.doc), str(self.root))
self.assertEqual((report['checked'], report['unchecked_offline']), (0, 1))
self.assertEqual(report['failures'], [])

def test_fetched_source_uses_the_same_lexical_boundary(self):
# Synthetic source transport only; not an actual remote verification.
from unittest.mock import patch
self.doc.write_text(HEADER + '| #1 | [r](https://github.com/o/r/pull/1'
'#issuecomment-2) — summary: VERIFIED |\n', encoding='utf-8')
with patch.object(check_doc_quotes.Sources, '_fetch', return_value=('UNVERIFIED', 'ok')):
report = check_doc_quotes.check_document(str(self.doc), str(self.root), github=True)
self.assertEqual(report['unchecked_offline'], 0)
self.assertEqual([f[0] for f in report['failures']], ['verdict-word-not-in-source'])

def test_cli_fails_for_a_lookalike_source(self):
import contextlib
import io
import json
self._report('UNVERIFIED', 'VERIFIED')
output = io.StringIO()
with contextlib.redirect_stdout(output):
rc = check_doc_quotes.main([str(self.doc), '--root', str(self.root)])
self.assertEqual(rc, 1)
self.assertFalse(json.loads(output.getvalue())['passed'])

def test_compact_decimal_record_references_remain_valid(self):
for word in check_doc_quotes.VERDICT:
for number in ('1', '5344158101', '5344767944'):
for suffix in ('', '_SCOPED'):
with self.subTest(word=word, number=number, suffix=suffix):
compact = word + number + suffix
self.assertEqual(self._report(compact, word)['failures'], [])
self.assertEqual(self._report(word, compact)['failures'], [])

def test_actual_compact_amend_review_openings(self):
for body, summary in (
('Catalog AMEND5344158101 is addressed at exact head9baa6609.',
'Catalog AMEND 5344158101 is addressed at exact head 9baa6609'),
('AMEND5344767944 addressed at exact head077e3d5.',
'AMEND 5344767944 addressed at exact head 077e3d5'),
):
with self.subTest(body=body):
self.assertEqual(self._report(body, summary)['failures'], [])

def test_compact_reference_inflections_share_the_family(self):
for word in ('ACCEPT', 'AMEND', 'REJECT'):
with self.subTest(word=word):
self.assertEqual(self._report(word + 'ED123', word)['failures'], [])
self.assertEqual(self._report(word, word + 'ED123')['failures'], [])

def test_reference_suffix_does_not_allow_word_lookalikes(self):
for word in check_doc_quotes.VERDICT:
for body in ('UN' + word + '123', 'NOT_' + word + '123',
word + '123abc', word + '123β', word + '123'):
with self.subTest(body=body):
self._rejected(body, word)

def test_negation_and_scope_entailment_are_not_claimed(self):
# This comparator checks vocabulary presence only, not what it means.
self.assertEqual(self._report('not VERIFIED', 'VERIFIED')['failures'], [])
self.assertEqual(self._report('AMEND_ENGINEERING_SCOPED', 'AMEND')['failures'], [])


if __name__ == '__main__':
unittest.main()
20 changes: 16 additions & 4 deletions tools/check_doc_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
* a GitHub pull-request review / comment link is checked against the live body only with
``--github`` (token read from ``GITHUB_TOKEN``); without it the passage is reported as
``unchecked_offline`` and does not fail the check;
* a ``summary:`` passage must not use a verdict word (ACCEPT, AMEND, HOLD, CONFIRMED,
VERIFIED, REJECT) that the linked record does not use, under the same offline rule;
* a ``summary:`` passage must not use a verdict family (ACCEPT/ACCEPTED,
AMEND/AMENDED, HOLD, CONFIRMED, VERIFIED, REJECT/REJECTED) absent from the linked
record, under the same offline rule. Complete tokens may carry a decimal record
reference (AMEND123) and uppercase alphanumeric underscore-delimited scope tags;
embedded lookalikes such as UNVERIFIED do not
supply VERIFIED. This is lexical presence, not negation or scope entailment;
* a passage with no preceding link in its segment fails (``no-source-link``).

Header and separator rows are skipped. Exit status 0 only when no checked passage fails;
Expand All @@ -36,6 +40,14 @@
QUOTE = re.compile(r'“([^”]*)”')
SUMMARY = re.compile(r'(?<![\w-])summary:\s*')
VERDICT = ('ACCEPT', 'AMEND', 'HOLD', 'CONFIRMED', 'VERIFIED', 'REJECT')
# Use the same closed lexical policy on both sides of a summary comparison.
# Python's Unicode word boundary also excludes embedded underscores and letters.
VERDICT_PATTERNS = {
word: re.compile(r'(?<!\w)' + word
+ (r'(?:ED)?' if word in ('ACCEPT', 'AMEND', 'REJECT') else '')
+ r'(?:[0-9]+)?(?:_[A-Z0-9]+)*(?!\w)')
for word in VERDICT
}
GITHUB = re.compile(r'https://github\.com/([^/]+)/([^/]+)/pull/(\d+)#(issuecomment|pullrequestreview|discussion_r)-?(\d+)')


Expand Down Expand Up @@ -176,8 +188,8 @@ def check_document(doc_path, root, github=False, token=None):
if not pieces_in_order(passage, body):
report['failures'].append(['not-verbatim', source, prefix])
else:
for word in VERDICT:
if re.search(r'\b' + word, passage) and word not in body:
for word, pattern in VERDICT_PATTERNS.items():
if pattern.search(passage) and not pattern.search(body):
report['failures'].append(['verdict-word-not-in-source', source, word + ': ' + prefix])
return report

Expand Down
Loading