diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..18813d3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + name: lint + unit tests (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Floor is 3.13 (the code uses types.CapsuleType + PEP 701 f-strings). + python-version: ["3.13", "3.14"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install fusil + tooling + run: | + python -m pip install --upgrade pip + pip install -e . # pulls python-ptrace (a hard runtime dependency) + pip install ruff==0.15.18 # pin so CI matches local lint results + + - name: Ruff (lint) + run: ruff check fusil/ tests/ fuzzers/fusil-python-threaded + + - name: Ruff (format check) + run: ruff format --check fusil/ tests/ fuzzers/fusil-python-threaded + + - name: Unit tests + # numpy/h5py are optional; without them the relevant tests skip gracefully. + run: python -m unittest discover -s tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1439242 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# Contributing to fusil + +Active development targets **only the Python fuzzer** (`fusil-python-threaded`, +`fusil.python` / `fusil.python.jit`). The legacy fuzzers and non-Python subsystems under +`*/notworking/` are out of scope. Start with `doc/python-fuzzer.md` for an architecture +overview and `CLAUDE.md` for repository orientation. + +## Requirements + +- **Python 3.13+** (the code uses `types.CapsuleType` and PEP 701 f-strings). +- **`python-ptrace`** — a hard runtime dependency (`fusil.application` imports it at module + load), so the fuzzer can't even start without it. + +## Dev setup + +```bash +python3.13 -m venv .venv && . .venv/bin/activate +pip install -e . # installs fusil + python-ptrace; adds the + # `fusil-python-threaded` console script +pip install -e '.[numpy,h5py]' # optional: enable the numpy/h5py argument generators +pip install ruff # linter/formatter (CI pins ruff==0.15.18) +``` + +A real fuzzing run drops the fuzzed child to a dedicated unprivileged `fusil` user; for quick +local runs pass `--unsafe` (runs children as you). **Never** point `--filenames` at files you +care about — fuzzed calls may overwrite them. + +## Tests, lint, format + +```bash +python -m unittest discover -s tests # the suite (unittest, NOT pytest) +ruff check fusil/ tests/ fuzzers/fusil-python-threaded +ruff format fusil/ tests/ fuzzers/fusil-python-threaded +``` + +numpy/h5py-dependent tests skip gracefully when those packages aren't installed. CI +(`.github/workflows/ci.yml`) runs ruff (check + format) and the unittest suite on Python 3.13 +and 3.14. + +### Writing tests + +Prefer **runtime-free** unit tests (no real fuzzing child, no ptrace). Good models: +`tests/test_oom_dedup.py`, `tests/test_process_limits.py`, `tests/test_mas.py`. For tests that +construct `WritePythonCode`, use `tests/python/_test_options.py:make_test_options()` — it +harvests the real option defaults so tests don't rot when new options are added. Seed `random` +in `setUp` for any generator that picks values at random. + +## Workflow + +- Branch off `main`; keep the test suite green at every commit. +- One concern per pull request. Run `ruff check`, `ruff format`, and the suite before pushing. +- `ruff format` runs as an isolated commit; bulk-reformat commits are listed in + `.git-blame-ignore-revs` (enable locally with + `git config blame.ignoreRevsFile .git-blame-ignore-revs`). +- Code generators (`write_python_code.py`, `jit/`, `h5py/`) build target source as strings and + carry scoped lint ignores in `pyproject.toml`; match the surrounding style. diff --git a/tests/python/test_blacklists.py b/tests/python/test_blacklists.py index b828c03..541cba0 100644 --- a/tests/python/test_blacklists.py +++ b/tests/python/test_blacklists.py @@ -36,8 +36,9 @@ class TestKnownEntriesPresent(unittest.TestCase): """Pin a few high-value entries so accidental deletion is caught.""" def test_sys_trace_hooks_blacklisted(self): - self.assertEqual(bl.BLACKLIST["sys"] & {"settrace", "setprofile"}, - {"settrace", "setprofile"}) + self.assertEqual( + bl.BLACKLIST["sys"] & {"settrace", "setprofile"}, {"settrace", "setprofile"} + ) def test_resource_setrlimit_blacklisted(self): self.assertIn("setrlimit", bl.BLACKLIST["resource"]) diff --git a/tests/test_mas.py b/tests/test_mas.py index 79adb66..72e1c8c 100644 --- a/tests/test_mas.py +++ b/tests/test_mas.py @@ -15,10 +15,17 @@ class _StubLogger: - def debug(self, *a, **k): pass - def info(self, *a, **k): pass - def warning(self, *a, **k): pass - def error(self, *a, **k): pass + def debug(self, *a, **k): + pass + + def info(self, *a, **k): + pass + + def warning(self, *a, **k): + pass + + def error(self, *a, **k): + pass class _StubApp: @@ -27,9 +34,11 @@ class _StubApp: def __init__(self): self.logger = _StubLogger() - def registerAgent(self, agent): pass + def registerAgent(self, agent): + pass - def unregisterAgent(self, agent, destroy=True): pass + def unregisterAgent(self, agent, destroy=True): + pass def _make_mta(): diff --git a/graph.sh b/tools/graph.sh similarity index 100% rename from graph.sh rename to tools/graph.sh diff --git a/lsall.sh b/tools/notworking/lsall.sh similarity index 100% rename from lsall.sh rename to tools/notworking/lsall.sh