ci: fixture-backed integration workflow and guarded fleet E2E workflow (lmxopcua#757)
v2-ci / unit-tests (push) Has been cancelled
v2-ci / unit-tests (push) Has been cancelled
v2-integration.yml runs the Sql, Historian.SqlServer, Historian.QuestDb and OpcUaClient integration projects against throwaway SQL Server/PostgreSQL/QuestDB/opc-plc containers started by scripts/ci/integration-fixtures.sh, with a MIN_EXECUTED=440 floor. v2-e2e.yml is manual/weekly, probes the shared fleet and skips cleanly when unreachable. v2-ci.yml is functionally unchanged. Unverified on a hosted runner until first run. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -56,3 +56,26 @@ Restore, Release build, README drift check, the serialized whole-solution test
|
||||
step, the 12,000-test floor and TRX upload all passed. The floor counted
|
||||
14,630 executed tests across 62 TRX files. This baseline predates the individual
|
||||
skip-reporting correction; its printed zero skip diagnostic is not skip evidence.
|
||||
|
||||
## Fixture-backed integration and guarded E2E workflows (#757)
|
||||
|
||||
**Both are UNVERIFIED on a hosted Gitea runner until their first run**; they were validated locally only
|
||||
(YAML parse, the exact `dotnet test` commands and fixture script against local throwaway containers).
|
||||
Neither touches `v2-ci.yml`; each has its own concurrency group (`lmxopcua-v2-integration`,
|
||||
`lmxopcua-v2-e2e`, `cancel-in-progress: false`) so a pending run of one cannot supersede a pending
|
||||
`lmxopcua-v2-ci` run.
|
||||
|
||||
- **`v2-integration.yml`** (push to `master`, path-filtered PRs, manual; `timeout-minutes: 60`).
|
||||
`integration-fixtures.sh up` starts throwaway SQL Server 2022, PostgreSQL 17 (seeded from `seed-pg.sql`),
|
||||
QuestDB and opc-plc (`otopcua-ci-*`, non-default host ports), exports the env vars the fixtures read, and
|
||||
`down` removes them. It uses plain `docker run`, not `services:` (act_runner support is undocumented and
|
||||
Postgres needs a seed step); the only runner requirement is a docker daemon. Runs the Sql, Historian.SqlServer,
|
||||
Historian.QuestDb, OpcUaClient and OpcUaClient.Browser `*.IntegrationTests` with `MIN_EXECUTED=440`.
|
||||
Local run: 468 executed / 8 skipped (Oracle x2, the blackhole `docker pause` gate, one QuestDB stress test,
|
||||
4 others), about 4.5 min of test time, QuestDB the longest (~3.3 min). Not covered (no pullable image or
|
||||
needs credentials): Modbus/S7/AB/FOCAS/TwinCAT sims, Mqtt, MTConnect, Oracle, Host real-infra mode.
|
||||
- **`v2-e2e.yml`** (manual + weekly cron only; `timeout-minutes: 90`). `probe-e2e-fixtures.sh` TCP-probes
|
||||
10.100.0.35 ports 5020/1102/50000; if any is closed every later step is skipped and the run is green with a
|
||||
notice. Otherwise runs the Modbus, S7, OpcUaClient and OpcUaClient.Browser integration projects against the
|
||||
shared fleet with `MIN_EXECUTED=35` (local: 42 executed / 38 skipped; Modbus skips the profiles that are not
|
||||
up). No test carries `Category=E2E` today, so the zero-match guard is this executed floor.
|
||||
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# integration-fixtures.sh — throwaway, self-contained fixtures for the v2-integration workflow (#757).
|
||||
#
|
||||
# up start SQL Server, PostgreSQL (seeded), QuestDB and opc-plc as containers named
|
||||
# otopcua-ci-*, wait until each is usable, and print the env exports the
|
||||
# *.IntegrationTests fixtures read (also appended to $GITHUB_ENV when set).
|
||||
# down remove those containers (idempotent; touches nothing else).
|
||||
#
|
||||
# Deliberately NOT the shared 10.100.0.35 host: CI must not depend on that box, and these
|
||||
# containers are created and destroyed inside the job. Host ports are non-default so a
|
||||
# developer's own fixtures are never clobbered when the script is run locally.
|
||||
#
|
||||
# Reaching the containers: on the host (or a host-network job) that is 127.0.0.1; when the job
|
||||
# itself runs inside a container (/.dockerenv) the published ports live on the docker host,
|
||||
# i.e. the container's default gateway. Override with FIXTURE_HOST.
|
||||
set -euo pipefail
|
||||
|
||||
SQL_PORT="${CI_SQL_PORT:-14340}"
|
||||
PG_PORT="${CI_PG_PORT:-15440}"
|
||||
QDB_PORT="${CI_QDB_PORT:-18812}"
|
||||
QDB_HTTP_PORT="${CI_QDB_HTTP_PORT:-19000}"
|
||||
OPC_PORT="${CI_OPC_PORT:-50100}"
|
||||
# Throwaway-container-only credentials (never used against anything else).
|
||||
SA_PASSWORD="${CI_SA_PASSWORD:-Ci_throwaway_Pw1}"
|
||||
PG_PASSWORD="${CI_PG_PASSWORD:-Ci_throwaway_pg1}"
|
||||
NAMES=(otopcua-ci-mssql otopcua-ci-pg otopcua-ci-questdb otopcua-ci-opcplc)
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
fixture_host() {
|
||||
if [ -n "${FIXTURE_HOST:-}" ]; then echo "$FIXTURE_HOST"; return; fi
|
||||
if [ -f /.dockerenv ] && command -v ip >/dev/null 2>&1; then
|
||||
ip route | awk '/^default/{print $3; exit}'; return
|
||||
fi
|
||||
echo 127.0.0.1
|
||||
}
|
||||
|
||||
down() { docker rm -f "${NAMES[@]}" >/dev/null 2>&1 || true; }
|
||||
|
||||
wait_for() { # <description> <timeout-seconds> <command...>
|
||||
local what="$1" t="$2"; shift 2
|
||||
local end=$((SECONDS + t))
|
||||
until "$@" >/dev/null 2>&1; do
|
||||
if [ "$SECONDS" -ge "$end" ]; then echo "TIMEOUT waiting for $what" >&2; return 1; fi
|
||||
sleep 3
|
||||
done
|
||||
echo "ready: $what"
|
||||
}
|
||||
|
||||
up() {
|
||||
command -v docker >/dev/null || { echo "docker not available" >&2; exit 1; }
|
||||
down
|
||||
docker run -d --name otopcua-ci-mssql -e ACCEPT_EULA=Y -e MSSQL_PID=Developer \
|
||||
-e "MSSQL_SA_PASSWORD=$SA_PASSWORD" -p "$SQL_PORT:1433" \
|
||||
mcr.microsoft.com/mssql/server:2022-latest >/dev/null
|
||||
docker run -d --name otopcua-ci-pg -e "POSTGRES_PASSWORD=$PG_PASSWORD" -e POSTGRES_DB=sqlpollfixture \
|
||||
-p "$PG_PORT:5432" postgres:17-alpine >/dev/null
|
||||
docker run -d --name otopcua-ci-questdb -p "$QDB_PORT:8812" -p "$QDB_HTTP_PORT:9000" \
|
||||
questdb/questdb:10.0.1 >/dev/null
|
||||
docker run -d --name otopcua-ci-opcplc -p "$OPC_PORT:$OPC_PORT" \
|
||||
mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn="$OPC_PORT" --ut --aa --alm >/dev/null
|
||||
|
||||
wait_for "postgres" 120 docker exec otopcua-ci-pg pg_isready -U postgres -d sqlpollfixture
|
||||
# pg_isready can answer during initdb's temp server; require a real query on the final server.
|
||||
wait_for "postgres query" 60 docker exec -e "PGPASSWORD=$PG_PASSWORD" otopcua-ci-pg \
|
||||
psql -h 127.0.0.1 -U postgres -d sqlpollfixture -c 'select 1'
|
||||
docker exec -i -e "PGPASSWORD=$PG_PASSWORD" otopcua-ci-pg \
|
||||
psql -v ON_ERROR_STOP=1 -v "app_password=$PG_PASSWORD" -h 127.0.0.1 -U postgres -d sqlpollfixture \
|
||||
< "$ROOT/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/postgres/seed-pg.sql" >/dev/null
|
||||
echo "seeded: postgres"
|
||||
|
||||
local sqlcmd=/opt/mssql-tools18/bin/sqlcmd
|
||||
wait_for "sql server" 300 docker exec otopcua-ci-mssql "$sqlcmd" -C -S localhost -U sa -P "$SA_PASSWORD" -Q 'SELECT 1'
|
||||
wait_for "questdb" 120 curl -fsS "http://$(fixture_host):$QDB_HTTP_PORT/exec?query=select%201"
|
||||
wait_for "opc-plc" 120 bash -c "exec 3<>/dev/tcp/$(fixture_host)/$OPC_PORT"
|
||||
|
||||
local h; h="$(fixture_host)"
|
||||
local -a exports=(
|
||||
"SQL_TEST_ENDPOINT=$h,$SQL_PORT"
|
||||
"SQL_TEST_USER=sa"
|
||||
"SQL_TEST_PASSWORD=$SA_PASSWORD"
|
||||
"SQL_PG_ENDPOINT=$h:$PG_PORT"
|
||||
"SQL_PG_PASSWORD=$PG_PASSWORD"
|
||||
"HISTORIAN_SQL_TEST_CONNECTION=Server=$h,$SQL_PORT;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=True;Encrypt=False"
|
||||
"HISTORIAN_QUESTDB_TEST_CONNECTION=Host=$h;Port=$QDB_PORT;Username=admin;Password=quest;Database=qdb;ServerCompatibilityMode=NoTypeLoading"
|
||||
"OPCUA_SIM_ENDPOINT=opc.tcp://$h:$OPC_PORT"
|
||||
)
|
||||
for e in "${exports[@]}"; do
|
||||
echo "export \"$e\""
|
||||
[ -n "${GITHUB_ENV:-}" ] && echo "$e" >> "$GITHUB_ENV"
|
||||
done
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
up) up ;;
|
||||
down) down ;;
|
||||
*) echo "usage: $0 up|down" >&2; exit 2 ;;
|
||||
esac
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# probe-e2e-fixtures.sh — reachability gate for the guarded v2-e2e workflow (#757).
|
||||
#
|
||||
# TCP-probes the shared fixture host's REQUIRED simulator ports. Prints `reachable=true|false` (and
|
||||
# appends it to $GITHUB_OUTPUT when set). Exit status is always 0: an unreachable fleet is an expected,
|
||||
# clean skip (the runner is off-VPN, the host is down), not a failure. Only the probe itself erring
|
||||
# (bad usage) exits non-zero.
|
||||
#
|
||||
# Env: E2E_FIXTURE_HOST (default 10.100.0.35)
|
||||
# E2E_REQUIRED_PORTS (default "5020 1102 50000" = Modbus, S7, opc-plc)
|
||||
# E2E_PROBE_TIMEOUT (seconds per port, default 5)
|
||||
set -uo pipefail
|
||||
host="${E2E_FIXTURE_HOST:-10.100.0.35}"
|
||||
ports="${E2E_REQUIRED_PORTS:-5020 1102 50000}"
|
||||
t="${E2E_PROBE_TIMEOUT:-5}"
|
||||
ok=true
|
||||
for p in $ports; do
|
||||
if timeout "$t" bash -c "exec 3<>/dev/tcp/$host/$p" 2>/dev/null; then
|
||||
echo "probe: $host:$p open"
|
||||
else
|
||||
echo "probe: $host:$p UNREACHABLE"; ok=false
|
||||
fi
|
||||
done
|
||||
echo "reachable=$ok"
|
||||
[ -n "${GITHUB_OUTPUT:-}" ] && echo "reachable=$ok" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user