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
46 changes: 33 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
matrix:
# 3.9 is the floor the README promises; macOS is half the fleet.
os: [ubuntu-latest, macos-latest]
python: ['3.9', '3.13']
python: ['3.9', '3.13', '3.14']
include:
- os: windows-latest
python: '3.13'
Expand Down Expand Up @@ -92,13 +92,23 @@ jobs:
- run: python -m unittest discover -s tests/docs -t . -v

package:
name: wheel and sdist
runs-on: ubuntu-latest
name: wheel and sdist (${{ matrix.os }}, python ${{ matrix.python }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
python: '3.13'
- os: ubuntu-latest
python: '3.14'
- os: macos-latest
python: '3.14'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
python-version: ${{ matrix.python }}
- run: python -m pip install --requirement .github/requirements/build.txt
- run: python tools/generate_session_peer.py --check
- run: python -m build
Expand All @@ -107,14 +117,23 @@ jobs:
SESSION_PEER_DIST_DIR: dist
run: python -m unittest discover -s tests/distribution -t . -v
- name: Install wheel and sdist independently
shell: bash
run: |
python -m venv /tmp/session-peer-wheel
/tmp/session-peer-wheel/bin/pip install dist/*.whl
/tmp/session-peer-wheel/bin/session-peer --version
/tmp/session-peer-wheel/bin/session-peer update --json
python -m venv /tmp/session-peer-sdist
/tmp/session-peer-sdist/bin/pip install dist/*.tar.gz
/tmp/session-peer-sdist/bin/session-peer --version
expected=$(python -c 'import session_peer; print(session_peer.__version__)')
for kind in wheel sdist; do
venv="$RUNNER_TEMP/session-peer-package-$kind"
python -m venv "$venv"
if test "$kind" = wheel; then
"$venv/bin/python" -m pip install dist/*.whl
else
"$venv/bin/python" -m pip install dist/*.tar.gz
fi
test "$("$venv/bin/python" -I -c 'import pathlib, session_peer, sysconfig; pathlib.Path(session_peer.__file__).resolve().relative_to(pathlib.Path(sysconfig.get_path("purelib")).resolve()); print(session_peer.__version__)')" = "$expected"
test "$("$venv/bin/session-peer" --version)" = "session-peer $expected"
"$venv/bin/session-peer" --help
"$venv/bin/python" -m pip check
done
"$RUNNER_TEMP/session-peer-package-wheel/bin/session-peer" update --json

shell:
name: shellcheck install.sh
Expand Down Expand Up @@ -169,7 +188,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python: ['3.10', '3.13']
python: ['3.10', '3.13', '3.14']
profile: [minimum, latest]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -251,9 +270,10 @@ jobs:
name: optional relay (${{ matrix.os }}, python ${{ matrix.python }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python: ['3.11', '3.13']
python: ['3.11', '3.13', '3.14']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development",
"Topic :: System :: Networking",
"Topic :: Utilities",
Expand Down
18 changes: 17 additions & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,29 @@ control integration suite additionally requires `npm ci` in `control/` and
`SESSION_PEER_CONTROL_INTEGRATION=1`; without that explicit opt-in, the live
Node/Python fixture is skipped.

The MCP CI matrix tests Python 3.10/3.13 on macOS/Linux with both the exact SDK
The CI matrix includes standard CPython 3.9/3.13/3.14 core fixture cells on
macOS/Linux and retains Windows CPython 3.13. The 3.14 core cells run the
generated-standalone, core, adapter, Codex and distribution-source checks.
Separate macOS/Linux
3.14 package cells build wheel/sdist, verify their full contents and install
each independently in fresh environments, checking the installed import origin,
exact CLI version, help and dependency consistency. The existing Linux 3.13
package cell is retained.

The MCP CI matrix tests Python 3.10/3.13/3.14 on macOS/Linux with both the exact SDK
floor (`.github/requirements/mcp-minimum.txt`) and latest compatible 1.x.
Transitive dependencies resolve normally, not at their individual minima.
Both runtime graphs (including pip/setuptools bootstrap packages) are audited
separately from the pinned build/audit requirements and retained
as CI artifacts. Existing latest-resolution and release audits remain required.

Relay fixtures also run on macOS/Linux with Python 3.11/3.13/3.14. All core,
package, MCP and Relay cells are required by the existing release gate. These
are fixture/package checks, not new live native-agent, SSH, Windows 3.14 or
free-threaded Python acceptance. Python floors remain core 3.9, MCP 3.10 and
Relay 3.11. CI outcomes must be recorded at the exact reviewed source before
expanding supported-version metadata.

After building exactly one wheel and one sdist, validate the full distribution
contract with:

Expand Down
2 changes: 1 addition & 1 deletion tests/docs/test_mcp_dependency_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_floor_matrix_and_release_gate_preserve_supported_contract(self):
self.assertIn('mcp>=1.30,<2; python_version >= \'3.10\'', manifest)
constraint = (ROOT / '.github/requirements/mcp-minimum.txt').read_text()
self.assertEqual(['mcp==1.30.0'], [x for x in constraint.splitlines() if x and not x.startswith('#')])
for text in ("os: [ubuntu-latest, macos-latest]", "python: ['3.10', '3.13']",
for text in ("os: [ubuntu-latest, macos-latest]", "python: ['3.10', '3.13', '3.14']",
'profile: [minimum, latest]', 'fail-fast: false'):
self.assertIn(text, MCP)
install = run_script(MCP, 'Install MCP runtime profile')
Expand Down
124 changes: 124 additions & 0 deletions tests/docs/test_python_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Keep interpreter/profile coverage and installed-artifact probes release-gated."""

from itertools import product
import os
from pathlib import Path
import re
import subprocess
import sys
import tempfile
import unittest
import venv

from tests.docs.test_dependency_audit_workflow import run_script


ROOT = Path(__file__).resolve().parents[2]
CI = (ROOT / '.github/workflows/ci.yml').read_text(encoding='utf-8')


def job(name):
start = re.search(r'^ ' + re.escape(name) + r':\n', CI, re.M)
if start is None:
raise AssertionError('Missing CI job: ' + name)
remainder = CI[start.end():]
next_job = re.search(r'^ [a-z][a-z0-9-]*:\n', remainder, re.M)
return remainder[:next_job.start()] if next_job else remainder


def cells(name):
# This is the small matrix subset used by this workflow, not a YAML parser.
# Accept only simple literal scalar data, never evaluate YAML expressions.
text = job(name).split(' steps:\n', 1)[0]
axes = []
for key, values in re.findall(r'^ ([a-z]+): (\[[^\n]+\])$', text, re.M):
scalars = []
for value in values[1:-1].split(','):
value = value.strip()
if not re.fullmatch(r"(?:'[A-Za-z0-9_.-]+'|\"[A-Za-z0-9_.-]+\"|[A-Za-z0-9_.-]+)", value):
raise AssertionError('Unsupported matrix literal')
scalars.append(value.strip("'\""))
axes.append((key, scalars))
result = {tuple(zip((key for key, _ in axes), values))
for values in product(*(values for _, values in axes))} if axes else set()
includes = re.findall(r'^ - os: ([^\n]+)\n python: \'([^\']+)\'$', text, re.M)
result.update((('os', os_name), ('python', python)) for os_name, python in includes)
return [dict(cell) for cell in result]


class PythonMatrix(unittest.TestCase):
def test_core_floors_new_posix_interpreter_and_windows_are_retained(self):
actual = {(x['os'], x['python']) for x in cells('test')}
expected = set(product(('ubuntu-latest', 'macos-latest'), ('3.9', '3.13', '3.14')))
expected.add(('windows-latest', '3.13'))
self.assertEqual(expected, actual)
for suite in ('core', 'adapters', 'codex', 'distribution'):
self.assertIn('unittest discover -s tests/' + suite, job('test'))
self.assertIn('tools/generate_session_peer.py --check', job('test'))

def test_optional_profiles_keep_their_minimums_and_all_release_gates(self):
mcp = {(x['os'], x['python'], x['profile']) for x in cells('mcp')}
self.assertEqual(set(product(('ubuntu-latest', 'macos-latest'),
('3.10', '3.13', '3.14'), ('minimum', 'latest'))), mcp)
relay = {(x['os'], x['python']) for x in cells('relay')}
self.assertEqual(set(product(('ubuntu-latest', 'macos-latest'), ('3.11', '3.13', '3.14'))), relay)
self.assertIn('--constraint .github/requirements/mcp-minimum.txt', job('mcp'))
self.assertIn('Audit resolved MCP runtime graph', job('mcp'))
self.assertIn('unittest discover -s tests/mcp_integration', job('mcp'))
self.assertIn('unittest discover -s tests/relay', job('relay'))
gate = job('release-gate')
for name in ('test', 'package', 'mcp', 'relay'):
self.assertRegex(gate, r'needs: \[[^\]]*\b' + name + r'\b')
self.assertIn('needs.' + name + '.result', gate)
for name in ('test', 'package', 'mcp', 'relay'):
self.assertIn('fail-fast: false', job(name))
self.assertNotIn('continue-on-error', job(name))

def test_package_cells_build_and_install_both_artifacts_on_314(self):
self.assertEqual({('ubuntu-latest', '3.13'), ('ubuntu-latest', '3.14'), ('macos-latest', '3.14')},
{(x['os'], x['python']) for x in cells('package')})
package = job('package')
self.assertIn('python-version: ${{ matrix.python }}', package)
self.assertIn('tools/generate_session_peer.py --check', package)
self.assertIn('python -m build', package)
self.assertIn('SESSION_PEER_DIST_DIR: dist', package)
script = run_script(package, 'Install wheel and sdist independently')
self.assertIn('for kind in wheel sdist', script)
self.assertIn('"$RUNNER_TEMP/session-peer-package-$kind"', script)
for marker in ('pip install dist/*.whl', 'pip install dist/*.tar.gz', '--help', 'pip check'):
self.assertIn(marker, script)

@unittest.skipIf(os.name == 'nt', 'package cells use POSIX shell')
def test_actual_package_version_probe_rejects_checkout_shadowing(self):
script = run_script(job('package'), 'Install wheel and sdist independently')
probe = next(line for line in script.splitlines() if '-I -c ' in line)
expected = re.search(r'^__version__ = "([^"]+)"$',
(ROOT / 'session_peer.py').read_text(), re.M).group(1)
with tempfile.TemporaryDirectory(prefix='codex-python-package-') as directory:
env_dir = Path(directory) / 'venv'
venv.EnvBuilder(with_pip=False, symlinks=True).create(env_dir)
python = str(env_dir / 'bin/python')
purelib = Path(subprocess.check_output(
[python, '-I', '-c', 'import sysconfig; print(sysconfig.get_path("purelib"))'],
text=True).strip())
installed = purelib / 'session_peer.py'
for index, mode in enumerate(('wrong-version', 'installed-version', 'outside-purelib')):
with self.subTest(mode=mode):
version = expected if mode != 'wrong-version' else '0.0.0'
installed.write_text('__version__ = ' + repr(version) + '\n' +
('__file__ = ' + repr(str(ROOT / 'session_peer.py')) + '\n'
if mode == 'outside-purelib' else ''))
# Equal-sized fixture versions written within one second
# must not reuse the preceding subprocess's timestamp pyc.
stamp = 1690000000 + index * 2
os.utime(installed, (stamp, stamp))
result = subprocess.run(
['bash', '--noprofile', '--norc', '-e', '-o', 'pipefail', '-c', probe],
cwd=ROOT, env=dict(os.environ, venv=str(env_dir), expected=expected,
PYTHONPATH=str(ROOT)), capture_output=True, text=True)
self.assertEqual(mode == 'installed-version', result.returncode == 0,
'Package import/version boundary mismatch')


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