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
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ 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 ./...

- name: Test Go examples (compile smoke)
working-directory: examples/go
run: go test ./...

python-examples:
runs-on: ubuntu-latest
steps:
Expand All @@ -49,6 +57,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:
Expand Down
13 changes: 13 additions & 0 deletions go/client/main_test.go
Original file line number Diff line number Diff line change
@@ -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.
}
13 changes: 13 additions & 0 deletions go/echo/main_test.go
Original file line number Diff line number Diff line change
@@ -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.
}
2 changes: 1 addition & 1 deletion go/go.mod
Original file line number Diff line number Diff line change
@@ -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

Expand Down
13 changes: 13 additions & 0 deletions go/httpclient/main_test.go
Original file line number Diff line number Diff line change
@@ -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.
}
13 changes: 13 additions & 0 deletions go/secure/main_test.go
Original file line number Diff line number Diff line change
@@ -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.
}
13 changes: 13 additions & 0 deletions go/webserver/main_test.go
Original file line number Diff line number Diff line change
@@ -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.
}
Empty file added python_sdk/tests/__init__.py
Empty file.
93 changes: 93 additions & 0 deletions python_sdk/tests/test_examples_syntax.py
Original file line number Diff line number Diff line change
@@ -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."
)
Loading