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
4 changes: 4 additions & 0 deletions .github/workflows/python_runtime_contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ on:
# change parses on the oldest supported Python. Lint runs on one, newer
# version and cannot see this class of break.
- 'scripts/**'
# The entry points are covered by the same suite and are equally able to
# break on an older Python
- 'vleapp.py'
- 'vleappGUI.py'

jobs:
runtime-contract:
Expand Down
49 changes: 49 additions & 0 deletions admin/test/scripts/test_entry_points.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""The command line and GUI entry points must load on every supported Python.

The artifact import test covers scripts/, but the entry points themselves sat
outside any version-matrixed check, so a change to either could break on the
oldest supported Python and only surface when a user ran it.

The two are checked differently on purpose. vleapp.py guards its startup with
'if __name__', so it can be imported outright, which catches import-time errors
as well as syntax. vleappGUI.py builds its window at module level, so importing
it would need a display that CI does not have; compiling it still catches the
syntax-level breakage this is chiefly guarding against, such as an f-string
that only parses on Python 3.12 (PEP 701).
"""
import importlib.util
import pathlib
import sys
import unittest

REPO_ROOT = pathlib.Path(__file__).resolve().parents[3]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))

CLI_ENTRY_POINT = REPO_ROOT / 'vleapp.py'
GUI_ENTRY_POINT = REPO_ROOT / 'vleappGUI.py'


class TestEntryPoints(unittest.TestCase):

def test_cli_entry_point_imports(self):
"""vleapp.py must import, and expose main()."""
self.assertTrue(CLI_ENTRY_POINT.is_file(), f'{CLI_ENTRY_POINT} is missing')
spec = importlib.util.spec_from_file_location('entry_point_check', CLI_ENTRY_POINT)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
self.assertTrue(hasattr(module, 'main'), 'vleapp.py no longer exposes main()')

def test_gui_entry_point_compiles(self):
"""vleappGUI.py must at least compile; it cannot be imported without a display."""
self.assertTrue(GUI_ENTRY_POINT.is_file(), f'{GUI_ENTRY_POINT} is missing')
source = GUI_ENTRY_POINT.read_text(encoding='utf-8', errors='replace')
try:
compile(source, str(GUI_ENTRY_POINT), 'exec')
except SyntaxError as error:
self.fail(f'vleappGUI.py does not compile on this Python: '
f'line {error.lineno}: {error.msg}')


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