250 lines
8.5 KiB
YAML
250 lines
8.5 KiB
YAML
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# 竖线分隔在任何错误(含网络错误)时回退到下一个源;默认的逗号只在
|
|
# 404/410 时回退,proxy.golang.org 瞬时故障会直接让整个 job 失败。
|
|
# 自托管 Runner 保留本机 Go 缓存;代码与编译参数变化由 Go 自动判定失效。
|
|
# 不再下载/上传 setup-go 缓存,也无需 main push 预热。
|
|
env:
|
|
GOPROXY: https://proxy.golang.org|direct
|
|
|
|
concurrency:
|
|
group: ci-pr-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: [self-hosted, macOS, ARM64]
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Validate and build web UI
|
|
uses: ./.github/actions/web-ci
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: false
|
|
|
|
- name: Check Go formatting
|
|
run: |
|
|
# go-format-check:start
|
|
formatted_files="$(gofmt -l .)"
|
|
test -z "${formatted_files}"
|
|
# go-format-check:end
|
|
|
|
- name: Check module graph
|
|
run: go mod tidy -diff
|
|
|
|
- name: Check embedded CPA bridge module
|
|
working-directory: third_party/cpaembedded
|
|
run: |
|
|
go mod tidy -diff
|
|
go vet ./...
|
|
|
|
- name: Run Go vet
|
|
run: go vet ./...
|
|
|
|
- name: Build backend
|
|
run: go build -o gpt-load .
|
|
|
|
- name: Check repository invariants
|
|
run: git diff --check
|
|
|
|
race-tests:
|
|
runs-on: [self-hosted, macOS, ARM64]
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: false
|
|
|
|
- name: Run race-enabled tests
|
|
run: go test -race -vet=off -count=1 -timeout=15m . ./internal/...
|
|
|
|
race-cpa:
|
|
runs-on: [self-hosted, Linux, ARM64]
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: false
|
|
|
|
- name: Run embedded CPA bridge race-enabled tests
|
|
working-directory: third_party/cpaembedded
|
|
run: go test -race -count=1 -timeout=15m ./...
|
|
|
|
database-contract:
|
|
name: Database contract (${{ matrix.driver }})
|
|
runs-on: [self-hosted, Linux, ARM64]
|
|
timeout-minutes: 15
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 2
|
|
matrix:
|
|
include:
|
|
- driver: mysql
|
|
image: mysql:8.4
|
|
data-dir: /var/lib/mysql
|
|
port: "3306"
|
|
health: mysqladmin ping -h 127.0.0.1 -proot
|
|
dsn: mysql://root:root@127.0.0.1:{0}/gpt_load?charset=utf8mb4&transaction_isolation=%27READ-COMMITTED%27
|
|
- driver: postgres
|
|
image: postgres:18
|
|
data-dir: /var/lib/postgresql
|
|
port: "5432"
|
|
health: pg_isready -U postgres -d gpt_load
|
|
dsn: postgres://postgres:postgres@127.0.0.1:{0}/gpt_load?sslmode=disable
|
|
# 仅临时合同测试数据库使用内存盘;每个实例按实际使用量占用,最多 2 GiB。
|
|
services:
|
|
database:
|
|
image: ${{ matrix.image }}
|
|
env:
|
|
MYSQL_ROOT_PASSWORD: root
|
|
MYSQL_DATABASE: gpt_load
|
|
POSTGRES_PASSWORD: postgres
|
|
POSTGRES_DB: gpt_load
|
|
ports:
|
|
- ${{ matrix.port }}
|
|
options: >-
|
|
--tmpfs ${{ matrix.data-dir }}:rw,nosuid,nodev,size=2g
|
|
--health-cmd="${{ matrix.health }}"
|
|
--health-interval=5s
|
|
--health-timeout=10s
|
|
--health-retries=20
|
|
# MySQL 两组使用独立实例,避免不同测试数据库争用同名 GET_LOCK。
|
|
secondary-mysql:
|
|
image: ${{ matrix.driver == 'mysql' && 'mysql:8.4' || '' }}
|
|
env:
|
|
MYSQL_ROOT_PASSWORD: root
|
|
MYSQL_DATABASE: gpt_load
|
|
ports:
|
|
- 3306
|
|
options: >-
|
|
--tmpfs /var/lib/mysql:rw,nosuid,nodev,size=2g
|
|
--health-cmd="mysqladmin ping -h 127.0.0.1 -proot"
|
|
--health-interval=5s
|
|
--health-timeout=10s
|
|
--health-retries=20
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: false
|
|
|
|
- name: Run external database contract
|
|
env:
|
|
GPT_LOAD_DATABASE_TEST_DSN: ${{ format(matrix.dsn, job.services.database.ports[format('{0}', matrix.port)]) }}
|
|
GPT_LOAD_DATABASE_SECONDARY_DSN: ${{ matrix.driver == 'mysql' && format(matrix.dsn, job.services.secondary-mysql.ports['3306']) || '' }}
|
|
run: .github/scripts/ci-database-contract.sh "${{ matrix.driver }}"
|
|
|
|
windows-encryption-acl:
|
|
runs-on: [self-hosted, Windows, X64]
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: false
|
|
|
|
- name: Test Windows secure file and storage ACLs
|
|
run: go test -v -count=1 ./internal/platform/securefile ./internal/platform/encryption ./internal/storage
|
|
|
|
- name: Test Windows service lifecycle
|
|
run: go test -v -count=1 .
|
|
|
|
- name: Test Windows smoke recovery
|
|
run: go test -count=1 ./internal/webui -run '^TestWindowsSmokeRecovery$'
|
|
|
|
- name: Smoke Windows service lifecycle
|
|
shell: pwsh
|
|
run: |
|
|
$binary = Join-Path $env:RUNNER_TEMP "gpt-load-service-smoke.exe"
|
|
go build -trimpath -o $binary .
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows service smoke build failed with exit code $LASTEXITCODE"
|
|
}
|
|
$env:CI_WINDOWS_SERVICE_BINARY = $binary
|
|
& .github/scripts/ci-windows-service-smoke.ps1
|
|
exit $LASTEXITCODE
|
|
|
|
- name: Build and smoke Windows installer
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$artifactDir = Join-Path $env:RUNNER_TEMP "gpt-load installer artifacts"
|
|
New-Item -ItemType Directory -Path $artifactDir | Out-Null
|
|
$binary = Join-Path $artifactDir "gpt-load-windows-amd64.exe"
|
|
go build -trimpath `
|
|
-ldflags "-X gpt-load/internal/platform/version.Version=ci" `
|
|
-o $binary .
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows installer binary build failed with exit code $LASTEXITCODE"
|
|
}
|
|
|
|
$compiler = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
|
|
if (-not (Test-Path $compiler)) {
|
|
throw "Inno Setup compiler is unavailable: $compiler"
|
|
}
|
|
& $compiler `
|
|
"/DAppVersion=ci" `
|
|
"/DSourceBinary=$binary" `
|
|
"/DOutputDir=$artifactDir" `
|
|
"packaging/windows/gpt-load.iss"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Inno Setup failed with exit code $LASTEXITCODE"
|
|
}
|
|
|
|
$setup = Join-Path $artifactDir "gpt-load-windows-setup.exe"
|
|
if (-not (Test-Path $setup)) {
|
|
throw "Windows setup was not generated"
|
|
}
|
|
$checksumFile = Join-Path $artifactDir "SHA256SUMS"
|
|
$setupHash = (Get-FileHash -Algorithm SHA256 $setup).Hash.ToLowerInvariant()
|
|
"$setupHash gpt-load-windows-setup.exe" |
|
|
Set-Content -Path $checksumFile -Encoding utf8NoBOM
|
|
$env:RELEASE_VERSION = "ci"
|
|
$env:RELEASE_WINDOWS_SETUP = $setup
|
|
$env:RELEASE_WINDOWS_BINARY = $binary
|
|
$env:RELEASE_SMOKE_CHECKSUM_FILE = $checksumFile
|
|
& .github/scripts/release-windows-installer-smoke.ps1
|
|
exit $LASTEXITCODE
|