github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/accounting/stats_test.go (about) 1 package accounting 2 3 import ( 4 "fmt" 5 "io" 6 "testing" 7 "time" 8 9 "github.com/pkg/errors" 10 "github.com/rclone/rclone/fs" 11 "github.com/rclone/rclone/fs/fserrors" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestETA(t *testing.T) { 17 for _, test := range []struct { 18 size, total int64 19 rate float64 20 wantETA time.Duration 21 wantOK bool 22 wantString string 23 }{ 24 // Custom String Cases 25 {size: 0, total: 365 * 86400, rate: 1.0, wantETA: 365 * 86400 * time.Second, wantOK: true, wantString: "1y"}, 26 {size: 0, total: 7 * 86400, rate: 1.0, wantETA: 7 * 86400 * time.Second, wantOK: true, wantString: "1w"}, 27 {size: 0, total: 1 * 86400, rate: 1.0, wantETA: 1 * 86400 * time.Second, wantOK: true, wantString: "1d"}, 28 {size: 0, total: 1110 * 86400, rate: 1.0, wantETA: 1110 * 86400 * time.Second, wantOK: true, wantString: "3y2w1d"}, 29 {size: 0, total: 15 * 86400, rate: 1.0, wantETA: 15 * 86400 * time.Second, wantOK: true, wantString: "2w1d"}, 30 // Composite Custom String Cases 31 {size: 0, total: 1.5 * 86400, rate: 1.0, wantETA: 1.5 * 86400 * time.Second, wantOK: true, wantString: "1d12h"}, 32 {size: 0, total: 95000, rate: 1.0, wantETA: 95000 * time.Second, wantOK: true, wantString: "1d2h23m20s"}, 33 // Standard Duration String Cases 34 {size: 0, total: 100, rate: 1.0, wantETA: 100 * time.Second, wantOK: true, wantString: "1m40s"}, 35 {size: 50, total: 100, rate: 1.0, wantETA: 50 * time.Second, wantOK: true, wantString: "50s"}, 36 {size: 100, total: 100, rate: 1.0, wantETA: 0 * time.Second, wantOK: true, wantString: "0s"}, 37 // No String Cases 38 {size: -1, total: 100, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, 39 {size: 200, total: 100, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, 40 {size: 10, total: -1, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, 41 {size: 10, total: 20, rate: 0.0, wantETA: 0, wantOK: false, wantString: "-"}, 42 {size: 10, total: 20, rate: -1.0, wantETA: 0, wantOK: false, wantString: "-"}, 43 {size: 0, total: 0, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, 44 } { 45 t.Run(fmt.Sprintf("size=%d/total=%d/rate=%f", test.size, test.total, test.rate), func(t *testing.T) { 46 gotETA, gotOK := eta(test.size, test.total, test.rate) 47 assert.Equal(t, test.wantETA, gotETA) 48 assert.Equal(t, test.wantOK, gotOK) 49 gotString := etaString(test.size, test.total, test.rate) 50 assert.Equal(t, test.wantString, gotString) 51 }) 52 } 53 } 54 55 func TestPercentage(t *testing.T) { 56 assert.Equal(t, percent(0, 1000), "0%") 57 assert.Equal(t, percent(1, 1000), "0%") 58 assert.Equal(t, percent(9, 1000), "1%") 59 assert.Equal(t, percent(500, 1000), "50%") 60 assert.Equal(t, percent(1000, 1000), "100%") 61 assert.Equal(t, percent(1e8, 1e9), "10%") 62 assert.Equal(t, percent(1e8, 1e9), "10%") 63 assert.Equal(t, percent(0, 0), "-") 64 assert.Equal(t, percent(100, -100), "-") 65 assert.Equal(t, percent(-100, 100), "-") 66 assert.Equal(t, percent(-100, -100), "-") 67 } 68 69 func TestStatsError(t *testing.T) { 70 s := NewStats() 71 assert.Equal(t, int64(0), s.GetErrors()) 72 assert.False(t, s.HadFatalError()) 73 assert.False(t, s.HadRetryError()) 74 assert.Equal(t, time.Time{}, s.RetryAfter()) 75 assert.Equal(t, nil, s.GetLastError()) 76 assert.False(t, s.Errored()) 77 78 t0 := time.Now() 79 t1 := t0.Add(time.Second) 80 81 _ = s.Error(nil) 82 assert.Equal(t, int64(0), s.GetErrors()) 83 assert.False(t, s.HadFatalError()) 84 assert.False(t, s.HadRetryError()) 85 assert.Equal(t, time.Time{}, s.RetryAfter()) 86 assert.Equal(t, nil, s.GetLastError()) 87 assert.False(t, s.Errored()) 88 89 _ = s.Error(io.EOF) 90 assert.Equal(t, int64(1), s.GetErrors()) 91 assert.False(t, s.HadFatalError()) 92 assert.True(t, s.HadRetryError()) 93 assert.Equal(t, time.Time{}, s.RetryAfter()) 94 assert.Equal(t, io.EOF, s.GetLastError()) 95 assert.True(t, s.Errored()) 96 97 e := fserrors.ErrorRetryAfter(t0) 98 _ = s.Error(e) 99 assert.Equal(t, int64(2), s.GetErrors()) 100 assert.False(t, s.HadFatalError()) 101 assert.True(t, s.HadRetryError()) 102 assert.Equal(t, t0, s.RetryAfter()) 103 assert.Equal(t, e, s.GetLastError()) 104 105 err := errors.Wrap(fserrors.ErrorRetryAfter(t1), "potato") 106 err = s.Error(err) 107 assert.Equal(t, int64(3), s.GetErrors()) 108 assert.False(t, s.HadFatalError()) 109 assert.True(t, s.HadRetryError()) 110 assert.Equal(t, t1, s.RetryAfter()) 111 assert.Equal(t, t1, fserrors.RetryAfterErrorTime(err)) 112 113 _ = s.Error(fserrors.FatalError(io.EOF)) 114 assert.Equal(t, int64(4), s.GetErrors()) 115 assert.True(t, s.HadFatalError()) 116 assert.True(t, s.HadRetryError()) 117 assert.Equal(t, t1, s.RetryAfter()) 118 119 s.ResetErrors() 120 assert.Equal(t, int64(0), s.GetErrors()) 121 assert.False(t, s.HadFatalError()) 122 assert.False(t, s.HadRetryError()) 123 assert.Equal(t, time.Time{}, s.RetryAfter()) 124 assert.Equal(t, nil, s.GetLastError()) 125 assert.False(t, s.Errored()) 126 127 _ = s.Error(fserrors.NoRetryError(io.EOF)) 128 assert.Equal(t, int64(1), s.GetErrors()) 129 assert.False(t, s.HadFatalError()) 130 assert.False(t, s.HadRetryError()) 131 assert.Equal(t, time.Time{}, s.RetryAfter()) 132 } 133 134 func TestStatsTotalDuration(t *testing.T) { 135 startTime := time.Now() 136 time1 := startTime.Add(-40 * time.Second) 137 time2 := time1.Add(10 * time.Second) 138 time3 := time2.Add(10 * time.Second) 139 time4 := time3.Add(10 * time.Second) 140 141 t.Run("Single completed transfer", func(t *testing.T) { 142 s := NewStats() 143 tr1 := &Transfer{ 144 startedAt: time1, 145 completedAt: time2, 146 } 147 s.AddTransfer(tr1) 148 149 s.mu.Lock() 150 total := s.totalDuration() 151 s.mu.Unlock() 152 153 assert.Equal(t, 1, len(s.startedTransfers)) 154 assert.Equal(t, 10*time.Second, total) 155 s.RemoveTransfer(tr1) 156 assert.Equal(t, 10*time.Second, total) 157 assert.Equal(t, 0, len(s.startedTransfers)) 158 }) 159 160 t.Run("Single uncompleted transfer", func(t *testing.T) { 161 s := NewStats() 162 tr1 := &Transfer{ 163 startedAt: time1, 164 } 165 s.AddTransfer(tr1) 166 167 s.mu.Lock() 168 total := s.totalDuration() 169 s.mu.Unlock() 170 171 assert.Equal(t, time.Since(time1)/time.Second, total/time.Second) 172 s.RemoveTransfer(tr1) 173 assert.Equal(t, time.Since(time1)/time.Second, total/time.Second) 174 }) 175 176 t.Run("Overlapping without ending", func(t *testing.T) { 177 s := NewStats() 178 tr1 := &Transfer{ 179 startedAt: time2, 180 completedAt: time3, 181 } 182 s.AddTransfer(tr1) 183 tr2 := &Transfer{ 184 startedAt: time2, 185 completedAt: time2.Add(time.Second), 186 } 187 s.AddTransfer(tr2) 188 tr3 := &Transfer{ 189 startedAt: time1, 190 completedAt: time3, 191 } 192 s.AddTransfer(tr3) 193 tr4 := &Transfer{ 194 startedAt: time3, 195 completedAt: time4, 196 } 197 s.AddTransfer(tr4) 198 tr5 := &Transfer{ 199 startedAt: time.Now(), 200 } 201 s.AddTransfer(tr5) 202 203 time.Sleep(time.Millisecond) 204 205 s.mu.Lock() 206 total := s.totalDuration() 207 s.mu.Unlock() 208 209 assert.Equal(t, time.Duration(30), total/time.Second) 210 s.RemoveTransfer(tr1) 211 assert.Equal(t, time.Duration(30), total/time.Second) 212 s.RemoveTransfer(tr2) 213 assert.Equal(t, time.Duration(30), total/time.Second) 214 s.RemoveTransfer(tr3) 215 assert.Equal(t, time.Duration(30), total/time.Second) 216 s.RemoveTransfer(tr4) 217 assert.Equal(t, time.Duration(30), total/time.Second) 218 }) 219 220 t.Run("Mixed completed and uncompleted transfers", func(t *testing.T) { 221 s := NewStats() 222 s.AddTransfer(&Transfer{ 223 startedAt: time1, 224 completedAt: time2, 225 }) 226 s.AddTransfer(&Transfer{ 227 startedAt: time2, 228 }) 229 s.AddTransfer(&Transfer{ 230 startedAt: time3, 231 }) 232 s.AddTransfer(&Transfer{ 233 startedAt: time3, 234 }) 235 236 s.mu.Lock() 237 total := s.totalDuration() 238 s.mu.Unlock() 239 240 assert.Equal(t, startTime.Sub(time1)/time.Second, total/time.Second) 241 }) 242 } 243 244 // make time ranges from string description for testing 245 func makeTimeRanges(t *testing.T, in []string) timeRanges { 246 trs := make(timeRanges, len(in)) 247 for i, Range := range in { 248 var start, end int64 249 n, err := fmt.Sscanf(Range, "%d-%d", &start, &end) 250 require.NoError(t, err) 251 require.Equal(t, 2, n) 252 trs[i] = timeRange{time.Unix(start, 0), time.Unix(end, 0)} 253 } 254 return trs 255 } 256 257 func (trs timeRanges) toStrings() (out []string) { 258 out = []string{} 259 for _, tr := range trs { 260 out = append(out, fmt.Sprintf("%d-%d", tr.start.Unix(), tr.end.Unix())) 261 } 262 return out 263 } 264 265 func TestTimeRangeMerge(t *testing.T) { 266 for _, test := range []struct { 267 in []string 268 want []string 269 }{{ 270 in: []string{}, 271 want: []string{}, 272 }, { 273 in: []string{"1-2"}, 274 want: []string{"1-2"}, 275 }, { 276 in: []string{"1-4", "2-3"}, 277 want: []string{"1-4"}, 278 }, { 279 in: []string{"2-3", "1-4"}, 280 want: []string{"1-4"}, 281 }, { 282 in: []string{"1-3", "2-4"}, 283 want: []string{"1-4"}, 284 }, { 285 in: []string{"2-4", "1-3"}, 286 want: []string{"1-4"}, 287 }, { 288 in: []string{"1-2", "2-3"}, 289 want: []string{"1-3"}, 290 }, { 291 in: []string{"2-3", "1-2"}, 292 want: []string{"1-3"}, 293 }, { 294 in: []string{"1-2", "3-4"}, 295 want: []string{"1-2", "3-4"}, 296 }, { 297 in: []string{"1-3", "7-8", "4-6", "2-5", "7-8", "7-8"}, 298 want: []string{"1-6", "7-8"}, 299 }} { 300 301 in := makeTimeRanges(t, test.in) 302 in.merge() 303 304 got := in.toStrings() 305 assert.Equal(t, test.want, got) 306 } 307 } 308 309 func TestTimeRangeCull(t *testing.T) { 310 for _, test := range []struct { 311 in []string 312 cutoff int64 313 want []string 314 wantDuration time.Duration 315 }{{ 316 in: []string{}, 317 cutoff: 1, 318 want: []string{}, 319 wantDuration: 0 * time.Second, 320 }, { 321 in: []string{"1-2"}, 322 cutoff: 1, 323 want: []string{"1-2"}, 324 wantDuration: 0 * time.Second, 325 }, { 326 in: []string{"2-5", "7-9"}, 327 cutoff: 1, 328 want: []string{"2-5", "7-9"}, 329 wantDuration: 0 * time.Second, 330 }, { 331 in: []string{"2-5", "7-9"}, 332 cutoff: 4, 333 want: []string{"2-5", "7-9"}, 334 wantDuration: 0 * time.Second, 335 }, { 336 in: []string{"2-5", "7-9"}, 337 cutoff: 5, 338 want: []string{"7-9"}, 339 wantDuration: 3 * time.Second, 340 }, { 341 in: []string{"2-5", "7-9", "2-5", "2-5"}, 342 cutoff: 6, 343 want: []string{"7-9"}, 344 wantDuration: 9 * time.Second, 345 }, { 346 in: []string{"7-9", "3-3", "2-5"}, 347 cutoff: 7, 348 want: []string{"7-9"}, 349 wantDuration: 3 * time.Second, 350 }, { 351 in: []string{"2-5", "7-9"}, 352 cutoff: 8, 353 want: []string{"7-9"}, 354 wantDuration: 3 * time.Second, 355 }, { 356 in: []string{"2-5", "7-9"}, 357 cutoff: 9, 358 want: []string{}, 359 wantDuration: 5 * time.Second, 360 }, { 361 in: []string{"2-5", "7-9"}, 362 cutoff: 10, 363 want: []string{}, 364 wantDuration: 5 * time.Second, 365 }} { 366 367 in := makeTimeRanges(t, test.in) 368 cutoff := time.Unix(test.cutoff, 0) 369 gotDuration := in.cull(cutoff) 370 371 what := fmt.Sprintf("in=%q, cutoff=%d", test.in, test.cutoff) 372 got := in.toStrings() 373 assert.Equal(t, test.want, got, what) 374 assert.Equal(t, test.wantDuration, gotDuration, what) 375 } 376 } 377 378 func TestTimeRangeDuration(t *testing.T) { 379 assert.Equal(t, 0*time.Second, timeRanges{}.total()) 380 assert.Equal(t, 1*time.Second, makeTimeRanges(t, []string{"1-2"}).total()) 381 assert.Equal(t, 91*time.Second, makeTimeRanges(t, []string{"1-2", "10-100"}).total()) 382 } 383 384 func TestPruneTransfers(t *testing.T) { 385 for _, test := range []struct { 386 Name string 387 Transfers int 388 Limit int 389 ExpectedStartedTransfers int 390 }{ 391 { 392 Name: "Limited number of StartedTransfers", 393 Limit: 100, 394 Transfers: 200, 395 ExpectedStartedTransfers: 100 + fs.Config.Transfers, 396 }, 397 { 398 Name: "Unlimited number of StartedTransfers", 399 Limit: -1, 400 Transfers: 200, 401 ExpectedStartedTransfers: 200, 402 }, 403 } { 404 t.Run(test.Name, func(t *testing.T) { 405 prevLimit := MaxCompletedTransfers 406 MaxCompletedTransfers = test.Limit 407 defer func() { MaxCompletedTransfers = prevLimit }() 408 409 s := NewStats() 410 for i := int64(1); i <= int64(test.Transfers); i++ { 411 s.AddTransfer(&Transfer{ 412 startedAt: time.Unix(i, 0), 413 completedAt: time.Unix(i+1, 0), 414 }) 415 } 416 417 s.mu.Lock() 418 assert.Equal(t, time.Duration(test.Transfers)*time.Second, s.totalDuration()) 419 assert.Equal(t, test.Transfers, len(s.startedTransfers)) 420 s.mu.Unlock() 421 422 for i := 0; i < test.Transfers; i++ { 423 s.PruneTransfers() 424 } 425 426 s.mu.Lock() 427 assert.Equal(t, time.Duration(test.Transfers)*time.Second, s.totalDuration()) 428 assert.Equal(t, test.ExpectedStartedTransfers, len(s.startedTransfers)) 429 s.mu.Unlock() 430 431 }) 432 } 433 }