Skip to content

Commit bb907a9

Browse files
committed
wip: cache go/analysis facts
1 parent 21bb2b4 commit bb907a9

File tree

28 files changed

+2979
-662
lines changed

28 files changed

+2979
-662
lines changed

internal/cache/cache.go

Lines changed: 500 additions & 0 deletions
Large diffs are not rendered by default.

internal/cache/cache_test.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cache
6+
7+
import (
8+
"bytes"
9+
"encoding/binary"
10+
"fmt"
11+
"io/ioutil"
12+
"os"
13+
"path/filepath"
14+
"testing"
15+
"time"
16+
)
17+
18+
func init() {
19+
verify = false // even if GODEBUG is set
20+
}
21+
22+
func TestBasic(t *testing.T) {
23+
dir, err := ioutil.TempDir("", "cachetest-")
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
defer os.RemoveAll(dir)
28+
_, err = Open(filepath.Join(dir, "notexist"))
29+
if err == nil {
30+
t.Fatal(`Open("tmp/notexist") succeeded, want failure`)
31+
}
32+
33+
cdir := filepath.Join(dir, "c1")
34+
if err := os.Mkdir(cdir, 0777); err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
c1, err := Open(cdir)
39+
if err != nil {
40+
t.Fatalf("Open(c1) (create): %v", err)
41+
}
42+
if err := c1.putIndexEntry(dummyID(1), dummyID(12), 13, true); err != nil {
43+
t.Fatalf("addIndexEntry: %v", err)
44+
}
45+
if err := c1.putIndexEntry(dummyID(1), dummyID(2), 3, true); err != nil { // overwrite entry
46+
t.Fatalf("addIndexEntry: %v", err)
47+
}
48+
if entry, err := c1.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 {
49+
t.Fatalf("c1.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3)
50+
}
51+
52+
c2, err := Open(cdir)
53+
if err != nil {
54+
t.Fatalf("Open(c2) (reuse): %v", err)
55+
}
56+
if entry, err := c2.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 {
57+
t.Fatalf("c2.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3)
58+
}
59+
if err := c2.putIndexEntry(dummyID(2), dummyID(3), 4, true); err != nil {
60+
t.Fatalf("addIndexEntry: %v", err)
61+
}
62+
if entry, err := c1.Get(dummyID(2)); err != nil || entry.OutputID != dummyID(3) || entry.Size != 4 {
63+
t.Fatalf("c1.Get(2) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(3), 4)
64+
}
65+
}
66+
67+
func TestGrowth(t *testing.T) {
68+
dir, err := ioutil.TempDir("", "cachetest-")
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
defer os.RemoveAll(dir)
73+
74+
c, err := Open(dir)
75+
if err != nil {
76+
t.Fatalf("Open: %v", err)
77+
}
78+
79+
n := 10000
80+
if testing.Short() {
81+
n = 1000
82+
}
83+
84+
for i := 0; i < n; i++ {
85+
if err := c.putIndexEntry(dummyID(i), dummyID(i*99), int64(i)*101, true); err != nil {
86+
t.Fatalf("addIndexEntry: %v", err)
87+
}
88+
id := ActionID(dummyID(i))
89+
entry, err := c.Get(id)
90+
if err != nil {
91+
t.Fatalf("Get(%x): %v", id, err)
92+
}
93+
if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 {
94+
t.Errorf("Get(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101)
95+
}
96+
}
97+
for i := 0; i < n; i++ {
98+
id := ActionID(dummyID(i))
99+
entry, err := c.Get(id)
100+
if err != nil {
101+
t.Fatalf("Get2(%x): %v", id, err)
102+
}
103+
if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 {
104+
t.Errorf("Get2(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101)
105+
}
106+
}
107+
}
108+
109+
func TestVerifyPanic(t *testing.T) {
110+
os.Setenv("GODEBUG", "gocacheverify=1")
111+
initEnv()
112+
defer func() {
113+
os.Unsetenv("GODEBUG")
114+
verify = false
115+
}()
116+
117+
if !verify {
118+
t.Fatal("initEnv did not set verify")
119+
}
120+
121+
dir, err := ioutil.TempDir("", "cachetest-")
122+
if err != nil {
123+
t.Fatal(err)
124+
}
125+
defer os.RemoveAll(dir)
126+
127+
c, err := Open(dir)
128+
if err != nil {
129+
t.Fatalf("Open: %v", err)
130+
}
131+
132+
id := ActionID(dummyID(1))
133+
if err := c.PutBytes(id, []byte("abc")); err != nil {
134+
t.Fatal(err)
135+
}
136+
137+
defer func() {
138+
if err := recover(); err != nil {
139+
t.Log(err)
140+
return
141+
}
142+
}()
143+
c.PutBytes(id, []byte("def"))
144+
t.Fatal("mismatched Put did not panic in verify mode")
145+
}
146+
147+
func dummyID(x int) [HashSize]byte {
148+
var out [HashSize]byte
149+
binary.LittleEndian.PutUint64(out[:], uint64(x))
150+
return out
151+
}
152+
153+
func TestCacheTrim(t *testing.T) {
154+
dir, err := ioutil.TempDir("", "cachetest-")
155+
if err != nil {
156+
t.Fatal(err)
157+
}
158+
defer os.RemoveAll(dir)
159+
160+
c, err := Open(dir)
161+
if err != nil {
162+
t.Fatalf("Open: %v", err)
163+
}
164+
const start = 1000000000
165+
now := int64(start)
166+
c.now = func() time.Time { return time.Unix(now, 0) }
167+
168+
checkTime := func(name string, mtime int64) {
169+
t.Helper()
170+
file := filepath.Join(c.dir, name[:2], name)
171+
info, err := os.Stat(file)
172+
if err != nil {
173+
t.Fatal(err)
174+
}
175+
if info.ModTime().Unix() != mtime {
176+
t.Fatalf("%s mtime = %d, want %d", name, info.ModTime().Unix(), mtime)
177+
}
178+
}
179+
180+
id := ActionID(dummyID(1))
181+
c.PutBytes(id, []byte("abc"))
182+
entry, _ := c.Get(id)
183+
c.PutBytes(ActionID(dummyID(2)), []byte("def"))
184+
mtime := now
185+
checkTime(fmt.Sprintf("%x-a", id), mtime)
186+
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
187+
188+
// Get should not change recent mtimes.
189+
now = start + 10
190+
c.Get(id)
191+
checkTime(fmt.Sprintf("%x-a", id), mtime)
192+
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
193+
194+
// Get should change distant mtimes.
195+
now = start + 5000
196+
mtime2 := now
197+
if _, err := c.Get(id); err != nil {
198+
t.Fatal(err)
199+
}
200+
c.OutputFile(entry.OutputID)
201+
checkTime(fmt.Sprintf("%x-a", id), mtime2)
202+
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime2)
203+
204+
// Trim should leave everything alone: it's all too new.
205+
c.Trim()
206+
if _, err := c.Get(id); err != nil {
207+
t.Fatal(err)
208+
}
209+
c.OutputFile(entry.OutputID)
210+
data, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt"))
211+
if err != nil {
212+
t.Fatal(err)
213+
}
214+
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
215+
216+
// Trim less than a day later should not do any work at all.
217+
now = start + 80000
218+
c.Trim()
219+
if _, err := c.Get(id); err != nil {
220+
t.Fatal(err)
221+
}
222+
c.OutputFile(entry.OutputID)
223+
data2, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt"))
224+
if err != nil {
225+
t.Fatal(err)
226+
}
227+
if !bytes.Equal(data, data2) {
228+
t.Fatalf("second trim did work: %q -> %q", data, data2)
229+
}
230+
231+
// Fast forward and do another trim just before the 5 day cutoff.
232+
// Note that because of usedQuantum the cutoff is actually 5 days + 1 hour.
233+
// We used c.Get(id) just now, so 5 days later it should still be kept.
234+
// On the other hand almost a full day has gone by since we wrote dummyID(2)
235+
// and we haven't looked at it since, so 5 days later it should be gone.
236+
now += 5 * 86400
237+
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
238+
c.Trim()
239+
if _, err := c.Get(id); err != nil {
240+
t.Fatal(err)
241+
}
242+
c.OutputFile(entry.OutputID)
243+
mtime3 := now
244+
if _, err := c.Get(dummyID(2)); err == nil { // haven't done a Get for this since original write above
245+
t.Fatalf("Trim did not remove dummyID(2)")
246+
}
247+
248+
// The c.Get(id) refreshed id's mtime again.
249+
// Check that another 5 days later it is still not gone,
250+
// but check by using checkTime, which doesn't bring mtime forward.
251+
now += 5 * 86400
252+
c.Trim()
253+
checkTime(fmt.Sprintf("%x-a", id), mtime3)
254+
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
255+
256+
// Half a day later Trim should still be a no-op, because there was a Trim recently.
257+
// Even though the entry for id is now old enough to be trimmed,
258+
// it gets a reprieve until the time comes for a new Trim scan.
259+
now += 86400 / 2
260+
c.Trim()
261+
checkTime(fmt.Sprintf("%x-a", id), mtime3)
262+
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
263+
264+
// Another half a day later, Trim should actually run, and it should remove id.
265+
now += 86400/2 + 1
266+
c.Trim()
267+
if _, err := c.Get(dummyID(1)); err == nil {
268+
t.Fatal("Trim did not remove dummyID(1)")
269+
}
270+
}

internal/cache/default.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cache
6+
7+
import (
8+
"fmt"
9+
"io/ioutil"
10+
"log"
11+
"os"
12+
"path/filepath"
13+
"sync"
14+
)
15+
16+
// Default returns the default cache to use.
17+
func Default() (*Cache, error) {
18+
defaultOnce.Do(initDefaultCache)
19+
return defaultCache, defaultDirErr
20+
}
21+
22+
var (
23+
defaultOnce sync.Once
24+
defaultCache *Cache
25+
)
26+
27+
// cacheREADME is a message stored in a README in the cache directory.
28+
// Because the cache lives outside the normal Go trees, we leave the
29+
// README as a courtesy to explain where it came from.
30+
const cacheREADME = `This directory holds cached build artifacts from golangci-lint.
31+
`
32+
33+
// initDefaultCache does the work of finding the default cache
34+
// the first time Default is called.
35+
func initDefaultCache() {
36+
dir := DefaultDir()
37+
if err := os.MkdirAll(dir, 0777); err != nil {
38+
log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
39+
}
40+
if _, err := os.Stat(filepath.Join(dir, "README")); err != nil {
41+
// Best effort.
42+
ioutil.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666)
43+
}
44+
45+
c, err := Open(dir)
46+
if err != nil {
47+
log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
48+
}
49+
defaultCache = c
50+
}
51+
52+
var (
53+
defaultDirOnce sync.Once
54+
defaultDir string
55+
defaultDirErr error
56+
)
57+
58+
// DefaultDir returns the effective GOLANGCI_LINT_CACHE setting.
59+
func DefaultDir() string {
60+
// Save the result of the first call to DefaultDir for later use in
61+
// initDefaultCache. cmd/go/main.go explicitly sets GOCACHE so that
62+
// subprocesses will inherit it, but that means initDefaultCache can't
63+
// otherwise distinguish between an explicit "off" and a UserCacheDir error.
64+
65+
defaultDirOnce.Do(func() {
66+
defaultDir = os.Getenv("GOLANGCI_LINT_CACHE")
67+
if filepath.IsAbs(defaultDir) {
68+
return
69+
}
70+
if defaultDir != "" {
71+
defaultDirErr = fmt.Errorf("GOLANGCI_LINT_CACHE is not an absolute path")
72+
return
73+
}
74+
75+
// Compute default location.
76+
dir, err := os.UserCacheDir()
77+
if err != nil {
78+
defaultDirErr = fmt.Errorf("GOLANGCI_LINT_CACHE is not defined and %v", err)
79+
return
80+
}
81+
defaultDir = filepath.Join(dir, "golangci-lint")
82+
})
83+
84+
return defaultDir
85+
}

0 commit comments

Comments
 (0)