Skip to content

Commit 04e3e8e

Browse files
committed
Apply quality framework: CI gates, benchmarks, destructive tests
Integrate the quality framework into the development workflow: - Add quality stage to GitLab CI with coverage, race detection, and vulnerability scanning gates - Add Makefile targets: test-race, test-bench, coverage-report, vulnerability-scan, quality (all-in-one) - Add benchmark tests for clone management (GetClones, FindWrapper, GetSnapshotList, concurrent set/get, GetCloningState) - Add benchmark tests for API utilities (WriteJSON, ReadJSON, WriteData) - Add destructive test harness for failure-mode testing (kill mid-clone, concurrent clones, restart with state preservation, snapshot integrity) - Update CLAUDE.md with quality commands and framework references https://claude.ai/code/session_01BMygsj1Bb967LXm7guNEBS
1 parent 898dbab commit 04e3e8e

File tree

6 files changed

+580
-1
lines changed

6 files changed

+580
-1
lines changed

CLAUDE.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,28 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
1616
- Build all components: `cd engine && make build`
1717
- Lint code: `cd engine && make run-lint`
1818
- Run unit tests: `cd engine && make test`
19+
- Run unit tests with race detection: `cd engine && make test-race`
1920
- Run integration tests: `cd engine && make test-ci-integration`
21+
- Run benchmark tests: `cd engine && make test-bench`
22+
- Run all quality checks: `cd engine && make quality`
23+
- Run coverage report: `cd engine && make coverage-report`
24+
- Run vulnerability scan: `cd engine && make vulnerability-scan`
2025
- Run a specific test: `cd engine && GO111MODULE=on go test -v ./path/to/package -run TestName`
2126
- Run UI: `cd ui && pnpm start:ce` (Community Edition) or `pnpm start:platform`
2227

28+
## Quality Engineering
29+
30+
See `quality/QUALITY_ENGINEERING_GUIDE.md` for the full quality framework.
31+
32+
Key references:
33+
- PR review checklist: `quality/checklists/pr-review-checklist.md`
34+
- Release readiness checklist: `quality/checklists/release-readiness-checklist.md`
35+
- AI PR review prompt: `quality/prompts/pr-review-system-prompt.md`
36+
- AI test generation prompt: `quality/prompts/test-generation-prompt.md`
37+
- CI quality gates: `quality/ci/quality-gates.yml`
38+
- Quality metrics template: `quality/metrics/quality-metrics-template.md`
39+
- Pre-push quality script: `quality/scripts/run-quality-checks.sh`
40+
2341
## Code Style Guidelines
2442
- Go code follows "Effective Go" and "Go Code Review Comments" guidelines
2543
- Use present tense and imperative mood in commit messages

engine/.gitlab-ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ default:
1515

1616
stages:
1717
- test
18+
- quality
1819
- build-binary
1920
- build
2021
- integration-test
@@ -66,6 +67,34 @@ lint:
6667
- cd engine
6768
- make lint
6869

70+
### Quality gates.
71+
coverage-check:
72+
<<: *only_engine
73+
stage: quality
74+
script:
75+
- cd engine
76+
- make coverage-report
77+
coverage: '/Total coverage: \d+.\d+%/'
78+
artifacts:
79+
paths:
80+
- engine/coverage.out
81+
expire_in: 30 days
82+
83+
race-check:
84+
<<: *only_engine
85+
stage: quality
86+
script:
87+
- cd engine
88+
- make test-race
89+
90+
vulnerability-scan:
91+
<<: *only_engine
92+
stage: quality
93+
script:
94+
- cd engine
95+
- make vulnerability-scan
96+
allow_failure: true
97+
6998
### Build binary.
7099
build-binary-alpine:
71100
<<: *only_engine

engine/Makefile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,26 @@ build-dle: build build-image ## Build Database Lab Engine binary and Docker imag
7575
test: ## Run unit tests
7676
${GOTEST} ./...
7777

78+
test-race: ## Run unit tests with race detector (explicit target for CI quality gate)
79+
${GOTEST} -count=1 -timeout=10m ./...
80+
7881
test-ci-integration: ## Run integration tests
7982
GO111MODULE=on go test -race -tags=integration ./...
8083

84+
test-bench: ## Run benchmark tests
85+
GO111MODULE=on go test -bench=. -benchmem -count=3 -timeout=30m ./... 2>&1 | tee bench-results.txt
86+
87+
coverage-report: ## Run tests with coverage and report result
88+
@GO111MODULE=on go test -coverprofile=coverage.out -covermode=atomic ./... 2>&1 | tail -1
89+
@TOTAL=$$(go tool cover -func=coverage.out | grep total | awk '{print $$3}' | sed 's/%//'); \
90+
echo "Total coverage: $${TOTAL}%"
91+
92+
vulnerability-scan: ## Scan dependencies for known vulnerabilities
93+
go install golang.org/x/vuln/cmd/govulncheck@latest
94+
govulncheck ./...
95+
96+
quality: fmt run-lint test coverage-report ## Run all quality checks (format, lint, test, coverage)
97+
8198
fmt: ## Format code
8299
go fmt $$(go list ./... | grep -v /vendor/)
83100

@@ -100,4 +117,4 @@ run-dle: build-dle
100117
-p "2345:2345" \
101118
dblab_server:local
102119

103-
.PHONY: help all build test run-lint install-lint lint fmt clean build-image build-dle build-ci-checker build-client build-rds-refresh run-dle
120+
.PHONY: help all build test test-race test-bench test-ci-integration run-lint install-lint lint fmt clean build-image build-dle build-ci-checker build-client build-rds-refresh run-dle coverage-report vulnerability-scan quality
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package cloning
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
11+
)
12+
13+
func BenchmarkGetClones(b *testing.B) {
14+
cloning := &Base{
15+
config: &Config{},
16+
clones: make(map[string]*CloneWrapper),
17+
snapshotBox: SnapshotBox{items: make(map[string]*models.Snapshot)},
18+
}
19+
20+
for i := 0; i < 100; i++ {
21+
id := fmt.Sprintf("clone-%03d", i)
22+
cloning.setWrapper(id, &CloneWrapper{
23+
Clone: &models.Clone{
24+
ID: id,
25+
CreatedAt: &models.LocalTime{Time: time.Now()},
26+
Status: models.Status{Code: models.StatusOK},
27+
},
28+
})
29+
}
30+
31+
b.ResetTimer()
32+
33+
for i := 0; i < b.N; i++ {
34+
_ = cloning.GetClones()
35+
}
36+
}
37+
38+
func BenchmarkFindWrapper(b *testing.B) {
39+
cloning := &Base{
40+
clones: make(map[string]*CloneWrapper),
41+
snapshotBox: SnapshotBox{items: make(map[string]*models.Snapshot)},
42+
}
43+
44+
for i := 0; i < 1000; i++ {
45+
id := fmt.Sprintf("clone-%04d", i)
46+
cloning.setWrapper(id, &CloneWrapper{
47+
Clone: &models.Clone{ID: id},
48+
})
49+
}
50+
51+
b.ResetTimer()
52+
53+
for i := 0; i < b.N; i++ {
54+
cloning.findWrapper("clone-0500")
55+
}
56+
}
57+
58+
func BenchmarkGetSnapshotList(b *testing.B) {
59+
cloning := &Base{
60+
clones: make(map[string]*CloneWrapper),
61+
snapshotBox: SnapshotBox{items: make(map[string]*models.Snapshot)},
62+
}
63+
64+
for i := 0; i < 50; i++ {
65+
id := fmt.Sprintf("snap-%03d", i)
66+
cloning.snapshotBox.items[id] = &models.Snapshot{
67+
ID: id,
68+
CreatedAt: &models.LocalTime{Time: time.Now()},
69+
}
70+
}
71+
72+
b.ResetTimer()
73+
74+
for i := 0; i < b.N; i++ {
75+
_ = cloning.getSnapshotList()
76+
}
77+
}
78+
79+
func BenchmarkSetGetWrapper_Concurrent(b *testing.B) {
80+
cloning := &Base{
81+
clones: make(map[string]*CloneWrapper),
82+
snapshotBox: SnapshotBox{items: make(map[string]*models.Snapshot)},
83+
}
84+
85+
b.RunParallel(func(pb *testing.PB) {
86+
i := 0
87+
for pb.Next() {
88+
id := fmt.Sprintf("clone-%04d", i%100)
89+
cloning.setWrapper(id, &CloneWrapper{Clone: &models.Clone{ID: id}})
90+
cloning.findWrapper(id)
91+
i++
92+
}
93+
})
94+
}
95+
96+
func BenchmarkGetCloningState(b *testing.B) {
97+
cloning := &Base{
98+
config: &Config{},
99+
clones: make(map[string]*CloneWrapper),
100+
snapshotBox: SnapshotBox{items: make(map[string]*models.Snapshot)},
101+
}
102+
103+
for i := 0; i < 50; i++ {
104+
id := fmt.Sprintf("clone-%03d", i)
105+
cloning.setWrapper(id, &CloneWrapper{
106+
Clone: &models.Clone{
107+
ID: id,
108+
CreatedAt: &models.LocalTime{Time: time.Now()},
109+
Status: models.Status{Code: models.StatusOK},
110+
},
111+
})
112+
}
113+
114+
b.ResetTimer()
115+
116+
for i := 0; i < b.N; i++ {
117+
_ = cloning.GetCloningState()
118+
}
119+
}
120+
121+
func TestBenchmarkSetup(t *testing.T) {
122+
cloning := &Base{
123+
config: &Config{},
124+
clones: make(map[string]*CloneWrapper),
125+
snapshotBox: SnapshotBox{items: make(map[string]*models.Snapshot)},
126+
}
127+
128+
cloning.setWrapper("test", &CloneWrapper{Clone: &models.Clone{ID: "test"}})
129+
w, ok := cloning.findWrapper("test")
130+
require.True(t, ok)
131+
require.Equal(t, "test", w.Clone.ID)
132+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package api
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"strings"
7+
"testing"
8+
"time"
9+
10+
"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
11+
)
12+
13+
func BenchmarkWriteJSON_SmallPayload(b *testing.B) {
14+
status := models.Status{Code: models.StatusOK, Message: "instance is running"}
15+
16+
b.ResetTimer()
17+
18+
for i := 0; i < b.N; i++ {
19+
w := httptest.NewRecorder()
20+
_ = WriteJSON(w, http.StatusOK, status)
21+
}
22+
}
23+
24+
func BenchmarkWriteJSON_LargePayload(b *testing.B) {
25+
clones := make([]*models.Clone, 100)
26+
for i := range clones {
27+
clones[i] = &models.Clone{
28+
ID: "clone-" + string(rune('a'+i%26)),
29+
CreatedAt: &models.LocalTime{Time: time.Now()},
30+
Status: models.Status{Code: models.StatusOK, Message: "clone is running"},
31+
DB: models.Database{DBName: "testdb", Username: "user"},
32+
}
33+
}
34+
35+
b.ResetTimer()
36+
37+
for i := 0; i < b.N; i++ {
38+
w := httptest.NewRecorder()
39+
_ = WriteJSON(w, http.StatusOK, clones)
40+
}
41+
}
42+
43+
func BenchmarkReadJSON(b *testing.B) {
44+
body := `{"id":"bench-clone","snapshot":{"id":"snap-1"},"db":{"username":"test","password":"test"}}`
45+
46+
b.ResetTimer()
47+
48+
for i := 0; i < b.N; i++ {
49+
req := httptest.NewRequest(http.MethodPost, "/clone", strings.NewReader(body))
50+
var result map[string]interface{}
51+
_ = ReadJSON(req, &result)
52+
}
53+
}
54+
55+
func BenchmarkWriteData(b *testing.B) {
56+
data := []byte(`{"status":"ok","engine":{"version":"3.5.0"},"cloning":{"numClones":42}}`)
57+
58+
b.ResetTimer()
59+
60+
for i := 0; i < b.N; i++ {
61+
w := httptest.NewRecorder()
62+
_ = WriteData(w, http.StatusOK, data)
63+
}
64+
}

0 commit comments

Comments
 (0)