Skip to content

Commit cb2e102

Browse files
danielayazclaude
andauthored
CLI live test framework + initial API group testing (#3267)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent baa9bea commit cb2e102

19 files changed

+1782
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/legal/
44
/prebuilt/
55
/test/bin/
6+
/test/live/bin/
67
/vendor/
78

89
# RPM/DEB

.semaphore/live-tests.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
version: v1.0
2+
name: Live Integration Tests for Confluent CLI
3+
agent:
4+
machine:
5+
type: s1-prod-ubuntu24-04-amd64-2
6+
7+
auto_cancel:
8+
running:
9+
when: "false"
10+
11+
global_job_config:
12+
prologue:
13+
commands:
14+
- checkout
15+
- sem-version go $(cat .go-version)
16+
- export PATH=$(go env GOPATH)/bin:$PATH
17+
18+
execution_time_limit:
19+
hours: 24
20+
21+
blocks:
22+
- name: "Live Integration Tests"
23+
task:
24+
jobs:
25+
- name: "Run Live Tests"
26+
commands:
27+
- . vault-sem-get-secret v1/ci/kv/apif/cli/live-testing-data
28+
- . vault-sem-get-secret v1/ci/kv/apif/cli/slack-notifications-live-testing
29+
30+
- |
31+
set -e
32+
33+
trap '
34+
RC=$?
35+
if [ $RC -ne 0 ]; then
36+
echo "Live tests failed, sending Slack notification..."
37+
curl -X POST -H "Content-type: application/json" --data "{}" "$SLACK_WEBHOOK_URL"
38+
fi
39+
exit $RC
40+
' EXIT
41+
42+
echo "Running all live tests..."
43+
make live-test

.semaphore/semaphore.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,5 @@ after_pipeline:
103103
promotions:
104104
- name: "Run macOS builds (manual)"
105105
pipeline_file: ".semaphore/macos.yml"
106+
- name: "Run live integration tests"
107+
pipeline_file: ".semaphore/live-tests.yml"

Makefile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,52 @@ endif
129129
.PHONY: test
130130
test: unit-test integration-test
131131

132+
.PHONY: build-for-live-test
133+
build-for-live-test:
134+
go build -ldflags="-s -w -X main.disableUpdates=true" -o test/live/bin/confluent ./cmd/confluent
135+
136+
.PHONY: live-test
137+
live-test: build-for-live-test
138+
@if [ -z "$(CLI_LIVE_TEST_GROUPS)" ]; then \
139+
CLI_LIVE_TEST=1 go test ./test/live/ -v -run=".*Live$$" \
140+
-tags="live_test,all" -timeout 1440m -parallel 10; \
141+
else \
142+
TAGS="live_test"; \
143+
for group in $$(echo "$(CLI_LIVE_TEST_GROUPS)" | tr ',' ' '); do \
144+
TAGS="$$TAGS,$$group"; \
145+
done; \
146+
CLI_LIVE_TEST=1 go test ./test/live/ -v -run=".*Live$$" \
147+
-tags="$$TAGS" -timeout 1440m -parallel 10; \
148+
fi
149+
150+
.PHONY: live-test-core
151+
live-test-core:
152+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core"
153+
154+
.PHONY: live-test-kafka
155+
live-test-kafka:
156+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="kafka"
157+
158+
.PHONY: live-test-schema-registry
159+
live-test-schema-registry:
160+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="schema_registry"
161+
162+
.PHONY: live-test-iam
163+
live-test-iam:
164+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="iam"
165+
166+
.PHONY: live-test-auth
167+
live-test-auth:
168+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="auth"
169+
170+
.PHONY: live-test-connect
171+
live-test-connect:
172+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="connect"
173+
174+
.PHONY: live-test-essential
175+
live-test-essential:
176+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core,kafka,schema_registry,auth"
177+
132178
.PHONY: generate-packaging-patch
133179
generate-packaging-patch:
134180
diff -u Makefile debian/Makefile | sed "1 s_Makefile_cli/Makefile_" > debian/patches/standard_build_layout.patch

debian/patches/standard_build_layout.patch

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
--- cli/Makefile 2026-01-12 14:36:34.668189995 -0500
2-
+++ debian/Makefile 2025-09-19 13:34:24.112882728 -0400
3-
@@ -1,143 +1,163 @@
1+
--- cli/Makefile 2026-02-23 12:50:33.525992931 -0500
2+
+++ debian/Makefile 2025-09-22 14:01:33.829653470 -0400
3+
@@ -1,189 +1,163 @@
44
-SHELL := /bin/bash
55
-GORELEASER_VERSION := v2.13.3
66
+SHELL=/bin/bash
@@ -284,6 +284,52 @@
284284
-.PHONY: test
285285
-test: unit-test integration-test
286286
-
287+
-.PHONY: build-for-live-test
288+
-build-for-live-test:
289+
- go build -ldflags="-s -w -X main.disableUpdates=true" -o test/live/bin/confluent ./cmd/confluent
290+
-
291+
-.PHONY: live-test
292+
-live-test: build-for-live-test
293+
- @if [ -z "$(CLI_LIVE_TEST_GROUPS)" ]; then \
294+
- CLI_LIVE_TEST=1 go test ./test/live/ -v -run=".*Live$$" \
295+
- -tags="live_test,all" -timeout 1440m -parallel 10; \
296+
- else \
297+
- TAGS="live_test"; \
298+
- for group in $$(echo "$(CLI_LIVE_TEST_GROUPS)" | tr ',' ' '); do \
299+
- TAGS="$$TAGS,$$group"; \
300+
- done; \
301+
- CLI_LIVE_TEST=1 go test ./test/live/ -v -run=".*Live$$" \
302+
- -tags="$$TAGS" -timeout 1440m -parallel 10; \
303+
- fi
304+
-
305+
-.PHONY: live-test-core
306+
-live-test-core:
307+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core"
308+
-
309+
-.PHONY: live-test-kafka
310+
-live-test-kafka:
311+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="kafka"
312+
-
313+
-.PHONY: live-test-schema-registry
314+
-live-test-schema-registry:
315+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="schema_registry"
316+
-
317+
-.PHONY: live-test-iam
318+
-live-test-iam:
319+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="iam"
320+
-
321+
-.PHONY: live-test-auth
322+
-live-test-auth:
323+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="auth"
324+
-
325+
-.PHONY: live-test-connect
326+
-live-test-connect:
327+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="connect"
328+
-
329+
-.PHONY: live-test-essential
330+
-live-test-essential:
331+
- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core,kafka,schema_registry,auth"
332+
-
287333
-.PHONY: generate-packaging-patch
288334
-generate-packaging-patch:
289335
- diff -u Makefile debian/Makefile | sed "1 s_Makefile_cli/Makefile_" > debian/patches/standard_build_layout.patch

test/live/README.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# CLI Live Integration Tests
2+
3+
Live integration tests run the real CLI binary against Confluent Cloud. They create, read, update, and delete real resources to verify end-to-end CLI behavior.
4+
5+
## Prerequisites
6+
7+
1. **CLI binary** — Build with `make build-for-live-test`
8+
2. **Confluent Cloud credentials** — Set the following environment variables:
9+
10+
| Variable | Required | Description |
11+
|---|---|---|
12+
| `CONFLUENT_CLOUD_EMAIL` | Yes | Confluent Cloud login email |
13+
| `CONFLUENT_CLOUD_PASSWORD` | Yes | Confluent Cloud login password |
14+
| `CLI_LIVE_TEST_CLOUD` | No | Cloud provider: `aws` (default), `gcp`, `azure` |
15+
| `CLI_LIVE_TEST_REGION` | No | Cloud region (default: `us-east-1`) |
16+
| `LIVE_TEST_ENVIRONMENT_ID` | Kafka topics only | Pre-existing environment ID for topic tests |
17+
| `KAFKA_STANDARD_AWS_CLUSTER_ID` | Kafka topics only | Pre-existing cluster ID for topic tests |
18+
19+
## Running Tests
20+
21+
### All tests
22+
```bash
23+
make live-test
24+
```
25+
26+
### By group
27+
```bash
28+
make live-test-core # environment, service account, API key
29+
make live-test-essential # core + kafka
30+
make live-test CLI_LIVE_TEST_GROUPS="kafka" # kafka only
31+
```
32+
33+
### Multi-cloud
34+
```bash
35+
CLI_LIVE_TEST_CLOUD=gcp CLI_LIVE_TEST_REGION=us-east1 make live-test-essential
36+
```
37+
38+
### Single test
39+
```bash
40+
CLI_LIVE_TEST=1 go test ./test/live/ -v -run TestLive/TestKafkaClusterCRUDLive \
41+
-tags="live_test,kafka" -timeout 30m
42+
```
43+
44+
## Test Groups
45+
46+
Tests are organized into groups via Go build tags:
47+
48+
| Group | Tag | Tests |
49+
|---|---|---|
50+
| Core | `core` | Environment, Service Account, API Key CRUD |
51+
| Kafka | `kafka` | Kafka Cluster CRUD, Kafka Topic CRUD |
52+
| All | `all` | Everything |
53+
54+
The `essential` group in Semaphore/Makefile maps to `core,kafka`.
55+
56+
## Concurrency Model
57+
58+
- Each test method calls `s.setupTestContext(t)` which creates an **isolated HOME directory** and logs in. This means each test has its own CLI config — no shared state.
59+
- Tests opt in to concurrency by calling `t.Parallel()` at the start. All current tests do this.
60+
- The `-parallel 10` flag in the Makefile controls max concurrent tests.
61+
- Tests that need sequential execution (e.g., tests modifying shared external state) should simply omit the `t.Parallel()` call.
62+
63+
## Writing a New Test
64+
65+
### 1. Create a test file
66+
67+
```go
68+
//go:build live_test && (all || mygroup)
69+
70+
package live
71+
72+
func (s *CLILiveTestSuite) TestMyResourceCRUDLive() {
73+
t := s.T()
74+
t.Parallel()
75+
state := s.setupTestContext(t)
76+
77+
// ... test body ...
78+
}
79+
```
80+
81+
The test method name **must** end with `Live` to match the `-run=".*Live$"` filter.
82+
83+
### 2. Define test steps
84+
85+
Use `CLILiveTest` structs for each CLI command:
86+
87+
```go
88+
steps := []CLILiveTest{
89+
{
90+
Name: "Create resource",
91+
Args: "resource create my-name -o json",
92+
JSONFieldsExist: []string{"id"},
93+
CaptureID: "resource_id", // captures JSON "id" field into state
94+
},
95+
{
96+
Name: "Describe resource",
97+
Args: "resource describe {{.resource_id}} -o json",
98+
UseStateVars: true, // enables {{.key}} template substitution
99+
JSONFields: map[string]string{"name": "my-name"},
100+
},
101+
}
102+
```
103+
104+
### 3. Register cleanup
105+
106+
Always register cleanup **before** creating resources (LIFO execution order):
107+
108+
```go
109+
s.registerCleanup(t, "resource delete {{.resource_id}} --force", state)
110+
```
111+
112+
### 4. Run steps
113+
114+
```go
115+
for _, step := range steps {
116+
t.Run(step.Name, func(t *testing.T) {
117+
s.runLiveCommand(t, step, state)
118+
})
119+
}
120+
```
121+
122+
### CLILiveTest Field Reference
123+
124+
| Field | Type | Description |
125+
|---|---|---|
126+
| `Name` | `string` | Step name shown in output |
127+
| `Args` | `string` | CLI arguments (supports `{{.key}}` when `UseStateVars` is true) |
128+
| `ExitCode` | `int` | Expected exit code (default 0) |
129+
| `Input` | `string` | Stdin content |
130+
| `Contains` | `[]string` | Strings that must appear in output |
131+
| `NotContains` | `[]string` | Strings that must NOT appear in output |
132+
| `Regex` | `[]string` | Regex patterns output must match |
133+
| `JSONFields` | `map[string]string` | JSON fields to check (empty value = any non-empty value) |
134+
| `JSONFieldsExist` | `[]string` | JSON fields that must exist (any value) |
135+
| `WantFunc` | `func(t, output, state)` | Custom assertion function |
136+
| `CaptureID` | `string` | State key to store extracted JSON "id" field |
137+
| `UseStateVars` | `bool` | Enable `{{.key}}` template substitution in Args |
138+
139+
### Async Operations
140+
141+
For operations that take time (e.g., cluster provisioning), use `waitForCondition`:
142+
143+
```go
144+
s.waitForCondition(t,
145+
"kafka cluster describe {{.cluster_id}} -o json",
146+
state,
147+
func(output string) bool {
148+
return strings.EqualFold(extractJSONField(t, output, "status"), "UP")
149+
},
150+
30*time.Second, // poll interval
151+
10*time.Minute, // timeout
152+
)
153+
```
154+
155+
## Adding a New Test Group
156+
157+
1. Create test file(s) with build tag: `//go:build live_test && (all || mygroup)`
158+
2. Add a Makefile target:
159+
```makefile
160+
.PHONY: live-test-mygroup
161+
live-test-mygroup:
162+
@$(MAKE) live-test CLI_LIVE_TEST_GROUPS="mygroup"
163+
```
164+
3. Update the Semaphore promotion parameters if the group should be selectable in CI.
165+
166+
## CI (Semaphore)
167+
168+
Live tests are triggered via the "Run live integration tests" promotion in `.semaphore/semaphore.yml`. Parameters:
169+
170+
- **CLI_LIVE_TEST_GROUPS** — Test group to run (default: `essential`)
171+
- **CLI_LIVE_TEST_CLOUD** — Cloud provider (default: `aws`)
172+
- **CLI_LIVE_TEST_REGION** — Cloud region (default: `us-east-1`)
173+
174+
Credentials are loaded from Vault secrets in the Semaphore pipeline.

0 commit comments

Comments
 (0)