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 select {
103 case k := <-logger.loads:
104 t.Errorf("spurious load of %q", k)
105 case k := <-logger.drops:
106 t.Errorf("spurious drops of %q", k)
107 default:
108 }
109}
110
111func TestDirWatcherLoadEmpty(t *testing.T) {
112 dir := t.TempDir()
113
114 logger := &loggingLoader{
115 loads: make(chan string, 10),
116 drops: make(chan string, 10),
117 }
118 dw, err := newDirectoryWatcher(dir, logger)
119 if err != nil {
120 t.Fatal(err)
121 }
122 advanceFS()
123 dw.Stop()
124
125 select {
126 case k := <-logger.loads:
127 t.Errorf("spurious load of %q", k)
128 case k := <-logger.drops:
129 t.Errorf("spurious drops of %q", k)
130 default:
131 }
132}
133
134func TestVersionFromPath(t *testing.T) {
135 cases := map[string]struct {
136 name string
137 version int
138 }{
139 "github.com%2Fgoogle%2Fzoekt_v16.00000.zoekt": {
140 name: "github.com%2Fgoogle%2Fzoekt",
141 version: 16,
142 },
143 "github.com%2Fgoogle%2Fsre_yield_v15.00000.zoekt": {
144 name: "github.com%2Fgoogle%2Fsre_yield",
145 version: 15,
146 },
147 "repos/github.com%2Fgoogle%2Fsre_yield_v15.00000.zoekt": {
148 name: "repos/github.com%2Fgoogle%2Fsre_yield",
149 version: 15,
150 },
151 "foo": {
152 name: "foo",
153 version: 0,
154 },
155 "foo_bar": {
156 name: "foo_bar",
157 version: 0,
158 },
159 "github.com%2Fgoogle%2Fzoekt_vfoo.00000.zoekt": {
160 name: "github.com%2Fgoogle%2Fzoekt_vfoo.00000.zoekt",
161 version: 0,
162 },
163 }
164 for path, tc := range cases {
165 name, version := versionFromPath(path)
166 if name != tc.name || version != tc.version {
167 t.Errorf("%s: got name %s and version %d, want name %s and version %d", path, name, version, tc.name, tc.version)
168 }
169 }
170}
171
172func TestDirWatcherLoadLatest(t *testing.T) {
173 dir := t.TempDir()
174
175 logger := &loggingLoader{
176 loads: make(chan string, 10),
177 drops: make(chan string, 10),
178 }
179 // Upstream fails if empty. Sourcegraph does not
180 // _, err := NewDirectoryWatcher(dir, logger)
181 // if err == nil || !strings.Contains(err.Error(), "empty") {
182 // t.Fatalf("got %v, want 'empty'", err)
183 // }
184
185 want := index.NextIndexFormatVersion
186 shardLatest := filepath.Join(dir, fmt.Sprintf("foo_v%d.00000.zoekt", want))
187
188 for delta := -1; delta <= 1; delta++ {
189 repo := fmt.Sprintf("foo_v%d.00000.zoekt", want+delta)
190 shard := filepath.Join(dir, repo)
191 if err := os.WriteFile(shard, []byte("hello"), 0o644); err != nil {
192 t.Fatalf("WriteFile: %v", err)
193 }
194 }
195
196 dw, err := newDirectoryWatcher(dir, logger)
197 if err != nil {
198 t.Fatalf("NewDirectoryWatcher: %v", err)
199 }
200 defer dw.Stop()
201
202 if got := <-logger.loads; got != shardLatest {
203 t.Fatalf("got load event %v, want %v", got, shardLatest)
204 }
205
206 advanceFS()
207 dw.Stop()
208
209 select {
210 case k := <-logger.loads:
211 t.Errorf("spurious load of %q", k)
212 case k := <-logger.drops:
213 t.Errorf("spurious drops of %q", k)
214 default:
215 }
216}
217
218func TestHumanTruncateList(t *testing.T) {
219 paths := []string{
220 "dir/1",
221 "dir/2",
222 "dir/3",
223 "dir/4",
224 }
225
226 assert := func(max int, want string) {
227 got := humanTruncateList(paths, max)
228 if got != want {
229 t.Errorf("unexpected humanTruncateList max=%d.\ngot: %s\nwant: %s", max, got, want)
230 }
231 }
232
233 assert(1, "1... 3 more")
234 assert(2, "1, 2... 2 more")
235 assert(3, "1, 2, 3... 1 more")
236 assert(4, "1, 2, 3, 4")
237 assert(5, "1, 2, 3, 4")
238}