new test for streaming and struct changes and additions

This commit is contained in:
2026-05-14 09:24:04 -06:00
parent 98a61e0137
commit f25d36a17e
4 changed files with 87 additions and 27 deletions
+9 -4
View File
@@ -10,8 +10,6 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"golang.org/x/sync/singleflight"
) )
const userAgent = "pacman/7.1.0 (Linux x86_64) libalpm/16.0.1" const userAgent = "pacman/7.1.0 (Linux x86_64) libalpm/16.0.1"
@@ -20,9 +18,10 @@ type Cache struct {
cfg CacheConfig cfg CacheConfig
cr *os.Root cr *os.Root
mirrorIdx atomic.Uint64 mirrorIdx atomic.Uint64
sf singleflight.Group //prevents duplicate downloads refreshMu sync.Mutex
mu sync.Mutex
client http.Client client http.Client
inFlight map[string]*inFlight
inFlightMu sync.Mutex
} }
type CacheConfig struct { type CacheConfig struct {
@@ -33,6 +32,11 @@ type CacheConfig struct {
ClientTimeout time.Duration ClientTimeout time.Duration
} }
type inFlight struct {
done chan struct{}
err error
}
type CacheFile struct { type CacheFile struct {
Reader io.ReadCloser Reader io.ReadCloser
Size int64 Size int64
@@ -71,6 +75,7 @@ func NewCache(cacheRoot string, mirrorURLs []string, mirroredRepos []string) (*C
Timeout: cfg.ClientTimeout, Timeout: cfg.ClientTimeout,
Transport: transport, Transport: transport,
}, },
inFlight: make(map[string]*inFlight),
}, nil }, nil
} }
+65
View File
@@ -233,3 +233,68 @@ func TestCreateSymlinks(t *testing.T) {
} }
} }
} }
func TestGetStreamMultiplClient(t *testing.T) {
firstBytesSend := make(chan struct{})
const expectedOne = "This is fake file contents"
const expectedTwo = "More fake file contents"
expected := expectedOne + expectedTwo
svr := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//nolint:errcheck //ephemeral no need to check
fmt.Fprint(w, expectedOne)
w.(http.Flusher).Flush()
close(firstBytesSend)
time.Sleep(2 * time.Second)
fmt.Fprint(w, expectedTwo)
}))
c := newTestCache(t, []string{svr.URL + "/"})
c.client.Timeout = 10 * time.Second
type fetchResult struct {
data []byte
err error
}
results := make(chan fetchResult, 2)
for range 2 {
go func() {
cf, err := c.Fetch("fakefile")
if err != nil {
results <- fetchResult{err: err}
return
}
defer cf.Reader.Close()
data, err := io.ReadAll(cf.Reader)
results <- fetchResult{data: data, err: err}
}()
}
<-firstBytesSend
c.inFlightMu.Lock()
_, ok := c.inFlight["fakefile"]
c.inFlightMu.Unlock()
if !ok {
t.Errorf("no matching key in map: %v", c.inFlight)
}
for range 2 {
result := <-results
if result.err != nil {
t.Errorf("a fetch failed: %v", result.err)
}
if !bytes.Equal(result.data, []byte(expected)) {
t.Errorf("expected result to contain %s got %s", expected, result.data)
}
}
data, err := c.cr.ReadFile("fakefile")
if err != nil {
t.Fatalf("Error reading file back: %v", err)
}
if !bytes.Equal(data, []byte(expected)) {
t.Errorf("expected file to contain %s got %s", expected, data)
}
}
+5 -15
View File
@@ -7,6 +7,9 @@ import (
) )
func (c *Cache) Fetch(relPath string) (*CacheFile, error) { func (c *Cache) Fetch(relPath string) (*CacheFile, error) {
// relPath is relative to the localRoot
// ie relPath includes /{repo}/os/{arch}/ and the actual name linux-x.x.x.pkg.tar.zst
// return file directly if exists in cache // return file directly if exists in cache
cf, err := c.getCachedFile(relPath) cf, err := c.getCachedFile(relPath)
if err == nil { if err == nil {
@@ -14,24 +17,11 @@ func (c *Cache) Fetch(relPath string) (*CacheFile, error) {
} }
// fetch file from upstream // fetch file from upstream
_, err, _ = c.sf.Do(relPath, func() (any, error) {
slog.Debug("calling fetch", "file", relPath) slog.Debug("calling fetch", "file", relPath)
return nil, c.fetch(relPath) return nil, c.getStream(relPath)
})
if err != nil {
return nil, err
} }
cf, err = c.getCachedFile(relPath) func (c *Cache) getStream(relPath string) error {
if err != nil {
return nil, err
}
return cf, nil
}
func (c *Cache) fetch(relPath string) error {
// relPath is relative to the localRoot
// ie relPath includes /{repo}/os/{arch}/ and the actual name linux-x.x.x.pkg.tar.zst
// declare vars outside loop // declare vars outside loop
var err error var err error
+3 -3
View File
@@ -3,10 +3,10 @@ package cache
import "path/filepath" import "path/filepath"
func (c *Cache) Refresh() error { func (c *Cache) Refresh() error {
if !c.mu.TryLock() { if !c.refreshMu.TryLock() {
return nil return nil
} }
defer c.mu.Unlock() defer c.refreshMu.Unlock()
for _, repo := range c.cfg.mirroredRepos { for _, repo := range c.cfg.mirroredRepos {
if err := c.refreshDB(repo); err != nil { if err := c.refreshDB(repo); err != nil {
@@ -19,7 +19,7 @@ func (c *Cache) Refresh() error {
func (c *Cache) refreshDB(repo string) error { func (c *Cache) refreshDB(repo string) error {
dbFile := repo + ".db.tar.gz" dbFile := repo + ".db.tar.gz"
dbPath := filepath.Join(repo, "os/x86_64", dbFile) dbPath := filepath.Join(repo, "os/x86_64", dbFile)
err := c.fetch(dbPath) err := c.getStream(dbPath)
if err != nil { if err != nil {
return err return err
} }