@@ -5,11 +5,14 @@ package terraform_test
5
5
import (
6
6
"context"
7
7
"encoding/json"
8
+ "fmt"
8
9
"os"
9
10
"path/filepath"
11
+ "runtime"
10
12
"sort"
11
13
"strings"
12
14
"testing"
15
+ "time"
13
16
14
17
"github.com/stretchr/testify/assert"
15
18
"github.com/stretchr/testify/require"
@@ -22,7 +25,15 @@ import (
22
25
"github.com/coder/coder/provisionersdk/proto"
23
26
)
24
27
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
+ }
26
37
cachePath := t .TempDir ()
27
38
client , server := provisionersdk .TransportPipe ()
28
39
ctx , cancelFunc := context .WithCancel (context .Background ())
@@ -39,18 +50,126 @@ func setupProvisioner(t *testing.T) (context.Context, proto.DRPCProvisionerClien
39
50
ServeOptions : & provisionersdk.ServeOptions {
40
51
Listener : server ,
41
52
},
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 ,
44
57
})
45
58
}()
46
59
api := proto .NewDRPCProvisionerClient (provisionersdk .Conn (client ))
47
60
return ctx , api
48
61
}
49
62
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\n exec %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
+
50
169
func TestProvision (t * testing.T ) {
51
170
t .Parallel ()
52
171
53
- ctx , api := setupProvisioner (t )
172
+ ctx , api := setupProvisioner (t , nil )
54
173
55
174
testCases := []struct {
56
175
Name string
@@ -209,7 +328,7 @@ func TestProvision(t *testing.T) {
209
328
210
329
directory := t .TempDir ()
211
330
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 )
213
332
require .NoError (t , err )
214
333
}
215
334
@@ -302,11 +421,11 @@ func TestProvision_ExtraEnv(t *testing.T) {
302
421
t .Setenv ("TF_LOG" , "INFO" )
303
422
t .Setenv ("TF_SUPERSECRET" , secretValue )
304
423
305
- ctx , api := setupProvisioner (t )
424
+ ctx , api := setupProvisioner (t , nil )
306
425
307
426
directory := t .TempDir ()
308
427
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 )
310
429
require .NoError (t , err )
311
430
312
431
request := & proto.Provision_Request {
0 commit comments