8
8
"os/exec"
9
9
"path/filepath"
10
10
"runtime"
11
+ "slices"
11
12
"strings"
12
13
"testing"
13
14
"time"
@@ -21,6 +22,20 @@ func TestMain(m *testing.M) {
21
22
os .Exit (m .Run ())
22
23
}
23
24
25
+ type downloadResult struct {
26
+ r * Result
27
+ err error
28
+ }
29
+
30
+ // We expect only few parallel downloads. Testing with larger number to find
31
+ // races quicker. 20 parallel downloads take about 120 milliseocnds on M1 Pro.
32
+ const parallelDownloads = 20
33
+
34
+ // When downloading in parallel usually all downloads completed with
35
+ // StatusDownload, but some may be delayed and find the data file when they
36
+ // start. Can be reproduced locally using 100 parallel downloads.
37
+ var parallelStatus = []Status {StatusDownloaded , StatusUsedCache }
38
+
24
39
func TestDownloadRemote (t * testing.T ) {
25
40
ts := httptest .NewServer (http .FileServer (http .Dir ("testdata" )))
26
41
t .Cleanup (ts .Close )
@@ -57,38 +72,90 @@ func TestDownloadRemote(t *testing.T) {
57
72
})
58
73
})
59
74
t .Run ("with cache" , func (t * testing.T ) {
60
- cacheDir := filepath .Join (t .TempDir (), "cache" )
61
- localPath := filepath .Join (t .TempDir (), t .Name ())
62
- r , err := Download (context .Background (), localPath , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
63
- assert .NilError (t , err )
64
- assert .Equal (t , StatusDownloaded , r .Status )
75
+ t .Run ("serial" , func (t * testing.T ) {
76
+ cacheDir := filepath .Join (t .TempDir (), "cache" )
77
+ localPath := filepath .Join (t .TempDir (), t .Name ())
78
+ r , err := Download (context .Background (), localPath , dummyRemoteFileURL ,
79
+ WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
80
+ assert .NilError (t , err )
81
+ assert .Equal (t , StatusDownloaded , r .Status )
65
82
66
- r , err = Download (context .Background (), localPath , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
67
- assert .NilError (t , err )
68
- assert .Equal (t , StatusSkipped , r .Status )
83
+ r , err = Download (context .Background (), localPath , dummyRemoteFileURL ,
84
+ WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
85
+ assert .NilError (t , err )
86
+ assert .Equal (t , StatusSkipped , r .Status )
69
87
70
- localPath2 := localPath + "-2"
71
- r , err = Download (context .Background (), localPath2 , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
72
- assert .NilError (t , err )
73
- assert .Equal (t , StatusUsedCache , r .Status )
88
+ localPath2 := localPath + "-2"
89
+ r , err = Download (context .Background (), localPath2 , dummyRemoteFileURL ,
90
+ WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
91
+ assert .NilError (t , err )
92
+ assert .Equal (t , StatusUsedCache , r .Status )
93
+ })
94
+ t .Run ("parallel" , func (t * testing.T ) {
95
+ cacheDir := filepath .Join (t .TempDir (), "cache" )
96
+ results := make (chan downloadResult , parallelDownloads )
97
+ for i := 0 ; i < parallelDownloads ; i ++ {
98
+ go func () {
99
+ // Parallel download is supported only for different instances with unique localPath.
100
+ localPath := filepath .Join (t .TempDir (), t .Name ())
101
+ r , err := Download (context .Background (), localPath , dummyRemoteFileURL ,
102
+ WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
103
+ results <- downloadResult {r , err }
104
+ }()
105
+ }
106
+ // We must process all results before cleanup.
107
+ for i := 0 ; i < parallelDownloads ; i ++ {
108
+ result := <- results
109
+ if result .err != nil {
110
+ t .Errorf ("Download failed: %s" , result .err )
111
+ } else if ! slices .Contains (parallelStatus , result .r .Status ) {
112
+ t .Errorf ("Expected download status %s, got %s" , parallelStatus , result .r .Status )
113
+ }
114
+ }
115
+ })
74
116
})
75
117
t .Run ("caching-only mode" , func (t * testing.T ) {
76
- _ , err := Download (context .Background (), "" , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ))
77
- assert .ErrorContains (t , err , "cache directory to be specified" )
118
+ t .Run ("serial" , func (t * testing.T ) {
119
+ _ , err := Download (context .Background (), "" , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ))
120
+ assert .ErrorContains (t , err , "cache directory to be specified" )
78
121
79
- cacheDir := filepath .Join (t .TempDir (), "cache" )
80
- r , err := Download (context .Background (), "" , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
81
- assert .NilError (t , err )
82
- assert .Equal (t , StatusDownloaded , r .Status )
122
+ cacheDir := filepath .Join (t .TempDir (), "cache" )
123
+ r , err := Download (context .Background (), "" , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ),
124
+ WithCacheDir (cacheDir ))
125
+ assert .NilError (t , err )
126
+ assert .Equal (t , StatusDownloaded , r .Status )
83
127
84
- r , err = Download (context .Background (), "" , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
85
- assert .NilError (t , err )
86
- assert .Equal (t , StatusUsedCache , r .Status )
128
+ r , err = Download (context .Background (), "" , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ),
129
+ WithCacheDir (cacheDir ))
130
+ assert .NilError (t , err )
131
+ assert .Equal (t , StatusUsedCache , r .Status )
87
132
88
- localPath := filepath .Join (t .TempDir (), t .Name ())
89
- r , err = Download (context .Background (), localPath , dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
90
- assert .NilError (t , err )
91
- assert .Equal (t , StatusUsedCache , r .Status )
133
+ localPath := filepath .Join (t .TempDir (), t .Name ())
134
+ r , err = Download (context .Background (), localPath , dummyRemoteFileURL ,
135
+ WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
136
+ assert .NilError (t , err )
137
+ assert .Equal (t , StatusUsedCache , r .Status )
138
+ })
139
+ t .Run ("parallel" , func (t * testing.T ) {
140
+ cacheDir := filepath .Join (t .TempDir (), "cache" )
141
+ results := make (chan downloadResult , parallelDownloads )
142
+ for i := 0 ; i < parallelDownloads ; i ++ {
143
+ go func () {
144
+ r , err := Download (context .Background (), "" , dummyRemoteFileURL ,
145
+ WithExpectedDigest (dummyRemoteFileDigest ), WithCacheDir (cacheDir ))
146
+ results <- downloadResult {r , err }
147
+ }()
148
+ }
149
+ // We must process all results before cleanup.
150
+ for i := 0 ; i < parallelDownloads ; i ++ {
151
+ result := <- results
152
+ if result .err != nil {
153
+ t .Errorf ("Download failed: %s" , result .err )
154
+ } else if ! slices .Contains (parallelStatus , result .r .Status ) {
155
+ t .Errorf ("Expected download status %s, got %s" , parallelStatus , result .r .Status )
156
+ }
157
+ }
158
+ })
92
159
})
93
160
t .Run ("cached" , func (t * testing.T ) {
94
161
_ , err := Cached (dummyRemoteFileURL , WithExpectedDigest (dummyRemoteFileDigest ))
0 commit comments