Skip to content

Commit 9c99f9a

Browse files
committed
test(qa): add gated integration harness
1 parent ccd4b78 commit 9c99f9a

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ concave/
3434
templates/
3535
scripts/
3636
tests/
37+
integration/
38+
benchmarks/
3739
docs/
3840
architecture.md
3941
concave-reference.md
@@ -219,6 +221,12 @@ Integration tests:
219221
- must skip unless `CONCAVE_INTEGRATION=1` is set
220222
- are for real environment validation, not default CI execution
221223

224+
Example:
225+
226+
```bash
227+
CONCAVE_INTEGRATION=1 go test ./tests/integration -v
228+
```
229+
222230
GPU-related changes must include manual validation notes in the PR description.
223231

224232
## Documentation Expectations

tests/integration/boosting_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
"time"
11+
)
12+
13+
func TestBoostingLifecycle(t *testing.T) {
14+
requireIntegration(t)
15+
16+
repoRoot := resolveRepoRoot(t)
17+
binaryPath := buildConcaveBinary(t, repoRoot)
18+
homeDir := t.TempDir()
19+
binDir := t.TempDir()
20+
openLog := filepath.Join(t.TempDir(), "opened-url.log")
21+
writeScript(t, filepath.Join(binDir, "xdg-open"), "#!/bin/sh\nprintf '%s\\n' \"$1\" >> \"$OPENED_URL_LOG\"\n")
22+
23+
env := append(os.Environ(),
24+
"HOME="+homeDir,
25+
"PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"),
26+
"OPENED_URL_LOG="+openLog,
27+
)
28+
29+
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 5*time.Minute)
30+
defer cleanupCancel()
31+
defer func() {
32+
_, _ = runConcave(cleanupCtx, repoRoot, binaryPath, env, "", "stop", "boosting")
33+
_, _ = runConcave(cleanupCtx, repoRoot, binaryPath, env, "y\n", "remove", "boosting")
34+
}()
35+
36+
runCtx, cancel := context.WithTimeout(context.Background(), 25*time.Minute)
37+
defer cancel()
38+
39+
if out, err := runConcave(runCtx, repoRoot, binaryPath, env, "", "workspace", "init"); err != nil {
40+
t.Fatalf("workspace init failed: %v\n%s", err, out)
41+
}
42+
if out, err := runConcave(runCtx, repoRoot, binaryPath, env, "", "install", "boosting"); err != nil {
43+
t.Fatalf("install boosting failed: %v\n%s", err, out)
44+
}
45+
if _, err := os.Stat(filepath.Join(homeDir, "gradient", "compose", "boosting.compose.yml")); err != nil {
46+
t.Fatalf("expected rendered compose file: %v", err)
47+
}
48+
if out, err := runConcave(runCtx, repoRoot, binaryPath, env, "", "start", "boosting"); err != nil {
49+
t.Fatalf("start boosting failed: %v\n%s", err, out)
50+
}
51+
52+
waitForLab(t, repoRoot, binaryPath, env, openLog)
53+
}
54+
55+
func requireIntegration(t *testing.T) {
56+
t.Helper()
57+
if os.Getenv("CONCAVE_INTEGRATION") != "1" {
58+
t.Skip("set CONCAVE_INTEGRATION=1 to run Docker-backed integration tests")
59+
}
60+
if _, err := exec.LookPath("docker"); err != nil {
61+
t.Skipf("docker not found: %v", err)
62+
}
63+
cmd := exec.Command("docker", "info")
64+
if out, err := cmd.CombinedOutput(); err != nil {
65+
t.Skipf("docker info failed: %v\n%s", err, string(out))
66+
}
67+
}
68+
69+
func resolveRepoRoot(t *testing.T) string {
70+
t.Helper()
71+
wd, err := os.Getwd()
72+
if err != nil {
73+
t.Fatalf("Getwd() error = %v", err)
74+
}
75+
root := filepath.Clean(filepath.Join(wd, "..", ".."))
76+
if _, err := os.Stat(filepath.Join(root, "go.mod")); err != nil {
77+
t.Fatalf("repo root lookup failed: %v", err)
78+
}
79+
return root
80+
}
81+
82+
func buildConcaveBinary(t *testing.T, repoRoot string) string {
83+
t.Helper()
84+
binaryPath := filepath.Join(t.TempDir(), "concave")
85+
cmd := exec.Command("go", "build", "-o", binaryPath, ".")
86+
cmd.Dir = repoRoot
87+
if out, err := cmd.CombinedOutput(); err != nil {
88+
t.Fatalf("go build failed: %v\n%s", err, string(out))
89+
}
90+
return binaryPath
91+
}
92+
93+
func writeScript(t *testing.T, path, content string) {
94+
t.Helper()
95+
if err := os.WriteFile(path, []byte(content), 0o755); err != nil {
96+
t.Fatalf("WriteFile(%s) error = %v", path, err)
97+
}
98+
}
99+
100+
func runConcave(ctx context.Context, repoRoot, binaryPath string, env []string, stdin string, args ...string) (string, error) {
101+
cmd := exec.CommandContext(ctx, binaryPath, args...)
102+
cmd.Dir = repoRoot
103+
cmd.Env = env
104+
cmd.Stdin = strings.NewReader(stdin)
105+
out, err := cmd.CombinedOutput()
106+
return string(out), err
107+
}
108+
109+
func waitForLab(t *testing.T, repoRoot, binaryPath string, env []string, openLog string) {
110+
t.Helper()
111+
112+
deadline := time.Now().Add(3 * time.Minute)
113+
var lastOutput string
114+
for time.Now().Before(deadline) {
115+
_ = os.WriteFile(openLog, nil, 0o644)
116+
117+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
118+
out, err := runConcave(ctx, repoRoot, binaryPath, env, "", "lab", "--suite", "boosting")
119+
cancel()
120+
lastOutput = out
121+
if err == nil {
122+
data, readErr := os.ReadFile(openLog)
123+
if readErr != nil {
124+
t.Fatalf("ReadFile(%s) error = %v", openLog, readErr)
125+
}
126+
url := strings.TrimSpace(string(data))
127+
if strings.Contains(url, "/lab?token=") {
128+
return
129+
}
130+
t.Fatalf("lab opened unexpected URL %q", url)
131+
}
132+
time.Sleep(5 * time.Second)
133+
}
134+
135+
t.Fatalf("lab never became ready; last output:\n%s", lastOutput)
136+
}

0 commit comments

Comments
 (0)