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
2 changes: 1 addition & 1 deletion docs/csharp-component-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ The kit ships a starter for C# in [`templates/component-contract/csharp/`](../te

**The filesets assume the layout of §1:** project folders at the repo root, named `<Prefix>.Domain`, `<Prefix>.Infrastructure` and `<Prefix>.Api`, test projects ending in `.Test` or `Tests`. A repo laid out differently (projects under `src/`, a layer named otherwise) edits the `filesets:` block once when seeding; a scope that matches nothing reports an error rather than passing, so a wrong glob is never silent.

The engine is `specfuse.loop.gen_contract` (specfuse-loop 0.30+, part of `pipx install specfuse`). The seeded `scripts/check-gen-contract.sh` runs it; `scripts/test-gen-contract.sh` is the mutation test.
The engine is `specfuse.loop.gen_contract` (specfuse-loop 0.30+, part of `pipx install specfuse` or `uv tool install specfuse`). The seeded `scripts/check-gen-contract.sh` runs it, finding the interpreter beside the `specfuse` command; a script seeded from kit 0.32.0 or earlier does not look there, so set `SPECFUSE_PYTHON` or copy the script from the template again; `scripts/test-gen-contract.sh` is the mutation test.

```bash
./scripts/check-gen-contract.sh # every rule; 0 green, 1 violations, 2 cannot run
Expand Down
40 changes: 40 additions & 0 deletions scripts/test-csharp-contract-template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,44 @@ expect_patches 1 "no 'Generator-Issue:' line" --check
git checkout -q -- "$PATCH"
git reset -q --hard HEAD~1

# ── The script finds the engine wherever `specfuse` is installed ───────────
# CI pip-installs the engine into python3, the one case that always worked, so
# python3 is stubbed here. `specfuse` refuses `gen-contract`, as the CLI does
# until it dispatches it.
echo "── the script finds the engine beside the specfuse command"
REAL_PY="$(command -v "$PY")"
TOOLS="$(mktemp -d "${TMPDIR:-/tmp}/csharp-contract-tools.XXXXXX")"
trap 'rm -rf "$WORK" "$TOOLS"' EXIT
stub() { mkdir -p "$(dirname "$1")"; printf '#!/bin/sh\n%s\n' "$2" > "$1"; chmod +x "$1"; }
stub "$TOOLS/no-engine/python3" 'exit 1'
stub "$TOOLS/env/bin/specfuse" 'exit 2'
stub "$TOOLS/env/bin/python" "exec \"$REAL_PY\" \"\$@\""
stub "$TOOLS/old-env/bin/specfuse" 'exit 2'
stub "$TOOLS/old-env/bin/python" 'exit 1'
stub "$TOOLS/no-python/specfuse" 'exit 2'
mkdir -p "$TOOLS/shim"
ln -s ../env/bin/specfuse "$TOOLS/shim/specfuse"

expect_find() { # <exit> <text or ""> <PATH prefix> <SPECFUSE_PYTHON>
local want="$1" text="$2" prefix="$3" chosen="$4" out rc=0
out="$(env -u SPECFUSE_PYTHON ${chosen:+SPECFUSE_PYTHON="$chosen"} PATH="$prefix:$PATH" \
./scripts/check-gen-contract.sh --list 2>&1)" || rc=$?
if [ "$rc" -ne "$want" ] || ! grep -qF -- "$text" <<<"$out"; then
echo "FAIL: PATH prefix $prefix, SPECFUSE_PYTHON '$chosen': wanted exit $want and '$text', got exit $rc:" >&2
printf '%s\n' "$out" >&2
exit 1
fi
}

# Through a relative symlink, as pipx and uv install it; python3 has no engine.
expect_find 0 "" "$TOOLS/shim:$TOOLS/no-engine" ""
# Directly on PATH, as a plain virtualenv has it.
expect_find 0 "" "$TOOLS/env/bin:$TOOLS/no-engine" ""
# A specfuse too old to carry the engine must not hide a python3 that has it.
expect_find 0 "" "$TOOLS/old-env/bin:$(dirname "$REAL_PY")" ""
# Nothing beside specfuse and no engine in python3: exit 2, naming the fix.
expect_find 2 "uv tool install specfuse" "$TOOLS/no-python:$TOOLS/no-engine" ""
# An explicit choice is not second-guessed.
expect_find 2 "not importable by $TOOLS/no-engine/python3" "$TOOLS/shim" "$TOOLS/no-engine/python3"

echo "OK: the C# starter contract is green on a fresh seed and every rule bites."
2 changes: 1 addition & 1 deletion templates/component-contract/csharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ cp "$KIT/.github/workflows/gen-contract.yml" .github/workflows/
sed -i.bak 's/__Prefix__/HelloOrders/g' gen-contract.yml docs/generated-code-expectations.md && rm -f *.bak docs/*.bak
mkdir -p gen-patches && cp "$KIT/gen-patches/README.md" gen-patches/
chmod +x scripts/check-gen-contract.sh scripts/test-gen-contract.sh scripts/apply-gen-patches.sh
python3 -m pip install 'specfuse-loop>=0.30' # or: pipx install specfuse
pipx install specfuse # or: uv tool install specfuse
./scripts/check-gen-contract.sh # must be green
./scripts/test-gen-contract.sh # every rule must bite
```
Expand Down
37 changes: 31 additions & 6 deletions templates/component-contract/csharp/scripts/check-gen-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,39 @@
# Exit 0 clean, 1 violations, 2 the checker could not run.
#
# The engine is `specfuse.loop.gen_contract`, shipped in specfuse-loop 0.30+
# (`pipx install specfuse` brings it). Until the umbrella CLI dispatches
# `specfuse gen-contract`, the module is run directly. Set SPECFUSE_PYTHON to
# the interpreter that has specfuse-loop installed when it is not `python3`.
# (`pipx install specfuse` or `uv tool install specfuse` brings it). Until the
# umbrella CLI dispatches `specfuse gen-contract`, the module is run directly,
# by the first of: SPECFUSE_PYTHON, the interpreter installed beside the
# `specfuse` command when it has the engine, `python3`.

set -uo pipefail
cd "$(git rev-parse --show-toplevel)"

PY="${SPECFUSE_PYTHON:-python3}"
# The Python beside the `specfuse` on PATH, following the link pipx and uv put
# there. Not the script's shebang: a long install path makes it `#!/bin/sh`.
python_beside_specfuse() {
local bin target
bin="$(command -v specfuse 2>/dev/null)" || return 0
while [ -L "$bin" ]; do
target="$(readlink "$bin")"
case "$target" in
/*) bin="$target" ;;
*) bin="$(dirname "$bin")/$target" ;;
esac
done
if [ -x "$(dirname "$bin")/python" ]; then
printf '%s\n' "$(dirname "$bin")/python"
fi
}

PY="${SPECFUSE_PYTHON:-}"
if [ -z "$PY" ]; then
PY=python3
BESIDE="$(python_beside_specfuse)"
if [ -n "$BESIDE" ] && "$BESIDE" -c "import specfuse.loop.gen_contract" >/dev/null 2>&1; then
PY="$BESIDE"
fi
fi

if command -v specfuse >/dev/null 2>&1 && specfuse gen-contract --help >/dev/null 2>&1; then
exec specfuse gen-contract "$@"
Expand All @@ -32,8 +57,8 @@ fi
if ! "$PY" -c "import specfuse.loop.gen_contract" >/dev/null 2>&1; then
cat >&2 <<EOF
check-gen-contract: the engine is not importable by $PY.
Install it: $PY -m pip install 'specfuse-loop>=0.30'
Or point SPECFUSE_PYTHON at an interpreter that has it (pipx: ~/.local/pipx/venvs/specfuse/bin/python).
Install it: pipx install specfuse (or: uv tool install specfuse)
Or point SPECFUSE_PYTHON at an interpreter that has it.
EOF
exit 2
fi
Expand Down
Loading