From d76730dfb6a24bf9b3d799c10d52ddb07995086e Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 06:59:26 +0200 Subject: [PATCH 1/3] ci: run the Go race detector on the control-loop packages CI runs go test without -race. Agents report "race tests pass" from local runs, and two races still merged: #787 and #789 fixed test fakes that raced after landing. On their parent commits (ee416d1f, 5d5f8104) this check fails with WARNING: DATA RACE. .github/scripts/race.sh runs -race on the 16 package trees that share state with the control loop. The same command runs locally and in the new race job, which the required go test + vet check now waits for. All of them pass on master today. --- .github/scripts/race.sh | 15 +++++++++++++++ .github/workflows/test.yml | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100755 .github/scripts/race.sh diff --git a/.github/scripts/race.sh b/.github/scripts/race.sh new file mode 100755 index 000000000..3d65233b3 --- /dev/null +++ b/.github/scripts/race.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Runs the Go race detector on the packages whose goroutines share state +# with the control loop. Same command locally and in CI: +# bash .github/scripts/race.sh +# Races in #787 and #789 merged because no check ran -race; on their parent +# commits this command fails with WARNING: DATA RACE. +set -euo pipefail +cd "$(dirname "$0")/../../go" +exec go test -race -count=1 -timeout 900s \ + ./internal/control/... ./internal/loadpoint/... ./internal/telemetry/... \ + ./internal/drivers/... ./internal/api/... ./internal/state/... \ + ./internal/mpc/... ./internal/cars/... ./internal/appproto/... \ + ./internal/appuplink/... ./internal/ocpp/... ./internal/ha/... \ + ./internal/config/... ./internal/backup/... ./internal/notifications/... \ + ./cmd/ftw/... diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2d99c058..599e04c7b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,6 +113,22 @@ jobs: - name: FTW Cloud uplink imports nothing that controls run: bash .github/scripts/check-cloudlink-imports.sh go + race: + name: core (Go race detector) + needs: changes + if: needs.changes.outputs.core == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version: '1.26' + cache-dependency-path: go/go.sum + - name: Fetch the bundled drivers + run: make drivers + - name: Race detector on the packages the control loop shares + run: bash .github/scripts/race.sh + web: name: web needs: changes @@ -350,11 +366,12 @@ jobs: name: go test + vet if: always() needs: - [changes, core, web, drivers, compose, e2e, contract] + [changes, core, race, web, drivers, compose, e2e, contract] runs-on: ubuntu-latest env: RESULTS: >- ${{ needs.changes.result }} ${{ needs.core.result }} + ${{ needs.race.result }} ${{ needs.web.result }} ${{ needs.drivers.result }} ${{ needs.compose.result }} From bcc32d7dc4c65e98b6614c91fa7fe0b0a2a96959 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 07:09:05 +0200 Subject: [PATCH 2/3] ci: skip the wall-clock retention budget test under -race The race detector slows code 5-10x, so the 3-second retention budget in TestForecastRetentionLargeOverflowWithinScoringBudget failed on CI with no data race. The plain test job still enforces the budget. --- .github/scripts/race.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/scripts/race.sh b/.github/scripts/race.sh index 3d65233b3..c2828f952 100755 --- a/.github/scripts/race.sh +++ b/.github/scripts/race.sh @@ -6,7 +6,11 @@ # commits this command fails with WARNING: DATA RACE. set -euo pipefail cd "$(dirname "$0")/../../go" -exec go test -race -count=1 -timeout 900s \ +# Wall-clock budget tests assert speed, not memory safety. The race +# detector makes code 5-10x slower, so they fail here without a bug; the +# plain test job still runs them. +skip='^TestForecastRetentionLargeOverflowWithinScoringBudget$' +exec go test -race -count=1 -timeout 900s -skip "$skip" \ ./internal/control/... ./internal/loadpoint/... ./internal/telemetry/... \ ./internal/drivers/... ./internal/api/... ./internal/state/... \ ./internal/mpc/... ./internal/cars/... ./internal/appproto/... \ From 079b96e2727833d56eb2265a57cc5607e1a4f104 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 07:38:57 +0200 Subject: [PATCH 3/3] ci: exercise race script edits in Core checks Signed-off-by: Fredrik Ahlgren --- .github/scripts/classify-test-changes.sh | 2 +- .github/scripts/test-change-classifier.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/scripts/classify-test-changes.sh b/.github/scripts/classify-test-changes.sh index c159f7352..8e4b57ef5 100644 --- a/.github/scripts/classify-test-changes.sh +++ b/.github/scripts/classify-test-changes.sh @@ -22,7 +22,7 @@ while IFS= read -r file; do ;; # contract/ is here because the Go suite checks the generated constants # against the registry. - go/*|contract/*|drivers/*|config*.yaml|Dockerfile|.dockerignore) + go/*|contract/*|drivers/*|config*.yaml|Dockerfile|.dockerignore|.github/scripts/race.sh) core=true ;; optimizer/*|Dockerfile.optimizer|go/internal/mpc/*|go/cmd/ftw/main.go) diff --git a/.github/scripts/test-change-classifier.sh b/.github/scripts/test-change-classifier.sh index 18035ef04..255f2cc9b 100644 --- a/.github/scripts/test-change-classifier.sh +++ b/.github/scripts/test-change-classifier.sh @@ -28,6 +28,10 @@ assert_paths 'drivers/example.lua' \ assert_paths 'config.example.yaml' \ 'core=true' 'optimizer=false' 'web=false' 'drivers=false' 'compose=false' +# Editing the race command alone must exercise it in CI. +assert_paths '.github/scripts/race.sh' \ + 'core=true' 'optimizer=false' 'web=false' 'drivers=false' 'compose=false' + assert_paths '.github/workflows/test.yml' \ 'core=true' 'optimizer=true' 'web=true' 'drivers=true' 'compose=true'