-
Notifications
You must be signed in to change notification settings - Fork 8
build: ask the SemVer gate about the pins, not about git history #709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ftw": patch | ||
| --- | ||
|
|
||
| Require a SemVer bump from any bundled driver whose bytes change between the old pin and the new one, asked of the pinned snapshots rather than of this repository's Git history. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
| // driverFile writes a minimal driver whose DRIVER table carries a version and | ||
| // whose body can be varied to change the file's bytes. | ||
| func driverFile(t *testing.T, dir, name, version, body string) { | ||
| t.Helper() | ||
| if err := os.MkdirAll(dir, 0o755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| source := "DRIVER = {\n id = \"" + strings.TrimSuffix(name, ".lua") + "\",\n" + | ||
| " version = \"" + version + "\",\n}\n\nfunction driver_poll()\n" + body + "\nend\n" | ||
| if err := os.WriteFile(filepath.Join(dir, name), []byte(source), 0o644); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| } | ||
|
|
||
| func snapshots(t *testing.T) (string, string) { | ||
| t.Helper() | ||
| root := t.TempDir() | ||
| oldDir := filepath.Join(root, "old") | ||
| newDir := filepath.Join(root, "new") | ||
| if err := os.MkdirAll(oldDir, 0o755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := os.MkdirAll(newDir, 0o755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| return oldDir, newDir | ||
| } | ||
|
|
||
| // The failure this gate exists for: a driver's bytes move between two pins | ||
| // while its version stands still. A gateway offered that driver cannot tell | ||
| // the two apart, and the signed channel refuses to publish changed bytes | ||
| // under a version it already published. | ||
| func TestChangedBytesWithoutAVersionBumpFail(t *testing.T) { | ||
| oldDir, newDir := snapshots(t) | ||
| driverFile(t, oldDir, "pixii.lua", "2.1.0", " return 5000") | ||
| driverFile(t, newDir, "pixii.lua", "2.1.0", " return 1000") | ||
|
|
||
| err := checkVersionsAcrossDirs(oldDir, newDir) | ||
| if err == nil { | ||
| t.Fatal("changed bytes under an unchanged version were accepted") | ||
| } | ||
| if !strings.Contains(err.Error(), "pixii.lua") { | ||
| t.Errorf("error does not name the driver: %v", err) | ||
| } | ||
| } | ||
|
|
||
| func TestChangedBytesWithABumpPass(t *testing.T) { | ||
| oldDir, newDir := snapshots(t) | ||
| driverFile(t, oldDir, "pixii.lua", "2.1.0", " return 5000") | ||
| driverFile(t, newDir, "pixii.lua", "2.1.1", " return 1000") | ||
|
|
||
| if err := checkVersionsAcrossDirs(oldDir, newDir); err != nil { | ||
| t.Fatalf("a bumped driver was rejected: %v", err) | ||
| } | ||
| } | ||
|
|
||
| func TestUnchangedDriversPass(t *testing.T) { | ||
| oldDir, newDir := snapshots(t) | ||
| driverFile(t, oldDir, "pixii.lua", "2.1.0", " return 5000") | ||
| driverFile(t, newDir, "pixii.lua", "2.1.0", " return 5000") | ||
|
|
||
| if err := checkVersionsAcrossDirs(oldDir, newDir); err != nil { | ||
| t.Fatalf("an untouched driver was rejected: %v", err) | ||
| } | ||
| } | ||
|
|
||
| // A driver added to the pin's bundle list has no previous bytes to compare | ||
| // against. That is not a version question. | ||
| func TestNewlyBundledDriverPasses(t *testing.T) { | ||
| oldDir, newDir := snapshots(t) | ||
| driverFile(t, newDir, "atmoce.lua", "1.0.0", " return 5000") | ||
|
|
||
| if err := checkVersionsAcrossDirs(oldDir, newDir); err != nil { | ||
| t.Fatalf("a newly bundled driver was rejected: %v", err) | ||
| } | ||
| } | ||
|
|
||
| // Dropping a driver from the bundle list is a coverage decision, not a | ||
| // version one, so a file present only in the old snapshot is ignored. | ||
| func TestDroppedDriverIsIgnored(t *testing.T) { | ||
| oldDir, newDir := snapshots(t) | ||
| driverFile(t, oldDir, "zap.lua", "1.0.0", " return 5000") | ||
| driverFile(t, newDir, "pixii.lua", "2.1.0", " return 5000") | ||
| driverFile(t, oldDir, "pixii.lua", "2.1.0", " return 5000") | ||
|
|
||
| if err := checkVersionsAcrossDirs(oldDir, newDir); err != nil { | ||
| t.Fatalf("a dropped driver was treated as a failure: %v", err) | ||
| } | ||
| } | ||
|
|
||
| // Every driver is reported, not just the first bad one, so a pin move that | ||
| // missed several bumps is fixed in one pass rather than one per run. | ||
| func TestAllOffendersAreReported(t *testing.T) { | ||
| oldDir, newDir := snapshots(t) | ||
| for _, name := range []string{"pixii.lua", "sungrow.lua", "kostal.lua"} { | ||
| driverFile(t, oldDir, name, "1.0.0", " return 5000") | ||
| driverFile(t, newDir, name, "1.0.0", " return 1000") | ||
| } | ||
|
|
||
| err := checkVersionsAcrossDirs(oldDir, newDir) | ||
| if err == nil { | ||
| t.Fatal("expected failures") | ||
| } | ||
| for _, name := range []string{"pixii.lua", "sungrow.lua", "kostal.lua"} { | ||
| if !strings.Contains(err.Error(), name) { | ||
| t.Errorf("%s missing from the report: %v", name, err) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| #!/usr/bin/env bash | ||
| # Require a SemVer bump from any bundled driver whose bytes changed. | ||
| # | ||
| # The drivers here are generated from srcfl/device-drivers at the commit | ||
| # pinned in drivers/BUNDLED_SOURCE.json, so what reaches a gateway is decided | ||
| # by where that pin points -- not by what this repository's history shows. | ||
| # This compares the drivers at the previous pin against the drivers at the | ||
| # current one and fails when a file changed without its DRIVER version moving. | ||
| # | ||
| # Asking the pins rather than the Git log keeps working once the .lua files | ||
| # stop being committed here, and is the more honest question even now: it | ||
| # measures what ships. | ||
| # | ||
| # Usage: | ||
| # scripts/check-driver-versions.sh <base-git-revision> | ||
| # | ||
| # The base revision is only used to read the previous drivers/BUNDLED_SOURCE.json. | ||
| # A pull request that does not move the pin has nothing to check. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| BASE="${1:-}" | ||
| [ -n "$BASE" ] || { echo "usage: $0 <base-git-revision>" >&2; exit 2; } | ||
|
|
||
| ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
| PIN="drivers/BUNDLED_SOURCE.json" | ||
|
|
||
| command -v jq >/dev/null || { echo "jq is required" >&2; exit 2; } | ||
|
|
||
| NEW_PIN_JSON="$(cat "${ROOT}/${PIN}")" | ||
| # A pull request that adds the pin for the first time has no previous one. | ||
| OLD_PIN_JSON="$(git -C "$ROOT" show "${BASE}:${PIN}" 2>/dev/null || echo '')" | ||
|
|
||
| if [ -z "$OLD_PIN_JSON" ]; then | ||
| echo "no ${PIN} at ${BASE}; nothing to compare" | ||
| exit 0 | ||
|
Comment on lines
+32
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| fi | ||
|
|
||
| REPOSITORY="$(printf '%s' "$NEW_PIN_JSON" | jq -r .repository)" | ||
| SOURCE_DIR="$(printf '%s' "$NEW_PIN_JSON" | jq -r .source_dir)" | ||
| NEW_COMMIT="$(printf '%s' "$NEW_PIN_JSON" | jq -r .commit)" | ||
| OLD_COMMIT="$(printf '%s' "$OLD_PIN_JSON" | jq -r .commit)" | ||
|
Comment on lines
+39
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a pin update also changes Useful? React with 👍 / 👎. |
||
|
|
||
| if [ "$OLD_COMMIT" = "$NEW_COMMIT" ]; then | ||
| echo "pin unchanged at ${NEW_COMMIT:0:12}; no driver bytes can have moved" | ||
| exit 0 | ||
| fi | ||
|
|
||
| WORK="$(mktemp -d)" | ||
| trap 'rm -rf "${WORK}"' EXIT | ||
|
|
||
| # Materialise one commit's drivers into a flat directory, limited to the | ||
| # drivers the current pin actually bundles. A driver added to the list has no | ||
| # counterpart in the old snapshot, which reads as "newly bundled". | ||
| materialise() { | ||
| local commit="$1" dest="$2" | ||
| mkdir -p "$dest" | ||
| local extract="${WORK}/extract-${commit:0:12}" | ||
| mkdir -p "$extract" | ||
| curl -fsSL "https://codeload.github.com/${REPOSITORY}/tar.gz/${commit}" \ | ||
| | tar -xz -C "$extract" | ||
| local src | ||
| src="$(find "$extract" -maxdepth 1 -mindepth 1 -type d | head -1)/${SOURCE_DIR}" | ||
| [ -d "$src" ] || { echo "no ${SOURCE_DIR} at ${commit}" >&2; exit 1; } | ||
| while IFS= read -r id; do | ||
| [ -n "$id" ] || continue | ||
| [ -f "${src}/${id}.lua" ] && cp "${src}/${id}.lua" "${dest}/${id}.lua" | ||
| done < <(printf '%s' "$NEW_PIN_JSON" | jq -r '.drivers[]') | ||
| } | ||
|
|
||
| echo "comparing ${REPOSITORY} ${OLD_COMMIT:0:12} -> ${NEW_COMMIT:0:12}" | ||
| materialise "$OLD_COMMIT" "${WORK}/old" | ||
| materialise "$NEW_COMMIT" "${WORK}/new" | ||
|
|
||
| cd "${ROOT}/go" | ||
| go run ./cmd/ftw-driver-repository check-versions \ | ||
| -old-dir "${WORK}/old" -new-dir "${WORK}/new" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In
.github/workflows/test.yml, thedriversjob is selected only for Lua files,BUNDLED_SOURCE.json,sync-bundled-drivers.sh, Makefile, or workflow changes; the newly addedscripts/check-driver-versions.shis absent from that classifier. A later PR changing only this gate will therefore skip this step and all of its related checks while the final required job still succeeds with skipped suites, so this script path should select the drivers job.Useful? React with 👍 / 👎.