91 lines
4.0 KiB
Python
91 lines
4.0 KiB
Python
import importlib.util
|
|
import tracemalloc
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
_HELPER = Path(__file__).with_name("revision_state_policy.py")
|
|
_SPEC = importlib.util.spec_from_file_location("revision_state_policy", _HELPER)
|
|
assert _SPEC is not None and _SPEC.loader is not None
|
|
_MODULE = importlib.util.module_from_spec(_SPEC)
|
|
_SPEC.loader.exec_module(_MODULE)
|
|
analyze_batch = _MODULE.analyze_batch
|
|
has_revision_state = _MODULE.has_revision_state
|
|
|
|
|
|
class RevisionStatePolicyTests(unittest.TestCase):
|
|
def test_ast_access_and_f_strings(self):
|
|
for source in (
|
|
"old = revision.state",
|
|
'old = workspaceRevision["state"]',
|
|
'old = record["selectedWorkspace"].state',
|
|
'old = f"{revision.state}"',
|
|
'old = revision["st" + "ate"]',
|
|
'old = record["revi" + "sion"].state',
|
|
'old = revision[f"state"]',
|
|
'old = record[f"revision"].state',
|
|
"old = revision[f\"st{'a'}te\"]",
|
|
"old = revision[f\"{'state'}\"]",
|
|
"old = record[f\"revi{'sion'}\"].state",
|
|
"old = revision[f\"{'st' + 'ate'}\"]",
|
|
"old = record[f\"{'revi' + 'sion'}\"].state",
|
|
"old = revision[f\"{'state':s}\"]",
|
|
):
|
|
with self.subTest(source=source):
|
|
self.assertTrue(has_revision_state(source))
|
|
|
|
def test_static_format_bindings(self):
|
|
for source in (
|
|
'"{0.state}".format(revision)',
|
|
'"{0[state]}".format(workspaceRevision)',
|
|
'"{item.state}".format(item=selectedWorkspace)',
|
|
'"{item[state]}".format_map({"item": revision})',
|
|
'("{0.state}").format(revision)',
|
|
'"{0:{1.state}}".format(value, revision)',
|
|
'"{0.state}".format(*[revision])',
|
|
'"{0[state]}".format(*(revision,))',
|
|
'"{1[state]}".format(*[other, workspaceRevision])',
|
|
'"{item.state}".format(**{"item": selectedWorkspace})',
|
|
'"{item[state]}".format(**{"outer": other, **{"item": revision}})',
|
|
'"{item.state}".format_map({**{"item": workspaceRevision}})',
|
|
'"{.state}".format(revision)',
|
|
'"{[state]}".format(revision)',
|
|
'"{:{.state}}".format(value, revision)',
|
|
'"{.name} {[state]}".format(other, revision)',
|
|
'"{item.state}".format(item=revision, **values)',
|
|
):
|
|
with self.subTest(source=source):
|
|
self.assertTrue(has_revision_state(source))
|
|
self.assertFalse(has_revision_state('"{0.state}".format(other)'))
|
|
# Dynamic unpacking is intentionally unresolved rather than guessed.
|
|
self.assertFalse(has_revision_state('"{0.state}".format(*values)'))
|
|
self.assertFalse(has_revision_state('"{.name} {[state]}".format(other, other)'))
|
|
self.assertFalse(has_revision_state('"{item.state}".format(**values)'))
|
|
# FormattedValue keys are dynamic and are not treated as static strings.
|
|
self.assertFalse(has_revision_state('revision[f"st{suffix}"]'))
|
|
|
|
def test_literals_are_not_active(self):
|
|
self.assertFalse(has_revision_state('text = "{revision.state}"'))
|
|
self.assertFalse(has_revision_state('text = "{{revision.state}}".format(value)'))
|
|
|
|
def test_oversized_static_format_fails_before_formatting(self):
|
|
tracemalloc.start()
|
|
with patch("builtins.format") as format_mock:
|
|
with self.assertRaisesRegex(ValueError, "width or precision"):
|
|
has_revision_state('revision[f"{1:.1000000000f}"]')
|
|
format_mock.assert_not_called()
|
|
_current, peak = tracemalloc.get_traced_memory()
|
|
tracemalloc.stop()
|
|
self.assertLess(peak, 1_000_000)
|
|
self.assertFalse(has_revision_state('revision[f"{1:04d}"]'))
|
|
|
|
def test_batch_contract(self):
|
|
self.assertEqual(
|
|
analyze_batch([{"label": "one.py", "source": "revision.state"}]),
|
|
["one.py"],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|