gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/skyfilereader_test.go (about) 1 package skymodules 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "mime/multipart" 10 "reflect" 11 "testing" 12 "time" 13 14 "gitlab.com/NebulousLabs/errors" 15 "gitlab.com/NebulousLabs/fastrand" 16 ) 17 18 // TestSkyfileReader verifies the functionality of the SkyfileReader. 19 func TestSkyfileReader(t *testing.T) { 20 t.Run("Basic", testSkyfileReaderBasic) 21 t.Run("ReadBuffer", testSkyfileReaderReadBuffer) 22 t.Run("MetadataTimeout", testSkyfileReaderMetadataTimeout) 23 } 24 25 // testSkyfileReaderBasic verifies the basic use case of the SkyfileReader 26 func testSkyfileReaderBasic(t *testing.T) { 27 t.Parallel() 28 29 // create upload parameters 30 sup := SkyfileUploadParameters{ 31 Filename: t.Name(), 32 Mode: DefaultFilePerm, 33 } 34 35 // create a reader 36 dataLen := fastrand.Intn(1000) + 11 37 data := fastrand.Bytes(dataLen) 38 reader := bytes.NewReader(data) 39 sfReader := NewSkyfileReader(reader, sup) 40 41 // read 1 byte 42 peek := make([]byte, 1) 43 n, err := sfReader.Read(peek) 44 if err != nil { 45 t.Fatal(err) 46 } 47 if n != 1 || !bytes.Equal(peek, data[:1]) { 48 t.Fatal("unexpected read") 49 } 50 51 // read another 9 bytes 52 next := make([]byte, 9) 53 n, err = sfReader.Read(next) 54 if err != nil { 55 t.Fatal(err) 56 } 57 if n != 9 || !bytes.Equal(next, data[1:10]) { 58 t.Fatal("unexpected read") 59 } 60 61 // read the remaining bytes 62 remainingLen := dataLen - 10 63 next = make([]byte, remainingLen) 64 n, err = sfReader.Read(next) 65 if err != nil { 66 t.Fatal(err) 67 } 68 if n != remainingLen || !bytes.Equal(next, data[10:]) { 69 t.Fatal("unexpected read") 70 } 71 72 // read again, expect EOF 73 n, err = sfReader.Read(next) 74 if err != io.EOF { 75 t.Fatal(err, n) 76 } 77 78 // fetch the metadata from the reader 79 metadata, err := sfReader.SkyfileMetadata(context.Background()) 80 if err != nil { 81 t.Fatal(err) 82 } 83 84 // check against what we expect it to be 85 if !reflect.DeepEqual(metadata, SkyfileMetadata{ 86 Filename: sup.Filename, 87 Mode: sup.Mode, 88 Length: uint64(dataLen), 89 }) { 90 t.Fatal("unexpected metadata", metadata) 91 } 92 } 93 94 // testSkyfileReaderReadBuffer verifies the functionality of the read buffer. 95 func testSkyfileReaderReadBuffer(t *testing.T) { 96 t.Parallel() 97 98 // create upload parameters 99 sup := SkyfileUploadParameters{ 100 Filename: t.Name(), 101 Mode: DefaultFilePerm, 102 } 103 104 // create a reader 105 size := 100 106 data := fastrand.Bytes(size) 107 reader := bytes.NewReader(data) 108 sfReader := NewSkyfileReader(reader, sup) 109 110 // read some data 111 buf := make([]byte, 40) 112 n, err := io.ReadFull(sfReader, buf) 113 if err != nil { 114 t.Fatal(err) 115 } 116 if n != 40 { 117 t.Fatal("unexpected read") 118 } 119 120 // set that data is read buffer 121 sfReader.SetReadBuffer(buf) 122 123 // read the rest of the data 124 rest := make([]byte, 100) 125 _, err = sfReader.Read(rest) 126 if err != nil { 127 t.Fatal(err) 128 } 129 130 // read again, expect EOF 131 _, err = sfReader.Read(make([]byte, 1)) 132 if err != io.EOF { 133 t.Fatal(err, n) 134 } 135 136 // verify we have just read all data, including the buffer 137 if !bytes.Equal(rest, data) { 138 t.Fatal("unexpected read") 139 } 140 141 // fetch the metadata from the reader 142 metadata, err := sfReader.SkyfileMetadata(context.Background()) 143 if err != nil { 144 t.Fatal(err) 145 } 146 147 // check against what we expect it to be 148 if !reflect.DeepEqual(metadata, SkyfileMetadata{ 149 Filename: sup.Filename, 150 Mode: sup.Mode, 151 Length: uint64(size), 152 }) { 153 t.Fatal("unexpected metadata", metadata) 154 } 155 } 156 157 // testSkyfileReaderMetadataTimeout verifies metadata returns on timeout, 158 // potentially before the reader is fully read 159 func testSkyfileReaderMetadataTimeout(t *testing.T) { 160 // Since this is a timeout test only run with long test 161 if testing.Short() { 162 t.SkipNow() 163 } 164 t.Parallel() 165 166 // create upload parameters 167 sup := SkyfileUploadParameters{ 168 Filename: t.Name(), 169 Mode: DefaultFilePerm, 170 } 171 172 // create a reader 173 dataLen := fastrand.Intn(1000) + 10 174 data := fastrand.Bytes(dataLen) 175 reader := bytes.NewReader(data) 176 sfReader := NewSkyfileReader(reader, sup) 177 178 // read less than dataLen 179 read := make([]byte, dataLen/2) 180 _, err := sfReader.Read(read) 181 if err != nil { 182 t.Fatal(err) 183 } 184 185 // cancel the context in a goroutine 186 ctx, cancel := context.WithCancel(context.Background()) 187 go func() { 188 time.Sleep(time.Second) 189 cancel() 190 }() 191 192 // fetch the metadata from the reader 193 metadata, err := sfReader.SkyfileMetadata(ctx) 194 if !errors.Contains(err, ErrSkyfileMetadataUnavailable) { 195 t.Fatal(err) 196 } 197 if !reflect.DeepEqual(metadata, SkyfileMetadata{}) { 198 t.Fatal("unexpected metadata", metadata) 199 } 200 } 201 202 // TestSkyfileMultipartReader verifies the functionality of the 203 // SkyfileMultipartReader. 204 func TestSkyfileMultipartReader(t *testing.T) { 205 t.Run("Basic", testSkyfileMultipartReaderBasic) 206 t.Run("IllegalFormName", testSkyfileMultipartReaderIllegalFormName) 207 t.Run("EmptyFilename", testSkyfileMultipartReaderEmptyFilename) 208 t.Run("RandomReadSize", testSkyfileMultipartReaderRandomReadSize) 209 t.Run("ReadBuffer", testSkyfileMultipartReaderReadBuffer) 210 t.Run("MetadataTimeout", testSkyfileMultipartReaderMetadataTimeout) 211 } 212 213 // testSkyfileMultipartReaderBasic verifies the basic use case of a skyfile 214 // multipart reader, reading out the exact parts. 215 func testSkyfileMultipartReaderBasic(t *testing.T) { 216 t.Parallel() 217 218 // create upload parameters 219 sup := SkyfileUploadParameters{ 220 Filename: t.Name(), 221 Mode: DefaultFilePerm, 222 } 223 224 // create a multipart writer 225 buffer := new(bytes.Buffer) 226 writer := multipart.NewWriter(buffer) 227 228 // prepare random file data 229 data1 := fastrand.Bytes(10) 230 data2 := fastrand.Bytes(20) 231 232 // write the multipart files 233 off := uint64(0) 234 md1, err1 := AddMultipartFile(writer, data1, "files[]", "part1", 0600, &off) 235 md2, err2 := AddMultipartFile(writer, data2, "files[]", "part2", 0600, &off) 236 if errors.Compose(err1, err2) != nil { 237 t.Fatal("unexpected") 238 } 239 240 // close the writer 241 err := writer.Close() 242 if err != nil { 243 t.Fatal(err) 244 } 245 246 // turn it into a skyfile reader 247 reader := bytes.NewReader(buffer.Bytes()) 248 multipartReader := multipart.NewReader(reader, writer.Boundary()) 249 sfReader := NewSkyfileMultipartReader(multipartReader, sup) 250 251 // verify we can read part 1 252 part1Data := make([]byte, 10) 253 n, err := sfReader.Read(part1Data) 254 if err != nil { 255 t.Fatal(err) 256 } 257 if n != 10 || !bytes.Equal(part1Data, data1) { 258 t.Fatal("unexpected read", n) 259 } 260 261 // verify we can read part 2 262 part2Data := make([]byte, 20) 263 n, err = sfReader.Read(part2Data) 264 if err != nil { 265 t.Fatal(err) 266 } 267 if n != 20 || !bytes.Equal(part2Data, data2) { 268 t.Fatal("unexpected read", n) 269 } 270 271 // we have to simulate a consecutive read to mimic not knowing how many 272 // parts the request contains, only then will the metadata be released 273 sfReader.Read(make([]byte, 1)) 274 275 // fetch the metadata from the reader 276 metadata, err := sfReader.SkyfileMetadata(context.Background()) 277 if err != nil { 278 t.Fatal(err) 279 } 280 281 part1Meta, ok := metadata.Subfiles["part1"] 282 if !ok || !reflect.DeepEqual(part1Meta, md1) { 283 t.Fatal("unexpected metadata") 284 } 285 286 part2Meta, ok := metadata.Subfiles["part2"] 287 if !ok || !reflect.DeepEqual(part2Meta, md2) { 288 t.Fatal("unexpected metadata") 289 } 290 } 291 292 // testSkyfileMultipartReaderIllegalFormName verifies the reader returns an 293 // error if the given form name is not one of the allowed values. 294 func testSkyfileMultipartReaderIllegalFormName(t *testing.T) { 295 t.Parallel() 296 297 // create upload parameters 298 sup := SkyfileUploadParameters{ 299 Filename: t.Name(), 300 Mode: DefaultFilePerm, 301 } 302 303 // create a multipart writer 304 buffer := new(bytes.Buffer) 305 writer := multipart.NewWriter(buffer) 306 307 // prepare random file data 308 data := fastrand.Bytes(10) 309 310 // write the multipart files 311 off := uint64(0) 312 _, err := AddMultipartFile(writer, data, "part", "part", 0600, &off) 313 if err != nil { 314 t.Fatal("unexpected") 315 } 316 317 // close the writer 318 err = writer.Close() 319 if err != nil { 320 t.Fatal(err) 321 } 322 323 // turn it into a skyfile reader 324 reader := bytes.NewReader(buffer.Bytes()) 325 multipartReader := multipart.NewReader(reader, writer.Boundary()) 326 sfReader := NewSkyfileMultipartReader(multipartReader, sup) 327 328 // verify we 329 _, err = ioutil.ReadAll(sfReader) 330 if !errors.Contains(err, ErrIllegalFormName) { 331 t.Fatalf("expected ErrIllegalFormName error, instead err was '%v'", err) 332 } 333 } 334 335 // testSkyfileMultipartReaderRandomReadSize creates a random multipart request 336 // and reads the entire request using random read sizes. 337 func testSkyfileMultipartReaderRandomReadSize(t *testing.T) { 338 t.Parallel() 339 340 // create upload parameters 341 sup := SkyfileUploadParameters{ 342 Filename: t.Name(), 343 Mode: DefaultFilePerm, 344 } 345 346 // create a multipart writer 347 buffer := new(bytes.Buffer) 348 writer := multipart.NewWriter(buffer) 349 350 // prepare some random data 351 randomParts := 3 //fastrand.Intn(10) + 3 352 randomPartsData := make([][]byte, randomParts) 353 for i := 0; i < randomParts; i++ { 354 randomPartLen := fastrand.Intn(10) + 1 355 randomPartsData[i] = fastrand.Bytes(randomPartLen) 356 } 357 358 // write the multipart files 359 off := uint64(0) 360 for i, data := range randomPartsData { 361 filename := fmt.Sprintf("file%d", i) 362 _, err := AddMultipartFile(writer, data, "files[]", filename, 644, &off) 363 if err != nil { 364 t.Fatal(err) 365 } 366 } 367 368 // close the writer 369 err := writer.Close() 370 if err != nil { 371 t.Fatal(err) 372 } 373 374 // turn it into a skyfile reader 375 reader := bytes.NewReader(buffer.Bytes()) 376 multipartReader := multipart.NewReader(reader, writer.Boundary()) 377 sfReader := NewSkyfileMultipartReader(multipartReader, sup) 378 379 // concat all data 380 expected := make([]byte, 0) 381 for _, data := range randomPartsData { 382 expected = append(expected, data...) 383 } 384 385 // read, randomly, until all data is read 386 actual := make([]byte, 0) 387 maxLen := len(expected) / 3 388 for { 389 randomLength := fastrand.Intn(maxLen) + 1 390 391 data := make([]byte, randomLength) 392 n, err := sfReader.Read(data) 393 394 actual = append(actual, data[:n]...) 395 if errors.Contains(err, io.EOF) { 396 break 397 } 398 } 399 400 // verify we've read all of the data 401 if !bytes.Equal(actual, expected) { 402 t.Fatal("unexpected data") 403 } 404 405 // fetch the metadata from the reader 406 metadata, err := sfReader.SkyfileMetadata(context.Background()) 407 if err != nil { 408 t.Fatal(err) 409 } 410 411 // verify the metadata is properly read 412 if len(metadata.Subfiles) != randomParts { 413 t.Fatal("unexpected amount of metadata") 414 } 415 416 currOffset := 0 417 for i, data := range randomPartsData { 418 filename := fmt.Sprintf("file%d", i) 419 metadata, ok := metadata.Subfiles[filename] 420 if !ok { 421 t.Fatal("metadata not found") 422 } 423 if metadata.Len != uint64(len(data)) { 424 t.Fatal("unexpected len") 425 } 426 if metadata.Offset != uint64(currOffset) { 427 t.Fatal("unexpected offset") 428 } 429 currOffset += len(data) 430 } 431 } 432 433 // testSkyfileMultipartReaderEmptyFilename verifies the reader returns an error 434 // if the filename is empty. 435 func testSkyfileMultipartReaderEmptyFilename(t *testing.T) { 436 t.Parallel() 437 438 // create upload parameters 439 sup := SkyfileUploadParameters{ 440 Filename: t.Name(), 441 Mode: DefaultFilePerm, 442 } 443 444 // create a multipart writer 445 buffer := new(bytes.Buffer) 446 writer := multipart.NewWriter(buffer) 447 448 // prepare random file data 449 data := fastrand.Bytes(10) 450 451 // write the multipart files 452 off := uint64(0) 453 _, err := AddMultipartFile(writer, data, "file", "", 0600, &off) 454 if err != nil { 455 t.Fatal("unexpected") 456 } 457 458 // close the writer 459 err = writer.Close() 460 if err != nil { 461 t.Fatal(err) 462 } 463 464 // turn it into a skyfile reader 465 reader := bytes.NewReader(buffer.Bytes()) 466 multipartReader := multipart.NewReader(reader, writer.Boundary()) 467 sfReader := NewSkyfileMultipartReader(multipartReader, sup) 468 469 // verify we get ErrEmptyFilename if we do not provide a filename 470 _, err = ioutil.ReadAll(sfReader) 471 if !errors.Contains(err, ErrEmptyFilename) { 472 t.Fatalf("expected ErrEmptyFilename error, instead err was '%v'", err) 473 } 474 } 475 476 // testSkyfileMultipartReaderReadBuffer verifies the functionality of the read 477 // buffer. 478 func testSkyfileMultipartReaderReadBuffer(t *testing.T) { 479 t.Parallel() 480 481 // create upload parameters 482 sup := SkyfileUploadParameters{ 483 Filename: t.Name(), 484 Mode: DefaultFilePerm, 485 } 486 487 // create a multipart writer 488 buffer := new(bytes.Buffer) 489 writer := multipart.NewWriter(buffer) 490 491 // prepare random file data 492 data1 := fastrand.Bytes(10) 493 data2 := fastrand.Bytes(20) 494 495 // write the multipart files 496 off := uint64(0) 497 _, err1 := AddMultipartFile(writer, data1, "files[]", "part1", 0600, &off) 498 _, err2 := AddMultipartFile(writer, data2, "files[]", "part2", 0600, &off) 499 if errors.Compose(err1, err2) != nil { 500 t.Fatal("unexpected") 501 } 502 503 // close the writer 504 err := writer.Close() 505 if err != nil { 506 t.Fatal(err) 507 } 508 509 // turn it into a skyfile reader 510 reader := bytes.NewReader(buffer.Bytes()) 511 multipartReader := multipart.NewReader(reader, writer.Boundary()) 512 sfReader := NewSkyfileMultipartReader(multipartReader, sup) 513 514 // read 5 bytes 515 data := make([]byte, 5) 516 _, err = sfReader.Read(data) 517 if err != nil { 518 t.Fatal(err) 519 } 520 521 // set them as buffer 522 sfReader.SetReadBuffer(data) 523 524 // read 20 bytes and compare them to what we expect to receive 525 expected := append(data1, data2[:10]...) 526 data = make([]byte, 20) 527 _, err = sfReader.Read(data) 528 if err != nil { 529 t.Fatal(err) 530 } 531 if !bytes.Equal(expected, data) { 532 t.Fatal("unexpected") 533 } 534 } 535 536 // testSkyfileMultipartReaderMetadataTimeout verifies metadata returns on 537 // timeout, potentially before the reader is fully read 538 func testSkyfileMultipartReaderMetadataTimeout(t *testing.T) { 539 // Since this is a timeout test only run with long test 540 if testing.Short() { 541 t.SkipNow() 542 } 543 t.Parallel() 544 545 // create upload parameters 546 sup := SkyfileUploadParameters{ 547 Filename: t.Name(), 548 Mode: DefaultFilePerm, 549 } 550 551 // create a multipart writer 552 buffer := new(bytes.Buffer) 553 writer := multipart.NewWriter(buffer) 554 555 // prepare random file data 556 dataLen := fastrand.Intn(100) + 10 557 data := fastrand.Bytes(dataLen) 558 559 // write the multipart files 560 off := uint64(0) 561 _, err := AddMultipartFile(writer, data, "files[]", "part", 0600, &off) 562 if err != nil { 563 t.Fatal("unexpected") 564 } 565 566 // close the writer 567 err = writer.Close() 568 if err != nil { 569 t.Fatal(err) 570 } 571 572 // turn it into a skyfile reader 573 reader := bytes.NewReader(buffer.Bytes()) 574 multipartReader := multipart.NewReader(reader, writer.Boundary()) 575 sfReader := NewSkyfileMultipartReader(multipartReader, sup) 576 577 // read less than dataLen 578 read := make([]byte, dataLen/2) 579 _, err = sfReader.Read(read) 580 if err != nil { 581 t.Fatal(err) 582 } 583 584 // cancel the context in a goroutine 585 ctx, cancel := context.WithCancel(context.Background()) 586 go func() { 587 time.Sleep(time.Second) 588 cancel() 589 }() 590 591 // fetch the metadata from the reader 592 metadata, err := sfReader.SkyfileMetadata(ctx) 593 if !errors.Contains(err, ErrSkyfileMetadataUnavailable) { 594 t.Fatal(err) 595 } 596 if !reflect.DeepEqual(metadata, SkyfileMetadata{}) { 597 t.Fatal("unexpected metadata", metadata) 598 } 599 }