Skip to content

Commit

Permalink
Add test for ObserveWorkspaceUntilStarted (#19050)
Browse files Browse the repository at this point in the history
  • Loading branch information
csweichel authored Nov 10, 2023
1 parent f38e12e commit f48c854
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 6 deletions.
11 changes: 5 additions & 6 deletions components/local-app/pkg/helper/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ func ObserveWorkspaceUntilStarted(ctx context.Context, clnt *client.Gitpod, work
}

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

var (
maxRetries = 5
retries = 0
delay = 100 * time.Millisecond
)
for {
for retries := 0; retries < maxRetries; retries++ {
stream, err := clnt.Workspaces.StreamWorkspaceStatus(ctx, connect.NewRequest(&v1.StreamWorkspaceStatusRequest{WorkspaceId: workspaceID}))
if err != nil {
if retries >= maxRetries {
return nil, prettyprint.MarkExceptional(fmt.Errorf("failed to stream workspace status after %d retries: %w", maxRetries, err))
}
retries++
delay *= 2
slog.Warn("failed to stream workspace status, retrying", "err", err, "retry", retries, "maxRetries", maxRetries)
continue
}

defer stream.Close()

for stream.Receive() {
Expand Down Expand Up @@ -225,7 +225,6 @@ func ObserveWorkspaceUntilStarted(ctx context.Context, clnt *client.Gitpod, work
slog.Warn("failed to stream workspace status, retrying", "err", err, "retry", retries, "maxRetries", maxRetries)
continue
}

return nil, prettyprint.MarkExceptional(fmt.Errorf("workspace stream ended unexpectedly"))
}
return nil, prettyprint.MarkExceptional(fmt.Errorf("workspace stream ended unexpectedly"))
}
161 changes: 161 additions & 0 deletions components/local-app/pkg/helper/workspace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package helper

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/bufbuild/connect-go"
"github.com/gitpod-io/gitpod/components/public-api/go/client"
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
gitpod_experimental_v1connect "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
"github.com/gitpod-io/local-app/pkg/prettyprint"
"github.com/google/go-cmp/cmp"
)

func TestObserveWorkspaceUntilStarted(t *testing.T) {
workspaceWithStatus := func(id string, phase v1.WorkspaceInstanceStatus_Phase) *v1.Workspace {
return &v1.Workspace{
WorkspaceId: id,
Status: &v1.WorkspaceStatus{
Instance: &v1.WorkspaceInstance{
WorkspaceId: id,
Status: &v1.WorkspaceInstanceStatus{
Phase: phase,
},
},
},
}
}

type Expectation struct {
Error string
SystemException bool
}
tests := []struct {
Name string
Expectation Expectation
PrepServer func(mux *http.ServeMux)
WorkspaceID string
}{
{
Name: "stream retry",
PrepServer: func(mux *http.ServeMux) {
mux.Handle(gitpod_experimental_v1connect.NewWorkspacesServiceHandler(&TestWorkspaceService{
Workspaces: []*v1.Workspace{
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_PENDING),
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_CREATING),
nil,
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_RUNNING),
},
}))
},
},
{
Name: "stream ends early",
PrepServer: func(mux *http.ServeMux) {
mux.Handle(gitpod_experimental_v1connect.NewWorkspacesServiceHandler(&TestWorkspaceService{
Workspaces: []*v1.Workspace{
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_CREATING),
},
}))
},
Expectation: Expectation{
Error: "workspace stream ended unexpectedly",
SystemException: true,
},
},
{
Name: "workspace starts",
PrepServer: func(mux *http.ServeMux) {
mux.Handle(gitpod_experimental_v1connect.NewWorkspacesServiceHandler(&TestWorkspaceService{
Workspaces: []*v1.Workspace{
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_PENDING),
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_CREATING),
workspaceWithStatus("workspaceID", v1.WorkspaceInstanceStatus_PHASE_RUNNING),
},
}))
},
},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
var act Expectation

mux := http.NewServeMux()
if test.PrepServer != nil {
test.PrepServer(mux)
}

apisrv := httptest.NewServer(mux)
t.Cleanup(apisrv.Close)

clnt, err := client.New(client.WithURL(apisrv.URL), client.WithCredentials("hello world"))
if err != nil {
t.Fatal(err)
}

_, err = ObserveWorkspaceUntilStarted(context.Background(), clnt, test.WorkspaceID)
if err != nil {
act.Error = err.Error()
_, act.SystemException = err.(*prettyprint.ErrSystemException)
}

if diff := cmp.Diff(test.Expectation, act); diff != "" {
t.Errorf("ObserveWorkspaceUntilStarted() mismatch (-want +got):\n%s", diff)
}
})
}
}

type TestWorkspaceService struct {
gitpod_experimental_v1connect.WorkspacesServiceHandler

Workspaces []*v1.Workspace
Pos int
}

func (srv *TestWorkspaceService) GetWorkspace(context.Context, *connect.Request[v1.GetWorkspaceRequest]) (*connect.Response[v1.GetWorkspaceResponse], error) {
if srv.Pos >= len(srv.Workspaces) {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("not found"))
}

resp := &connect.Response[v1.GetWorkspaceResponse]{
Msg: &v1.GetWorkspaceResponse{
Result: srv.Workspaces[srv.Pos],
},
}
srv.Pos++
return resp, nil
}

func (srv *TestWorkspaceService) StreamWorkspaceStatus(ctx context.Context, req *connect.Request[v1.StreamWorkspaceStatusRequest], resp *connect.ServerStream[v1.StreamWorkspaceStatusResponse]) error {
if srv.Pos >= len(srv.Workspaces) {
return nil
}
if srv.Workspaces[srv.Pos] == nil {
srv.Pos++
return nil
}

for ; srv.Pos < len(srv.Workspaces); srv.Pos++ {
ws := srv.Workspaces[srv.Pos]
if ws == nil {
return nil
}
err := resp.Send(&v1.StreamWorkspaceStatusResponse{
Result: ws.Status,
})
if err != nil {
return err
}
}
return nil
}

0 comments on commit f48c854

Please sign in to comment.