mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
327 lines
7.9 KiB
Go
327 lines
7.9 KiB
Go
// Copyright (c) 2020 TypeFox 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 session
|
|
|
|
import (
|
|
"context"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gitpod-io/gitpod/content-service/pkg/storage"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func init() {
|
|
logrus.SetLevel(logrus.WarnLevel)
|
|
}
|
|
|
|
type emptySP struct{}
|
|
|
|
func (*emptySP) NewStorage() (storage.DirectAccess, error) {
|
|
return &storage.DirectNoopStorage{}, nil
|
|
}
|
|
|
|
func getTestStore() (*Store, error) {
|
|
loc, err := ioutil.TempDir("", "wsdaemon-test")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewStore(context.Background(), loc, nil)
|
|
}
|
|
|
|
func addRandomWorkspace(s *Store) (*Workspace, error) {
|
|
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
randString := func(n int) string {
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
var (
|
|
instanceID = randString(16)
|
|
owner = randString(16)
|
|
workspaceID = randString(16)
|
|
)
|
|
factory := func(ctx context.Context, loc string) (*Workspace, error) {
|
|
return &Workspace{
|
|
Location: loc,
|
|
CheckoutLocation: randString(16),
|
|
CreatedAt: time.Now(),
|
|
Owner: owner,
|
|
WorkspaceID: workspaceID,
|
|
InstanceID: instanceID,
|
|
}, nil
|
|
}
|
|
|
|
location := filepath.Join(s.Location, instanceID)
|
|
return s.NewWorkspace(context.Background(), instanceID, location, factory)
|
|
}
|
|
|
|
func TestPersistAndLoadWorkspace(t *testing.T) {
|
|
store, err := getTestStore()
|
|
if err != nil {
|
|
t.Errorf("cannot create test store: %v", err)
|
|
return
|
|
}
|
|
|
|
var hookFired bool
|
|
store.hooks = map[WorkspaceState][]WorkspaceLivecycleHook{
|
|
WorkspaceReady: {
|
|
func(ctx context.Context, ws *Workspace) error {
|
|
hookFired = true
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
originalWS, err := addRandomWorkspace(store)
|
|
if err != nil {
|
|
t.Errorf("cannot create test workspace: %v", err)
|
|
return
|
|
}
|
|
|
|
err = originalWS.MarkInitDone(context.Background())
|
|
if err != nil {
|
|
t.Errorf("cannot mark workspace as init done: %v", err)
|
|
return
|
|
}
|
|
|
|
workspaces, err := filepath.Glob(filepath.Join(store.Location, "*.workspace.json"))
|
|
if err != nil {
|
|
t.Errorf("cannot glob workspaces: %v", err)
|
|
return
|
|
}
|
|
if len(workspaces) != 1 {
|
|
t.Errorf("found more than one workspace in our test store")
|
|
return
|
|
}
|
|
|
|
reloadedWS, err := loadWorkspace(context.Background(), workspaces[0])
|
|
if err != nil {
|
|
t.Errorf("cannot load workspace %s: %v", workspaces[0], err)
|
|
return
|
|
}
|
|
|
|
if diff := cmp.Diff(originalWS, reloadedWS, cmpopts.IgnoreUnexported(Workspace{})); diff != "" {
|
|
t.Errorf("unexpected workspace change (-want +got):\n%s", diff)
|
|
}
|
|
|
|
if !hookFired {
|
|
t.Errorf("hook did not fire")
|
|
}
|
|
}
|
|
|
|
func TestWaitForInit(t *testing.T) {
|
|
tests := []struct {
|
|
Desc string
|
|
State WorkspaceState
|
|
MarkInit bool
|
|
ExpectReady bool
|
|
}{
|
|
{"regular init", WorkspaceInitializing, true, true},
|
|
{"ready", WorkspaceReady, false, true},
|
|
{"init again", WorkspaceReady, false, true},
|
|
{"disposing", WorkspaceDisposing, false, false},
|
|
{"init while disposing", WorkspaceDisposing, true, false},
|
|
{"disposed", WorkspaceDisposed, false, false},
|
|
{"init while disposed", WorkspaceDisposed, true, false},
|
|
}
|
|
|
|
store, err := getTestStore()
|
|
if err != nil {
|
|
t.Errorf("cannot create test store: %v", err)
|
|
return
|
|
}
|
|
for _, test := range tests {
|
|
ws, err := addRandomWorkspace(store)
|
|
if err != nil {
|
|
t.Errorf("%s: cannot create test workspace: %v", test.Desc, err)
|
|
continue
|
|
}
|
|
ws.state = test.State
|
|
|
|
waitStarted := make(chan struct{})
|
|
waitComplete := make(chan struct{})
|
|
markInitComplete := make(chan struct{})
|
|
go func() {
|
|
waitStarted <- struct{}{}
|
|
ready := ws.WaitForInit(context.Background())
|
|
if ready != test.ExpectReady {
|
|
t.Errorf("%s: WaitForInit returned ready == %v, expected %v", test.Desc, ready, test.ExpectReady)
|
|
}
|
|
waitComplete <- struct{}{}
|
|
}()
|
|
go func() {
|
|
<-waitStarted
|
|
if !test.MarkInit {
|
|
markInitComplete <- struct{}{}
|
|
return
|
|
}
|
|
|
|
<-time.After(500 * time.Millisecond) // just for good measure
|
|
|
|
err := ws.MarkInitDone(context.Background())
|
|
if err != nil {
|
|
t.Errorf("%s: MarkInitDone returned an error: %v", test.Desc, err)
|
|
}
|
|
markInitComplete <- struct{}{}
|
|
}()
|
|
|
|
select {
|
|
case <-waitComplete:
|
|
// all is well
|
|
case <-time.After(1 * time.Second):
|
|
t.Errorf("%s: WaitForInit did not complete", test.Desc)
|
|
}
|
|
select {
|
|
case <-markInitComplete:
|
|
// all is well
|
|
case <-time.After(1 * time.Second):
|
|
t.Errorf("%s: MarkInitDone did not complete", test.Desc)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWaitOrMarkForDisposalRace(t *testing.T) {
|
|
store, err := getTestStore()
|
|
if err != nil {
|
|
t.Errorf("cannot create test store: %v", err)
|
|
return
|
|
}
|
|
ws, err := addRandomWorkspace(store)
|
|
if err != nil {
|
|
t.Errorf("cannot create test workspace: %v", err)
|
|
return
|
|
}
|
|
ws.state = WorkspaceReady
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
var (
|
|
c int32
|
|
wg sync.WaitGroup
|
|
)
|
|
for i := 0; i < 1000; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
done, _, err := ws.WaitOrMarkForDisposal(ctx)
|
|
if err != nil {
|
|
t.Errorf("WaitOrMarkForDisposal failed: %w", err)
|
|
}
|
|
if !done {
|
|
atomic.AddInt32(&c, 1)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
t.Logf("num go routines: %03d", runtime.NumGoroutine())
|
|
ws.Dispose(ctx)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
switch c {
|
|
case 0:
|
|
t.Errorf("WaitOrMarkForDisposal never returned !done: count = %d", c)
|
|
case 1:
|
|
// all is well
|
|
default:
|
|
t.Errorf("WaitOrMarkForDisposal was racey: count = %d", c)
|
|
}
|
|
}
|
|
|
|
func TestWaitOrMarkForDisposal(t *testing.T) {
|
|
tests := []struct {
|
|
Desc string
|
|
State WorkspaceState
|
|
Dispose bool
|
|
ExpectDone1 bool
|
|
ExpectDone2 bool
|
|
}{
|
|
{"regular dispose", WorkspaceReady, true, false, true},
|
|
{"disposed", WorkspaceDisposed, false, true, true},
|
|
{"dispose again", WorkspaceDisposed, false, true, true},
|
|
{"dispose while initializing", WorkspaceInitializing, true, false, true},
|
|
}
|
|
|
|
store, err := getTestStore()
|
|
if err != nil {
|
|
t.Errorf("cannot create test store: %v", err)
|
|
return
|
|
}
|
|
for _, test := range tests {
|
|
ws, err := addRandomWorkspace(store)
|
|
if err != nil {
|
|
t.Errorf("%s: cannot create test workspace: %v", test.Desc, err)
|
|
continue
|
|
}
|
|
ws.state = test.State
|
|
|
|
waitStarted := make(chan struct{})
|
|
waitComplete := make(chan struct{})
|
|
disposeComplete := make(chan struct{})
|
|
go func() {
|
|
waitStarted <- struct{}{}
|
|
done, _, err := ws.WaitOrMarkForDisposal(context.Background())
|
|
if err != nil {
|
|
t.Errorf("%s: WaitOrMarkForDisposal (1st) returned an error: %v", test.Desc, err)
|
|
}
|
|
if done != test.ExpectDone1 {
|
|
t.Errorf("%s: WaitOrMarkForDisposal (1st) returned done == %v, expected %v", test.Desc, done, test.ExpectDone1)
|
|
}
|
|
|
|
done, _, err = ws.WaitOrMarkForDisposal(context.Background())
|
|
if err != nil {
|
|
t.Errorf("%s: WaitOrMarkForDisposal (2nd) returned an error: %v", test.Desc, err)
|
|
}
|
|
if done != test.ExpectDone2 {
|
|
t.Errorf("%s: WaitOrMarkForDisposal (2nd) returned done == %v, expected %v", test.Desc, done, test.ExpectDone2)
|
|
}
|
|
waitComplete <- struct{}{}
|
|
}()
|
|
go func() {
|
|
<-waitStarted
|
|
if !test.Dispose {
|
|
disposeComplete <- struct{}{}
|
|
return
|
|
}
|
|
|
|
<-time.After(500 * time.Millisecond) // just for good measure
|
|
|
|
err := ws.Dispose(context.Background())
|
|
if err != nil {
|
|
t.Errorf("%s: Dispose returned an error: %v", test.Desc, err)
|
|
}
|
|
disposeComplete <- struct{}{}
|
|
}()
|
|
|
|
select {
|
|
case <-waitComplete:
|
|
// all is well
|
|
case <-time.After(1 * time.Second):
|
|
t.Errorf("%s: WaitForInit did not complete", test.Desc)
|
|
}
|
|
select {
|
|
case <-disposeComplete:
|
|
// all is well
|
|
case <-time.After(1 * time.Second):
|
|
t.Errorf("%s: disposeComplete did not complete", test.Desc)
|
|
}
|
|
}
|
|
}
|