github.com/uber/kraken@v0.1.4/lib/store/base/file_entry_test.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package base 15 16 import ( 17 "fmt" 18 "io/ioutil" 19 "os" 20 "path/filepath" 21 "reflect" 22 "runtime" 23 "strings" 24 "testing" 25 26 "github.com/uber/kraken/core" 27 "github.com/uber/kraken/lib/store/metadata" 28 "github.com/uber/kraken/utils/randutil" 29 30 "github.com/stretchr/testify/require" 31 ) 32 33 func checkListNames(t *testing.T, factory FileEntryFactory, state FileState, expected []FileEntry) { 34 t.Helper() 35 36 var expectedNames []string 37 for _, e := range expected { 38 expectedNames = append(expectedNames, e.GetName()) 39 } 40 41 names, err := factory.ListNames(state) 42 require.NoError(t, err) 43 44 require.ElementsMatch(t, expectedNames, names) 45 } 46 47 func TestFileEntryFactoryListNames(t *testing.T) { 48 for _, factory := range []FileEntryFactory{ 49 NewLocalFileEntryFactory(), 50 NewCASFileEntryFactory(), 51 } { 52 fname := reflect.Indirect(reflect.ValueOf(factory)).Type().Name() 53 t.Run(fname, func(t *testing.T) { 54 require := require.New(t) 55 56 state, _, _, cleanup := fileStatesFixture() 57 defer cleanup() 58 59 // ListNames should show all created entries. 60 var entries []FileEntry 61 for i := 0; i < 100; i++ { 62 entry, err := factory.Create(core.DigestFixture().Hex(), state) 63 require.NoError(err) 64 require.NoError(entry.Create(state, 1)) 65 entries = append(entries, entry) 66 } 67 checkListNames(t, factory, state, entries) 68 69 // ListNames should not show deleted entries. 70 for _, e := range entries[:50] { 71 require.NoError(e.Delete()) 72 } 73 checkListNames(t, factory, state, entries[50:]) 74 }) 75 } 76 } 77 78 func TestLocalFileEntryFactoryListNamesWithSlashes(t *testing.T) { 79 require := require.New(t) 80 81 state, _, _, cleanup := fileStatesFixture() 82 defer cleanup() 83 84 factory := NewLocalFileEntryFactory() 85 86 // ListNames should show all created entries. 87 var entries []FileEntry 88 for i := 0; i < 100; i++ { 89 name := fmt.Sprintf("dir%d/subdir", i) 90 entry, err := factory.Create(name, state) 91 require.NoError(err) 92 require.NoError(entry.Create(state, 1)) 93 entries = append(entries, entry) 94 } 95 checkListNames(t, factory, state, entries) 96 } 97 98 func TestLocalFileEntryFactoryCreate(t *testing.T) { 99 state, _, _, cleanup := fileStatesFixture() 100 defer cleanup() 101 102 testCases := []struct { 103 desc string 104 name string 105 }{ 106 {"simple", "foo"}, 107 {"dot prefix", ".foo"}, 108 {"dot suffix", "foo."}, 109 {"dot reference", "fo.o"}, 110 {"dot dot prefix", "..foo"}, 111 {"dot dot suffix", "foo.."}, 112 {"dot dot reference", "fo..o"}, 113 {"slash references", "x/y/z"}, 114 {"slash references and dot", "x/.y/z"}, 115 {"slash references and dot dot", "x/..y/z"}, 116 } 117 118 for _, tc := range testCases { 119 t.Run(tc.desc, func(t *testing.T) { 120 require := require.New(t) 121 factory := NewLocalFileEntryFactory() 122 entry, err := factory.Create(tc.name, state) 123 require.NoError(err) 124 require.NotNil(entry) 125 }) 126 } 127 } 128 129 func TestLocalFileEntryFactoryCreateError(t *testing.T) { 130 state, _, _, cleanup := fileStatesFixture() 131 defer cleanup() 132 133 testCases := []struct { 134 desc string 135 name string 136 }{ 137 {"slash prefix", "/foo"}, 138 {"slash suffix", "foo/"}, 139 {"slash prefix and suffix", "/foo/"}, 140 {"dot slash prefix", "./foo"}, 141 {"dot slash reference", "foo/./bar"}, 142 {"slash dot suffix", "foo/."}, 143 {"dot dot slash prefix", "../foo"}, 144 {"dot dot slash reference", "foo/../bar"}, 145 {"slash dot dot suffix", "foo/.."}, 146 } 147 148 for _, tc := range testCases { 149 t.Run(tc.desc, func(t *testing.T) { 150 require := require.New(t) 151 factory := NewLocalFileEntryFactory() 152 _, err := factory.Create(tc.name, state) 153 require.Equal(ErrInvalidName, err) 154 }) 155 } 156 } 157 158 // These tests should pass for all FileEntry implementations 159 func TestFileEntry(t *testing.T) { 160 stores := []struct { 161 name string 162 fixture func() (bundle *fileEntryTestBundle, cleanup func()) 163 }{ 164 {"LocalFileEntry", fileEntryLocalFixture}, 165 } 166 167 tests := []func(require *require.Assertions, bundle *fileEntryTestBundle){ 168 testCreate, 169 testCreateExisting, 170 testCreateFail, 171 testMoveFrom, 172 testMoveFromExisting, 173 testMoveFromWrongState, 174 testMoveFromWrongSourcePath, 175 testMove, 176 testLinkTo, 177 testDelete, 178 testDeleteFailsForPersistedFile, 179 testGetMetadataAndSetMetadata, 180 testGetMetadataFail, 181 testSetMetadataAt, 182 testGetOrSetMetadata, 183 testDeleteMetadata, 184 testRangeMetadata, 185 } 186 187 for _, store := range stores { 188 t.Run(store.name, func(t *testing.T) { 189 for _, test := range tests { 190 testName := runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name() 191 parts := strings.Split(testName, ".") 192 t.Run(parts[len(parts)-1], func(t *testing.T) { 193 require := require.New(t) 194 s, cleanup := store.fixture() 195 defer cleanup() 196 test(require, s) 197 }) 198 } 199 }) 200 } 201 } 202 203 func testCreate(require *require.Assertions, bundle *fileEntryTestBundle) { 204 fe := bundle.entry 205 s1 := bundle.state1 206 207 fp := fe.GetPath() 208 testFileSize := int64(123) 209 210 // Create succeeds with correct state. 211 err := fe.Create(s1, testFileSize) 212 require.NoError(err) 213 info, err := os.Stat(fp) 214 require.NoError(err) 215 require.Equal(info.Size(), testFileSize) 216 } 217 218 func testCreateExisting(require *require.Assertions, bundle *fileEntryTestBundle) { 219 fe := bundle.entry 220 s1 := bundle.state1 221 222 fp := fe.GetPath() 223 testFileSize := int64(123) 224 225 // Create succeeds with correct state. 226 err := fe.Create(s1, testFileSize) 227 require.NoError(err) 228 info, err := os.Stat(fp) 229 require.NoError(err) 230 require.Equal(info.Size(), testFileSize) 231 232 // Create fails with existing file. 233 err = fe.Create(s1, testFileSize-1) 234 require.True(os.IsExist(err)) 235 info, err = os.Stat(fp) 236 require.NoError(err) 237 require.Equal(info.Size(), testFileSize) 238 } 239 240 func testCreateFail(require *require.Assertions, bundle *fileEntryTestBundle) { 241 fe := bundle.entry 242 s2 := bundle.state2 243 244 fp := fe.GetPath() 245 testFileSize := int64(123) 246 247 // Create fails with wrong state. 248 err := fe.Create(s2, testFileSize) 249 require.Error(err) 250 require.True(IsFileStateError(err)) 251 _, err = os.Stat(fp) 252 require.Error(err) 253 require.True(os.IsNotExist(err)) 254 } 255 256 func testMoveFrom(require *require.Assertions, bundle *fileEntryTestBundle) { 257 fe := bundle.entry 258 s1 := bundle.state1 259 s3 := bundle.state3 260 261 fp := fe.GetPath() 262 testSourceFile, err := ioutil.TempFile(s3.GetDirectory(), "") 263 require.NoError(err) 264 265 // MoveFrom succeeds with correct state and source path. 266 err = fe.MoveFrom(s1, testSourceFile.Name()) 267 require.NoError(err) 268 _, err = os.Stat(fp) 269 require.NoError(err) 270 } 271 272 func testMoveFromExisting(require *require.Assertions, bundle *fileEntryTestBundle) { 273 fe := bundle.entry 274 s1 := bundle.state1 275 s3 := bundle.state3 276 277 fp := fe.GetPath() 278 testSourceFile, err := ioutil.TempFile(s3.GetDirectory(), "") 279 require.NoError(err) 280 281 // MoveFrom succeeds with correct state and source path. 282 err = fe.MoveFrom(s1, testSourceFile.Name()) 283 require.NoError(err) 284 _, err = os.Stat(fp) 285 require.NoError(err) 286 287 // MoveFrom fails with existing file. 288 testSourceFile2, err := ioutil.TempFile(s3.GetDirectory(), "") 289 err = fe.MoveFrom(s1, testSourceFile2.Name()) 290 require.True(os.IsExist(err)) 291 _, err = os.Stat(fp) 292 require.NoError(err) 293 } 294 295 func testMoveFromWrongState(require *require.Assertions, bundle *fileEntryTestBundle) { 296 fe := bundle.entry 297 s2 := bundle.state2 298 s3 := bundle.state3 299 300 fp := fe.GetPath() 301 testSourceFile, err := ioutil.TempFile(s3.GetDirectory(), "") 302 require.NoError(err) 303 304 // MoveFrom fails with wrong state. 305 err = fe.MoveFrom(s2, testSourceFile.Name()) 306 require.Error(err) 307 require.True(IsFileStateError(err)) 308 _, err = os.Stat(fp) 309 require.Error(err) 310 require.True(os.IsNotExist(err)) 311 } 312 313 func testMoveFromWrongSourcePath(require *require.Assertions, bundle *fileEntryTestBundle) { 314 fe := bundle.entry 315 s1 := bundle.state1 316 317 fp := fe.GetPath() 318 319 // MoveFrom fails with wrong source path. 320 err := fe.MoveFrom(s1, "") 321 require.Error(err) 322 require.True(os.IsNotExist(err)) 323 _, err = os.Stat(fp) 324 require.Error(err) 325 require.True(os.IsNotExist(err)) 326 } 327 328 func testMove(require *require.Assertions, bundle *fileEntryTestBundle) { 329 fe := bundle.entry 330 s1 := bundle.state1 331 s2 := bundle.state2 332 s3 := bundle.state3 333 334 fn := fe.GetName() 335 fp := fe.GetPath() 336 testFileSize := int64(123) 337 m := getMockMetadataOne() 338 m.content = randutil.Blob(8) 339 mm := getMockMetadataMovable() 340 mm.content = randutil.Blob(8) 341 342 // Create file first. 343 err := fe.Create(s1, testFileSize) 344 require.NoError(err) 345 346 // Write metadata 347 updated, err := fe.SetMetadata(m) 348 require.NoError(err) 349 require.True(updated) 350 updated, err = fe.SetMetadata(mm) 351 require.NoError(err) 352 require.True(updated) 353 354 // Verify metadata is readable. 355 mresult := getMockMetadataOne() 356 require.NoError(fe.GetMetadata(mresult)) 357 require.Equal(m.content, mresult.content) 358 359 mmresult := getMockMetadataMovable() 360 require.NoError(fe.GetMetadata(mmresult)) 361 require.Equal(mm.content, mmresult.content) 362 363 // Move file, removes non-movable metadata. 364 err = fe.Move(s3) 365 require.NoError(err) 366 _, err = os.Stat(fp) 367 require.Error(err) 368 require.True(os.IsNotExist(err)) 369 _, err = os.Stat(fe.GetPath()) 370 require.NoError(err) 371 372 // Verify metadata that's not movable is deleted. 373 err = fe.GetMetadata(getMockMetadataOne()) 374 require.Error(err) 375 require.True(os.IsNotExist(err)) 376 for _, s := range []FileState{s1, s2, s3} { 377 _, err = os.Stat(filepath.Join(s.GetDirectory(), fn, getMockMetadataOne().GetSuffix())) 378 require.Error(err) 379 require.True(os.IsNotExist(err)) 380 } 381 382 // Verify metadata that's movable should have been moved along with the file entry. 383 mmresult = getMockMetadataMovable() 384 require.NoError(fe.GetMetadata(mmresult)) 385 require.Equal(mm.content, mmresult.content) 386 387 _, err = os.Stat(filepath.Join(s3.GetDirectory(), fn)) 388 require.Nil(err) 389 _, err = os.Stat(filepath.Join(s1.GetDirectory(), fn, getMockMetadataMovable().GetSuffix())) 390 require.Error(err) 391 require.True(os.IsNotExist(err)) 392 _, err = os.Stat(filepath.Join(s2.GetDirectory(), fn, getMockMetadataMovable().GetSuffix())) 393 require.Error(err) 394 require.True(os.IsNotExist(err)) 395 _, err = os.Stat(filepath.Join(s3.GetDirectory(), fn, getMockMetadataMovable().GetSuffix())) 396 require.NoError(err) 397 } 398 399 func testLinkTo(require *require.Assertions, bundle *fileEntryTestBundle) { 400 fe := bundle.entry 401 s1 := bundle.state1 402 s3 := bundle.state3 403 404 // Create file first. 405 testFileSize := int64(123) 406 err := fe.Create(s1, testFileSize) 407 testDstFile := filepath.Join(s3.GetDirectory(), "test_dst") 408 409 // LinkTo succeeds with correct source path. 410 require.NoError(fe.LinkTo(testDstFile)) 411 _, err = os.Stat(testDstFile) 412 require.NoError(err) 413 414 // LinkTo fails with existing source path. 415 require.True(os.IsExist(fe.LinkTo(testDstFile))) 416 } 417 418 func testDelete(require *require.Assertions, bundle *fileEntryTestBundle) { 419 fe := bundle.entry 420 s1 := bundle.state1 421 422 fn := fe.GetName() 423 fp := fe.GetPath() 424 testFileSize := int64(123) 425 m := getMockMetadataOne() 426 m.content = randutil.Blob(8) 427 mm := getMockMetadataMovable() 428 mm.content = randutil.Blob(8) 429 430 // Create file first. 431 err := fe.Create(s1, testFileSize) 432 require.NoError(err) 433 434 // Write metadata. 435 updated, err := fe.SetMetadata(m) 436 require.NoError(err) 437 require.True(updated) 438 updated, err = fe.SetMetadata(mm) 439 require.NoError(err) 440 require.True(updated) 441 442 // Delete. 443 err = fe.Delete() 444 require.NoError(err) 445 446 // Verify the data file and metadata files are all deleted. 447 _, err = os.Stat(fp) 448 require.Error(err) 449 require.True(os.IsNotExist(err)) 450 _, err = os.Stat(filepath.Join(s1.GetDirectory(), fn, getMockMetadataOne().GetSuffix())) 451 require.Error(err) 452 require.True(os.IsNotExist(err)) 453 _, err = os.Stat(filepath.Join(s1.GetDirectory(), fn, getMockMetadataMovable().GetSuffix())) 454 require.Error(err) 455 require.True(os.IsNotExist(err)) 456 } 457 458 func testDeleteFailsForPersistedFile(require *require.Assertions, bundle *fileEntryTestBundle) { 459 fe := bundle.entry 460 461 _, err := fe.SetMetadata(metadata.NewPersist(true)) 462 require.NoError(err) 463 464 require.Equal(ErrFilePersisted, fe.Delete()) 465 466 require.NoError(fe.DeleteMetadata(&metadata.Persist{})) 467 468 require.NoError(fe.Delete()) 469 } 470 471 func testGetMetadataAndSetMetadata(require *require.Assertions, bundle *fileEntryTestBundle) { 472 fe := bundle.entry 473 474 m := getMockMetadataOne() 475 m.content = randutil.Blob(8) 476 477 // Write metadata. 478 updated, err := fe.SetMetadata(m) 479 require.NoError(err) 480 require.True(updated) 481 482 updated, err = fe.SetMetadata(m) 483 require.NoError(err) 484 require.False(updated) 485 486 // Read metadata. 487 result := getMockMetadataOne() 488 require.NoError(fe.GetMetadata(result)) 489 require.Equal(m.content, result.content) 490 491 // Set metadata shorter. 492 m.content = randutil.Blob(4) 493 updated, err = fe.SetMetadata(m) 494 require.NoError(err) 495 require.True(updated) 496 497 // Read metadata. 498 result = getMockMetadataOne() 499 require.NoError(fe.GetMetadata(result)) 500 require.Equal(m.content, result.content) 501 } 502 503 func testGetMetadataFail(require *require.Assertions, bundle *fileEntryTestBundle) { 504 fe := bundle.entry 505 506 m1 := getMockMetadataOne() 507 m2 := getMockMetadataTwo() 508 509 // Invalid read. 510 err := fe.GetMetadata(m1) 511 require.True(os.IsNotExist(err)) 512 513 // Invalid read. 514 err = fe.GetMetadata(m2) 515 require.True(os.IsNotExist(err)) 516 } 517 518 func testSetMetadataAt(require *require.Assertions, bundle *fileEntryTestBundle) { 519 fe := bundle.entry 520 521 m := getMockMetadataOne() 522 m.content = []byte{1, 2, 3, 4} 523 524 updated, err := fe.SetMetadata(m) 525 require.NoError(err) 526 require.True(updated) 527 528 updated, err = fe.SetMetadataAt(m, []byte{5, 5}, 1) 529 require.NoError(err) 530 require.True(updated) 531 532 updated, err = fe.SetMetadataAt(m, []byte{5, 5}, 1) 533 require.NoError(err) 534 require.False(updated) 535 536 result := getMockMetadataOne() 537 require.NoError(fe.GetMetadata(result)) 538 require.Equal([]byte{1, 5, 5, 4}, result.content) 539 } 540 541 func testGetOrSetMetadata(require *require.Assertions, bundle *fileEntryTestBundle) { 542 fe := bundle.entry 543 544 original := []byte("foo") 545 546 m := getMockMetadataOne() 547 m.content = original 548 549 // First GetOrSet should write. 550 require.NoError(fe.GetOrSetMetadata(m)) 551 require.Equal(original, m.content) 552 553 m.content = []byte("bar") 554 555 // Second GetOrSet should read. 556 require.NoError(fe.GetOrSetMetadata(m)) 557 require.Equal(original, m.content) 558 } 559 560 func testDeleteMetadata(require *require.Assertions, bundle *fileEntryTestBundle) { 561 fe := bundle.entry 562 563 m := getMockMetadataOne() 564 m.content = randutil.Blob(8) 565 566 _, err := fe.SetMetadata(m) 567 require.NoError(err) 568 569 require.NoError(fe.GetMetadata(getMockMetadataOne())) 570 571 require.NoError(fe.DeleteMetadata(m)) 572 573 err = fe.GetMetadata(getMockMetadataOne()) 574 require.Error(err) 575 require.True(os.IsNotExist(err)) 576 } 577 578 func testRangeMetadata(require *require.Assertions, bundle *fileEntryTestBundle) { 579 fe := bundle.entry 580 581 ms := []metadata.Metadata{ 582 getMockMetadataOne(), 583 getMockMetadataTwo(), 584 getMockMetadataMovable(), 585 } 586 for _, m := range ms { 587 _, err := fe.SetMetadata(m) 588 require.NoError(err) 589 } 590 591 var result []metadata.Metadata 592 require.NoError(fe.RangeMetadata(func(md metadata.Metadata) error { 593 result = append(result, md) 594 return nil 595 })) 596 597 require.ElementsMatch(ms, result) 598 }