forked from microsoft/go-mssqldb
-
Notifications
You must be signed in to change notification settings - Fork 0
258 lines (247 loc) · 11.9 KB
/
Copy pathpr-validation.yml
File metadata and controls
258 lines (247 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
name: pr-validation
on:
pull_request:
branches:
- main
# Remove all default token permissions at the workflow level.
# Individual jobs grant only the permissions they need.
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
env:
# Honour the matrix version instead of upgrading via the go.mod toolchain line.
GOTOOLCHAIN: local
strategy:
fail-fast: false
matrix:
# 1.27 and 1.26 are the supported Go releases; 1.25 is the floor declared
# by the go directive. Crossed with every SQL image because crypto/tls
# changes between Go releases and TLS negotiation differs by server version.
go: ['1.25.x', '1.26.x', '1.27.x']
sqlImage: ['2017-latest','2019-latest','2022-latest','2025-latest']
steps:
# actions/checkout v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup go
# actions/setup-go v6
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '${{ matrix.go }}'
- name: Install sqlcmd
run: |
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
curl -sSL https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18
echo "/opt/mssql-tools18/bin" >> $GITHUB_PATH
- name: Run tests against Linux SQL
shell: bash
run: |
go version
export SQLCMDPASSWORD=$(date +%s|sha256sum|base64|head -c 32)
export SQLCMDUSER=sa
export SQLUSER=sa
export SQLPASSWORD=$SQLCMDPASSWORD
export DATABASE=master
export HOST=localhost
# Build connection string - SQL 2017 and 2025 Docker images use self-signed certificates
if [ "${{ matrix.sqlImage }}" = "2017-latest" ]; then
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
# SQL 2017's self-signed certificate has a negative serial number that Go 1.23+ rejects by default.
# This GODEBUG override is only for CI testing against SQL Server 2017 and MUST NOT be used in production.
export GODEBUG=x509negativeserial=1
elif [ "${{ matrix.sqlImage }}" = "2025-latest" ]; then
# SQL 2025 Docker image also uses a self-signed certificate
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
else
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}"
fi
docker run -m 2GB -e ACCEPT_EULA=1 -d --name sqlserver -p 1433:1433 -e SA_PASSWORD=$SQLCMDPASSWORD mcr.microsoft.com/mssql/server:${{ matrix.sqlImage }}
# Wait for SQL Server to be ready - retry up to 60 seconds (30 attempts x 2s)
READY=0
for i in {1..30}; do
if sqlcmd -S localhost -U sa -P "$SQLCMDPASSWORD" -C -Q "SELECT 1" > /dev/null 2>&1; then
echo "SQL Server is ready (attempt $i)"
READY=1
break
fi
echo "Waiting for SQL Server to start... (attempt $i/30)"
sleep 2
done
if [ "$READY" -eq 0 ]; then
echo "SQL Server did not become ready within 60 seconds; aborting tests."
exit 1
fi
go test -coverprofile=coverage.out -covermode=atomic -v ./...
- name: Upload coverage to Codecov
# codecov/codecov-action v6
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
with:
files: coverage.out
flags: unittests
name: go-${{ matrix.go }}-sql-${{ matrix.sqlImage }}
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Show SQL Server logs on failure
if: failure()
shell: bash
run: |
if docker ps -a --format '{{.Names}}' | grep -Fxq sqlserver; then
docker logs sqlserver || echo "Unable to read logs from sqlserver."
else
echo "SQL Server container 'sqlserver' was not found."
fi
benchmarks:
runs-on: ubuntu-latest
timeout-minutes: 75
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Setup go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.27.x'
- name: Install sqlcmd
run: |
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
curl -sSL https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18
echo "/opt/mssql-tools18/bin" >> $GITHUB_PATH
- name: Start SQL Server container
shell: bash
run: |
export SQLCMDPASSWORD=$(date +%s|sha256sum|base64|head -c 32)
echo "SQLCMDPASSWORD=$SQLCMDPASSWORD" >> $GITHUB_ENV
docker run -m 2GB -e ACCEPT_EULA=1 -d --name sqlserver \
-p 1433:1433 -e SA_PASSWORD=$SQLCMDPASSWORD \
mcr.microsoft.com/mssql/server:2025-latest
# Wait for SQL Server to be ready
for i in {1..30}; do
if sqlcmd -S localhost -U sa -P "$SQLCMDPASSWORD" -C -Q "SELECT 1" > /dev/null 2>&1; then
echo "SQL Server is ready (attempt $i)"
break
fi
echo "Waiting for SQL Server... (attempt $i/30)"
sleep 2
done
- name: Warmup run (stabilize CPU/caches)
shell: bash
run: |
export SQLUSER=sa
export SQLPASSWORD=$SQLCMDPASSWORD
export DATABASE=master
export HOST=localhost
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
BENCH_PATTERN='Benchmark(BulkMakeParam|ConvertAssign|Decode|Encode|ManglePassword|Parse|Read|RoundTrip|Send|Str2ucs2|TdsBuffer|Ucs22str|Write)'
# Throwaway run to warm CPU caches, prime the Go runtime, and settle
# the OS scheduler. Results are discarded — ensures both measurement
# runs start from the same steady-state conditions.
go test -run='^$' -bench="$BENCH_PATTERN" \
-benchtime=100ms -count=1 -timeout=10m . ./msdsn > /dev/null 2>&1
- name: Run baseline benchmarks (main)
shell: bash
run: |
export SQLUSER=sa
export SQLPASSWORD=$SQLCMDPASSWORD
export DATABASE=master
export HOST=localhost
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
BENCH_PATTERN='Benchmark(BulkMakeParam|ConvertAssign|Decode|Encode|ManglePassword|Parse|Read|RoundTrip|Send|Str2ucs2|TdsBuffer|Ucs22str|Write)'
git worktree add ../main-bench origin/main
cp -v *_benchmark_test.go ../main-bench/ 2>/dev/null || true
cp -v msdsn/*_benchmark_test.go ../main-bench/msdsn/ 2>/dev/null || true
cd ../main-bench
go test -run='^$' -bench="$BENCH_PATTERN" \
-benchtime=1s -count=10 -benchmem -timeout=25m . ./msdsn 2>&1 | \
tee "$GITHUB_WORKSPACE/bench_old_full.log"
grep -E '^(Benchmark|goos:|goarch:|pkg:|cpu:)' "$GITHUB_WORKSPACE/bench_old_full.log" > "$GITHUB_WORKSPACE/bench_old.txt"
cd "$GITHUB_WORKSPACE"
git worktree remove ../main-bench --force
- name: Run PR benchmarks
shell: bash
run: |
export SQLUSER=sa
export SQLPASSWORD=$SQLCMDPASSWORD
export DATABASE=master
export HOST=localhost
export SQLSERVER_DSN="sqlserver://${SQLUSER}:${SQLPASSWORD}@localhost:1433?database=${DATABASE}&trustServerCertificate=true"
BENCH_PATTERN='Benchmark(BulkMakeParam|ConvertAssign|Decode|Encode|ManglePassword|Parse|Read|RoundTrip|Send|Str2ucs2|TdsBuffer|Ucs22str|Write)'
go test -run='^$' -bench="$BENCH_PATTERN" \
-benchtime=1s -count=10 -benchmem -timeout=25m . ./msdsn 2>&1 | \
tee bench_new_full.log
grep -E '^(Benchmark|goos:|goarch:|pkg:|cpu:)' bench_new_full.log > bench_new.txt
- name: Compare benchmarks
shell: bash
run: |
go install golang.org/x/perf/cmd/benchstat@latest
echo "## Benchmark Comparison (main vs PR)" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
benchstat -alpha=0.01 bench_old.txt bench_new.txt | tee -a "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
benchstat -alpha=0.01 bench_old.txt bench_new.txt > bench_diff.txt
- name: Check for regressions
shell: bash
run: |
if [ ! -f bench_diff.txt ]; then
echo "No comparison available, skipping regression check."
exit 0
fi
# Report statistically significant improvements (real %, not ~)
if grep -v '~' bench_diff.txt | grep -E '^\S+\s+.+\s+-[0-9]+\.[0-9]+%'; then
echo ""
echo "::notice::Performance improvements detected (see above)"
fi
# Fail on statistically significant regressions exceeding 15%
# Sequential CI runs produce systematic drift up to ~12%, so we require
# both statistical significance (no ~ marker) AND >15% magnitude.
# Exclude TdsBuffer_Write_Large: ~120ns operation with multi-flush path
# shows 30-46% swings between sequential CI runs due to cache sensitivity.
REGRESSED=$(grep -v '~' bench_diff.txt | grep -v 'TdsBuffer_Write_Large' | grep -E '^\S+\s+.+\s+\+[0-9]+\.[0-9]+%' | awk -F'+' '{split($2,a,"%"); if (a[1]+0 >= 15) print}')
if [ -n "$REGRESSED" ]; then
echo "$REGRESSED"
echo "::error::Statistically significant regression detected (>15%, p<0.01)"
exit 1
fi
echo "No significant regressions detected."
- name: Post benchmark results to PR
# Fork PRs get read-only tokens; comment will be skipped gracefully.
continue-on-error: true
if: always() && github.event_name == 'pull_request'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ ! -f bench_diff.txt ]; then
echo "No benchmark comparison to post."
exit 0
fi
BODY="## Benchmark Results (main vs PR)
<details>
<summary>Click to expand benchstat output</summary>
\`\`\`
$(cat bench_diff.txt)
\`\`\`
</details>
*Generated by CI — commit $(git rev-parse --short HEAD)*"
# Remove leading whitespace from heredoc-style indentation
BODY=$(echo "$BODY" | sed 's/^ //')
# Find and update existing benchmark comment, or create a new one
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
--jq '.[] | select(.body | startswith("## Benchmark Results")) | .id' | head -1)
if [ -n "$COMMENT_ID" ]; then
gh api "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" \
-X PATCH -f body="$BODY"
echo "Updated existing benchmark comment."
else
gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY"
echo "Posted new benchmark comment."
fi