Skip to content

Commit 97fdb17

Browse files
committed
- Don't double kill the test application in CLI tests (otherwise hangs on cmd.Wait()
- Just move the test app lifecycle around the other tests - Support timeout in dbos start - Handle signals in CLI test app
1 parent 9b1ff6f commit 97fdb17

File tree

3 files changed

+74
-94
lines changed

3 files changed

+74
-94
lines changed

cmd/dbos/cli_integration_test.go

Lines changed: 53 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"bytes"
54
"context"
65
"database/sql"
76
_ "embed"
@@ -14,6 +13,7 @@ import (
1413
"os/exec"
1514
"path/filepath"
1615
"runtime"
16+
"syscall"
1717
"testing"
1818
"time"
1919

@@ -96,21 +96,28 @@ func TestCLIWorkflow(t *testing.T) {
9696
testProjectInitialization(t, cliPath)
9797
})
9898

99-
t.Run("ApplicationLifecycle", func(t *testing.T) {
100-
cmd := testApplicationLifecycle(t, cliPath)
101-
t.Cleanup(func() {
102-
if cmd.Process != nil {
103-
/*
104-
fmt.Println(cmd.Stderr)
105-
fmt.Println(cmd.Stdout)
106-
*/
107-
err := cmd.Process.Signal(os.Interrupt)
108-
require.NoError(t, err, "Failed to send interrupt signal to application process")
109-
time.Sleep(1 * time.Second) // Give it a moment to shut down gracefully
110-
err = cmd.Process.Kill()
111-
require.NoError(t, err, "Failed to kill application process")
112-
}
113-
})
99+
// Start a test application using dbos start
100+
cmd := exec.CommandContext(context.Background(), cliPath, "start")
101+
cmd.Env = append(os.Environ(), "DBOS_SYSTEM_DATABASE_URL="+getDatabaseURL())
102+
err = cmd.Start()
103+
require.NoError(t, err, "Failed to start application")
104+
// Wait for server to be ready
105+
require.Eventually(t, func() bool {
106+
resp, err := http.Get("http://localhost:" + testServerPort)
107+
if err != nil {
108+
return false
109+
}
110+
resp.Body.Close()
111+
return resp.StatusCode == http.StatusOK
112+
}, 10*time.Second, 500*time.Millisecond, "Server should start within 10 seconds")
113+
114+
t.Cleanup(func() {
115+
fmt.Printf("Cleaning up application process %d\n", cmd.Process.Pid)
116+
// fmt.Println(cmd.Stderr)
117+
// fmt.Println(cmd.Stdout)
118+
err := syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
119+
require.NoError(t, err, "Failed to send interrupt signal to application process")
120+
_ = cmd.Wait()
114121
})
115122

116123
t.Run("WorkflowCommands", func(t *testing.T) {
@@ -124,7 +131,6 @@ func TestCLIWorkflow(t *testing.T) {
124131

125132
// testProjectInitialization verifies project initialization
126133
func testProjectInitialization(t *testing.T, cliPath string) {
127-
128134
// Initialize project
129135
cmd := exec.Command(cliPath, "init", testProjectName)
130136
output, err := cmd.CombinedOutput()
@@ -168,71 +174,6 @@ func testProjectInitialization(t *testing.T, cliPath string) {
168174
require.NoError(t, err, "go mod tidy failed: %s", string(modOutput))
169175
}
170176

171-
// testApplicationLifecycle starts the application and triggers workflows
172-
func testApplicationLifecycle(t *testing.T, cliPath string) *exec.Cmd {
173-
// Should already be in project directory from previous test
174-
175-
// Start the application in background
176-
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
177-
defer cancel()
178-
179-
cmd := exec.CommandContext(ctx, cliPath, "start")
180-
cmd.Env = append(os.Environ(), "DBOS_SYSTEM_DATABASE_URL="+getDatabaseURL())
181-
182-
// Capture output for debugging
183-
var stdout, stderr bytes.Buffer
184-
cmd.Stdout = &stdout
185-
cmd.Stderr = &stderr
186-
187-
err := cmd.Start()
188-
require.NoError(t, err, "Failed to start application")
189-
190-
// Wait for server to be ready
191-
require.Eventually(t, func() bool {
192-
resp, err := http.Get("http://localhost:" + testServerPort)
193-
if err != nil {
194-
return false
195-
}
196-
resp.Body.Close()
197-
return resp.StatusCode == http.StatusOK
198-
}, 10*time.Second, 500*time.Millisecond, "Server should start within 10 seconds")
199-
200-
// Trigger workflows via HTTP endpoints
201-
t.Run("TriggerExampleWorkflow", func(t *testing.T) {
202-
resp, err := http.Get("http://localhost:" + testServerPort + "/workflow")
203-
require.NoError(t, err, "Failed to trigger workflow")
204-
defer resp.Body.Close()
205-
206-
body, err := io.ReadAll(resp.Body)
207-
require.NoError(t, err)
208-
209-
assert.Equal(t, http.StatusOK, resp.StatusCode, "Workflow endpoint should return 200")
210-
assert.Contains(t, string(body), "Workflow result", "Should contain workflow result")
211-
})
212-
213-
t.Run("TriggerQueueWorkflow", func(t *testing.T) {
214-
resp, err := http.Get("http://localhost:" + testServerPort + "/queue")
215-
require.NoError(t, err, "Failed to trigger queue workflow")
216-
defer resp.Body.Close()
217-
218-
body, err := io.ReadAll(resp.Body)
219-
require.NoError(t, err)
220-
221-
assert.Equal(t, http.StatusOK, resp.StatusCode, "Queue endpoint should return 200")
222-
223-
// Parse JSON response to get workflow ID
224-
var response map[string]string
225-
err = json.Unmarshal(body, &response)
226-
require.NoError(t, err, "Should be valid JSON response")
227-
228-
workflowID, exists := response["workflow_id"]
229-
assert.True(t, exists, "Response should contain workflow_id")
230-
assert.NotEmpty(t, workflowID, "Workflow ID should not be empty")
231-
})
232-
233-
return cmd
234-
}
235-
236177
// testWorkflowCommands comprehensively tests all workflow CLI commands
237178
func testWorkflowCommands(t *testing.T, cliPath string) {
238179

@@ -260,8 +201,29 @@ func testWorkflowCommands(t *testing.T, cliPath string) {
260201
// testListWorkflows tests various workflow listing scenarios
261202
func testListWorkflows(t *testing.T, cliPath string) {
262203
// Create some test workflows first to ensure we have data to filter
263-
// The previous test functions have already created workflows that we can query
204+
resp, err := http.Get("http://localhost:" + testServerPort + "/workflow")
205+
require.NoError(t, err, "Failed to trigger workflow")
206+
defer resp.Body.Close()
207+
body, err := io.ReadAll(resp.Body)
208+
require.NoError(t, err)
209+
assert.Equal(t, http.StatusOK, resp.StatusCode, "Workflow endpoint should return 200")
210+
assert.Contains(t, string(body), "Workflow result", "Should contain workflow result")
211+
212+
resp, err = http.Get("http://localhost:" + testServerPort + "/queue")
213+
require.NoError(t, err, "Failed to trigger queue workflow")
214+
defer resp.Body.Close()
215+
body, err = io.ReadAll(resp.Body)
216+
require.NoError(t, err)
217+
assert.Equal(t, http.StatusOK, resp.StatusCode, "Queue endpoint should return 200")
218+
219+
// Parse JSON response to get workflow ID
220+
var response map[string]string
221+
err = json.Unmarshal(body, &response)
222+
require.NoError(t, err, "Should be valid JSON response")
264223

224+
workflowID, exists := response["workflow_id"]
225+
assert.True(t, exists, "Response should contain workflow_id")
226+
assert.NotEmpty(t, workflowID, "Workflow ID should not be empty")
265227
// Get the current time for time-based filtering
266228
currentTime := time.Now()
267229

@@ -728,14 +690,14 @@ func buildCLI(t *testing.T) string {
728690
// Build output path in the cmd directory
729691
cliPath := filepath.Join(cmdDir, "dbos-cli-test")
730692

731-
// Check if already built
732-
if _, err := os.Stat(cliPath); os.IsNotExist(err) {
733-
// Build the CLI from the cmd directory
734-
buildCmd := exec.Command("go", "build", "-o", "dbos-cli-test", ".")
735-
buildCmd.Dir = cmdDir
736-
buildOutput, buildErr := buildCmd.CombinedOutput()
737-
require.NoError(t, buildErr, "Failed to build CLI: %s", string(buildOutput))
738-
}
693+
// Delete any existing binary before building
694+
os.Remove(cliPath)
695+
696+
// Build the CLI from the cmd directory
697+
buildCmd := exec.Command("go", "build", "-o", "dbos-cli-test", ".")
698+
buildCmd.Dir = cmdDir
699+
buildOutput, buildErr := buildCmd.CombinedOutput()
700+
require.NoError(t, buildErr, "Failed to build CLI: %s", string(buildOutput))
739701

740702
// Return absolute path
741703
absPath, err := filepath.Abs(cliPath)

cmd/dbos/cli_test_app.go.test

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"net/http"
88
"os"
9+
"os/signal"
10+
"syscall"
911
"time"
1012

1113
"github.com/dbos-inc/dbos-transact-golang/dbos"
@@ -68,7 +70,7 @@ func QueueWorkflow(ctx dbos.DBOSContext, _ string) (string, error) {
6870
}
6971
handles[i] = handle
7072
}
71-
time.Sleep(10 * time.Second) // give some time for our tests to do wf management
73+
time.Sleep(10 * time.Second) // give some time for our tests to do wf management
7274
return fmt.Sprintf("Successfully enqueued %d steps", len(handles)), nil
7375
}
7476

@@ -114,6 +116,16 @@ func main() {
114116
http.HandleFunc("/queue", queueHandler)
115117
http.HandleFunc("/", healthHandler)
116118

119+
// Set up signal handling
120+
sigChan := make(chan os.Signal, 1)
121+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
122+
123+
go func() {
124+
<-sigChan
125+
fmt.Println("Received interrupt signal, shutting down...")
126+
os.Exit(0)
127+
}()
128+
117129
fmt.Println("Server starting on http://localhost:8080")
118130
err = http.ListenAndServe(":8080", nil)
119131
if err != nil {
@@ -156,5 +168,5 @@ func queueHandler(w http.ResponseWriter, r *http.Request) {
156168
}
157169

158170
func healthHandler(w http.ResponseWriter, r *http.Request) {
159-
fmt.Fprintf(w, "healthy")
160-
}
171+
fmt.Fprintf(w, "healthy")
172+
}

cmd/dbos/start.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os/signal"
88
"runtime"
99
"syscall"
10+
"time"
1011

1112
"github.com/spf13/cobra"
1213
)
@@ -90,6 +91,11 @@ func runStart(cmd *cobra.Command, args []string) error {
9091
if runtime.GOOS != "windows" {
9192
syscall.Kill(-process.Process.Pid, syscall.SIGKILL)
9293
}
94+
case <-time.After(10 * time.Second):
95+
// Force kill after timeout
96+
if runtime.GOOS != "windows" {
97+
syscall.Kill(-process.Process.Pid, syscall.SIGKILL)
98+
}
9399
}
94100

95101
os.Exit(0)

0 commit comments

Comments
 (0)