github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/curl/schemes_test.go (about) 1 // Copyright 2017-2020 the u-root 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 curl 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/url" 14 "testing" 15 16 "github.com/cenkalti/backoff" 17 "github.com/u-root/u-root/pkg/uio" 18 ) 19 20 var ( 21 errTest = errors.New("Test error") 22 testURL = &url.URL{ 23 Scheme: "fooftp", 24 Host: "192.168.0.1", 25 Path: "/foo/pxelinux.cfg/default", 26 } 27 ) 28 29 var tests = []struct { 30 name string 31 // scheme returns a scheme for testing and a MockScheme to 32 // confirm number of calls to Fetch. The distinction is useful 33 // when MockScheme is decorated by a SchemeWithRetries. In many 34 // cases, the same value is returned twice. 35 scheme func() (FileScheme, *MockScheme) 36 url *url.URL 37 err error 38 want string 39 wantFetchCount uint 40 }{ 41 { 42 name: "successful fetch", 43 scheme: func() (FileScheme, *MockScheme) { 44 s := NewMockScheme("fooftp") 45 s.Add("192.168.0.1", "/foo/pxelinux.cfg/default", "haha") 46 return s, s 47 }, 48 url: testURL, 49 want: "haha", 50 wantFetchCount: 1, 51 }, 52 { 53 name: "scheme does not exist", 54 scheme: func() (FileScheme, *MockScheme) { 55 s := NewMockScheme("fooftp") 56 return s, s 57 }, 58 url: &url.URL{ 59 Scheme: "nosuch", 60 }, 61 err: ErrNoSuchScheme, 62 wantFetchCount: 0, 63 }, 64 { 65 name: "host does not exist", 66 scheme: func() (FileScheme, *MockScheme) { 67 s := NewMockScheme("fooftp") 68 return s, s 69 }, 70 url: &url.URL{ 71 Scheme: "fooftp", 72 Host: "someotherplace", 73 }, 74 err: ErrNoSuchHost, 75 wantFetchCount: 1, 76 }, 77 { 78 name: "file does not exist", 79 scheme: func() (FileScheme, *MockScheme) { 80 s := NewMockScheme("fooftp") 81 s.Add("somehost", "somefile", "somecontent") 82 return s, s 83 }, 84 url: &url.URL{ 85 Scheme: "fooftp", 86 Host: "somehost", 87 Path: "/someotherfile", 88 }, 89 err: ErrNoSuchFile, 90 wantFetchCount: 1, 91 }, 92 { 93 name: "always err", 94 scheme: func() (FileScheme, *MockScheme) { 95 s := NewMockScheme("fooftp") 96 s.Add("192.168.0.1", "/foo/pxelinux.cfg/default", "haha") 97 s.SetErr(errTest, 9999) 98 return s, s 99 }, 100 url: testURL, 101 err: errTest, 102 wantFetchCount: 1, 103 }, 104 { 105 name: "retries but not necessary", 106 scheme: func() (FileScheme, *MockScheme) { 107 s := NewMockScheme("fooftp") 108 s.Add("192.168.0.1", "/foo/pxelinux.cfg/default", "haha") 109 r := &SchemeWithRetries{ 110 Scheme: s, 111 // backoff.ZeroBackOff so unit tests run fast. 112 BackOff: backoff.WithMaxRetries(&backoff.ZeroBackOff{}, 10), 113 } 114 return r, s 115 }, 116 url: testURL, 117 want: "haha", 118 wantFetchCount: 1, 119 }, 120 { 121 name: "not enough retries", 122 scheme: func() (FileScheme, *MockScheme) { 123 s := NewMockScheme("fooftp") 124 s.Add("192.168.0.1", "/foo/pxelinux.cfg/default", "haha") 125 s.SetErr(errTest, 9999) 126 r := &SchemeWithRetries{ 127 Scheme: s, 128 // backoff.ZeroBackOff so unit tests run fast. 129 BackOff: backoff.WithMaxRetries(&backoff.ZeroBackOff{}, 10), 130 } 131 return r, s 132 }, 133 url: testURL, 134 err: errTest, 135 wantFetchCount: 11, 136 }, 137 { 138 name: "sufficient retries", 139 scheme: func() (FileScheme, *MockScheme) { 140 s := NewMockScheme("fooftp") 141 s.Add("192.168.0.1", "/foo/pxelinux.cfg/default", "haha") 142 s.SetErr(errTest, 5) 143 r := &SchemeWithRetries{ 144 Scheme: s, 145 // backoff.ZeroBackOff so unit tests run fast. 146 BackOff: backoff.WithMaxRetries(&backoff.ZeroBackOff{}, 10), 147 } 148 return r, s 149 }, 150 url: testURL, 151 want: "haha", 152 wantFetchCount: 6, 153 }, 154 { 155 name: "retry filter", 156 scheme: func() (FileScheme, *MockScheme) { 157 s := NewMockScheme("fooftp") 158 s.Add("192.168.0.1", "/foo/pxelinux.cfg/default", "haha") 159 s.SetErr(errTest, 5) 160 r := &SchemeWithRetries{ 161 DoRetry: func(u *url.URL, err error) bool { 162 return err != errTest 163 }, 164 Scheme: s, 165 // backoff.ZeroBackOff so unit tests run fast. 166 BackOff: backoff.WithMaxRetries(&backoff.ZeroBackOff{}, 10), 167 } 168 return r, s 169 }, 170 url: testURL, 171 err: errTest, 172 wantFetchCount: 1, 173 }, 174 } 175 176 func TestFetch(t *testing.T) { 177 for i, tt := range tests { 178 t.Run(fmt.Sprintf("Test #%02d: %s", i, tt.name), func(t *testing.T) { 179 var r io.ReaderAt 180 var err error 181 182 fs, ms := tt.scheme() 183 s := make(Schemes) 184 s.Register(ms.Scheme, fs) 185 186 r, err = s.Fetch(context.TODO(), tt.url) 187 if uErr, ok := err.(*URLError); ok && uErr.Err != tt.err { 188 t.Errorf("Fetch() = %v, want %v", uErr.Err, tt.err) 189 } else if !ok && err != tt.err { 190 t.Errorf("Fetch() = %v, want %v", err, tt.err) 191 } 192 193 // Check number of calls before reading the file. 194 numCalled := ms.NumCalled(tt.url) 195 if numCalled != tt.wantFetchCount { 196 t.Errorf("number times Fetch() called = %v, want %v", 197 ms.NumCalled(tt.url), tt.wantFetchCount) 198 } 199 if err != nil { 200 return 201 } 202 203 // Read the entire file. 204 content, err := ioutil.ReadAll(uio.Reader(r)) 205 if err != nil { 206 t.Errorf("bytes.Buffer read returned an error? %v", err) 207 } 208 if got, want := string(content), tt.want; got != want { 209 t.Errorf("Fetch() = %v, want %v", got, want) 210 } 211 212 // Check number of calls after reading the file. 213 numCalled = ms.NumCalled(tt.url) 214 if numCalled != tt.wantFetchCount { 215 t.Errorf("number times Fetch() called = %v, want %v", 216 ms.NumCalled(tt.url), tt.wantFetchCount) 217 } 218 }) 219 } 220 } 221 222 func TestLazyFetch(t *testing.T) { 223 for i, tt := range tests { 224 t.Run(fmt.Sprintf("Test #%02d: %s", i, tt.name), func(t *testing.T) { 225 var r io.ReaderAt 226 var err error 227 228 fs, ms := tt.scheme() 229 s := make(Schemes) 230 s.Register(ms.Scheme, fs) 231 232 r, err = s.LazyFetch(tt.url) 233 // Errors are deferred to when file is read except for ErrNoSuchScheme. 234 if tt.err == ErrNoSuchScheme { 235 if uErr, ok := err.(*URLError); ok && uErr.Err != ErrNoSuchScheme { 236 t.Errorf("LazyFetch() = %v, want %v", uErr.Err, tt.err) 237 } 238 } else if err != nil { 239 t.Errorf("LazyFetch() = %v, want nil", err) 240 } 241 242 // Check number of calls before reading the file. 243 numCalled := ms.NumCalled(tt.url) 244 if numCalled != 0 { 245 t.Errorf("number times Fetch() called = %v, want 0", numCalled) 246 } 247 if err != nil { 248 return 249 } 250 251 // Read the entire file. 252 content, err := ioutil.ReadAll(uio.Reader(r)) 253 if uErr, ok := err.(*URLError); ok && uErr.Err != tt.err { 254 t.Errorf("ReadAll() = %v, want %v", uErr.Err, tt.err) 255 } else if !ok && err != tt.err { 256 t.Errorf("ReadAll() = %v, want %v", err, tt.err) 257 } 258 if got, want := string(content), tt.want; got != want { 259 t.Errorf("ReadAll() = %v, want %v", got, want) 260 } 261 262 // Check number of calls after reading the file. 263 numCalled = ms.NumCalled(tt.url) 264 if numCalled != tt.wantFetchCount { 265 t.Errorf("number times Fetch() called = %v, want %v", 266 ms.NumCalled(tt.url), tt.wantFetchCount) 267 } 268 }) 269 } 270 }