Skip to content

Commit 685c583

Browse files
committed
fix: Add test for interrupt/cancellation
1 parent 3e4005a commit 685c583

File tree

4 files changed

+133
-14
lines changed

4 files changed

+133
-14
lines changed

provisioner/terraform/parse_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
func TestParse(t *testing.T) {
1717
t.Parallel()
1818

19-
ctx, api := setupProvisioner(t)
19+
ctx, api := setupProvisioner(t, nil)
2020

2121
testCases := []struct {
2222
Name string
@@ -171,7 +171,7 @@ func TestParse(t *testing.T) {
171171
// Write all files to the temporary test directory.
172172
directory := t.TempDir()
173173
for path, content := range testCase.Files {
174-
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0600)
174+
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0o600)
175175
require.NoError(t, err)
176176
}
177177

provisioner/terraform/provision.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
7373
start := request.GetStart()
7474

7575
e := s.executor(start.Directory)
76-
if err = e.checkMinVersion(stream.Context()); err != nil {
76+
if err = e.checkMinVersion(ctx); err != nil {
7777
return err
7878
}
7979
if err = logTerraformEnvVars(logr); err != nil {

provisioner/terraform/provision_test.go

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ package terraform_test
55
import (
66
"context"
77
"encoding/json"
8+
"fmt"
89
"os"
910
"path/filepath"
11+
"runtime"
1012
"sort"
1113
"strings"
1214
"testing"
15+
"time"
1316

1417
"github.com/stretchr/testify/assert"
1518
"github.com/stretchr/testify/require"
@@ -22,7 +25,15 @@ import (
2225
"github.com/coder/coder/provisionersdk/proto"
2326
)
2427

25-
func setupProvisioner(t *testing.T) (context.Context, proto.DRPCProvisionerClient) {
28+
type provisionerServeOptions struct {
29+
binaryPath string
30+
exitTimeout time.Duration
31+
}
32+
33+
func setupProvisioner(t *testing.T, opts *provisionerServeOptions) (context.Context, proto.DRPCProvisionerClient) {
34+
if opts == nil {
35+
opts = &provisionerServeOptions{}
36+
}
2637
cachePath := t.TempDir()
2738
client, server := provisionersdk.TransportPipe()
2839
ctx, cancelFunc := context.WithCancel(context.Background())
@@ -39,18 +50,126 @@ func setupProvisioner(t *testing.T) (context.Context, proto.DRPCProvisionerClien
3950
ServeOptions: &provisionersdk.ServeOptions{
4051
Listener: server,
4152
},
42-
CachePath: cachePath,
43-
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
53+
BinaryPath: opts.binaryPath,
54+
CachePath: cachePath,
55+
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
56+
ExitTimeout: opts.exitTimeout,
4457
})
4558
}()
4659
api := proto.NewDRPCProvisionerClient(provisionersdk.Conn(client))
4760
return ctx, api
4861
}
4962

63+
func TestProvision_Cancel(t *testing.T) {
64+
if runtime.GOOS == "windows" {
65+
t.Skip("This test uses interrupts and is not supported on Windows")
66+
}
67+
68+
t.Parallel()
69+
70+
cwd, err := os.Getwd()
71+
require.NoError(t, err)
72+
fakeBin := filepath.Join(cwd, "testdata", "bin", "terraform_fake_cancel.sh")
73+
74+
tests := []struct {
75+
name string
76+
mode string
77+
startSequence []string
78+
wantLog []string
79+
}{
80+
{
81+
name: "Cancel init",
82+
mode: "init",
83+
startSequence: []string{"init_start"},
84+
wantLog: []string{"interrupt", "exit"},
85+
},
86+
{
87+
name: "Cancel apply",
88+
mode: "apply",
89+
startSequence: []string{"init", "apply_start"},
90+
wantLog: []string{"interrupt", "exit"},
91+
},
92+
}
93+
for _, tt := range tests {
94+
tt := tt
95+
t.Run(tt.name, func(t *testing.T) {
96+
t.Parallel()
97+
98+
dir := t.TempDir()
99+
binPath := filepath.Join(dir, "terraform")
100+
101+
// Example: exec /path/to/terrafork_fake_cancel.sh 1.2.1 apply "$@"
102+
content := fmt.Sprintf("#!/bin/sh\nexec %q %s %s \"$@\"\n", fakeBin, terraform.TerraformVersion.String(), tt.mode)
103+
err = os.WriteFile(binPath, []byte(content), 0o755) //#nosec
104+
require.NoError(t, err)
105+
106+
ctx, api := setupProvisioner(t, &provisionerServeOptions{
107+
binaryPath: binPath,
108+
exitTimeout: time.Nanosecond,
109+
})
110+
111+
response, err := api.Provision(ctx)
112+
require.NoError(t, err)
113+
err = response.Send(&proto.Provision_Request{
114+
Type: &proto.Provision_Request_Start{
115+
Start: &proto.Provision_Start{
116+
Directory: dir,
117+
DryRun: false,
118+
ParameterValues: []*proto.ParameterValue{{
119+
DestinationScheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
120+
Name: "A",
121+
Value: "example",
122+
}},
123+
Metadata: &proto.Provision_Metadata{},
124+
},
125+
},
126+
})
127+
require.NoError(t, err)
128+
129+
for _, line := range tt.startSequence {
130+
LoopStart:
131+
msg, err := response.Recv()
132+
require.NoError(t, err)
133+
134+
t.Log(msg.Type)
135+
136+
log := msg.GetLog()
137+
if log == nil {
138+
goto LoopStart
139+
}
140+
require.NotNil(t, log)
141+
require.Equal(t, line, log.Output)
142+
}
143+
144+
err = response.Send(&proto.Provision_Request{
145+
Type: &proto.Provision_Request_Cancel{
146+
Cancel: &proto.Provision_Cancel{},
147+
},
148+
})
149+
require.NoError(t, err)
150+
151+
var gotLog []string
152+
for {
153+
msg, err := response.Recv()
154+
require.NoError(t, err)
155+
156+
if log := msg.GetLog(); log != nil {
157+
gotLog = append(gotLog, log.Output)
158+
}
159+
if c := msg.GetComplete(); c != nil {
160+
require.Contains(t, c.Error, "exit status 1")
161+
break
162+
}
163+
}
164+
require.Equal(t, tt.wantLog, gotLog)
165+
})
166+
}
167+
}
168+
50169
func TestProvision(t *testing.T) {
51170
t.Parallel()
52171

53-
ctx, api := setupProvisioner(t)
172+
ctx, api := setupProvisioner(t, nil)
54173

55174
testCases := []struct {
56175
Name string
@@ -209,7 +328,7 @@ func TestProvision(t *testing.T) {
209328

210329
directory := t.TempDir()
211330
for path, content := range testCase.Files {
212-
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0600)
331+
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0o600)
213332
require.NoError(t, err)
214333
}
215334

@@ -302,11 +421,11 @@ func TestProvision_ExtraEnv(t *testing.T) {
302421
t.Setenv("TF_LOG", "INFO")
303422
t.Setenv("TF_SUPERSECRET", secretValue)
304423

305-
ctx, api := setupProvisioner(t)
424+
ctx, api := setupProvisioner(t, nil)
306425

307426
directory := t.TempDir()
308427
path := filepath.Join(directory, "main.tf")
309-
err := os.WriteFile(path, []byte(`resource "null_resource" "A" {}`), 0600)
428+
err := os.WriteFile(path, []byte(`resource "null_resource" "A" {}`), 0o600)
310429
require.NoError(t, err)
311430

312431
request := &proto.Provision_Request{

provisioner/terraform/serve.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import (
1717
)
1818

1919
var (
20-
// This is the exact version of Terraform used internally
21-
// when Terraform is missing on the system.
22-
terraformVersion = version.Must(version.NewVersion("1.2.1"))
20+
// TerraformVersion is the version of Terraform used internally
21+
// when Terraform is not available on the system.
22+
TerraformVersion = version.Must(version.NewVersion("1.2.1"))
2323

2424
minTerraformVersion = version.Must(version.NewVersion("1.1.0"))
2525
maxTerraformVersion = version.Must(version.NewVersion("1.2.1"))
@@ -96,7 +96,7 @@ func Serve(ctx context.Context, options *ServeOptions) error {
9696
installer := &releases.ExactVersion{
9797
InstallDir: options.CachePath,
9898
Product: product.Terraform,
99-
Version: terraformVersion,
99+
Version: TerraformVersion,
100100
}
101101

102102
execPath, err := installer.Install(ctx)

0 commit comments

Comments
 (0)