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
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,49 @@ jobs:
fail_on_failure: false
require_tests: true
annotate_only: true

# Dogfoods actions/setup (the #56 consumer bootstrap): a synthetic
# consumer Cargo.lock pins this very commit, the action installs the
# tools from the checkout (source-path), and its pins gate must hold
# the fixture to that rev. Asserts the advertised outputs and bin
# names on top.
actions-setup-smoke:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
run: rustup show active-toolchain || rustup toolchain install

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: actions-setup-smoke
cache-all-crates: true

- name: Synthesize a consumer Cargo.lock pinned to this commit
run: |
mkdir -p "$RUNNER_TEMP/consumer"
cat > "$RUNNER_TEMP/consumer/Cargo.lock" <<EOF
version = 4

[[package]]
name = "component-test-sdk"
version = "0.1.0"
source = "git+https://github.com/polymorph-components/polymorph-test?rev=${{ github.sha }}#${{ github.sha }}"
EOF

- name: Run the setup action from source
id: setup
uses: ./actions/setup
with:
cargo-lock: ${{ runner.temp }}/consumer/Cargo.lock
install-root: ${{ runner.temp }}/consumer/ct-tools
source-path: .

- name: Assert outputs and binaries
run: |
test "${{ steps.setup.outputs.rev }}" = "${{ github.sha }}"
test -x "${{ steps.setup.outputs.bin }}/component-test"
test -x "${{ steps.setup.outputs.bin }}/ct-runner"
53 changes: 53 additions & 0 deletions actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,59 @@ Composite actions for consumers of the stack (#14), one per concern.
Reference them by pinned rev, like every other consumption path here:
`uses: polymorph-components/polymorph-test/actions/aggregate@<rev>`.

## `setup`

Installs the component-test tools at the revision the consumer's
Cargo.lock pins (crate-name-anchored on `component-test-sdk`, so a
renamed repository still resolves), caches the install keyed on
`(os, rev, rust-toolchain.toml)`, and runs the `component-test pins`
gate over every declared lockfile — the one-rev-everywhere check. The
Cargo.lock is the single source of truth; the action cannot be pointed
at a different rev than the workspace builds against.

```yaml
- uses: polymorph-components/polymorph-test/actions/setup@<rev>
id: ct
with:
cargo-lock: Cargo.lock
js-locks: |
conformance/driver-ct/jco/package-lock.json
# tools: component-test-cli component-test-runner (default)
# install-root: target/ct-tools (default)
# steps.ct.outputs.rev — the pinned revision
# steps.ct.outputs.bin — directory holding component-test / ct-runner
```

`source-path: .` installs from a checkout instead of `--git` (used by
this repository's own CI smoke, and the local-override analogue).

### The local half: one canonical `_ct-tools` recipe

CI is only half the bootstrap; local `just` runs need the same tools.
Consumers should carry exactly this recipe (module-path-adjusted), so
the copies diff empty against each other:

```just
# Install the component-test CLI and host runner at the revision the
# workspace pins (read from Cargo.lock, so a bump cannot half-apply),
# then hold every other appearance of the pin to it.
_ct-tools:
#!/usr/bin/env bash
set -euo pipefail
cd {{root}}
src=$(grep -m1 -A2 '^name = "component-test-sdk"' Cargo.lock | grep '^source = "git+')
url=$(printf '%s' "$src" | sed -E 's/^source = "git\+([^?]+)\?.*/\1/')
rev=$(printf '%s' "$src" | grep -oE '[0-9a-f]{40}' | head -n1)
if [ ! -x target/ct-tools/bin/component-test ] || [ "$(cat target/ct-tools/.rev 2>/dev/null)" != "$rev" ]; then
cargo install --locked --root target/ct-tools --git "$url" --rev "$rev" component-test-cli component-test-runner
printf '%s' "$rev" > target/ct-tools/.rev
fi
target/ct-tools/bin/component-test pins --cargo-lock Cargo.lock --expect "$rev" > /dev/null
```

(Append `--js-lock <lockfile>` for each JS lockfile the repo carries;
drop `component-test-runner` if the runner is embedded as a library.)

## `aggregate`

Validates per-target results-JSONL against a lockfile + target
Expand Down
132 changes: 132 additions & 0 deletions actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
name: component-test setup
description: >
Install the component-test tools (`component-test`, `ct-runner`) at
the revision the consumer's Cargo.lock pins, with a rev-keyed cache,
and hold every declared pin to that revision with `component-test
pins`. The Cargo.lock is the single source of truth: the action
derives the revision from the `component-test-sdk` entry
(crate-name-anchored, so a renamed repository still resolves), so a
pin bump cannot half-apply.

inputs:
cargo-lock:
description: Path to the consumer's Cargo.lock.
default: Cargo.lock
js-locks:
description: >
JS lockfiles to include in the pins gate (package-lock.json /
pnpm-lock.yaml), one path per line. Optional.
default: ""
tools:
description: >
Space-separated crates to install from this repository.
default: component-test-cli component-test-runner
install-root:
description: >
`cargo install --root`; binaries land under `<install-root>/bin`.
default: target/ct-tools
source-path:
description: >
When set, install from this checkout path (`cargo install
--path <source-path>/crates/<tool>`) instead of `--git` at the
pinned revision, and skip the cache. For testing the action and
for local-override parity with the consumers' justfile recipes.
The pins gate still runs against the Cargo.lock revision.
default: ""

outputs:
rev:
description: The revision derived from the Cargo.lock.
value: ${{ steps.pin.outputs.rev }}
bin:
description: Directory holding the installed binaries.
value: ${{ steps.pin.outputs.bin }}

runs:
using: composite
steps:
- name: Derive the pinned revision from the Cargo.lock
id: pin
shell: bash
env:
INPUT_CARGO_LOCK: ${{ inputs.cargo-lock }}
INPUT_INSTALL_ROOT: ${{ inputs.install-root }}
run: |
set -euo pipefail
src=$(grep -m1 -A2 '^name = "component-test-sdk"' "$INPUT_CARGO_LOCK" | grep '^source = "git+' || true)
if [ -z "$src" ]; then
echo "::error title=component-test setup::no component-test-sdk git pin in $INPUT_CARGO_LOCK" >&2
exit 1
fi
url=$(printf '%s' "$src" | sed -E 's/^source = "git\+([^?]+)\?.*/\1/')
rev=$(printf '%s' "$src" | grep -oE '[0-9a-f]{40}' | head -n1)
{
echo "rev=$rev"
echo "url=$url"
echo "bin=$INPUT_INSTALL_ROOT/bin"
} >> "$GITHUB_OUTPUT"

- name: Cache the installed tools
if: inputs.source-path == ''
uses: actions/cache@v4
with:
path: ${{ inputs.install-root }}
key: component-test-tools-${{ runner.os }}-${{ steps.pin.outputs.rev }}-${{ hashFiles('rust-toolchain.toml') }}

- name: Install the tools
shell: bash
env:
INPUT_TOOLS: ${{ inputs.tools }}
INPUT_INSTALL_ROOT: ${{ inputs.install-root }}
INPUT_SOURCE_PATH: ${{ inputs.source-path }}
PIN_URL: ${{ steps.pin.outputs.url }}
PIN_REV: ${{ steps.pin.outputs.rev }}
run: |
set -euo pipefail
# Bin name per crate (cargo install exposes the bin, not the
# crate name).
bin_of() {
case "$1" in
component-test-cli) echo component-test ;;
component-test-runner) echo ct-runner ;;
*) echo "$1" ;;
esac
}
missing=()
for tool in $INPUT_TOOLS; do
[ -x "$INPUT_INSTALL_ROOT/bin/$(bin_of "$tool")" ] || missing+=("$tool")
done
if [ "${#missing[@]}" -eq 0 ]; then
echo "tools already present in $INPUT_INSTALL_ROOT/bin (cache hit)"
exit 0
fi
if [ -n "$INPUT_SOURCE_PATH" ]; then
for tool in "${missing[@]}"; do
cargo install -q --locked --root "$INPUT_INSTALL_ROOT" \
--path "$INPUT_SOURCE_PATH/crates/$tool"
done
else
cargo install -q --locked --root "$INPUT_INSTALL_ROOT" \
--git "$PIN_URL" --rev "$PIN_REV" "${missing[@]}"
fi

- name: Hold every declared pin to the Cargo.lock revision
shell: bash
env:
INPUT_CARGO_LOCK: ${{ inputs.cargo-lock }}
INPUT_JS_LOCKS: ${{ inputs.js-locks }}
INPUT_INSTALL_ROOT: ${{ inputs.install-root }}
PIN_REV: ${{ steps.pin.outputs.rev }}
run: |
set -euo pipefail
cli="$INPUT_INSTALL_ROOT/bin/component-test"
if [ ! -x "$cli" ]; then
echo "::error title=component-test setup::the pins gate needs the component-test CLI; keep component-test-cli in the tools input" >&2
exit 1
fi
args=(pins --cargo-lock "$INPUT_CARGO_LOCK" --expect "$PIN_REV")
while IFS= read -r line; do
line="${line#"${line%%[![:space:]]*}"}"
[ -n "$line" ] && args+=(--js-lock "$line")
done <<< "$INPUT_JS_LOCKS"
"$cli" "${args[@]}" > /dev/null
Loading