fork of https://github.com/sourcegraph/zoekt
1// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package search
16
17import (
18 "fmt"
19 "os"
20 "path/filepath"
21 "testing"
22 "time"
23
24 "github.com/sourcegraph/zoekt/index"
25)
26
27type loggingLoader struct {
28 loads chan string
29 drops chan string
30}
31
32func (l *loggingLoader) load(keys ...string) {
33 for _, key := range keys {
34 l.loads <- key
35 }
36}
37
38func (l *loggingLoader) drop(keys ...string) {
39 for _, key := range keys {
40 l.drops <- key
41 }
42}
43
44func advanceFS() {
45 time.Sleep(10 * time.Millisecond)
46}
47
48func TestDirWatcherUnloadOnce(t *testing.T) {
49 dir := t.TempDir()
50
51 logger := &loggingLoader{
52 loads: make(chan string, 10),
53 drops: make(chan string, 10),
54 }
55 // Upstream fails if empty. Sourcegraph does not
56 // _, err := NewDirectoryWatcher(dir, logger)
57 // if err == nil || !strings.Contains(err.Error(), "empty") {
58 // t.Fatalf("got %v, want 'empty'", err)
59 // }
60
61 shard := filepath.Join(dir, "foo.zoekt")
62 if err := os.WriteFile(shard, []byte("hello"), 0o644); err != nil {
63 t.Fatalf("WriteFile: %v", err)
64 }
65
66 dw, err := newDirectoryWatcher(dir, logger)
67 if err != nil {
68 t.Fatalf("NewDirectoryWatcher: %v", err)
69 }
70 defer dw.Stop()
71
72 if got := <-logger.loads; got != shard {
73 t.Fatalf("got load event %v, want %v", got, shard)
74 }
75
76 // Must sleep because of FS timestamp resolution.
77 advanceFS()
78 if err := os.WriteFile(shard, []byte("changed"), 0o644); err != nil {
79 t.Fatalf("WriteFile: %v", err)
80 }
81
82 if got := <-logger.loads; got != shard {
83 t.Fatalf("got load event %v, want %v", got, shard)
84 }
85
86 advanceFS()
87 if err := os.Remove(shard); err != nil {
88 t.Fatalf("Remove: %v", err)
89 }
90
91 if got := <-logger.drops; got != shard {
92 t.Fatalf("got drops event %v, want %v", got, shard)
93 }
94
95 advanceFS()
96 if err := os.WriteFile(shard+".bla", []byte("changed"), 0o644); err != nil {
97 t.Fatalf("WriteFile: %v", err)
98 }
99
100 dw.Stop()
101
102 // fsnotify can produce multiple events for a single write, which can lead to
103 // extra queued load notifications by the time we stop. Drain them and only
104 // assert we don't drop the same shard again.
105 for len(logger.loads) > 0 {
106 <-logger.loads
107 }
108
109 select {
110 case k := <-logger.drops:
111 t.Errorf("spurious drops of %q", k)
112 default:
113 }
114}
115
116func TestDirWatcherLoadEmpty(t *testing.T) {
117 dir := t.TempDir()
118
119 logger := &loggingLoader{
120 loads: make(chan string, 10),
121 drops: make(chan string, 10),
122 }
123 dw, err := newDirectoryWatcher(dir, logger)
124 if err != nil {
125 t.Fatal(err)
126 }
127 advanceFS()
128 dw.Stop()
129
130 select {
131 case k := <-logger.loads:
132 t.Errorf("spurious load of %q", k)
133 case k := <-logger.drops:
134 t.Errorf("spurious drops of %q", k)
135 default:
136 }
137}
138
139func TestVersionFromPath(t *testing.T) {
140 cases := map[string]struct {
141 name string
142 version int
143 }{
144 "github.com%2Fgoogle%2Fzoekt_v16.00000.zoekt": {
145 name: "github.com%2Fgoogle%2Fzoekt",
146 version: 16,
147 },
148 "github.com%2Fgoogle%2Fsre_yield_v15.00000.zoekt": {
149 name: "github.com%2Fgoogle%2Fsre_yield",
150 version: 15,
151 },
152 "repos/github.com%2Fgoogle%2Fsre_yield_v15.00000.zoekt": {
153 name: "repos/github.com%2Fgoogle%2Fsre_yield",
154 version: 15,
155 },
156 "foo": {
157 name: "foo",
158 version: 0,
159 },
160 "foo_bar": {
161 name: "foo_bar",
162 version: 0,
163 },
164 "github.com%2Fgoogle%2Fzoekt_vfoo.00000.zoekt": {
165 name: "github.com%2Fgoogle%2Fzoekt_vfoo.00000.zoekt",
166 version: 0,
167 },
168 }
169 for path, tc := range cases {
170 name, version := versionFromPath(path)
171 if name != tc.name || version != tc.version {
172 t.Errorf("%s: got name %s and version %d, want name %s and version %d", path, name, version, tc.name, tc.version)
173 }
174 }
175}
176
177func TestDirWatcherLoadLatest(t *testing.T) {
178 dir := t.TempDir()
179
180 logger := &loggingLoader{
181 loads: make(chan string, 10),
182 drops: make(chan string, 10),
183 }
184 // Upstream fails if empty. Sourcegraph does not
185 // _, err := NewDirectoryWatcher(dir, logger)
186 // if err == nil || !strings.Contains(err.Error(), "empty") {
187 // t.Fatalf("got %v, want 'empty'", err)
188 // }
189
190 want := index.NextIndexFormatVersion
191 shardLatest := filepath.Join(dir, fmt.Sprintf("foo_v%d.00000.zoekt", want))
192
193 for delta := -1; delta <= 1; delta++ {
194 repo := fmt.Sprintf("foo_v%d.00000.zoekt", want+delta)
195 shard := filepath.Join(dir, repo)
196 if err := os.WriteFile(shard, []byte("hello"), 0o644); err != nil {
197 t.Fatalf("WriteFile: %v", err)
198 }
199 }
200
201 dw, err := newDirectoryWatcher(dir, logger)
202 if err != nil {
203 t.Fatalf("NewDirectoryWatcher: %v", err)
204 }
205 defer dw.Stop()
206
207 if got := <-logger.loads; got != shardLatest {
208 t.Fatalf("got load event %v, want %v", got, shardLatest)
209 }
210
211 advanceFS()
212 dw.Stop()
213
214 select {
215 case k := <-logger.loads:
216 t.Errorf("spurious load of %q", k)
217 case k := <-logger.drops:
218 t.Errorf("spurious drops of %q", k)
219 default:
220 }
221}
222
223func TestHumanTruncateList(t *testing.T) {
224 paths := []string{
225 "dir/1",
226 "dir/2",
227 "dir/3",
228 "dir/4",
229 }
230
231 assert := func(max int, want string) {
232 got := humanTruncateList(paths, max)
233 if got != want {
234 t.Errorf("unexpected humanTruncateList max=%d.\ngot: %s\nwant: %s", max, got, want)
235 }
236 }
237
238 assert(1, "1... 3 more")
239 assert(2, "1, 2... 2 more")
240 assert(3, "1, 2, 3... 1 more")
241 assert(4, "1, 2, 3, 4")
242 assert(5, "1, 2, 3, 4")
243}