2019-10-30 02:01:42 +01:00
package main
import (
"bytes"
2020-06-10 20:24:41 +02:00
"fmt"
2019-10-30 02:01:42 +01:00
"net/http"
"net/http/httptest"
2019-11-01 13:07:48 +01:00
"net/url"
2019-10-30 02:01:42 +01:00
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
2019-11-07 10:15:16 +01:00
"github.com/hectane/go-acl"
2023-02-21 16:24:11 +01:00
"github.com/jedisct1/dlog"
2019-11-07 10:15:16 +01:00
"github.com/jedisct1/go-minisign"
2023-02-21 16:24:11 +01:00
"github.com/powerman/check"
2019-10-30 02:01:42 +01:00
)
type SourceFixture struct {
suffix string
content [ ] byte
length string // HTTP Content-Length header
perms os . FileMode
mtime time . Time
}
type SourceTestState uint8
const (
TestStateCorrect SourceTestState = iota // valid files
TestStateExpired // modification time of files set in distant past (cache only)
TestStatePartial // incomplete files
TestStatePartialSig // incomplete .minisig
2020-02-14 16:01:19 +01:00
TestStateMissing // non-existent files
TestStateMissingSig // non-existent .minisig
2019-10-30 02:01:42 +01:00
TestStateReadErr // I/O error on reading files (download only)
TestStateReadSigErr // I/O error on reading .minisig (download only)
TestStateOpenErr // I/O error on opening files
TestStateOpenSigErr // I/O error on opening .minisig
TestStatePathErr // unparseable path to files (download only)
)
type SourceTestData struct {
n int // subtest counter
xTransport * XTransport
key * minisign . PublicKey
keyStr , tempDir string
sources [ ] string
fixtures map [ SourceTestState ] map [ string ] SourceFixture
timeNow , timeOld , timeUpd time . Time
server * httptest . Server
reqActual , reqExpect map [ string ] uint
cacheTests map [ string ] SourceTestState
downloadTests map [ string ] [ ] SourceTestState
}
type SourceTestExpect struct {
2019-11-01 08:56:57 +01:00
success bool
err , cachePath string
cache [ ] SourceFixture
2019-11-10 11:52:42 +01:00
mtime time . Time
2019-11-01 13:07:48 +01:00
urls [ ] string
2019-11-01 08:56:57 +01:00
Source * Source
delay time . Duration
2021-01-01 14:04:12 +01:00
prefix string
2019-10-30 02:01:42 +01:00
}
func readFixture ( t * testing . T , name string ) [ ] byte {
2023-02-02 19:38:24 +01:00
bin , err := os . ReadFile ( filepath . Join ( "testdata" , name ) )
2019-10-30 02:01:42 +01:00
if err != nil {
t . Fatalf ( "Unable to read test fixture %s: %v" , name , err )
}
return bin
}
2019-11-10 11:52:42 +01:00
func writeSourceCache ( t * testing . T , e * SourceTestExpect ) {
for _ , f := range e . cache {
2019-10-30 02:01:42 +01:00
if f . content == nil {
continue
}
2019-11-10 11:52:42 +01:00
path := e . cachePath + f . suffix
2019-10-30 02:01:42 +01:00
perms := f . perms
if perms == 0 {
2023-02-11 14:27:12 +01:00
perms = 0 o644
2019-10-30 02:01:42 +01:00
}
2023-02-02 19:38:24 +01:00
if err := os . WriteFile ( path , f . content , perms ) ; err != nil {
2019-10-30 02:01:42 +01:00
t . Fatalf ( "Unable to write cache file %s: %v" , path , err )
}
2019-11-07 10:15:16 +01:00
if err := acl . Chmod ( path , perms ) ; err != nil {
t . Fatalf ( "Unable to set permissions on cache file %s: %v" , path , err )
}
2019-11-10 11:52:42 +01:00
if f . suffix != "" {
2019-11-06 05:37:29 +01:00
continue
}
2019-11-10 11:52:42 +01:00
mtime := f . mtime
if f . mtime . IsZero ( ) {
mtime = e . mtime
}
if err := os . Chtimes ( path , mtime , mtime ) ; err != nil {
2019-11-06 05:37:29 +01:00
t . Fatalf ( "Unable to touch cache file %s to %v: %v" , path , f . mtime , err )
2019-10-30 02:01:42 +01:00
}
}
}
2019-11-10 11:52:42 +01:00
func checkSourceCache ( c * check . C , e * SourceTestExpect ) {
for _ , f := range e . cache {
path := e . cachePath + f . suffix
2023-02-11 14:27:12 +01:00
_ = acl . Chmod ( path , 0 o644 ) // don't worry if this fails, reading it will catch the same problem
2023-02-02 19:38:24 +01:00
got , err := os . ReadFile ( path )
2019-11-10 11:52:42 +01:00
c . DeepEqual ( got , f . content , "Unexpected content for cache file '%s', err %v" , path , err )
if f . suffix != "" {
continue
}
if fi , err := os . Stat ( path ) ; err == nil { // again, if this failed it was already caught above
mtime := f . mtime
if f . mtime . IsZero ( ) {
mtime = e . mtime
}
c . EQ ( fi . ModTime ( ) , mtime , "Unexpected timestamp for cache file '%s'" , path )
}
2019-10-30 02:01:42 +01:00
}
}
func loadSnakeoil ( t * testing . T , d * SourceTestData ) {
key , err := minisign . NewPublicKeyFromFile ( filepath . Join ( "testdata" , "snakeoil.pub" ) )
if err != nil {
t . Fatalf ( "Unable to load snakeoil key: %v" , err )
}
d . keyStr = string ( bytes . SplitN ( readFixture ( t , "snakeoil.pub" ) , [ ] byte ( "\n" ) , 2 ) [ 1 ] )
d . key = & key
}
func loadTestSourceNames ( t * testing . T , d * SourceTestData ) {
2023-02-02 19:44:51 +01:00
files , err := os . ReadDir ( filepath . Join ( "testdata" , "sources" ) )
2019-10-30 02:01:42 +01:00
if err != nil {
t . Fatalf ( "Unable to load list of test sources: %v" , err )
}
for _ , file := range files {
if ! file . IsDir ( ) && strings . HasSuffix ( file . Name ( ) , ".minisig" ) {
d . sources = append ( d . sources , strings . TrimSuffix ( file . Name ( ) , ".minisig" ) )
}
}
}
2024-04-03 16:49:37 +02:00
func generateFixtureState ( _ * testing . T , d * SourceTestData , suffix , file string , state SourceTestState ) {
2019-11-06 05:37:29 +01:00
if _ , ok := d . fixtures [ state ] ; ! ok {
d . fixtures [ state ] = map [ string ] SourceFixture { }
}
if suffix != ".minisig" {
switch state {
case TestStatePartialSig , TestStateMissingSig , TestStateReadSigErr , TestStateOpenSigErr :
d . fixtures [ state ] [ file ] = d . fixtures [ TestStateCorrect ] [ file ]
return
}
}
2019-11-10 11:52:42 +01:00
f := SourceFixture { suffix : suffix }
2019-11-06 05:37:29 +01:00
switch state {
case TestStateExpired :
f . content , f . mtime = d . fixtures [ TestStateCorrect ] [ file ] . content , d . timeOld
case TestStatePartial , TestStatePartialSig :
f . content = d . fixtures [ TestStateCorrect ] [ file ] . content [ : 1 ]
case TestStateReadErr , TestStateReadSigErr :
f . content , f . length = [ ] byte { } , "1"
case TestStateOpenErr , TestStateOpenSigErr :
2023-02-11 14:27:12 +01:00
f . content , f . perms = d . fixtures [ TestStateCorrect ] [ file ] . content [ : 1 ] , 0 o200
2019-11-06 05:37:29 +01:00
}
d . fixtures [ state ] [ file ] = f
}
2019-10-30 02:01:42 +01:00
func loadFixtures ( t * testing . T , d * SourceTestData ) {
2019-11-08 11:28:41 +01:00
d . fixtures = map [ SourceTestState ] map [ string ] SourceFixture { TestStateCorrect : { } }
2019-10-30 02:01:42 +01:00
for _ , source := range d . sources {
for _ , suffix := range [ ... ] string { "" , ".minisig" } {
file := source + suffix
2019-11-06 05:37:29 +01:00
d . fixtures [ TestStateCorrect ] [ file ] = SourceFixture {
suffix : suffix ,
content : readFixture ( t , filepath . Join ( "sources" , file ) ) ,
}
for _ , state := range [ ... ] SourceTestState {
TestStateExpired ,
TestStatePartial ,
TestStateReadErr ,
TestStateOpenErr ,
TestStatePartialSig ,
TestStateMissingSig ,
TestStateReadSigErr ,
TestStateOpenSigErr ,
} {
generateFixtureState ( t , d , suffix , file , state )
2019-10-30 02:01:42 +01:00
}
}
}
}
func makeTempDir ( t * testing . T , d * SourceTestData ) {
2023-02-02 19:44:51 +01:00
name , err := os . MkdirTemp ( "" , "sources_test.go." + t . Name ( ) )
2019-10-30 02:01:42 +01:00
if err != nil {
t . Fatalf ( "Unable to create temporary directory: %v" , err )
}
d . tempDir = name
}
func makeTestServer ( t * testing . T , d * SourceTestData ) {
d . reqActual , d . reqExpect = map [ string ] uint { } , map [ string ] uint { }
d . server = httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
var data [ ] byte = nil
d . reqActual [ r . URL . Path ] ++
pathParts := strings . SplitN ( strings . TrimPrefix ( r . URL . Path , "/" ) , "/" , 2 )
state , _ := strconv . ParseUint ( pathParts [ 0 ] , 10 , 8 )
if fixture , ok := d . fixtures [ SourceTestState ( state ) ] [ pathParts [ 1 ] ] ; ok {
if len ( fixture . length ) > 0 {
w . Header ( ) . Set ( "Content-Length" , fixture . length ) // client will return unexpected EOF
}
data = fixture . content
}
if data != nil {
if _ , err := w . Write ( data ) ; err != nil {
t . Logf ( "Error writing HTTP response for request [%s]: %v" , r . URL . Path , err )
}
} else {
w . WriteHeader ( http . StatusNotFound )
}
} ) )
}
func checkTestServer ( c * check . C , d * SourceTestData ) {
c . DeepEqual ( d . reqActual , d . reqExpect , "Unexpected HTTP request log" )
d . reqActual , d . reqExpect = map [ string ] uint { } , map [ string ] uint { }
}
func setupSourceTest ( t * testing . T ) ( func ( ) , * SourceTestData ) {
d := & SourceTestData { n : - 1 , xTransport : NewXTransport ( ) }
d . cacheTests = map [ string ] SourceTestState { // determines cache files written to disk before each call
2019-11-01 11:15:10 +01:00
"correct" : TestStateCorrect ,
"expired" : TestStateExpired ,
2019-11-01 02:23:32 +01:00
"partial" : TestStatePartial ,
"partial-sig" : TestStatePartialSig ,
"missing" : TestStateMissing ,
"missing-sig" : TestStateMissingSig ,
"open-err" : TestStateOpenErr ,
"open-sig-err" : TestStateOpenSigErr ,
2019-10-30 02:01:42 +01:00
}
d . downloadTests = map [ string ] [ ] SourceTestState { // determines the list of URLs passed in each call and how they will respond
2019-11-01 13:07:48 +01:00
"correct" : { TestStateCorrect } ,
"partial" : { TestStatePartial } ,
"partial-sig" : { TestStatePartialSig } ,
"missing" : { TestStateMissing } ,
"missing-sig" : { TestStateMissingSig } ,
"read-err" : { TestStateReadErr } ,
"read-sig-err" : { TestStateReadSigErr } ,
"open-err" : { TestStateOpenErr } ,
"open-sig-err" : { TestStateOpenSigErr } ,
"path-err" : { TestStatePathErr } ,
2019-11-01 02:23:32 +01:00
"partial,correct" : { TestStatePartial , TestStateCorrect } ,
"partial-sig,correct" : { TestStatePartialSig , TestStateCorrect } ,
"missing,correct" : { TestStateMissing , TestStateCorrect } ,
"missing-sig,correct" : { TestStateMissingSig , TestStateCorrect } ,
"read-err,correct" : { TestStateReadErr , TestStateCorrect } ,
"read-sig-err,correct" : { TestStateReadSigErr , TestStateCorrect } ,
"open-err,correct" : { TestStateOpenErr , TestStateCorrect } ,
"open-sig-err,correct" : { TestStateOpenSigErr , TestStateCorrect } ,
2019-11-01 13:07:48 +01:00
"path-err,correct" : { TestStatePathErr , TestStateCorrect } ,
"no-urls" : { } ,
2019-10-30 02:01:42 +01:00
}
d . xTransport . rebuildTransport ( )
2019-11-14 13:40:52 +01:00
d . timeNow = time . Now ( ) . AddDate ( 0 , 0 , 0 ) . Truncate ( time . Second )
2019-10-31 05:32:21 +01:00
d . timeOld = d . timeNow . Add ( DefaultPrefetchDelay * - 4 )
d . timeUpd = d . timeNow . Add ( DefaultPrefetchDelay )
2019-11-03 07:34:59 +01:00
timeNow = func ( ) time . Time { return d . timeNow } // originally defined in sources.go, replaced during testing to ensure consistent results
2019-10-30 02:01:42 +01:00
makeTempDir ( t , d )
makeTestServer ( t , d )
loadSnakeoil ( t , d )
loadTestSourceNames ( t , d )
loadFixtures ( t , d )
return func ( ) {
os . RemoveAll ( d . tempDir )
d . server . Close ( )
} , d
}
func prepSourceTestCache ( t * testing . T , d * SourceTestData , e * SourceTestExpect , source string , state SourceTestState ) {
e . cache = [ ] SourceFixture { d . fixtures [ state ] [ source ] , d . fixtures [ state ] [ source + ".minisig" ] }
switch state {
case TestStateCorrect :
2023-04-07 15:21:00 +02:00
e . Source . bin , e . success = e . cache [ 0 ] . content , true
2019-10-30 02:01:42 +01:00
case TestStateExpired :
2023-04-07 15:21:00 +02:00
e . Source . bin = e . cache [ 0 ] . content
2019-10-30 02:01:42 +01:00
case TestStatePartial , TestStatePartialSig :
e . err = "signature"
2019-11-07 10:15:16 +01:00
case TestStateMissing , TestStateMissingSig , TestStateOpenErr , TestStateOpenSigErr :
2019-11-01 02:23:32 +01:00
e . err = "open"
2019-10-30 02:01:42 +01:00
}
2019-11-10 11:52:42 +01:00
writeSourceCache ( t , e )
2019-10-30 02:01:42 +01:00
}
2022-03-23 17:48:48 +01:00
func prepSourceTestDownload (
2024-04-03 16:49:37 +02:00
_ * testing . T ,
2022-03-23 17:48:48 +01:00
d * SourceTestData ,
e * SourceTestExpect ,
source string ,
downloadTest [ ] SourceTestState ,
) {
2019-11-06 05:37:29 +01:00
if len ( downloadTest ) == 0 {
return
}
for _ , state := range downloadTest {
path := "/" + strconv . FormatUint ( uint64 ( state ) , 10 ) + "/" + source
2020-06-10 20:24:41 +02:00
serverURL := d . server . URL
2019-11-06 05:37:29 +01:00
switch state {
case TestStateMissing , TestStateMissingSig :
e . err = "404 Not Found"
case TestStatePartial , TestStatePartialSig :
e . err = "signature"
case TestStateReadErr , TestStateReadSigErr :
e . err = "unexpected EOF"
case TestStateOpenErr , TestStateOpenSigErr :
2020-06-10 20:24:41 +02:00
if u , err := url . Parse ( serverURL + path ) ; err == nil {
host , port := ExtractHostAndPort ( u . Host , - 1 )
2022-03-23 17:48:48 +01:00
u . Host = fmt . Sprintf (
"%s:%d" ,
host ,
port | 0x10000 ,
) // high numeric port is parsed but then fails to connect
2020-06-10 20:24:41 +02:00
serverURL = u . String ( )
}
2020-06-10 20:43:32 +02:00
e . err = "invalid port"
2019-11-06 05:37:29 +01:00
case TestStatePathErr :
path = "..." + path // non-numeric port fails URL parsing
2019-10-30 02:01:42 +01:00
}
2020-06-10 20:24:41 +02:00
if u , err := url . Parse ( serverURL + path ) ; err == nil {
2019-11-06 05:37:29 +01:00
e . Source . urls = append ( e . Source . urls , u )
}
2020-06-10 20:24:41 +02:00
e . urls = append ( e . urls , serverURL + path )
2019-11-03 07:34:59 +01:00
if e . success {
2019-11-06 05:37:29 +01:00
continue
2019-11-03 07:34:59 +01:00
}
2019-11-06 05:37:29 +01:00
switch state {
case TestStateCorrect :
e . cache = [ ] SourceFixture { d . fixtures [ state ] [ source ] , d . fixtures [ state ] [ source + ".minisig" ] }
2023-04-07 15:21:00 +02:00
e . Source . bin , e . success = e . cache [ 0 ] . content , true
2019-11-06 05:37:29 +01:00
fallthrough
case TestStateMissingSig , TestStatePartial , TestStatePartialSig , TestStateReadSigErr :
d . reqExpect [ path + ".minisig" ] ++
fallthrough
case TestStateMissing , TestStateReadErr :
d . reqExpect [ path ] ++
2019-11-01 13:07:48 +01:00
}
2019-10-30 02:01:42 +01:00
}
2019-11-06 05:37:29 +01:00
if e . success {
e . err = ""
2023-08-11 00:51:34 +02:00
e . delay = DefaultPrefetchDelay
2019-11-06 05:37:29 +01:00
} else {
e . delay = MinimumPrefetchInterval
}
if len ( e . Source . urls ) > 0 {
e . Source . refresh = d . timeNow . Add ( e . delay )
} else {
e . success = false
}
2019-10-30 02:01:42 +01:00
}
func setupSourceTestCase ( t * testing . T , d * SourceTestData , i int ,
2023-02-11 14:27:12 +01:00
cacheTest * SourceTestState , downloadTest [ ] SourceTestState ,
) ( id string , e * SourceTestExpect ) {
2019-10-30 02:01:42 +01:00
id = strconv . Itoa ( d . n ) + "-" + strconv . Itoa ( i )
e = & SourceTestExpect {
2019-10-31 02:04:08 +01:00
cachePath : filepath . Join ( d . tempDir , id ) ,
2019-11-10 11:52:42 +01:00
mtime : d . timeNow ,
2019-10-30 02:01:42 +01:00
}
2023-02-11 14:27:12 +01:00
e . Source = & Source {
name : id , urls : [ ] * url . URL { } , format : SourceFormatV2 , minisignKey : d . key ,
2023-08-11 00:51:34 +02:00
cacheFile : e . cachePath , cacheTTL : DefaultPrefetchDelay * 3 , prefetchDelay : DefaultPrefetchDelay ,
2023-02-11 14:27:12 +01:00
}
2019-10-30 02:01:42 +01:00
if cacheTest != nil {
prepSourceTestCache ( t , d , e , d . sources [ i ] , * cacheTest )
i = ( i + 1 ) % len ( d . sources ) // make the cached and downloaded fixtures different
}
prepSourceTestDownload ( t , d , e , d . sources [ i ] , downloadTest )
return
}
func TestNewSource ( t * testing . T ) {
2023-02-21 16:24:11 +01:00
if testing . Verbose ( ) {
dlog . SetLogLevel ( dlog . SeverityDebug )
dlog . UseSyslog ( false )
}
2019-10-30 02:01:42 +01:00
teardown , d := setupSourceTest ( t )
defer teardown ( )
2019-10-31 05:32:21 +01:00
checkResult := func ( t * testing . T , e * SourceTestExpect , got * Source , err error ) {
2019-10-30 02:01:42 +01:00
c := check . T ( t )
if len ( e . err ) > 0 {
c . Match ( err , e . err , "Unexpected error" )
} else {
c . Nil ( err , "Unexpected error" )
}
2019-10-31 02:22:48 +01:00
c . DeepEqual ( got , e . Source , "Unexpected return" )
2019-10-30 02:01:42 +01:00
checkTestServer ( c , d )
2019-11-10 11:52:42 +01:00
checkSourceCache ( c , e )
2019-10-30 02:01:42 +01:00
}
d . n ++
for _ , tt := range [ ] struct {
2019-11-10 12:58:53 +01:00
v , key string
refreshDelay time . Duration
e * SourceTestExpect
2019-10-30 02:01:42 +01:00
} {
2023-08-11 00:51:34 +02:00
{ "" , "" , 0 , & SourceTestExpect { err : " " , Source : & Source { name : "short refresh delay" , urls : [ ] * url . URL { } , cacheTTL : DefaultPrefetchDelay , prefetchDelay : DefaultPrefetchDelay , prefix : "" } } } ,
{ "v1" , d . keyStr , DefaultPrefetchDelay * 2 , & SourceTestExpect { err : "Unsupported source format" , Source : & Source { name : "old format" , urls : [ ] * url . URL { } , cacheTTL : DefaultPrefetchDelay * 2 , prefetchDelay : DefaultPrefetchDelay } } } ,
{ "v2" , "" , DefaultPrefetchDelay * 3 , & SourceTestExpect { err : "Invalid encoded public key" , Source : & Source { name : "invalid public key" , urls : [ ] * url . URL { } , cacheTTL : DefaultPrefetchDelay * 3 , prefetchDelay : DefaultPrefetchDelay } } } ,
2019-10-30 02:01:42 +01:00
} {
2019-11-01 08:56:57 +01:00
t . Run ( tt . e . Source . name , func ( t * testing . T ) {
2022-03-23 17:48:48 +01:00
got , err := NewSource (
tt . e . Source . name ,
d . xTransport ,
tt . e . urls ,
tt . key ,
tt . e . cachePath ,
tt . v ,
tt . refreshDelay ,
tt . e . prefix ,
)
2019-10-31 05:32:21 +01:00
checkResult ( t , tt . e , got , err )
2019-10-30 02:01:42 +01:00
} )
}
for cacheTestName , cacheTest := range d . cacheTests {
for downloadTestName , downloadTest := range d . downloadTests {
d . n ++
for i := range d . sources {
id , e := setupSourceTestCase ( t , d , i , & cacheTest , downloadTest )
t . Run ( "cache " + cacheTestName + ", download " + downloadTestName + "/" + id , func ( t * testing . T ) {
2022-03-23 17:48:48 +01:00
got , err := NewSource (
id ,
d . xTransport ,
e . urls ,
d . keyStr ,
e . cachePath ,
"v2" ,
DefaultPrefetchDelay * 3 ,
"" ,
)
2019-10-31 05:32:21 +01:00
checkResult ( t , e , got , err )
2019-10-30 02:01:42 +01:00
} )
}
}
}
}
2019-10-31 05:32:21 +01:00
func TestPrefetchSources ( t * testing . T ) {
2023-02-21 16:24:11 +01:00
if testing . Verbose ( ) {
dlog . SetLogLevel ( dlog . SeverityDebug )
dlog . UseSyslog ( false )
}
2019-10-30 02:01:42 +01:00
teardown , d := setupSourceTest ( t )
defer teardown ( )
2019-10-31 05:32:21 +01:00
checkResult := func ( t * testing . T , expects [ ] * SourceTestExpect , got time . Duration ) {
2019-10-30 02:01:42 +01:00
c := check . T ( t )
2019-10-31 05:32:21 +01:00
expectDelay := MinimumPrefetchInterval
2019-10-30 02:01:42 +01:00
for _ , e := range expects {
2019-11-01 07:16:42 +01:00
if e . delay >= MinimumPrefetchInterval && ( expectDelay == MinimumPrefetchInterval || expectDelay > e . delay ) {
expectDelay = e . delay
2019-10-30 02:01:42 +01:00
}
}
2019-10-31 05:32:21 +01:00
c . InDelta ( got , expectDelay , time . Second , "Unexpected return" )
2019-10-30 02:01:42 +01:00
checkTestServer ( c , d )
for _ , e := range expects {
2019-11-10 11:52:42 +01:00
checkSourceCache ( c , e )
2019-10-30 02:01:42 +01:00
}
}
2019-11-01 07:16:42 +01:00
timeNow = func ( ) time . Time { return d . timeUpd } // since the fixtures are prepared using real now, make the tested code think it's the future
2019-10-30 02:01:42 +01:00
for downloadTestName , downloadTest := range d . downloadTests {
d . n ++
2019-10-31 05:32:21 +01:00
sources := [ ] * Source { }
2019-10-30 02:01:42 +01:00
expects := [ ] * SourceTestExpect { }
for i := range d . sources {
_ , e := setupSourceTestCase ( t , d , i , nil , downloadTest )
2019-11-10 11:52:42 +01:00
e . mtime = d . timeUpd
2019-11-10 12:32:38 +01:00
s := & Source { }
* s = * e . Source
2023-04-07 15:21:00 +02:00
s . bin = nil
2019-11-10 12:32:38 +01:00
sources = append ( sources , s )
2019-10-30 02:01:42 +01:00
expects = append ( expects , e )
}
t . Run ( "download " + downloadTestName , func ( t * testing . T ) {
2019-10-31 05:32:21 +01:00
got := PrefetchSources ( d . xTransport , sources )
checkResult ( t , expects , got )
2019-10-30 02:01:42 +01:00
} )
}
}
func TestMain ( m * testing . M ) { check . TestMain ( m ) }