Skip to content

Commit bee7c39

Browse files
authored
Merge pull request #62 from dispatchrun/dispatch_run_tests
`dispatch run` integration tests
2 parents ea4a980 + 712c631 commit bee7c39

File tree

4 files changed

+196
-5
lines changed

4 files changed

+196
-5
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ jobs:
5454
go-version: ${{ env.GOVERSION }}
5555
- run: git config --global url.https://${{ secrets.PRIVATE_REPO }}@github.com.insteadOf https://github.com
5656
- run: go mod download
57-
- run: go test -cover ./...
57+
- run: make test-cover

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ REGISTRY ?= 714918108619.dkr.ecr.us-west-2.amazonaws.com
1111
DISPATCH = $(BUILD)/dispatch
1212
IMAGE = $(REGISTRY)/dispatch:$(TAG)
1313

14-
test:
14+
test: dispatch
1515
$(GO) test ./...
1616

17+
test-cover: dispatch
18+
$(GO) test -cover ./...
19+
1720
lint:
1821
golangci-lint run ./...
1922

cli/run.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,8 @@ a pristine environment in which function calls can be dispatched and
8080
handled by the local application. To start the command using a previous
8181
session, use the --session option to specify a session ID from a
8282
previous run.`, defaultEndpoint),
83-
Args: cobra.MinimumNArgs(1),
84-
SilenceUsage: true,
85-
GroupID: "dispatch",
83+
Args: cobra.MinimumNArgs(1),
84+
GroupID: "dispatch",
8685
PreRunE: func(cmd *cobra.Command, args []string) error {
8786
return runConfigFlow()
8887
},

cli/run_test.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package cli
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"runtime"
11+
"strings"
12+
"testing"
13+
"time"
14+
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
var dispatchBinary = filepath.Join("../build", runtime.GOOS, runtime.GOARCH, "dispatch")
19+
20+
func TestRunCommand(t *testing.T) {
21+
t.Run("Run with non-existent env file", func(t *testing.T) {
22+
t.Parallel()
23+
24+
buff, err := execRunCommand(&[]string{}, "run", "--env-file", "non-existent.env", "--", "echo", "hello")
25+
if err != nil {
26+
t.Fatal(err.Error())
27+
}
28+
29+
assert.Regexp(t, "Error: failed to load env file from .+/dispatch/cli/non-existent.env: open non-existent.env: no such file or directory\n", buff.String())
30+
})
31+
32+
t.Run("Run with env file", func(t *testing.T) {
33+
t.Parallel()
34+
35+
envFile, err := createEnvFile(t.TempDir(), []byte("CHARACTER=rick_sanchez"))
36+
defer os.Remove(envFile)
37+
if err != nil {
38+
t.Fatalf("Failed to write env file: %v", err)
39+
}
40+
41+
buff, err := execRunCommand(&[]string{}, "run", "--env-file", envFile, "--", "printenv", "CHARACTER")
42+
if err != nil {
43+
t.Fatal(err.Error())
44+
}
45+
46+
result, found := findEnvVariableInLogs(&buff)
47+
if !found {
48+
t.Fatalf("Expected printenv in the output: %s", buff.String())
49+
}
50+
assert.Equal(t, "rick_sanchez", result, fmt.Sprintf("Expected 'printenv | rick_sanchez' in the output, got 'printenv | %s'", result))
51+
})
52+
53+
t.Run("Run with env variable", func(t *testing.T) {
54+
t.Parallel()
55+
56+
// Set environment variables
57+
envVars := []string{"CHARACTER=morty_smith"}
58+
59+
buff, err := execRunCommand(&envVars, "run", "--", "printenv", "CHARACTER")
60+
if err != nil {
61+
t.Fatal(err.Error())
62+
}
63+
64+
result, found := findEnvVariableInLogs(&buff)
65+
if !found {
66+
t.Fatalf("Expected printenv in the output: %s", buff.String())
67+
}
68+
assert.Equal(t, "morty_smith", result, fmt.Sprintf("Expected 'printenv | morty_smith' in the output, got 'printenv | %s'", result))
69+
})
70+
71+
t.Run("Run with env variable in command line has priority over the one in the env file", func(t *testing.T) {
72+
t.Parallel()
73+
74+
envFile, err := createEnvFile(t.TempDir(), []byte("CHARACTER=rick_sanchez"))
75+
defer os.Remove(envFile)
76+
if err != nil {
77+
t.Fatalf("Failed to write env file: %v", err)
78+
}
79+
80+
// Set environment variables
81+
envVars := []string{"CHARACTER=morty_smith"}
82+
buff, err := execRunCommand(&envVars, "run", "--env-file", envFile, "--", "printenv", "CHARACTER")
83+
if err != nil {
84+
t.Fatal(err.Error())
85+
}
86+
87+
result, found := findEnvVariableInLogs(&buff)
88+
if !found {
89+
t.Fatalf("Expected printenv in the output: %s", buff.String())
90+
}
91+
assert.Equal(t, "morty_smith", result, fmt.Sprintf("Expected 'printenv | morty_smith' in the output, got 'printenv | %s'", result))
92+
})
93+
94+
t.Run("Run with env variable in local env vars has priority over the one in the env file", func(t *testing.T) {
95+
// Do not use t.Parallel() here as we are manipulating the environment!
96+
97+
// Set environment variables
98+
os.Setenv("CHARACTER", "morty_smith")
99+
defer os.Unsetenv("CHARACTER")
100+
101+
envFile, err := createEnvFile(t.TempDir(), []byte("CHARACTER=rick_sanchez"))
102+
defer os.Remove(envFile)
103+
104+
if err != nil {
105+
t.Fatalf("Failed to write env file: %v", err)
106+
}
107+
108+
buff, err := execRunCommand(&[]string{}, "run", "--env-file", envFile, "--", "printenv", "CHARACTER")
109+
if err != nil {
110+
t.Fatal(err.Error())
111+
}
112+
113+
result, found := findEnvVariableInLogs(&buff)
114+
if !found {
115+
t.Fatalf("Expected printenv in the output: %s\n\n", buff.String())
116+
}
117+
assert.Equal(t, "morty_smith", result, fmt.Sprintf("Expected 'printenv | morty_smith' in the output, got 'printenv | %s'", result))
118+
})
119+
}
120+
121+
func execRunCommand(envVars *[]string, arg ...string) (bytes.Buffer, error) {
122+
// Create a context with a timeout to ensure the process doesn't run indefinitely
123+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
124+
defer cancel()
125+
126+
// add the api key to the arguments so the command can run without `dispatch login` being run first
127+
arg = append(arg[:1], append([]string{"--api-key", "00000000"}, arg[1:]...)...)
128+
129+
// Set up the command
130+
cmd := exec.CommandContext(ctx, dispatchBinary, arg...)
131+
132+
if len(*envVars) != 0 {
133+
// Set environment variables
134+
cmd.Env = append(os.Environ(), *envVars...)
135+
}
136+
137+
// Capture the standard error
138+
var errBuf bytes.Buffer
139+
cmd.Stderr = &errBuf
140+
141+
// Start the command
142+
if err := cmd.Start(); err != nil {
143+
return errBuf, fmt.Errorf("Failed to start command: %w", err)
144+
}
145+
146+
// Wait for the command to finish or for the context to timeout
147+
// We use Wait() instead of Run() so that we can capture the error
148+
// For example:
149+
// FOO=bar ./build/darwin/amd64/dispatch run -- printenv FOO
150+
// This will exit with
151+
// Error: command 'printenv FOO' exited unexpectedly
152+
// but also it will print...
153+
// printenv | bar
154+
// to the logs and that is exactly what we want to test
155+
// If context timeout occurs, than something went wrong
156+
// and `dispatch run` is running indefinitely.
157+
if err := cmd.Wait(); err != nil {
158+
// Check if the error is due to context timeout (command running too long)
159+
if ctx.Err() == context.DeadlineExceeded {
160+
return errBuf, fmt.Errorf("Command timed out: %w", err)
161+
}
162+
}
163+
164+
return errBuf, nil
165+
}
166+
167+
func createEnvFile(path string, content []byte) (string, error) {
168+
envFile := filepath.Join(path, "test.env")
169+
err := os.WriteFile(envFile, content, 0600)
170+
return envFile, err
171+
}
172+
173+
func findEnvVariableInLogs(buf *bytes.Buffer) (string, bool) {
174+
var result string
175+
found := false
176+
177+
// Split the log into lines
178+
lines := strings.Split(buf.String(), "\n")
179+
180+
// Iterate over each line and check for the condition
181+
for _, line := range lines {
182+
if strings.Contains(line, "printenv | ") {
183+
result = strings.Split(line, "printenv | ")[1]
184+
found = true
185+
break
186+
}
187+
}
188+
return result, found
189+
}

0 commit comments

Comments
 (0)