Alter source tests to cover entire prefetch algorithm and make it pass

This commit is contained in:
William Elwood 2019-10-31 04:32:21 +00:00 committed by Frank Denis
parent 7e73a26a2f
commit 1e225dbb67
2 changed files with 40 additions and 25 deletions

View file

@ -26,7 +26,8 @@ const (
) )
const ( const (
MinSourcesUpdateDelay = time.Duration(24) * time.Hour DefaultPrefetchDelay time.Duration = 24 * time.Hour
MinimumPrefetchInterval time.Duration = 10 * time.Minute
) )
type Source struct { type Source struct {
@ -50,8 +51,8 @@ var timeNow = time.Now
func fetchFromCache(cacheFile string, refreshDelay time.Duration) (bin []byte, delayTillNextUpdate time.Duration, err error) { func fetchFromCache(cacheFile string, refreshDelay time.Duration) (bin []byte, delayTillNextUpdate time.Duration, err error) {
delayTillNextUpdate = time.Duration(0) delayTillNextUpdate = time.Duration(0)
if refreshDelay < MinSourcesUpdateDelay { if refreshDelay < DefaultPrefetchDelay {
refreshDelay = MinSourcesUpdateDelay refreshDelay = DefaultPrefetchDelay
} }
var fi os.FileInfo var fi os.FileInfo
if fi, err = os.Stat(cacheFile); err != nil { if fi, err = os.Stat(cacheFile); err != nil {
@ -62,7 +63,7 @@ func fetchFromCache(cacheFile string, refreshDelay time.Duration) (bin []byte, d
} }
if elapsed := timeNow().Sub(fi.ModTime()); elapsed < refreshDelay { if elapsed := timeNow().Sub(fi.ModTime()); elapsed < refreshDelay {
dlog.Debugf("Cache file [%s] is still fresh", cacheFile) dlog.Debugf("Cache file [%s] is still fresh", cacheFile)
delayTillNextUpdate = MinSourcesUpdateDelay - elapsed delayTillNextUpdate = DefaultPrefetchDelay - elapsed
} else { } else {
dlog.Debugf("Cache file [%s] needs to be refreshed", cacheFile) dlog.Debugf("Cache file [%s] needs to be refreshed", cacheFile)
} }
@ -105,7 +106,7 @@ func fetchWithCache(xTransport *XTransport, urlStr string, cacheFile string, ref
dlog.Warnf("%s: %s", absPath, err) dlog.Warnf("%s: %s", absPath, err)
} }
} }
delayTillNextUpdate = MinSourcesUpdateDelay delayTillNextUpdate = DefaultPrefetchDelay
return return
} }
@ -178,6 +179,7 @@ func NewSource(xTransport *XTransport, urls []string, minisignKeyStr string, cac
func PrefetchSources(xTransport *XTransport, sources []*Source) time.Duration { func PrefetchSources(xTransport *XTransport, sources []*Source) time.Duration {
now := timeNow() now := timeNow()
interval := MinimumPrefetchInterval
for _, source := range sources { for _, source := range sources {
for _, urlToPrefetch := range source.prefetch { for _, urlToPrefetch := range source.prefetch {
if now.After(urlToPrefetch.when) { if now.After(urlToPrefetch.when) {
@ -186,11 +188,15 @@ func PrefetchSources(xTransport *XTransport, sources []*Source) time.Duration {
dlog.Debugf("Prefetching [%s] failed: %s", urlToPrefetch.url, err) dlog.Debugf("Prefetching [%s] failed: %s", urlToPrefetch.url, err)
} else { } else {
dlog.Debugf("Prefetching [%s] succeeded. Next refresh scheduled for %v", urlToPrefetch.url, urlToPrefetch.when) dlog.Debugf("Prefetching [%s] succeeded. Next refresh scheduled for %v", urlToPrefetch.url, urlToPrefetch.when)
delay := urlToPrefetch.when.Sub(now)
if delay >= MinimumPrefetchInterval && (interval == MinimumPrefetchInterval || interval > delay) {
interval = delay
} }
} }
} }
} }
return 60 * time.Second }
return interval
} }
func (source *Source) Parse(prefix string) ([]RegisteredServer, error) { func (source *Source) Parse(prefix string) ([]RegisteredServer, error) {
@ -268,7 +274,7 @@ PartsLoop:
} }
func PrefetchSourceURL(xTransport *XTransport, urlToPrefetch *URLToPrefetch) error { func PrefetchSourceURL(xTransport *XTransport, urlToPrefetch *URLToPrefetch) error {
_, delayTillNextUpdate, err := fetchWithCache(xTransport, urlToPrefetch.url, urlToPrefetch.cacheFile, MinSourcesUpdateDelay) _, delayTillNextUpdate, err := fetchWithCache(xTransport, urlToPrefetch.url, urlToPrefetch.cacheFile, DefaultPrefetchDelay)
urlToPrefetch.when = timeNow().Add(delayTillNextUpdate) urlToPrefetch.when = timeNow().Add(delayTillNextUpdate)
return err return err
} }

View file

@ -228,8 +228,8 @@ func setupSourceTest(t *testing.T) (func(), *SourceTestData) {
} }
d.xTransport.rebuildTransport() d.xTransport.rebuildTransport()
d.timeNow = time.Now().AddDate(0, 0, 0) d.timeNow = time.Now().AddDate(0, 0, 0)
d.timeOld = d.timeNow.Add(MinSourcesUpdateDelay * -4) d.timeOld = d.timeNow.Add(DefaultPrefetchDelay * -4)
d.timeUpd = d.timeNow.Add(MinSourcesUpdateDelay) d.timeUpd = d.timeNow.Add(DefaultPrefetchDelay)
timeNow = func() time.Time { return d.timeNow } // originally defined in sources.go, replaced during testing to ensure consistent results timeNow = func() time.Time { return d.timeNow } // originally defined in sources.go, replaced during testing to ensure consistent results
makeTempDir(t, d) makeTempDir(t, d)
makeTestServer(t, d) makeTestServer(t, d)
@ -267,7 +267,7 @@ func prepSourceTestDownload(t *testing.T, d *SourceTestData, e *SourceTestExpect
switch state { switch state {
case TestStateCorrect: case TestStateCorrect:
e.cache = []SourceFixture{d.fixtures[state][source], d.fixtures[state][source+".minisig"]} e.cache = []SourceFixture{d.fixtures[state][source], d.fixtures[state][source+".minisig"]}
e.Source.in, e.success, e.refresh = e.cache[0].content, true, d.timeUpd e.Source.in, e.success, e.download, e.refresh = e.cache[0].content, true, true, d.timeUpd
fallthrough fallthrough
case TestStateMissingSig, TestStatePartial, TestStatePartialSig, TestStateReadSigErr: case TestStateMissingSig, TestStatePartial, TestStatePartialSig, TestStateReadSigErr:
d.reqExpect[path+".minisig"]++ d.reqExpect[path+".minisig"]++
@ -321,7 +321,7 @@ func setupSourceTestCase(t *testing.T, d *SourceTestData, i int,
func TestNewSource(t *testing.T) { func TestNewSource(t *testing.T) {
teardown, d := setupSourceTest(t) teardown, d := setupSourceTest(t)
defer teardown() defer teardown()
doTest := func(t *testing.T, e *SourceTestExpect, got *Source, err error) { checkResult := func(t *testing.T, e *SourceTestExpect, got *Source, err error) {
c := check.T(t) c := check.T(t)
if len(e.err) > 0 { if len(e.err) > 0 {
c.Match(err, e.err, "Unexpected error") c.Match(err, e.err, "Unexpected error")
@ -338,14 +338,14 @@ func TestNewSource(t *testing.T) {
refresh time.Duration refresh time.Duration
e *SourceTestExpect e *SourceTestExpect
}{ }{
{"old format", d.keyStr, "v1", MinSourcesUpdateDelay * 3, &SourceTestExpect{ {"old format", d.keyStr, "v1", DefaultPrefetchDelay * 3, &SourceTestExpect{
Source: &Source{}, err: "Unsupported source format"}}, Source: &Source{}, err: "Unsupported source format"}},
{"invalid public key", "", "v2", MinSourcesUpdateDelay * 3, &SourceTestExpect{ {"invalid public key", "", "v2", DefaultPrefetchDelay * 3, &SourceTestExpect{
Source: &Source{}, err: "Invalid encoded public key"}}, Source: &Source{}, err: "Invalid encoded public key"}},
} { } {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := NewSource(d.xTransport, tt.e.Source.urls, tt.key, tt.e.cachePath, tt.v, tt.refresh) got, err := NewSource(d.xTransport, tt.e.Source.urls, tt.key, tt.e.cachePath, tt.v, tt.refresh)
doTest(t, tt.e, got, err) checkResult(t, tt.e, got, err)
}) })
} }
for cacheTestName, cacheTest := range d.cacheTests { for cacheTestName, cacheTest := range d.cacheTests {
@ -354,28 +354,28 @@ func TestNewSource(t *testing.T) {
for i := range d.sources { for i := range d.sources {
id, e := setupSourceTestCase(t, d, i, &cacheTest, downloadTest) id, e := setupSourceTestCase(t, d, i, &cacheTest, downloadTest)
t.Run("cache "+cacheTestName+", download "+downloadTestName+"/"+id, func(t *testing.T) { t.Run("cache "+cacheTestName+", download "+downloadTestName+"/"+id, func(t *testing.T) {
got, err := NewSource(d.xTransport, e.Source.urls, d.keyStr, e.cachePath, "v2", MinSourcesUpdateDelay*3) got, err := NewSource(d.xTransport, e.Source.urls, d.keyStr, e.cachePath, "v2", DefaultPrefetchDelay*3)
doTest(t, e, got, err) checkResult(t, e, got, err)
}) })
} }
} }
} }
} }
func TestPrefetchSourceURL(t *testing.T) { func TestPrefetchSources(t *testing.T) {
teardown, d := setupSourceTest(t) teardown, d := setupSourceTest(t)
defer teardown() defer teardown()
doTest := func(t *testing.T, expects []*SourceTestExpect) { checkResult := func(t *testing.T, expects []*SourceTestExpect, got time.Duration) {
c := check.T(t) c := check.T(t)
expectDelay := MinimumPrefetchInterval
for _, e := range expects { for _, e := range expects {
for _, url := range e.Source.urls { if e.refresh.After(d.timeNow) {
for _, suffix := range []string{"", ".minisig"} { expectDelay = e.refresh.Sub(d.timeNow)
pf := &URLToPrefetch{url + suffix, e.cachePath + suffix, d.timeOld} } else if e.download {
PrefetchSourceURL(d.xTransport, pf) expectDelay = DefaultPrefetchDelay
c.InDelta(pf.when, e.refresh, time.Second, "Unexpected prefetch refresh time")
}
} }
} }
c.InDelta(got, expectDelay, time.Second, "Unexpected return")
checkTestServer(c, d) checkTestServer(c, d)
for _, e := range expects { for _, e := range expects {
checkSourceCache(c, e.cachePath, e.cache) checkSourceCache(c, e.cachePath, e.cache)
@ -383,13 +383,22 @@ func TestPrefetchSourceURL(t *testing.T) {
} }
for downloadTestName, downloadTest := range d.downloadTests { for downloadTestName, downloadTest := range d.downloadTests {
d.n++ d.n++
sources := []*Source{}
expects := []*SourceTestExpect{} expects := []*SourceTestExpect{}
for i := range d.sources { for i := range d.sources {
_, e := setupSourceTestCase(t, d, i, nil, downloadTest) _, e := setupSourceTestCase(t, d, i, nil, downloadTest)
sources = append(sources, e.Source)
expects = append(expects, e) expects = append(expects, e)
for _, pf := range e.Source.prefetch {
pf.when = d.timeOld
}
if e.download {
e.refresh = d.timeUpd
}
} }
t.Run("download "+downloadTestName, func(t *testing.T) { t.Run("download "+downloadTestName, func(t *testing.T) {
doTest(t, expects) got := PrefetchSources(d.xTransport, sources)
checkResult(t, expects, got)
}) })
} }
} }