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  }