@@ -17,6 +17,7 @@ import (
17
17
"os/exec"
18
18
"path"
19
19
"path/filepath"
20
+ "regexp"
20
21
"runtime"
21
22
"strings"
22
23
"testing"
@@ -148,8 +149,35 @@ func TestSSH(t *testing.T) {
148
149
t .Run ("StartStoppedWorkspaceConflict" , func (t * testing.T ) {
149
150
t .Parallel ()
150
151
152
+ // Intercept builds to synchronize execution of the SSH command.
153
+ // The purpose here is to make sure all commands try to trigger
154
+ // a start build of the workspace.
155
+ isFirstBuild := true
156
+ buildURL := regexp .MustCompile ("/api/v2/workspaces/.*/builds" )
157
+ buildReq := make (chan struct {}) // Buffer to allow the initial workspace build.
158
+ buildResume := make (chan struct {})
159
+ buildSyncMW := func (next http.Handler ) http.Handler {
160
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
161
+ // Block execution until all commands are trying to build to synchronize execution.
162
+ t .Log ("buildSyncMW" , r .Method , r .URL .Path )
163
+ if r .Method == http .MethodPost && buildURL .MatchString (r .URL .Path ) {
164
+ if ! isFirstBuild {
165
+ t .Log ("buildSyncMW: blocking build" )
166
+ buildReq <- struct {}{}
167
+ <- buildResume
168
+ t .Log ("buildSyncMW: resuming build" )
169
+ }
170
+ isFirstBuild = false
171
+ }
172
+ next .ServeHTTP (w , r )
173
+ })
174
+ }
175
+
151
176
authToken := uuid .NewString ()
152
- ownerClient := coderdtest .New (t , & coderdtest.Options {IncludeProvisionerDaemon : true })
177
+ ownerClient := coderdtest .New (t , & coderdtest.Options {
178
+ IncludeProvisionerDaemon : true ,
179
+ APIMiddleware : buildSyncMW ,
180
+ })
153
181
owner := coderdtest .CreateFirstUser (t , ownerClient )
154
182
client , _ := coderdtest .CreateAnotherUser (t , ownerClient , owner .OrganizationID , rbac .RoleTemplateAdmin ())
155
183
version := coderdtest .CreateTemplateVersion (t , client , owner .OrganizationID , & echo.Responses {
@@ -165,21 +193,28 @@ func TestSSH(t *testing.T) {
165
193
workspaceBuild := coderdtest .CreateWorkspaceBuild (t , client , workspace , database .WorkspaceTransitionStop )
166
194
coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , workspaceBuild .ID )
167
195
168
- ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitSuperLong )
196
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitMedium )
169
197
defer cancel ()
170
198
171
- ptys := make ( []* ptytest.PTY , 3 )
172
- for i := range ptys {
199
+ var ptys []* ptytest.PTY
200
+ for i := 0 ; i < 3 ; i ++ {
173
201
// SSH to the workspace which should autostart it
174
202
inv , root := clitest .New (t , "ssh" , workspace .Name )
175
203
176
- ptys [i ] = ptytest .New (t ).Attach (inv )
204
+ pty := ptytest .New (t ).Attach (inv )
205
+ ptys = append (ptys , pty )
177
206
clitest .SetupConfig (t , client , root )
178
- clitest . StartWithAssert (t , inv , func (* testing. T , error ) {
179
- // Noop.
207
+ testutil . Go (t , func () {
208
+ _ = inv . WithContext ( ctx ). Run ()
180
209
})
181
210
}
182
211
212
+ for _ , pty := range ptys {
213
+ pty .ExpectMatchContext (ctx , "Workspace was stopped, starting workspace to allow connecting to" )
214
+ testutil .RequireRecvCtx (ctx , t , buildReq )
215
+ }
216
+ close (buildResume )
217
+
183
218
var foundConflict int
184
219
for _ , pty := range ptys {
185
220
// Either allow the command to start the workspace or fail
@@ -191,8 +226,7 @@ func TestSSH(t *testing.T) {
191
226
pty .ExpectMatchContext (ctx , "Waiting for the workspace agent to connect" )
192
227
}
193
228
}
194
- // TODO(mafredri): Remove this if it's racy.
195
- require .Greater (t , foundConflict , 0 , "expected at least one conflict" )
229
+ require .Equal (t , foundConflict , 2 , "expected 2 conflicts" )
196
230
})
197
231
t .Run ("RequireActiveVersion" , func (t * testing.T ) {
198
232
t .Parallel ()
0 commit comments