Skip to content

Commit f48c854

Browse files
authored
Add test for ObserveWorkspaceUntilStarted (#19050)
1 parent f38e12e commit f48c854

File tree

2 files changed

+166
-6
lines changed

2 files changed

+166
-6
lines changed

components/local-app/pkg/helper/workspace.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ func ObserveWorkspaceUntilStarted(ctx context.Context, clnt *client.Gitpod, work
163163
}
164164

165165
ws := wsInfo.Msg.GetResult()
166+
if ws.Status == nil || ws.Status.Instance == nil || ws.Status.Instance.Status == nil {
167+
return nil, fmt.Errorf("cannot get workspace status")
168+
}
166169
if ws.Status.Instance.Status.Phase == v1.WorkspaceInstanceStatus_PHASE_RUNNING {
167170
// workspace is running - we're done
168171
return ws.Status, nil
@@ -177,21 +180,18 @@ func ObserveWorkspaceUntilStarted(ctx context.Context, clnt *client.Gitpod, work
177180

178181
var (
179182
maxRetries = 5
180-
retries = 0
181183
delay = 100 * time.Millisecond
182184
)
183-
for {
185+
for retries := 0; retries < maxRetries; retries++ {
184186
stream, err := clnt.Workspaces.StreamWorkspaceStatus(ctx, connect.NewRequest(&v1.StreamWorkspaceStatusRequest{WorkspaceId: workspaceID}))
185187
if err != nil {
186188
if retries >= maxRetries {
187189
return nil, prettyprint.MarkExceptional(fmt.Errorf("failed to stream workspace status after %d retries: %w", maxRetries, err))
188190
}
189-
retries++
190191
delay *= 2
191192
slog.Warn("failed to stream workspace status, retrying", "err", err, "retry", retries, "maxRetries", maxRetries)
192193
continue
193194
}
194-
195195
defer stream.Close()
196196

197197
for stream.Receive() {
@@ -225,7 +225,6 @@ func ObserveWorkspaceUntilStarted(ctx context.Context, clnt *client.Gitpod, work
225225
slog.Warn("failed to stream workspace status, retrying", "err", err, "retry", retries, "maxRetries", maxRetries)
226226
continue
227227
}
228-
229-
return nil, prettyprint.MarkExceptional(fmt.Errorf("workspace stream ended unexpectedly"))
230228
}
229+
return nil, prettyprint.MarkExceptional(fmt.Errorf("workspace stream ended unexpectedly"))
231230
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package helper
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"net/http"
11+
"net/http/httptest"
12+
"testing"
13+
14+
"github.com/bufbuild/connect-go"
15+
"github.com/gitpod-io/gitpod/components/public-api/go/client"
16+
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
17+
gitpod_experimental_v1connect "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
18+
"github.com/gitpod-io/local-app/pkg/prettyprint"
19+
"github.com/google/go-cmp/cmp"
20+
)
21+
22+
func TestObserveWorkspaceUntilStarted(t *testing.T) {
23+
workspaceWithStatus := func(id string, phase v1.WorkspaceInstanceStatus_Phase) *v1.Workspace {
24+
return &v1.Workspace{
25+
WorkspaceId: id,
26+
Status: &v1.WorkspaceStatus{
27+
Instance: &v1.WorkspaceInstance{
28+
WorkspaceId: id,
29+
Status: &v1.WorkspaceInstanceStatus{
30+
Phase: phase,
31+
},
32+
},
33+
},
34+
}
35+
}
36+
37+
type Expectation struct {
38+
Error string
39+
SystemException bool
40+
}
41+
tests := []struct {
42+
Name string
43+
Expectation Expectation
44+
PrepServer func(mux *http.ServeMux)
45+
WorkspaceID string
46+
}{
47+
{
48+
Name: "stream retry",
49+
PrepServer: func(mux *http.ServeMux) {
50+
mux.Handle(gitpod_experimental_v1connect.NewWorkspacesServiceHandler(&TestWorkspaceService{
51+
Workspaces: []*v1.Workspace{
52+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_PENDING),
53+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_CREATING),
54+
nil,
55+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_RUNNING),
56+
},
57+
}))
58+
},
59+
},
60+
{
61+
Name: "stream ends early",
62+
PrepServer: func(mux *http.ServeMux) {
63+
mux.Handle(gitpod_experimental_v1connect.NewWorkspacesServiceHandler(&TestWorkspaceService{
64+
Workspaces: []*v1.Workspace{
65+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_CREATING),
66+
},
67+
}))
68+
},
69+
Expectation: Expectation{
70+
Error: "workspace stream ended unexpectedly",
71+
SystemException: true,
72+
},
73+
},
74+
{
75+
Name: "workspace starts",
76+
PrepServer: func(mux *http.ServeMux) {
77+
mux.Handle(gitpod_experimental_v1connect.NewWorkspacesServiceHandler(&TestWorkspaceService{
78+
Workspaces: []*v1.Workspace{
79+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_PENDING),
80+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_CREATING),
81+
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_RUNNING),
82+
},
83+
}))
84+
},
85+
},
86+
}
87+
88+
for _, test := range tests {
89+
t.Run(test.Name, func(t *testing.T) {
90+
var act Expectation
91+
92+
mux := http.NewServeMux()
93+
if test.PrepServer != nil {
94+
test.PrepServer(mux)
95+
}
96+
97+
apisrv := httptest.NewServer(mux)
98+
t.Cleanup(apisrv.Close)
99+
100+
clnt, err := client.New(client.WithURL(apisrv.URL), client.WithCredentials("hello world"))
101+
if err != nil {
102+
t.Fatal(err)
103+
}
104+
105+
_, err = ObserveWorkspaceUntilStarted(context.Background(), clnt, test.WorkspaceID)
106+
if err != nil {
107+
act.Error = err.Error()
108+
_, act.SystemException = err.(*prettyprint.ErrSystemException)
109+
}
110+
111+
if diff := cmp.Diff(test.Expectation, act); diff != "" {
112+
t.Errorf("ObserveWorkspaceUntilStarted() mismatch (-want +got):\n%s", diff)
113+
}
114+
})
115+
}
116+
}
117+
118+
type TestWorkspaceService struct {
119+
gitpod_experimental_v1connect.WorkspacesServiceHandler
120+
121+
Workspaces []*v1.Workspace
122+
Pos int
123+
}
124+
125+
func (srv *TestWorkspaceService) GetWorkspace(context.Context, *connect.Request[v1.GetWorkspaceRequest]) (*connect.Response[v1.GetWorkspaceResponse], error) {
126+
if srv.Pos >= len(srv.Workspaces) {
127+
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("not found"))
128+
}
129+
130+
resp := &connect.Response[v1.GetWorkspaceResponse]{
131+
Msg: &v1.GetWorkspaceResponse{
132+
Result: srv.Workspaces[srv.Pos],
133+
},
134+
}
135+
srv.Pos++
136+
return resp, nil
137+
}
138+
139+
func (srv *TestWorkspaceService) StreamWorkspaceStatus(ctx context.Context, req *connect.Request[v1.StreamWorkspaceStatusRequest], resp *connect.ServerStream[v1.StreamWorkspaceStatusResponse]) error {
140+
if srv.Pos >= len(srv.Workspaces) {
141+
return nil
142+
}
143+
if srv.Workspaces[srv.Pos] == nil {
144+
srv.Pos++
145+
return nil
146+
}
147+
148+
for ; srv.Pos < len(srv.Workspaces); srv.Pos++ {
149+
ws := srv.Workspaces[srv.Pos]
150+
if ws == nil {
151+
return nil
152+
}
153+
err := resp.Send(&v1.StreamWorkspaceStatusResponse{
154+
Result: ws.Status,
155+
})
156+
if err != nil {
157+
return err
158+
}
159+
}
160+
return nil
161+
}

0 commit comments

Comments
 (0)