Skip to content

Commit 21ab5d4

Browse files
authored
[test] Fix flakes and speed up prebuild tests (#17996)
1 parent 043a55b commit 21ab5d4

File tree

3 files changed

+121
-115
lines changed

3 files changed

+121
-115
lines changed

test/pkg/integration/workspace.go

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,19 @@ func stopWsF(t *testing.T, instanceID string, workspaceID string, api *Component
546546
type WaitForWorkspaceOpt func(*waitForWorkspaceOpts)
547547

548548
type waitForWorkspaceOpts struct {
549-
CanFail bool
549+
CanFail bool
550+
WaitForStopped bool
550551
}
551552

552553
// WorkspaceCanFail doesn't fail the test if the workspace fails to start
553554
func WorkspaceCanFail(o *waitForWorkspaceOpts) {
554555
o.CanFail = true
555556
}
556557

558+
func WaitForStopped(o *waitForWorkspaceOpts) {
559+
o.WaitForStopped = true
560+
}
561+
557562
// WaitForWorkspace waits until a workspace is running. Fails the test if the workspace
558563
// fails or does not become RUNNING before the context is canceled.
559564
func WaitForWorkspaceStart(t *testing.T, ctx context.Context, instanceID string, workspaceID string, api *ComponentAPI, opts ...WaitForWorkspaceOpt) (lastStatus *wsmanapi.WorkspaceStatus, err error) {
@@ -562,6 +567,34 @@ func WaitForWorkspaceStart(t *testing.T, ctx context.Context, instanceID string,
562567
o(&cfg)
563568
}
564569

570+
checkStatus := func(status *wsmanapi.WorkspaceStatus) (done bool, err error) {
571+
if status == nil {
572+
return false, nil
573+
}
574+
if !cfg.CanFail && status.Conditions != nil && status.Conditions.Failed != "" {
575+
return true, xerrors.Errorf("workspace instance %s failed: %s", instanceID, status.Conditions.Failed)
576+
}
577+
578+
switch status.Phase {
579+
case wsmanapi.WorkspacePhase_RUNNING:
580+
if !cfg.WaitForStopped {
581+
// Done.
582+
return true, nil
583+
}
584+
case wsmanapi.WorkspacePhase_STOPPING:
585+
if !cfg.WaitForStopped {
586+
return true, ErrWorkspaceInstanceStopping
587+
}
588+
case wsmanapi.WorkspacePhase_STOPPED:
589+
if !cfg.WaitForStopped {
590+
return true, ErrWorkspaceInstanceStopped
591+
} else {
592+
return true, nil
593+
}
594+
}
595+
return false, nil
596+
}
597+
565598
done := make(chan *wsmanapi.WorkspaceStatus)
566599
errStatus := make(chan error)
567600
reboot := make(chan struct{}, 1)
@@ -595,7 +628,11 @@ func WaitForWorkspaceStart(t *testing.T, ctx context.Context, instanceID string,
595628
close(done)
596629
}()
597630
for {
598-
t.Logf("check if the status of workspace is in the running phase: %s", instanceID)
631+
waitForPhase := "running"
632+
if cfg.WaitForStopped {
633+
waitForPhase = "stopped"
634+
}
635+
t.Logf("check if the status of workspace is in the %s phase: %s", waitForPhase, instanceID)
599636
resp, err := sub.Recv()
600637
if err != nil {
601638
if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {
@@ -630,30 +667,15 @@ func WaitForWorkspaceStart(t *testing.T, ctx context.Context, instanceID string,
630667

631668
t.Logf("status: %s, %s", s.Id, s.Phase)
632669

633-
if cfg.CanFail {
634-
if s.Phase == wsmanapi.WorkspacePhase_STOPPING {
635-
return
636-
}
637-
if s.Phase == wsmanapi.WorkspacePhase_STOPPED {
638-
return
639-
}
640-
} else {
641-
if s.Conditions.Failed != "" {
642-
errStatus <- xerrors.Errorf("workspace instance %s failed: %s", instanceID, s.Conditions.Failed)
643-
return
644-
} else if s.Phase == wsmanapi.WorkspacePhase_STOPPING || s.Phase == wsmanapi.WorkspacePhase_STOPPED {
645-
errStatus <- xerrors.Errorf("workspace instance %s is %s", instanceID, s.Phase)
646-
return
647-
}
648-
}
649-
if s.Phase != wsmanapi.WorkspacePhase_RUNNING {
650-
// we're still starting
651-
continue
670+
done2, err := checkStatus(s)
671+
if err != nil {
672+
errStatus <- err
673+
return
652674
}
653675

654-
// all is well, the workspace is running
655-
t.Logf("confirmed that the worksapce is running: %s, %s", s.Id, s.Phase)
656-
return
676+
if done2 {
677+
return
678+
}
657679
}
658680
}()
659681

@@ -675,30 +697,18 @@ func WaitForWorkspaceStart(t *testing.T, ctx context.Context, instanceID string,
675697
return nil, false, nil
676698
}
677699
}
678-
if desc != nil && desc.Status != nil {
679-
switch desc.Status.Phase {
680-
case wsmanapi.WorkspacePhase_RUNNING:
681-
return desc.Status, false, nil
682-
case wsmanapi.WorkspacePhase_STOPPING:
683-
if !cfg.CanFail {
684-
return nil, false, ErrWorkspaceInstanceStopping
685-
}
686-
case wsmanapi.WorkspacePhase_STOPPED:
687-
if !cfg.CanFail {
688-
return nil, false, ErrWorkspaceInstanceStopped
689-
}
690-
}
691-
}
692-
return nil, true, nil
700+
701+
done, err := checkStatus(desc.Status)
702+
return desc.Status, done, err
693703
}
694704

695705
ticker := time.NewTicker(1 * time.Second)
696706
for {
697707
select {
698708
case <-ticker.C:
699709
// For in case missed the status change
700-
desc, cont, err := handle()
701-
if cont {
710+
desc, done, err := handle()
711+
if !done {
702712
continue
703713
} else if err != nil {
704714
return nil, err
@@ -707,8 +717,8 @@ func WaitForWorkspaceStart(t *testing.T, ctx context.Context, instanceID string,
707717
}
708718
case <-reboot:
709719
// Consider workspace state changes during subscriber reboot
710-
desc, cont, err := handle()
711-
if cont {
720+
desc, done, err := handle()
721+
if !done {
712722
continue
713723
} else if err != nil {
714724
return nil, err
@@ -1008,17 +1018,12 @@ func DeleteWorkspace(ctx context.Context, api *ComponentAPI, instanceID string)
10081018
Id: instanceID,
10091019
})
10101020
if err != nil {
1021+
s, ok := status.FromError(err)
1022+
if ok && s.Code() == codes.NotFound {
1023+
// Workspace is already gone.
1024+
return nil
1025+
}
10111026
return err
10121027
}
1013-
1014-
if err == nil {
1015-
return nil
1016-
}
1017-
1018-
s, ok := status.FromError(err)
1019-
if ok && s.Code() == codes.NotFound {
1020-
return nil
1021-
}
1022-
1023-
return err
1028+
return nil
10241029
}

test/tests/components/ws-manager/content_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ func TestMissingBackup(t *testing.T) {
242242
}
243243
w.Spec.FeatureFlags = test.FF
244244
return nil
245-
}), integration.WithWaitWorkspaceForOpts(integration.WorkspaceCanFail))
245+
}), integration.WithWaitWorkspaceForOpts(integration.WorkspaceCanFail, integration.WaitForStopped))
246246
if err != nil {
247247
t.Fatal(err)
248248
}

test/tests/components/ws-manager/prebuild_test.go

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestPrebuildWorkspaceTaskSuccess(t *testing.T) {
4242
CheckoutLocation: "empty",
4343
WorkspaceRoot: "/workspace/empty",
4444
Task: []gitpod.TasksItems{
45-
{Init: "echo \"some output\" > someFile; sleep 10; exit 0;"},
45+
{Init: "echo \"some output\" > someFile; exit 0;"},
4646
},
4747
},
4848
}
@@ -85,7 +85,7 @@ func TestPrebuildWorkspaceTaskSuccess(t *testing.T) {
8585
}
8686
req.Spec.WorkspaceLocation = test.CheckoutLocation
8787
return nil
88-
}))
88+
}), integration.WithWaitWorkspaceForOpts(integration.WaitForStopped))
8989
if err != nil {
9090
t.Fatalf("cannot launch a workspace: %q", err)
9191
}
@@ -95,18 +95,16 @@ func TestPrebuildWorkspaceTaskSuccess(t *testing.T) {
9595
t.Errorf("cannot stop workspace: %q", err)
9696
}
9797
})
98-
_, err = integration.WaitForWorkspace(ctx, api, ws.Req.Id, func(status *wsmanapi.WorkspaceStatus) bool {
99-
if status.Phase != wsmanapi.WorkspacePhase_STOPPED {
100-
return false
101-
}
102-
if status.Conditions.HeadlessTaskFailed != "" {
103-
t.Logf("Conditions: %v", status.Conditions)
104-
t.Fatal("unexpected HeadlessTaskFailed condition")
105-
}
106-
return true
107-
})
108-
if err != nil {
109-
t.Fatalf("failed for wait workspace stop: %q", err)
98+
99+
if ws.LastStatus == nil {
100+
t.Fatal("workspace status is nil")
101+
}
102+
if ws.LastStatus.Phase != wsmanapi.WorkspacePhase_STOPPED {
103+
t.Fatalf("unexpected workspace phase: %v", ws.LastStatus.Phase)
104+
}
105+
if ws.LastStatus.Conditions != nil && ws.LastStatus.Conditions.HeadlessTaskFailed != "" {
106+
t.Logf("Conditions: %v", ws.LastStatus.Conditions)
107+
t.Fatalf("unexpected HeadlessTaskFailed condition: %v", ws.LastStatus.Conditions.HeadlessTaskFailed)
110108
}
111109
})
112110
}
@@ -135,10 +133,10 @@ func TestPrebuildWorkspaceTaskFail(t *testing.T) {
135133
req.Type = wsmanapi.WorkspaceType_PREBUILD
136134
req.Spec.Envvars = append(req.Spec.Envvars, &wsmanapi.EnvironmentVariable{
137135
Name: "GITPOD_TASKS",
138-
Value: `[{ "init": "echo \"some output\" > someFile; sleep 10; exit 1;" }]`,
136+
Value: `[{ "init": "echo \"some output\" > someFile; exit 1;" }]`,
139137
})
140138
return nil
141-
}))
139+
}), integration.WithWaitWorkspaceForOpts(integration.WaitForStopped))
142140
if err != nil {
143141
t.Fatalf("cannot start workspace: %q", err)
144142
}
@@ -150,18 +148,15 @@ func TestPrebuildWorkspaceTaskFail(t *testing.T) {
150148
}
151149
})
152150

153-
_, err = integration.WaitForWorkspace(ctx, api, ws.Req.Id, func(status *wsmanapi.WorkspaceStatus) bool {
154-
if status.Phase != wsmanapi.WorkspacePhase_STOPPED {
155-
return false
156-
}
157-
if status.Conditions.HeadlessTaskFailed == "" {
158-
t.Logf("Conditions: %v", status.Conditions)
159-
t.Fatal("expected HeadlessTaskFailed condition")
160-
}
161-
return true
162-
})
163-
if err != nil {
164-
t.Fatalf("failed for wait workspace stop: %q", err)
151+
if ws.LastStatus == nil {
152+
t.Fatal("workspace status is nil")
153+
}
154+
if ws.LastStatus.Phase != wsmanapi.WorkspacePhase_STOPPED {
155+
t.Fatalf("unexpected workspace phase: %v", ws.LastStatus.Phase)
156+
}
157+
if ws.LastStatus.Conditions == nil || ws.LastStatus.Conditions.HeadlessTaskFailed == "" {
158+
t.Logf("Status: %v", ws.LastStatus)
159+
t.Fatal("expected HeadlessTaskFailed condition")
165160
}
166161

167162
return ctx
@@ -174,7 +169,7 @@ func TestPrebuildWorkspaceTaskFail(t *testing.T) {
174169
const (
175170
prebuildLogPath string = "/workspace/.gitpod"
176171
prebuildLog string = "'🍊 This task ran as a workspace prebuild'"
177-
initTask string = "echo \"some output\" > someFile; sleep 10;"
172+
initTask string = "echo \"some output\" > someFile;"
178173
regularPrefix string = "ws-"
179174
)
180175

@@ -227,25 +222,30 @@ func TestOpenWorkspaceFromPrebuildSerialOnly(t *testing.T) {
227222
// create a prebuild and stop workspace
228223
// TODO: change to use server API to launch the workspace, so we could run the integration test as the user code flow
229224
// which is client -> server -> ws-manager rather than client -> ws-manager directly
230-
ws, prebuildStopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(func(req *wsmanapi.StartWorkspaceRequest) error {
231-
req.Type = wsmanapi.WorkspaceType_PREBUILD
232-
req.Spec.Envvars = append(req.Spec.Envvars, &wsmanapi.EnvironmentVariable{
233-
Name: "GITPOD_TASKS",
234-
Value: fmt.Sprintf(`[{ "init": %q }]`, initTask),
235-
})
236-
req.Spec.FeatureFlags = test.FF
237-
req.Spec.Initializer = &csapi.WorkspaceInitializer{
238-
Spec: &csapi.WorkspaceInitializer_Git{
239-
Git: &csapi.GitInitializer{
240-
RemoteUri: test.ContextURL,
241-
CheckoutLocation: test.CheckoutLocation,
242-
Config: &csapi.GitConfig{},
225+
ws, prebuildStopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api,
226+
integration.WithRequestModifier(func(req *wsmanapi.StartWorkspaceRequest) error {
227+
req.Type = wsmanapi.WorkspaceType_PREBUILD
228+
req.Spec.Envvars = append(req.Spec.Envvars, &wsmanapi.EnvironmentVariable{
229+
Name: "GITPOD_TASKS",
230+
Value: fmt.Sprintf(`[{ "init": %q }]`, initTask),
231+
})
232+
req.Spec.FeatureFlags = test.FF
233+
req.Spec.Initializer = &csapi.WorkspaceInitializer{
234+
Spec: &csapi.WorkspaceInitializer_Git{
235+
Git: &csapi.GitInitializer{
236+
RemoteUri: test.ContextURL,
237+
CheckoutLocation: test.CheckoutLocation,
238+
Config: &csapi.GitConfig{},
239+
},
243240
},
244-
},
245-
}
246-
req.Spec.WorkspaceLocation = test.CheckoutLocation
247-
return nil
248-
}))
241+
}
242+
req.Spec.WorkspaceLocation = test.CheckoutLocation
243+
return nil
244+
}),
245+
// Wait for the prebuild to finish, as this can happen quickly before we
246+
// would have time to observe the workspace to stop and get its status.
247+
integration.WithWaitWorkspaceForOpts(integration.WaitForStopped),
248+
)
249249
if err != nil {
250250
t.Fatalf("cannot launch a workspace: %q", err)
251251
}
@@ -255,7 +255,7 @@ func TestOpenWorkspaceFromPrebuildSerialOnly(t *testing.T) {
255255
}
256256
}()
257257

258-
prebuildSnapshot, _, err = watchStopWorkspaceAndFindSnapshot(t, ctx, ws.Req.Id, ws.WorkspaceID, api)
258+
prebuildSnapshot, _, err = findSnapshotFromStoppedWs(t, ctx, ws.LastStatus)
259259
if err != nil {
260260
_ = stopWorkspace(t, cfg, prebuildStopWs)
261261
t.Fatalf("stop workspace and find snapshot error: %v", err)
@@ -472,9 +472,9 @@ func TestOpenWorkspaceFromOutdatedPrebuild(t *testing.T) {
472472
req.Spec.WorkspaceLocation = test.CheckoutLocation
473473
return nil
474474
}),
475-
// The init task only runs for a short duration, so it's possible we miss the Running state, as it quickly transitions to Stopping.
476-
// Therefore ignore if we can't see the Running state.
477-
integration.WithWaitWorkspaceForOpts(integration.WorkspaceCanFail))
475+
// The init task only runs for a short duration, so it's possible we miss the Running state, as it quickly transitions to Stopping/Stopped.
476+
// Therefore wait for the workspace to stop to get its last status.
477+
integration.WithWaitWorkspaceForOpts(integration.WaitForStopped))
478478
if err != nil {
479479
t.Fatalf("cannot launch a workspace: %q", err)
480480
}
@@ -484,7 +484,7 @@ func TestOpenWorkspaceFromOutdatedPrebuild(t *testing.T) {
484484
t.Errorf("cannot stop workspace: %q", err)
485485
}
486486
}()
487-
prebuildSnapshot, _, err := watchStopWorkspaceAndFindSnapshot(t, ctx, ws.Req.Id, ws.WorkspaceID, api)
487+
prebuildSnapshot, _, err := findSnapshotFromStoppedWs(t, ctx, ws.LastStatus)
488488
if err != nil {
489489
t.Fatalf("stop workspace and find snapshot error: %v", err)
490490
}
@@ -690,18 +690,19 @@ func checkGitFolderPermission(t *testing.T, rsa *integration.RpcClient, workspac
690690
}
691691
}
692692

693-
func watchStopWorkspaceAndFindSnapshot(t *testing.T, ctx context.Context, instanceId string, workspaceID string, api *integration.ComponentAPI) (string, *wsmanapi.VolumeSnapshotInfo, error) {
694-
ready := make(chan struct{}, 1)
695-
lastStatus, err := integration.WaitForWorkspaceStop(t, ctx, ready, api, instanceId, workspaceID)
696-
if err != nil {
697-
return "", nil, err
693+
func findSnapshotFromStoppedWs(t *testing.T, ctx context.Context, lastStatus *wsmanapi.WorkspaceStatus) (string, *wsmanapi.VolumeSnapshotInfo, error) {
694+
if lastStatus == nil {
695+
return "", nil, fmt.Errorf("did not get last workspace status")
698696
}
699-
if lastStatus.Conditions != nil && lastStatus.Conditions.HeadlessTaskFailed != "" {
700-
return "", nil, errors.New("unexpected HeadlessTaskFailed condition")
697+
if lastStatus.Phase != wsmanapi.WorkspacePhase_STOPPED {
698+
return "", nil, fmt.Errorf("workspace is not stopped: %s", lastStatus.Phase)
701699
}
702-
if lastStatus == nil || lastStatus.Conditions == nil {
700+
if lastStatus.Conditions == nil {
703701
return "", nil, nil
704702
}
703+
if lastStatus.Conditions.HeadlessTaskFailed != "" {
704+
return "", nil, errors.New("unexpected HeadlessTaskFailed condition")
705+
}
705706
return lastStatus.Conditions.Snapshot, lastStatus.Conditions.VolumeSnapshot, nil
706707
}
707708

0 commit comments

Comments
 (0)