1
1
package cli_test
2
2
3
3
import (
4
+ "context"
4
5
"io"
5
6
"net"
6
7
"runtime"
@@ -14,6 +15,7 @@ import (
14
15
15
16
"cdr.dev/slog"
16
17
"cdr.dev/slog/sloggers/slogtest"
18
+
17
19
"github.com/coder/coder/agent"
18
20
"github.com/coder/coder/cli/clitest"
19
21
"github.com/coder/coder/coderd/coderdtest"
@@ -23,49 +25,52 @@ import (
23
25
"github.com/coder/coder/pty/ptytest"
24
26
)
25
27
28
+ func setupWorkspaceForSSH (t * testing.T ) (* codersdk.Client , codersdk.Workspace , string ) {
29
+ client := coderdtest .New (t , & coderdtest.Options {IncludeProvisionerD : true })
30
+ user := coderdtest .CreateFirstUser (t , client )
31
+ agentToken := uuid .NewString ()
32
+ version := coderdtest .CreateTemplateVersion (t , client , user .OrganizationID , & echo.Responses {
33
+ Parse : echo .ParseComplete ,
34
+ ProvisionDryRun : echo .ProvisionComplete ,
35
+ Provision : []* proto.Provision_Response {{
36
+ Type : & proto.Provision_Response_Complete {
37
+ Complete : & proto.Provision_Complete {
38
+ Resources : []* proto.Resource {{
39
+ Name : "dev" ,
40
+ Type : "google_compute_instance" ,
41
+ Agents : []* proto.Agent {{
42
+ Id : uuid .NewString (),
43
+ Auth : & proto.Agent_Token {
44
+ Token : agentToken ,
45
+ },
46
+ }},
47
+ }},
48
+ },
49
+ },
50
+ }},
51
+ })
52
+ coderdtest .AwaitTemplateVersionJob (t , client , version .ID )
53
+ template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID )
54
+ workspace := coderdtest .CreateWorkspace (t , client , user .OrganizationID , template .ID )
55
+
56
+ return client , workspace , agentToken
57
+ }
58
+
26
59
func TestSSH (t * testing.T ) {
27
- t .Skip ("This is causing test flakes. TODO @cian fix this" )
28
60
t .Parallel ()
29
61
t .Run ("ImmediateExit" , func (t * testing.T ) {
30
62
t .Parallel ()
31
- client := coderdtest .New (t , & coderdtest.Options {IncludeProvisionerD : true })
32
- user := coderdtest .CreateFirstUser (t , client )
33
- agentToken := uuid .NewString ()
34
- version := coderdtest .CreateTemplateVersion (t , client , user .OrganizationID , & echo.Responses {
35
- Parse : echo .ParseComplete ,
36
- ProvisionDryRun : echo .ProvisionComplete ,
37
- Provision : []* proto.Provision_Response {{
38
- Type : & proto.Provision_Response_Complete {
39
- Complete : & proto.Provision_Complete {
40
- Resources : []* proto.Resource {{
41
- Name : "dev" ,
42
- Type : "google_compute_instance" ,
43
- Agents : []* proto.Agent {{
44
- Id : uuid .NewString (),
45
- Auth : & proto.Agent_Token {
46
- Token : agentToken ,
47
- },
48
- }},
49
- }},
50
- },
51
- },
52
- }},
53
- })
54
- coderdtest .AwaitTemplateVersionJob (t , client , version .ID )
55
- template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID )
56
- workspace := coderdtest .CreateWorkspace (t , client , user .OrganizationID , template .ID )
63
+ client , workspace , agentToken := setupWorkspaceForSSH (t )
57
64
cmd , root := clitest .New (t , "ssh" , workspace .Name )
58
65
clitest .SetupConfig (t , client , root )
59
- doneChan := make (chan struct {})
60
66
pty := ptytest .New (t )
61
67
cmd .SetIn (pty .Input ())
62
68
cmd .SetErr (pty .Output ())
63
69
cmd .SetOut (pty .Output ())
64
- go func () {
65
- defer close (doneChan )
70
+ tGo (t , func () {
66
71
err := cmd .Execute ()
67
72
assert .NoError (t , err )
68
- }( )
73
+ })
69
74
pty .ExpectMatch ("Waiting" )
70
75
coderdtest .AwaitWorkspaceBuildJob (t , client , workspace .LatestBuild .ID )
71
76
agentClient := codersdk .New (client .URL )
@@ -78,37 +83,12 @@ func TestSSH(t *testing.T) {
78
83
})
79
84
// Shells on Mac, Windows, and Linux all exit shells with the "exit" command.
80
85
pty .WriteLine ("exit" )
81
- <- doneChan
82
86
})
83
87
t .Run ("Stdio" , func (t * testing.T ) {
84
88
t .Parallel ()
85
- client := coderdtest .New (t , & coderdtest.Options {IncludeProvisionerD : true })
86
- user := coderdtest .CreateFirstUser (t , client )
87
- agentToken := uuid .NewString ()
88
- version := coderdtest .CreateTemplateVersion (t , client , user .OrganizationID , & echo.Responses {
89
- Parse : echo .ParseComplete ,
90
- ProvisionDryRun : echo .ProvisionComplete ,
91
- Provision : []* proto.Provision_Response {{
92
- Type : & proto.Provision_Response_Complete {
93
- Complete : & proto.Provision_Complete {
94
- Resources : []* proto.Resource {{
95
- Name : "dev" ,
96
- Type : "google_compute_instance" ,
97
- Agents : []* proto.Agent {{
98
- Id : uuid .NewString (),
99
- Auth : & proto.Agent_Token {
100
- Token : agentToken ,
101
- },
102
- }},
103
- }},
104
- },
105
- },
106
- }},
107
- })
108
- coderdtest .AwaitTemplateVersionJob (t , client , version .ID )
109
- template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID )
110
- workspace := coderdtest .CreateWorkspace (t , client , user .OrganizationID , template .ID )
111
- go func () {
89
+ client , workspace , agentToken := setupWorkspaceForSSH (t )
90
+
91
+ tGoContext (t , func (ctx context.Context ) {
112
92
// Run this async so the SSH command has to wait for
113
93
// the build and agent to connect!
114
94
coderdtest .AwaitWorkspaceBuildJob (t , client , workspace .LatestBuild .ID )
@@ -117,25 +97,22 @@ func TestSSH(t *testing.T) {
117
97
agentCloser := agent .New (agentClient .ListenWorkspaceAgent , & agent.Options {
118
98
Logger : slogtest .Make (t , nil ).Leveled (slog .LevelDebug ),
119
99
})
120
- t .Cleanup (func () {
121
- _ = agentCloser .Close ()
122
- })
123
- }()
100
+ <- ctx .Done ()
101
+ _ = agentCloser .Close ()
102
+ })
124
103
125
104
clientOutput , clientInput := io .Pipe ()
126
105
serverOutput , serverInput := io .Pipe ()
127
106
128
107
cmd , root := clitest .New (t , "ssh" , "--stdio" , workspace .Name )
129
108
clitest .SetupConfig (t , client , root )
130
- doneChan := make (chan struct {})
131
109
cmd .SetIn (clientOutput )
132
110
cmd .SetOut (serverInput )
133
111
cmd .SetErr (io .Discard )
134
- go func () {
135
- defer close (doneChan )
112
+ tGo (t , func () {
136
113
err := cmd .Execute ()
137
114
assert .NoError (t , err )
138
- }( )
115
+ })
139
116
140
117
conn , channels , requests , err := ssh .NewClientConn (& stdioConn {
141
118
Reader : serverOutput ,
@@ -157,10 +134,45 @@ func TestSSH(t *testing.T) {
157
134
err = sshClient .Close ()
158
135
require .NoError (t , err )
159
136
_ = clientOutput .Close ()
160
- <- doneChan
161
137
})
162
138
}
163
139
140
+ // tGoContext runs fn in a goroutine passing a context that will be
141
+ // canceled on test completion and wait until fn has finished executing.
142
+ //
143
+ // NOTE(mafredri): This could be moved to a helper library.
144
+ func tGoContext (t * testing.T , fn func (context.Context )) {
145
+ t .Helper ()
146
+
147
+ ctx , cancel := context .WithCancel (context .Background ())
148
+ done := make (chan struct {})
149
+ t .Cleanup (func () {
150
+ cancel ()
151
+ <- done
152
+ })
153
+ go func () {
154
+ fn (ctx )
155
+ close (done )
156
+ }()
157
+ }
158
+
159
+ // tGo runs fn in a goroutine and waits until fn has completed before
160
+ // test completion.
161
+ //
162
+ // NOTE(mafredri): This could be moved to a helper library.
163
+ func tGo (t * testing.T , fn func ()) {
164
+ t .Helper ()
165
+
166
+ done := make (chan struct {})
167
+ t .Cleanup (func () {
168
+ <- done
169
+ })
170
+ go func () {
171
+ fn ()
172
+ close (done )
173
+ }()
174
+ }
175
+
164
176
type stdioConn struct {
165
177
io.Reader
166
178
io.Writer
0 commit comments