1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package zip
import (
"context"
"io/ioutil"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/metrics"
)
func TestVFSRoot(t *testing.T) {
url, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip", nil)
defer cleanup()
tests := map[string]struct {
path string
expectedErrMsg string
}{
"zip_file_exists": {
path: "/public.zip",
},
"zip_file_does_not_exist": {
path: "/unknown",
expectedErrMsg: "404 Not Found",
},
"invalid_url": {
path: "/%",
expectedErrMsg: "invalid URL",
},
}
vfs := New()
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
root, err := vfs.Root(context.Background(), url+tt.path)
if tt.expectedErrMsg != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedErrMsg)
return
}
require.NoError(t, err)
require.IsType(t, &zipArchive{}, root)
f, err := root.Open(context.Background(), "index.html")
require.NoError(t, err)
content, err := ioutil.ReadAll(f)
require.NoError(t, err)
require.Equal(t, "zip.gitlab.io/project/index.html\n", string(content))
fi, err := root.Lstat(context.Background(), "index.html")
require.NoError(t, err)
require.Equal(t, "index.html", fi.Name())
link, err := root.Readlink(context.Background(), "symlink.html")
require.NoError(t, err)
require.Equal(t, "subdir/linked.html", link)
})
}
}
func TestVFSFindOrOpenArchiveConcurrentAccess(t *testing.T) {
testServerURL, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip", nil)
defer cleanup()
path := testServerURL + "/public.zip"
vfs := New().(*zipVFS)
root, err := vfs.Root(context.Background(), path)
require.NoError(t, err)
done := make(chan struct{})
defer close(done)
// Try to hit a condition between the invocation
// of cache.GetWithExpiration and cache.Add
go func() {
for {
select {
case <-done:
return
default:
vfs.cache.Flush()
vfs.cache.SetDefault(path, root)
}
}
}()
require.Eventually(t, func() bool {
_, err := vfs.findOrOpenArchive(context.Background(), path)
return err == errAlreadyCached
}, time.Second, time.Nanosecond)
}
func TestVFSFindOrCreateArchiveCacheEvict(t *testing.T) {
testServerURL, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip", nil)
defer cleanup()
path := testServerURL + "/public.zip"
vfs := New().(*zipVFS)
archivesMetric := metrics.ZipCachedEntries.WithLabelValues("archive")
archivesCount := testutil.ToFloat64(archivesMetric)
// create a new archive and increase counters
archive, err := vfs.findOrOpenArchive(context.Background(), path)
require.NoError(t, err)
require.NotNil(t, archive)
// inject into cache to be "expired"
// (we could as well wait `defaultCacheExpirationInterval`)
vfs.cache.Set(path, archive, time.Nanosecond)
// a new object is created
archive2, err := vfs.findOrOpenArchive(context.Background(), path)
require.NoError(t, err)
require.NotNil(t, archive2)
require.NotEqual(t, archive, archive2, "a different archive is returned")
archivesCountEnd := testutil.ToFloat64(archivesMetric)
require.Equal(t, float64(1), archivesCountEnd-archivesCount, "all expired archives are evicted")
}
|