1 Commits
Author SHA1 Message Date
mdaleo404 62d505a17e Slim CI to a single PR job and fix environment-dependent tests
- Consolidate workflow into one job (Python 3.11/3.13), drop pip-audit,
  package build, and best-effort integration tests
- Make helper-ownership tests root-safe and the timefmt zone test
  deterministic across host timezones
2026-09-25 11:43:55 +01:00
3 changed files with 38 additions and 57 deletions
+12 -56
View File
@@ -1,73 +1,29 @@
name: CI
on:
push:
pull_request:
jobs:
precommit-and-security:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install pre-commit
run: pip install pre-commit
- name: Run pre-commit hooks
run: pre-commit run --all-files --color always
- name: Install Poetry and export plugin
run: |
pip install poetry
poetry self add poetry-plugin-export
- name: Install pip-audit
run: pip install pip-audit
- name: Audit dev dependencies (Poetry lockfile)
run: |
poetry export -f requirements.txt --without-hashes --with dev \
| pip-audit -r /dev/stdin
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install mypy
- run: mypy
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e . pytest
- run: pytest tests/unit tests/security
- name: Integration tests (best effort)
continue-on-error: true
run: pytest tests/integration
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install build
- run: python -m build
- name: Install project and tooling
run: pip install -e . pytest mypy pre-commit
- name: Lint and typecheck
if: matrix.python-version == '3.13'
run: |
pre-commit run --all-files --color always
mypy
- name: Unit and security tests
run: pytest tests/unit tests/security
+14
View File
@@ -20,6 +20,18 @@ from schedls.security import (
validate_name,
)
_UNTRUSTED_UID = 65534
def _relinquish_ownership(path) -> None:
"""Own a fixture by a non-root user when the suite itself runs as root.
The helper checks distinguish root-owned files from user-owned ones, so a
root test process must make its "untrusted" fixtures owned by someone else.
"""
if os.geteuid() == 0:
os.chown(path, _UNTRUSTED_UID, _UNTRUSTED_UID)
@pytest.mark.parametrize(
"name",
@@ -114,6 +126,7 @@ def test_resolve_helper_rejects_user_owned_dir_when_root(tmp_path, monkeypatch)
helper = helper_dir / "evilhelper"
helper.write_text("#!/bin/sh\ntrue\n")
helper.chmod(0o755)
_relinquish_ownership(helper_dir)
monkeypatch.setenv("PATH", str(helper_dir))
monkeypatch.setattr(os, "geteuid", lambda: 0)
assert resolve_helper("evilhelper") is None
@@ -134,6 +147,7 @@ def test_runner_refuses_untrusted_absolute_helper(tmp_path, monkeypatch) -> None
helper = tmp_path / "evil"
helper.write_text("#!/bin/sh\ntrue\n")
helper.chmod(0o755)
_relinquish_ownership(helper)
monkeypatch.setattr(os, "geteuid", lambda: 0)
with pytest.raises(SafetyRefusalError):
CommandRunner().run([str(helper)])
+12 -1
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import time
from datetime import UTC, datetime, timedelta, timezone
import pytest
@@ -7,6 +8,15 @@ import pytest
from schedls import timefmt
@pytest.fixture
def london_tz(monkeypatch):
monkeypatch.setenv("TZ", "Europe/London")
time.tzset()
yield
monkeypatch.undo()
time.tzset()
@pytest.mark.parametrize(
("value", "seconds"),
[
@@ -50,10 +60,11 @@ def test_parse_placeholders() -> None:
assert timefmt.parse_systemd_timestamp("0") is None
def test_format_datetime_round_trip_zone() -> None:
def test_format_datetime_round_trip_zone(london_tz) -> None:
dt = datetime(2026, 9, 25, 2, 0, 0, tzinfo=timezone(timedelta(hours=1)))
text = timefmt.format_datetime(dt)
assert text.startswith("Fri 25 Sep 2026 02:00:00")
assert text.endswith("BST")
def test_format_short_relative() -> None: