From 5236e5f953ab60a367d0fd4f572c278edc3a192a Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Wed, 27 May 2026 19:18:21 -0700 Subject: [PATCH 1/2] tests: smoke-test all Go + Python examples for compile/syntax regressions Add a placeholder TestExampleCompiles in each Go example dir so `go test ./...` exercises the type-check + link path per example. This catches upstream pkg/protocol or SDK API breakage that plain `go build ./...` already catches today, but routes it through the test runner so future test-only CI gates also exercise the examples. Add tests/test_examples_syntax.py at the python_sdk level that parameterizes over every top-level *.py demo and asserts two properties: the file parses as valid Python and it gates side effects on `if __name__ == "__main__":`. Validates the demos remain import-safe so downstream tooling can introspect them without firing real daemon calls. Wire both into ci.yml: `go test ./...` in the go-examples job and `pytest tests/` in the python-examples job. --- .github/workflows/ci.yml | 11 +++ go/client/main_test.go | 13 ++++ go/echo/main_test.go | 13 ++++ go/httpclient/main_test.go | 13 ++++ go/secure/main_test.go | 13 ++++ go/webserver/main_test.go | 13 ++++ python_sdk/tests/__init__.py | 0 python_sdk/tests/test_examples_syntax.py | 93 ++++++++++++++++++++++++ 8 files changed, 169 insertions(+) create mode 100644 go/client/main_test.go create mode 100644 go/echo/main_test.go create mode 100644 go/httpclient/main_test.go create mode 100644 go/secure/main_test.go create mode 100644 go/webserver/main_test.go create mode 100644 python_sdk/tests/__init__.py create mode 100644 python_sdk/tests/test_examples_syntax.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45a2d96..3ef3dbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,10 @@ jobs: working-directory: examples/go run: go build ./... + - name: Test Go examples (compile smoke) + working-directory: examples/go + run: go test ./... + python-examples: runs-on: ubuntu-latest steps: @@ -49,6 +53,13 @@ jobs: done echo "all Python examples parse" + - name: Install pytest + run: python -m pip install --upgrade pip pytest + + - name: Run Python example smoke tests + working-directory: python_sdk + run: python -m pytest tests/ -v + shell-examples: runs-on: ubuntu-latest steps: diff --git a/go/client/main_test.go b/go/client/main_test.go new file mode 100644 index 0000000..bd9d12c --- /dev/null +++ b/go/client/main_test.go @@ -0,0 +1,13 @@ +package main + +import "testing" + +// TestExampleCompiles is a placeholder so `go test ./...` exercises the +// compile + link path for this example. The example itself is a main +// package; running its main() requires a live daemon, so we only assert +// that it builds cleanly. Compile = type-check + link, which catches +// upstream pkg/protocol or SDK API breakage as web4 evolves. +func TestExampleCompiles(t *testing.T) { + // Build success is implicit: the test binary cannot link if main.go + // references a missing or renamed symbol in github.com/TeoSlayer/pilotprotocol. +} diff --git a/go/echo/main_test.go b/go/echo/main_test.go new file mode 100644 index 0000000..bd9d12c --- /dev/null +++ b/go/echo/main_test.go @@ -0,0 +1,13 @@ +package main + +import "testing" + +// TestExampleCompiles is a placeholder so `go test ./...` exercises the +// compile + link path for this example. The example itself is a main +// package; running its main() requires a live daemon, so we only assert +// that it builds cleanly. Compile = type-check + link, which catches +// upstream pkg/protocol or SDK API breakage as web4 evolves. +func TestExampleCompiles(t *testing.T) { + // Build success is implicit: the test binary cannot link if main.go + // references a missing or renamed symbol in github.com/TeoSlayer/pilotprotocol. +} diff --git a/go/httpclient/main_test.go b/go/httpclient/main_test.go new file mode 100644 index 0000000..bd9d12c --- /dev/null +++ b/go/httpclient/main_test.go @@ -0,0 +1,13 @@ +package main + +import "testing" + +// TestExampleCompiles is a placeholder so `go test ./...` exercises the +// compile + link path for this example. The example itself is a main +// package; running its main() requires a live daemon, so we only assert +// that it builds cleanly. Compile = type-check + link, which catches +// upstream pkg/protocol or SDK API breakage as web4 evolves. +func TestExampleCompiles(t *testing.T) { + // Build success is implicit: the test binary cannot link if main.go + // references a missing or renamed symbol in github.com/TeoSlayer/pilotprotocol. +} diff --git a/go/secure/main_test.go b/go/secure/main_test.go new file mode 100644 index 0000000..bd9d12c --- /dev/null +++ b/go/secure/main_test.go @@ -0,0 +1,13 @@ +package main + +import "testing" + +// TestExampleCompiles is a placeholder so `go test ./...` exercises the +// compile + link path for this example. The example itself is a main +// package; running its main() requires a live daemon, so we only assert +// that it builds cleanly. Compile = type-check + link, which catches +// upstream pkg/protocol or SDK API breakage as web4 evolves. +func TestExampleCompiles(t *testing.T) { + // Build success is implicit: the test binary cannot link if main.go + // references a missing or renamed symbol in github.com/TeoSlayer/pilotprotocol. +} diff --git a/go/webserver/main_test.go b/go/webserver/main_test.go new file mode 100644 index 0000000..bd9d12c --- /dev/null +++ b/go/webserver/main_test.go @@ -0,0 +1,13 @@ +package main + +import "testing" + +// TestExampleCompiles is a placeholder so `go test ./...` exercises the +// compile + link path for this example. The example itself is a main +// package; running its main() requires a live daemon, so we only assert +// that it builds cleanly. Compile = type-check + link, which catches +// upstream pkg/protocol or SDK API breakage as web4 evolves. +func TestExampleCompiles(t *testing.T) { + // Build success is implicit: the test binary cannot link if main.go + // references a missing or renamed symbol in github.com/TeoSlayer/pilotprotocol. +} diff --git a/python_sdk/tests/__init__.py b/python_sdk/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_sdk/tests/test_examples_syntax.py b/python_sdk/tests/test_examples_syntax.py new file mode 100644 index 0000000..e74ff7d --- /dev/null +++ b/python_sdk/tests/test_examples_syntax.py @@ -0,0 +1,93 @@ +"""Smoke tests for the Python SDK example scripts. + +Goals +----- +Catch regressions in the user-facing demos as the Pilot Protocol Python +SDK evolves. The demos themselves require a live daemon and trusted +peers to execute, so we cannot import them and run them under pytest. +Instead, we validate two cheap properties per demo: + +1. The file is syntactically valid Python (``ast.parse``). +2. The demo gates its side-effect entry point on + ``if __name__ == "__main__":`` so that future ``importlib``-based + loaders can introspect it without firing real network calls. + +Adding a new demo? Drop it next to ``basic_usage.py`` and it is picked +up automatically by ``iter_demo_files``. +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import pytest + + +EXAMPLES_DIR = Path(__file__).resolve().parent.parent + + +def iter_demo_files() -> list[Path]: + """Return every top-level ``*.py`` file in ``python_sdk/``. + + Tests under ``python_sdk/tests/`` are intentionally excluded so the + suite never tries to syntax-check itself. + """ + return sorted(p for p in EXAMPLES_DIR.glob("*.py") if p.is_file()) + + +DEMOS = iter_demo_files() + + +def test_demo_directory_is_non_empty() -> None: + """Guard against the glob silently matching zero files. + + Without this, a future refactor that moves the demos to a sub-package + would turn every parameterized test below into a no-op and the suite + would still pass green. + """ + assert DEMOS, f"no demos found in {EXAMPLES_DIR}" + + +@pytest.mark.parametrize("demo", DEMOS, ids=lambda p: p.name) +def test_demo_parses_as_valid_python(demo: Path) -> None: + """The demo must be syntactically valid Python. + + ``ast.parse`` is a pure parse — no imports run, so a missing SDK + install does not cause spurious failures. This catches accidental + syntax breakage from search-and-replace edits across the demos. + """ + source = demo.read_text(encoding="utf-8") + try: + ast.parse(source, filename=str(demo)) + except SyntaxError as e: # pragma: no cover - failure path + pytest.fail(f"{demo.name} failed to parse: {e}") + + +@pytest.mark.parametrize("demo", DEMOS, ids=lambda p: p.name) +def test_demo_guards_entry_point(demo: Path) -> None: + """The demo must gate side effects on ``if __name__ == "__main__"``. + + Importing a demo (via ``importlib`` or ``python -c "import foo"``) + should never connect to a daemon, open sockets, or block on input. + Without the guard, downstream tooling that snapshots the example + catalogue would hang or crash on import. + """ + tree = ast.parse(demo.read_text(encoding="utf-8"), filename=str(demo)) + for node in ast.walk(tree): + if ( + isinstance(node, ast.If) + and isinstance(node.test, ast.Compare) + and isinstance(node.test.left, ast.Name) + and node.test.left.id == "__name__" + and len(node.test.ops) == 1 + and isinstance(node.test.ops[0], ast.Eq) + and len(node.test.comparators) == 1 + and isinstance(node.test.comparators[0], ast.Constant) + and node.test.comparators[0].value == "__main__" + ): + return + pytest.fail( + f"{demo.name} has no `if __name__ == \"__main__\":` guard; " + "importing it would execute side effects." + ) From 818f8e2a5ef35784280b6c363bb65b2bcd103efc Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Wed, 27 May 2026 19:19:47 -0700 Subject: [PATCH 2/2] ci: tidy go module before build to absorb upstream web4 drift The examples module floats against web4 via a relative replace directive. When web4 HEAD advances its go directive or transitive deps, go.sum here goes stale and `go build ./...` fails with "updates to go.mod needed". Run `go mod tidy` in CI before build so the drift is absorbed automatically. Also bump the go directive to 1.25.10 to match what current web4 already requires. --- .github/workflows/ci.yml | 4 ++++ go/go.mod | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ef3dbc..f6d9b42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,10 @@ jobs: cache: true cache-dependency-path: examples/go/go.sum + - name: Tidy Go examples (absorb web4 drift) + working-directory: examples/go + run: go mod tidy + - name: Build Go examples working-directory: examples/go run: go build ./... diff --git a/go/go.mod b/go/go.mod index 2b670ff..dae89b2 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,6 +1,6 @@ module github.com/pilot-protocol/examples/go -go 1.25.3 +go 1.25.10 require github.com/TeoSlayer/pilotprotocol v0.0.0