8000 add tests · coder/coder@a7187b2 · GitHub
[go: up one dir, main page]

Skip to content

Commit a7187b2

Browse files
committed
add tests
1 parent 9c4c3ef commit a7187b2

File tree

5 files changed

+134
-26
lines changed

5 files changed

+134
-26
lines changed

coderd/files/cache.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ func NewFromStore(store database.Store) Cache {
2929
}
3030

3131
return Cache{
32+
lock: sync.Mutex{},
33+
data: make(map[uuid.UUID]*cacheEntry),
3234
fetcher: fetcher,
3335
}
3436
}
@@ -41,15 +43,16 @@ func NewFromStore(store database.Store) Cache {
4143
type Cache struct {
4244
lock sync.Mutex
4345
data map[uuid.UUID]*cacheEntry
44-
45-
fetcher func(context.Context, uuid.UUID) (fs.FS, error)
46+
fetcher
4647
}
4748

4849
type cacheEntry struct {
4950
refCount *atomic.Int64
5051
value *lazy.ValueWithError[fs.FS]
5152
}
5253

54+
type fetcher func(context.Context, uuid.UUID) (fs.FS, error)
55+
5356
// Acquire will load the fs.FS for the given file. It guarantees that parallel
5457
// calls for the same fileID will only result in one fetch, and that parallel
5558
// calls for distinct fileIDs will fetch in parallel.

coderd/files/cache_internal_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package files
2+
3+
import (
4+
"context"
5+
"io/fs"
6+
"sync"
7+
"sync/atomic"
8+
"testing"
9+
"time"
10+
11+
"github.com/coder/coder/v2/testutil"
12+
"github.com/google/uuid"
13+
"github.com/spf13/afero"
14+
"github.com/stretchr/testify/require"
15+
"golang.org/x/sync/errgroup"
16+
)
17+
18+
func TestTryAgain(t *testing.T) {
19+
var fetches atomic.Int64
20+
c := newTestCache(func(_ context.Context, _ uuid.UUID) (fs.FS, error) {
21+
fetches.Add(1)
22+
// Wait long enough before returning to make sure that all of the goroutines
23+
// will be waiting in line, ensuring that no one duplicated a fetch.
24+
time.Sleep(testutil.IntervalMedium)
25+
return afero.NewIOFS(afero.NewMemMapFs()), nil
26+
})
27+
28+
batches := 1000
29+
groups := make([]*errgroup.Group, 0, batches)
30+
for range batches {
31+
groups = append(groups, new(errgroup.Group))
32+
}
33+
34+
// Call Acquire with a unique ID per batch, many times per batch, with many
35+
// batches all in parallel. This gives us
36+
batchSize := 10
37+
for _, g := range groups {
38+
id := uuid.New()
39+
for range batchSize {
40+
g.Go(func() error {
41+
_, err := c.Acquire(t.Context(), id)
42+
return err
43+
})
44+
}
45+
}
46+
47+
for _, g := range groups {
48+
require.NoError(t, g.Wait())
49+
}
50+
require.Equal(t, int64(batches), fetches.Load())
51+
}
52+
53+
func newTestCache(fetcher func(context.Context, uuid.UUID) (fs.FS, error)) Cache {
54+
return Cache{
55+
lock: sync.Mutex{},
56+
data: make(map[uuid.UUID]*cacheEntry),
57+
fetcher: fetcher,
58+
}
59+
}

coderd/util/lazy/value.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,3 @@ func (v *Value[T]) Load() T {
2626
func New[T any](fn func() T) *Value[T] {
2727
return &Value[T]{fn: fn}
2828
}
29-
30-
type ValueWithError[T any] struct {
31-
inner Value[result[T]]
32-
}
33-
34-
type result[T any] struct {
35-
value T
36-
err error
37-
}
38-
39-
// NewWithError allows you to provide a lazy initializer that can fail.
40-
func NewWithError[T any](fn func() (T, error)) *ValueWithError[T] {
41-
return &ValueWithError[T]{
42-
inner: Value[result[T]]{fn: func() result[T] {
43-
value, err := fn()
44-
return result[T]{value: value, err: err}
45-
}},
46-
}
47-
}
48-
49-
func (v *ValueWithError[T]) Load() (T, error) {
50-
result := v.inner.Load()
51-
return result.value, result.err
52-
}

coderd/util/lazy/valuewitherror.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package lazy
2+
3+
type ValueWithError[T any] struct {
4+
inner Value[result[T]]
5+
}
6+
7+
type result[T any] struct {
8+
value T
9+
err error
10+
}
11+
12+
// NewWithError allows you to provide a lazy initializer that can fail.
13+
func NewWithError[T any](fn func() (T, error)) *ValueWithError[T] {
14+
return &ValueWithError[T]{
15+
inner: Value[result[T]]{fn: func() result[T] {
16+
value, err := fn()
17+
return result[T]{value: value, err: err}
18+
}},
19+
}
20+
}
21+
22+
func (v *ValueWithError[T]) Load() (T, error) {
23+
result := v.inner.Load()
24+
return result.value, result.err
25+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package lazy_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/coder/coder/v2/coderd/util/lazy"
7+
"github.com/stretchr/testify/require"
8+
"golang.org/x/xerrors"
9+
)
10+
11+
func TestLazyWithErrorOK(t *testing.T) {
12+
l := lazy.NewWithError(func() (int, error) {
13+
return 1, nil
14+
})
15+
16+
i, err := l.Load()
17+
require.NoError(t, err)
18+
require.Equal(t, 1, i)
19+
}
20+
21+
func TestLazyWithErrorErr(t *testing.T) {
22+
l := lazy.NewWithError(func() (int, error) {
23+
return 0, xerrors.New("oh no! everything that could went horribly wrong!")
24+
})
25+
26+
i, err := l.Load()
27+
require.Error(t, err)
28+
require.Equal(t, 0, i)
29+
}
30+
31+
func TestLazyWithErrorPointers(t *testing.T) {
32+
a := 1
33+
l := lazy.NewWithError(func() (*int, error) {
34+
return &a, nil
35+
})
36+
37+
b, err := l.Load()
38+
require.NoError(t, err)
39+
c, err := l.Load()
40+
require.NoError(t, err)
41+
42+
*b += 1
43+
*c += 1
44+
require.Equal(t, 3, a)
45+
}

0 commit comments

Comments
 (0)
0