github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/fs/api_test.go (about)

     1  package fs
     2  
     3  import (
     4  	"bytes"
     5  	"math"
     6  	"strings"
     7  	"syscall"
     8  	"testing"
     9  	"time"
    10  
    11  	"golang.org/x/sys/unix"
    12  
    13  	"github.com/swiftstack/ProxyFS/blunder"
    14  	"github.com/swiftstack/ProxyFS/inode"
    15  )
    16  
    17  // TODO: Enhance this to do a stat() as well and check number of files
    18  func expectDirectory(t *testing.T, userID inode.InodeUserID, groupID inode.InodeGroupID, inodeNum inode.InodeNumber, expectedEntries []string) {
    19  	readdirEntries, numEntries, moreEntries, err := testVolumeStruct.Readdir(userID, groupID, nil, inodeNum, 0, "")
    20  	if nil != err {
    21  		t.Fatalf("Readdir() [#1] returned error: %v", err)
    22  	}
    23  	if uint64(len(expectedEntries)) != numEntries {
    24  		t.Fatalf("Readdir() [#1] returned unexpected number of entries (%v) -  should have been %v", numEntries, len(expectedEntries))
    25  	}
    26  	if moreEntries {
    27  		t.Fatalf("Readdir() [#1] returned moreEntries == true... should have been false")
    28  	}
    29  
    30  	entriesFound := make(map[string]bool)
    31  	for i := uint64(0); i < numEntries; i++ {
    32  		entriesFound[readdirEntries[i].Basename] = true
    33  	}
    34  
    35  	for i := 0; i < len(expectedEntries); i++ {
    36  		expected := expectedEntries[i]
    37  		_, found := entriesFound[expected]
    38  		if !found {
    39  			t.Errorf("Expected entry %s not found in readdirEntries", expected)
    40  		}
    41  	}
    42  }
    43  
    44  func createTestDirectory(t *testing.T, dirname string) (dirInode inode.InodeNumber) {
    45  	var err error
    46  
    47  	// Get root dir inode number
    48  	rootDirInodeNumber := inode.RootDirInodeNumber
    49  
    50  	dirInode, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, dirname, inode.PosixModePerm)
    51  	if nil != err {
    52  		t.Fatalf("Mkdir() returned error: %v", err)
    53  	}
    54  
    55  	return dirInode
    56  }
    57  
    58  // TODO:  Ultimately, each of these tests should at least run in their own directory
    59  //        a la createTestDirectory(), or preferably some stronger effort should be
    60  //        made to insulate them from each other.
    61  func TestCreateAndLookup(t *testing.T) {
    62  	testSetup(t, false)
    63  
    64  	rootDirInodeNumber := inode.RootDirInodeNumber
    65  	basename := "create_lookup.test"
    66  
    67  	createdFileInodeNumber, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename, inode.PosixModePerm)
    68  	if err != nil {
    69  		t.Fatalf("Unexpectedly couldn't create file: %v", err)
    70  	}
    71  
    72  	foundFileInodeNumber, err := testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename)
    73  	if err != nil {
    74  		t.Fatalf("Unexpectedly failed to look up %v", basename)
    75  	}
    76  
    77  	if createdFileInodeNumber != foundFileInodeNumber {
    78  		t.Fatalf("Expected created inode number %v to equal found inode number %v", createdFileInodeNumber, foundFileInodeNumber)
    79  	}
    80  
    81  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename)
    82  	if nil != err {
    83  		t.Fatalf("Unlink() returned error: %v", err)
    84  	}
    85  
    86  	testTeardown(t)
    87  }
    88  
    89  func TestGetstat(t *testing.T) {
    90  	testSetup(t, false)
    91  
    92  	rootDirInodeNumber := inode.RootDirInodeNumber
    93  	basename := "getstat.test"
    94  	timeBeforeCreation := uint64(time.Now().UnixNano())
    95  
    96  	inodeNumber, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename, inode.PosixModePerm)
    97  	if err != nil {
    98  		t.Fatalf("couldn't create file: %v", err)
    99  	}
   100  
   101  	stat, err := testVolumeStruct.Getstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, inodeNumber)
   102  	if err != nil {
   103  		t.Fatalf("couldn't stat inode %v: %v", inodeNumber, err)
   104  	}
   105  
   106  	if !(math.Abs(float64(int64(timeBeforeCreation)-int64(stat[StatCRTime]))) < 0.1*1000000000) { // nanoseconds
   107  		t.Errorf("unexpectedly skewed StatCRTime %v is not close to %v", stat[StatCRTime], timeBeforeCreation)
   108  	}
   109  	if !(math.Abs(float64(int64(timeBeforeCreation)-int64(stat[StatMTime]))) < 0.1*1000000000) { // nanoseconds
   110  		t.Errorf("unexpectedly skewed StatMTime %v is not close to %v", stat[StatMTime], timeBeforeCreation)
   111  	}
   112  	if stat[StatSize] != 0 {
   113  		t.Errorf("expected size to be 0")
   114  	}
   115  	if stat[StatNLink] != 1 {
   116  		t.Errorf("expected number of links to be one, got %v", stat[StatNLink])
   117  	}
   118  
   119  	// TODO: perform a write, check that size has changed accordingly
   120  	// TODO: make and delete hardlinks, check that link count has changed accordingly
   121  
   122  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename)
   123  	if nil != err {
   124  		t.Fatalf("Unlink() returned error: %v", err)
   125  	}
   126  
   127  	testTeardown(t)
   128  }
   129  
   130  // TestAllAPIPositiveCases() follows the following "positive" test steps:
   131  //
   132  //    Mount                   A                                    : mount the specified test Volume (must be empty)
   133  //    Mkdir                   A/B/                                 : create a subdirectory within Volume directory
   134  //    Create               #1 A/C                                  : create and open a normal file within Volume directory
   135  //    Lookup               #1 A/C                                  : fetch the inode name of the just created normal file
   136  //    Write                   A/C                                  : write something to normal file
   137  //    Read                    A/C                                  : read back what was just written to normal file
   138  //    FetchExtentMapChunk     A/C                                  : fetch extentMapChunk for entire file
   139  //    Getstat              #1 A/C                                  : check the current size of the normal file
   140  //    Resize                  A/C                                  : truncate the file
   141  //    Getstat              #2 A/C                                  : verify the size of the normal file is now zero
   142  //    Symlink                 A/D->A/C                             : create a symlink to the normal file
   143  //    Lookup               #2 A/D                                  : fetch the inode name of the just created symlink
   144  //    Readsymlink             A/D                                  : read the symlink to ensure it points to the normal file
   145  //    Lookup               #3 A/B/                                 : fetch the inode name of the subdirectory
   146  //    Create               #2 A/B/E                                : create a normal file within subdirectory
   147  //    Readdir              #1 A/B/ (prev == "",  max_entries == 0) : ensure we get only ".", "..", and "E"
   148  //    Statfs                  A                                    : should report A has 4 "files" (normal & symlink) and 1 directory "ideally"
   149  //    Unlink               #1 A/B/E                                : delete the normal file within the subdirectory
   150  //    Readdir              #2 A/   (prev == "",  max_entries == 3) : ensure we get only ".", ".." & "B"
   151  //    Readdir              #3 A/   (prev == "B", max_entries == 3) : ensure we get only "C" & "D"
   152  //    Unlink               #2 A/D                                  : delete the symlink
   153  //    Unlink               #3 A/C                                  : delete the normal file
   154  //    Unlink               #4 A/B                                  : delete the subdirectory
   155  //    Unmount                 A                                    : unmount the Volume
   156  //
   157  // TODO: Rename(), Link() tests
   158  
   159  var tempVolumeName string // TODO: This is currently the local file system full path
   160  
   161  func TestAllAPIPositiveCases(t *testing.T) {
   162  	var (
   163  		err error
   164  	)
   165  
   166  	testSetup(t, false)
   167  
   168  	// Get root dir inode number
   169  	rootDirInodeNumber := inode.RootDirInodeNumber
   170  
   171  	//    Mkdir          A/B/                                 : create a subdirectory within Volume directory
   172  	_, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSubDirectory", inode.PosixModePerm)
   173  	//	newDirInodeNum, err := testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSubDirectory")
   174  	if nil != err {
   175  		t.Fatalf("Mkdir() returned error: %v", err)
   176  	}
   177  
   178  	//    Create      #1 A/C                                  : create and open a normal file within Volume directory
   179  	basename := "TestNormalFile"
   180  	createdFileInodeNumber, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename, inode.PosixModePerm)
   181  	if err != nil {
   182  		t.Fatalf("Create() [#1] returned error: %v", err)
   183  	}
   184  
   185  	//    Lookup      #1 A/C                                  : fetch the inode name of the just created normal file
   186  	foundFileInodeNumber, err := testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename)
   187  	if err != nil {
   188  		t.Fatalf("Lookup() [#1] returned error: %v", err)
   189  	}
   190  	if createdFileInodeNumber != foundFileInodeNumber {
   191  		t.Fatalf("Expected created inode number %v to equal found inode number %v", createdFileInodeNumber, foundFileInodeNumber)
   192  	}
   193  
   194  	//    Write          A/C                                  : write something to normal file
   195  	bufToWrite := []byte{0x41, 0x42, 0x43}
   196  	write_rspSize, err := testVolumeStruct.Write(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber, 0, bufToWrite, nil)
   197  	if nil != err {
   198  		t.Fatalf("Write() returned error: %v", err)
   199  	}
   200  	if uint64(len(bufToWrite)) != write_rspSize {
   201  		t.Fatalf("Write() expected to write %v bytes but actually wrote %v bytes", len(bufToWrite), write_rspSize)
   202  	}
   203  
   204  	// don't forget to flush
   205  	err = testVolumeStruct.Flush(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber)
   206  	if err != nil {
   207  		t.Fatalf("Flush() returned error: %v", err)
   208  	}
   209  
   210  	//    Read           A/C                                  : read back what was just written to normal file
   211  	read_buf, err := testVolumeStruct.Read(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber, 0, uint64(len(bufToWrite)), nil)
   212  	if nil != err {
   213  		t.Fatalf("Read() returned error: %v", err)
   214  	}
   215  	if len(bufToWrite) != len(read_buf) {
   216  		t.Fatalf("Read() expected to read %v bytes but actually read %v bytes", len(bufToWrite), len(read_buf))
   217  	}
   218  	if 0 != bytes.Compare(bufToWrite, read_buf) {
   219  		t.Fatalf("Read() returned data different from what was written")
   220  	}
   221  
   222  	extent_map_chunk, err := testVolumeStruct.FetchExtentMapChunk(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber, uint64(0), int64(1), int64(0))
   223  	if nil != err {
   224  		t.Fatalf("FetchExtentMapChunk() returned error: %v", err)
   225  	}
   226  	if 0 != extent_map_chunk.FileOffsetRangeStart {
   227  		t.Fatalf("FetchExtentMapChunk() returned unexpected FileOffsetRangeStart: %v (should be 0)", extent_map_chunk.FileOffsetRangeStart)
   228  	}
   229  	if uint64(len(bufToWrite)) != extent_map_chunk.FileOffsetRangeEnd {
   230  		t.Fatalf("FetchExtentMapChunk() returned unexpected FileOffsetRangeEnd: %v (should be %v)", len(bufToWrite), extent_map_chunk.FileOffsetRangeEnd)
   231  	}
   232  	if uint64(len(bufToWrite)) != extent_map_chunk.FileSize {
   233  		t.Fatalf("FetchExtentMapChunk() returned unexpected FileSize: %v (should be %v)", len(bufToWrite), extent_map_chunk.FileSize)
   234  	}
   235  	if 1 != len(extent_map_chunk.ExtentMapEntry) {
   236  		t.Fatalf("FetchExtentMapChunk() returned unexpected len(ExtentMapEntry slice): %v (should be 1)", len(extent_map_chunk.ExtentMapEntry))
   237  	}
   238  	if uint64(0) != extent_map_chunk.ExtentMapEntry[0].FileOffset {
   239  		t.Fatalf("FetchExtentMapChunk() returned unexpected ExtentMapEntry[0].FileOffset: %v (should be 0)", extent_map_chunk.ExtentMapEntry[0].FileOffset)
   240  	}
   241  	if uint64(len(bufToWrite)) != extent_map_chunk.ExtentMapEntry[0].Length {
   242  		t.Fatalf("FetchExtentMapChunk() returned unexpected ExtentMapEntry[0].Length: %v (should be %v)", extent_map_chunk.ExtentMapEntry[0].Length, uint64(len(bufToWrite)))
   243  	}
   244  	if uint64(0) != extent_map_chunk.ExtentMapEntry[0].LogSegmentOffset {
   245  		t.Fatalf("FetchExtentMapChunk() returned unexpected ExtentMapEntry[0].LogSegmentOffset: %v (should be 0)", extent_map_chunk.ExtentMapEntry[0].LogSegmentOffset)
   246  	}
   247  
   248  	//    Getstat     #1 A/C                                  : check the current size of the normal file
   249  	getstat_1_rspStat, err := testVolumeStruct.Getstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, foundFileInodeNumber)
   250  	if nil != err {
   251  		t.Fatalf("Getstat() returned error: %v", err)
   252  	}
   253  	getstat_1_size, getstat_1_size_ok := getstat_1_rspStat[StatSize]
   254  	if !getstat_1_size_ok {
   255  		t.Fatalf("Getstat() returned no StatSize")
   256  	}
   257  	if uint64(len(bufToWrite)) != getstat_1_size {
   258  		t.Fatalf("Getstat() returned StatSize == %v instead of the expected %v", getstat_1_size, len(bufToWrite))
   259  	}
   260  
   261  	//    Resize         A/C                                  : truncate the file
   262  	err = testVolumeStruct.Resize(inode.InodeRootUserID, inode.InodeGroupID(0), nil, foundFileInodeNumber, 0)
   263  	if nil != err {
   264  		t.Fatalf("Resize() returned error: %v", err)
   265  	}
   266  
   267  	//    Getstat     #2 A/C                                  : verify the size of the normal file is now zero
   268  	getstat_2_rspStat, err := testVolumeStruct.Getstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, foundFileInodeNumber)
   269  	if nil != err {
   270  		t.Fatalf("Getstat() [#2] returned error: %v", err)
   271  	}
   272  	getstat_2_size, getstat_2_size_ok := getstat_2_rspStat[StatSize]
   273  	if !getstat_2_size_ok {
   274  		t.Fatalf("Getstat() [#2] returned no StatSize")
   275  	}
   276  	if 0 != getstat_2_size {
   277  		t.Fatalf("Getstat() [#2] returned StatSize == %v instead of the expected %v", getstat_2_size, 0)
   278  	}
   279  
   280  	//    Symlink        A/D->A/C                             : create a symlink to the normal file
   281  	createdSymlinkInodeNumber, err := testVolumeStruct.Symlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSymlink", "TestNormalFile")
   282  	if nil != err {
   283  		t.Fatalf("Symlink() returned error: %v", err)
   284  	}
   285  
   286  	//    Lookup      #2 A/D                                  : fetch the inode name of the just created symlink
   287  	lookup_2_inodeHandle, err := testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSymlink")
   288  	if nil != err {
   289  		t.Fatalf("Lookup() [#2] returned error: %v", err)
   290  	}
   291  	if lookup_2_inodeHandle != createdSymlinkInodeNumber {
   292  		t.Fatalf("Lookup() [#2] returned unexpected InodeNumber")
   293  	}
   294  
   295  	//    Readsymlink    A/D                                  : read the symlink to ensure it points to the normal file
   296  	readsymlink_target, err := testVolumeStruct.Readsymlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lookup_2_inodeHandle)
   297  	if nil != err {
   298  		t.Fatalf("Readsymlink() returned error: %v", err)
   299  	}
   300  	if 0 != strings.Compare("TestNormalFile", readsymlink_target) {
   301  		t.Fatalf("Readsymlink() data different from what was written")
   302  	}
   303  
   304  	//    Lookup      #3 A/B/                                 : fetch the inode name of the subdirectory
   305  	lookup_3_inodeHandle, err := testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSubDirectory")
   306  	if nil != err {
   307  		t.Fatalf("Lookup() [#3] returned error: %v", err)
   308  	}
   309  
   310  	//    Create      #2 A/B/E                                : create a normal file within subdirectory
   311  	testSubDirectoryFileInode, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lookup_3_inodeHandle, "TestSubDirectoryFile", inode.PosixModePerm)
   312  	if nil != err {
   313  		t.Fatalf("Create() [#2] returned error: %v", err)
   314  	}
   315  
   316  	//    Readdir and examine contents
   317  	entriesExpected := []string{".", "..", "TestSubDirectoryFile"}
   318  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), lookup_3_inodeHandle, entriesExpected)
   319  
   320  	//    Link A/B/E
   321  	err = testVolumeStruct.Link(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lookup_3_inodeHandle, "TestSubDirectoryFileHardLink", testSubDirectoryFileInode)
   322  	if nil != err {
   323  		t.Fatalf("Link() returned error: %v", err)
   324  	}
   325  
   326  	entriesExpected = []string{".", "..", "TestSubDirectoryFile", "TestSubDirectoryFileHardLink"}
   327  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), lookup_3_inodeHandle, entriesExpected)
   328  
   329  	//    Unlink      #1 A/B/E                                : delete the normal file within the subdirectory
   330  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lookup_3_inodeHandle, "TestSubDirectoryFile")
   331  	if nil != err {
   332  		t.Fatalf("Unlink() [#1] returned error: %v", err)
   333  	}
   334  
   335  	entriesExpected = []string{".", "..", "TestSubDirectoryFileHardLink"}
   336  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), lookup_3_inodeHandle, entriesExpected)
   337  
   338  	//    Unlink      #1.5 A/B/E                                : delete the normal file within the subdirectory
   339  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lookup_3_inodeHandle, "TestSubDirectoryFileHardLink")
   340  	if nil != err {
   341  		t.Fatalf("Unlink() [#1.5] returned error: %v", err)
   342  	}
   343  
   344  	entriesExpected = []string{".", "..", "TestSymlink", "TestNormalFile", "TestSubDirectory"}
   345  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), rootDirInodeNumber, entriesExpected)
   346  
   347  	//    Unlink      #2 A/D                                  : delete the symlink
   348  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSymlink")
   349  
   350  	if nil != err {
   351  		t.Fatalf("Unlink() [#2] returned error: %v", err)
   352  	}
   353  
   354  	//    Unlink      #3 A/C                                  : delete the normal file
   355  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestNormalFile")
   356  	if nil != err {
   357  		t.Fatalf("Unlink() [#3] returned error: %v", err)
   358  	}
   359  
   360  	//    Rmdir       #4 A/B                                  : delete the subdirectory
   361  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, "TestSubDirectory")
   362  	if nil != err {
   363  		t.Fatalf("Unlink() [#4] returned error: %v", err)
   364  	}
   365  
   366  	entriesExpected = []string{".", ".."}
   367  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), rootDirInodeNumber, entriesExpected)
   368  
   369  	testTeardown(t)
   370  }
   371  
   372  // TODO: flesh this out with other boundary condition testing for Link
   373  func TestBadLinks(t *testing.T) {
   374  	testSetup(t, false)
   375  
   376  	testDirInode := createTestDirectory(t, "BadLinks")
   377  
   378  	validFile := "PerfectlyValidFile"
   379  	validFileInodeNumber, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, validFile, inode.PosixModePerm)
   380  	if err != nil {
   381  		t.Fatalf("Create() returned error: %v", err)
   382  	}
   383  
   384  	nameTooLong := strings.Repeat("x", FileNameMax+1)
   385  	err = testVolumeStruct.Link(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, nameTooLong, validFileInodeNumber)
   386  	if nil != err {
   387  		if blunder.IsNot(err, blunder.NameTooLongError) {
   388  			t.Fatalf("Link() returned error %v, expected %v(%d).", blunder.Errno(err), blunder.NameTooLongError, blunder.NameTooLongError.Value())
   389  		}
   390  	} else {
   391  		t.Fatal("Link() unexpectedly succeeded on too-long filename!")
   392  	}
   393  
   394  	entriesExpected := []string{".", "..", validFile}
   395  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), testDirInode, entriesExpected)
   396  
   397  	testTeardown(t)
   398  }
   399  
   400  func TestMkdir(t *testing.T) {
   401  	testSetup(t, false)
   402  
   403  	testDirInode := createTestDirectory(t, "Mkdir")
   404  	longButLegalFilename := strings.Repeat("x", FileNameMax)
   405  	nameTooLong := strings.Repeat("x", FileNameMax+1)
   406  
   407  	_, err := testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, nameTooLong, inode.PosixModePerm)
   408  	if nil != err {
   409  		if blunder.IsNot(err, blunder.NameTooLongError) {
   410  			t.Fatalf("Mkdir() returned error %v, expected %v(%d).", blunder.Errno(err), blunder.NameTooLongError, blunder.NameTooLongError.Value())
   411  		}
   412  	} else {
   413  		t.Fatal("Mkdir() unexpectedly succeeded on too-long filename!")
   414  	}
   415  
   416  	_, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, longButLegalFilename, inode.PosixModePerm)
   417  	if err != nil {
   418  		t.Fatalf("Mkdir() returned error: %v", err)
   419  	}
   420  
   421  	entriesExpected := []string{".", "..", longButLegalFilename}
   422  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), testDirInode, entriesExpected)
   423  
   424  	longButLegalFullPath := "/Mkdir/" + longButLegalFilename
   425  	ino, err := testVolumeStruct.LookupPath(inode.InodeRootUserID, inode.InodeGroupID(0), nil, longButLegalFullPath)
   426  	if err != nil {
   427  		t.Fatalf("LookupPath() returned error: %v", err)
   428  	}
   429  
   430  	_, err = testVolumeStruct.Getstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, inode.InodeNumber(ino))
   431  	if err != nil {
   432  		t.Fatalf("GetStat() returned error: %v", err)
   433  	}
   434  
   435  	// trying to make the directory a second time should fail with EEXIST
   436  	_, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   437  		testDirInode, longButLegalFilename, inode.PosixModePerm)
   438  	if err == nil {
   439  		t.Fatalf("Mkdir() of existing entry returned success")
   440  	}
   441  	if blunder.IsNot(err, blunder.FileExistsError) {
   442  		t.Fatalf("Mkdir() of existing entry should return FileExistsError, but got %v", err)
   443  	}
   444  
   445  	testTeardown(t)
   446  }
   447  
   448  func TestRmdir(t *testing.T) {
   449  	testSetup(t, false)
   450  	defer testTeardown(t)
   451  
   452  	testDirInode := createTestDirectory(t, "Rmdir")
   453  
   454  	_, err := testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   455  		testDirInode, "test1", inode.PosixModePerm)
   456  	if err != nil {
   457  		t.Fatalf("Mkdir(\"test1\") returned error: %v", err)
   458  	}
   459  
   460  	// the test directory can't be removed until its empty
   461  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   462  		inode.RootDirInodeNumber, "Rmdir")
   463  	if err == nil {
   464  		t.Fatalf("Rmdir() [#0] should have failed")
   465  	}
   466  	if blunder.IsNot(err, blunder.NotEmptyError) {
   467  		t.Fatalf("Rmdir() [#0] should have returned 'NotEmptyError', err: %v", err)
   468  	}
   469  
   470  	// empty the test directory
   471  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   472  		testDirInode, "test1")
   473  	if err != nil {
   474  		t.Fatalf("Rmdir() [#1] returned error: %v", err)
   475  	}
   476  
   477  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   478  		inode.RootDirInodeNumber, "Rmdir")
   479  	if err != nil {
   480  		t.Fatalf("Rmdir() [#2] returned error: %v", err)
   481  	}
   482  }
   483  
   484  // TODO: flesh this out with other boundary condition testing for Rename
   485  func TestBadRename(t *testing.T) {
   486  	testSetup(t, false)
   487  
   488  	testDirInode := createTestDirectory(t, "BadRename")
   489  	nameTooLong := strings.Repeat("x", FileNameMax+1)
   490  
   491  	validFile := "PerfectlyValidFile"
   492  	_, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, validFile, inode.PosixModePerm)
   493  	if nil != err {
   494  		t.Fatalf("Create() returned error: %v", err)
   495  	}
   496  
   497  	// Try to rename a valid file to a name that is too long
   498  	err = testVolumeStruct.Rename(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, validFile, testDirInode, nameTooLong)
   499  	if nil != err {
   500  		if blunder.IsNot(err, blunder.NameTooLongError) {
   501  			t.Fatalf("Link() returned error %v, expected %v(%d).", blunder.Errno(err), blunder.NameTooLongError, blunder.NameTooLongError.Value())
   502  		}
   503  	} else {
   504  		t.Fatal("Link() unexpectedly succeeded on too-long filename!")
   505  	}
   506  
   507  	entriesExpected := []string{".", "..", validFile}
   508  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), testDirInode, entriesExpected)
   509  
   510  	// Try to rename a nonexistent file with a name that is too long
   511  	err = testVolumeStruct.Rename(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, nameTooLong, testDirInode, "AlsoAGoodFilename")
   512  	if nil != err {
   513  		if blunder.IsNot(err, blunder.NameTooLongError) {
   514  			t.Fatalf("Link() returned error %v, expected %v(%d).", blunder.Errno(err), blunder.NameTooLongError, blunder.NameTooLongError.Value())
   515  		}
   516  	} else {
   517  		t.Fatal("Link() unexpectedly succeeded on too-long filename!")
   518  	}
   519  
   520  	entriesExpected = []string{".", "..", validFile}
   521  	expectDirectory(t, inode.InodeRootUserID, inode.InodeGroupID(0), testDirInode, entriesExpected)
   522  
   523  	testTeardown(t)
   524  }
   525  
   526  func TestBadChownChmod(t *testing.T) {
   527  	var (
   528  		err error
   529  	)
   530  
   531  	testSetup(t, false)
   532  
   533  	// Get root dir inode number
   534  	rootDirInodeNumber := inode.RootDirInodeNumber
   535  
   536  	// Create file to play with
   537  	basename := "TestFile"
   538  	createdFileInodeNumber, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename, inode.PosixModePerm)
   539  	if err != nil {
   540  		t.Fatalf("Create() %v returned error: %v", basename, err)
   541  	}
   542  
   543  	// Since we are playing some games with size of mode/userid/groupid, make sure that we
   544  	// correctly handle cases where the value is > uint32
   545  	var tooBigForUint32 uint64 = math.MaxUint32 + 7<<48
   546  
   547  	// Validate too-big Mode
   548  	stat := make(Stat)
   549  	stat[StatMode] = tooBigForUint32
   550  	err = testVolumeStruct.Setstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber, stat)
   551  	if blunder.IsNot(err, blunder.InvalidFileModeError) {
   552  		t.Fatalf("Setstat() %v returned error %v, expected %v(%d).", basename, blunder.Errno(err), blunder.InvalidFileModeError, blunder.InvalidFileModeError.Value())
   553  	}
   554  	delete(stat, StatMode)
   555  
   556  	// Validate too-big UserID
   557  	stat[StatUserID] = tooBigForUint32
   558  	err = testVolumeStruct.Setstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber, stat)
   559  	if blunder.Errno(err) != int(blunder.InvalidFileModeError) {
   560  		t.Fatalf("Setstat() %v returned error %v, expected %v(%d).", basename, blunder.Errno(err), blunder.InvalidFileModeError, blunder.InvalidFileModeError.Value())
   561  	}
   562  	delete(stat, StatUserID)
   563  
   564  	// Validate too-big GroupID
   565  	stat[StatGroupID] = tooBigForUint32
   566  	err = testVolumeStruct.Setstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, createdFileInodeNumber, stat)
   567  	if blunder.Errno(err) != int(blunder.InvalidFileModeError) {
   568  		t.Fatalf("Setstat() %v returned error %v, expected %v(%d).", basename, blunder.Errno(err), blunder.InvalidFileModeError, blunder.InvalidFileModeError.Value())
   569  	}
   570  	delete(stat, StatGroupID)
   571  
   572  	testTeardown(t)
   573  }
   574  
   575  func TestFlock(t *testing.T) {
   576  	var (
   577  		err error
   578  	)
   579  
   580  	testSetup(t, false)
   581  
   582  	rootDirInodeNumber := inode.RootDirInodeNumber
   583  
   584  	// Create file to play with
   585  	basename := "TestLockFile"
   586  	lockFileInodeNumber, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename, inode.PosixModePerm)
   587  	if err != nil {
   588  		t.Fatalf("Create() %v returned error: %v", basename, err)
   589  	}
   590  
   591  	// Resize the file to a 1M so that we can apply byte range locks:
   592  	err = testVolumeStruct.Resize(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, 1024*1024)
   593  	if err != nil {
   594  		t.Fatalf("Resize() %v returned error: %v", basename, err)
   595  	}
   596  
   597  	// Write lock test:
   598  	var lock FlockStruct
   599  	lock.Type = syscall.F_WRLCK
   600  	lock.Start = 0
   601  	lock.Len = 0
   602  	lock.Pid = 1
   603  
   604  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock)
   605  	if err != nil {
   606  		t.Fatalf("Write lock on file failed: %v", err)
   607  	}
   608  
   609  	lock.Type = syscall.F_UNLCK
   610  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock)
   611  	if err != nil {
   612  		t.Fatalf("Unlock on file failed: %v", blunder.Errno(err))
   613  	}
   614  
   615  	lock.Type = syscall.F_WRLCK
   616  	lock.Pid = 1
   617  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock)
   618  	if err != nil {
   619  		t.Fatalf("Write lock on file failed: %v", err)
   620  	}
   621  
   622  	// Try another write lock from a different pid, it should fail:
   623  	var lock1 FlockStruct
   624  	lock1 = lock
   625  	lock1.Pid = 2
   626  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock1)
   627  	if blunder.Errno(err) != int(blunder.TryAgainError) {
   628  		t.Fatalf("Write lock on a locked file should fail with EAGAIN instead got : %v", err)
   629  	}
   630  
   631  	// Lock again from pid1, it should succeed:
   632  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock)
   633  	if err != nil {
   634  		t.Fatalf("Relocking from same PID on file failed: %v", err)
   635  	}
   636  
   637  	lock.Type = syscall.F_UNLCK
   638  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock)
   639  	if err != nil {
   640  		t.Fatalf("Unlock failed : %v", err)
   641  	}
   642  
   643  	// Read lock test:
   644  	lock.Type = syscall.F_RDLCK
   645  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock)
   646  	if err != nil {
   647  		t.Fatalf("Read lock pid - 1 failed: %v", err)
   648  	}
   649  
   650  	lock1.Type = syscall.F_RDLCK
   651  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock1)
   652  	if err != nil {
   653  		t.Fatalf("Read lock pid - 2 failed: %v", err)
   654  	}
   655  
   656  	// Try write lock it should fail:
   657  	lock3 := lock
   658  	lock3.Type = syscall.F_WRLCK
   659  	lock3.Pid = 3
   660  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock3)
   661  	if blunder.Errno(err) != int(blunder.TryAgainError) {
   662  		t.Fatalf("Write lock should have failed with EAGAIN instead got - %v", err)
   663  	}
   664  
   665  	lock11 := lock1
   666  	lock11.Type = syscall.F_UNLCK
   667  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock11)
   668  	if err != nil {
   669  		t.Fatalf("Unlock of (readlock) - 2 failed: %v", err)
   670  	}
   671  
   672  	lock01 := lock
   673  	lock01.Type = syscall.F_UNLCK
   674  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock01)
   675  	if err != nil {
   676  		t.Fatalf("Unlock of (readlock) - 1 failed: %v", err)
   677  	}
   678  
   679  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock3)
   680  	if err != nil {
   681  		t.Fatalf("Write lock should have succeeded instead got - %v", err.Error())
   682  	}
   683  
   684  	lock3.Type = syscall.F_UNLCK
   685  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock3)
   686  	if err != nil {
   687  		t.Fatalf("Unlock of (write after read) failed: %v", err)
   688  	}
   689  
   690  	// Multiple Range lock testing:
   691  
   692  	var lock10 FlockStruct
   693  	lock10.Pid = 1
   694  	lock10.Start = 100
   695  	lock10.Len = 100
   696  	lock10.Type = syscall.F_WRLCK
   697  	lock10.Whence = 0
   698  
   699  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock10)
   700  	if err != nil {
   701  		t.Fatalf("Range test failed to lock range (100 - 200), err %v", err)
   702  	}
   703  
   704  	lock201 := lock10
   705  	lock201.Pid = 2
   706  	lock201.Type = syscall.F_RDLCK
   707  	lock201.Start = 10
   708  	lock201.Len = 10
   709  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock201)
   710  	if err != nil {
   711  		t.Fatalf("Range test failed to read lock range (10 - 20) by pid2, err %v", err)
   712  	}
   713  
   714  	lock202 := lock201
   715  	lock202.Start = 90
   716  	lock202.Len = 10
   717  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock202)
   718  	if err != nil {
   719  		t.Fatalf("Range test failed to read lock range (90 - 100) by pid2, err %v", err)
   720  	}
   721  
   722  	lock203 := lock202
   723  	lock203.Start = 80
   724  	lock203.Len = 40
   725  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock203)
   726  	if err == nil {
   727  		t.Fatalf("Range test read lock of range (80 - 120) should have failed for pid2  err %v", err)
   728  	}
   729  
   730  	lock204 := lock203
   731  	lock204.Start = 180
   732  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock204)
   733  	if err == nil {
   734  		t.Fatalf("Range test read lock of range (180 - 220) should have failed for pid2  err %v", err)
   735  	}
   736  
   737  	lock205 := lock204
   738  	lock205.Start = 200
   739  	lock205.Len = 10
   740  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock205)
   741  	if err != nil {
   742  		t.Fatalf("Range test read lock of range (200 - 210) should have succeeded for pid2  err %v", err)
   743  	}
   744  
   745  	lock206 := lock205
   746  	lock206.Start = 240
   747  	lock206.Len = 10
   748  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock206)
   749  	if err != nil {
   750  		t.Fatalf("Range test read lock of range (240 - 250) should have succeeded for pid2  err %v", err)
   751  	}
   752  
   753  	lock101 := lock10
   754  	lock101.Type = syscall.F_RDLCK
   755  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock101)
   756  	if err != nil {
   757  		t.Fatalf("Range test converting write lock to read lock of pid1 range 100 - 200 failed, err %v", err)
   758  	}
   759  
   760  	// Now, lock 203 and 204 should succceed.
   761  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock203)
   762  	if err != nil {
   763  		t.Fatalf("Range test read lock of range (80 - 120) should have succeeded for pid2  err %v", err)
   764  	}
   765  
   766  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock204)
   767  	if err != nil {
   768  		t.Fatalf("Range test read lock of range (180 - 220) should have succeeded for pid2  err %v", err)
   769  	}
   770  
   771  	lock30 := lock10
   772  	lock30.Pid = 3
   773  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock30)
   774  	if err == nil {
   775  		t.Fatalf("Range test write lock of range 100 - 200 should have failed for pid3 err %v", err)
   776  	}
   777  
   778  	lock102 := lock10
   779  	lock102.Type = syscall.F_UNLCK
   780  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock102)
   781  	if err != nil {
   782  		t.Fatalf("Range test unlock of range 100 - 200 for pid1 should have succeeded, err - %v", err)
   783  	}
   784  
   785  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock30)
   786  	if err == nil {
   787  		t.Fatalf("Range test write lock of range 100 - 200 should have failed for pid3 err %v", err)
   788  	}
   789  
   790  	lock207 := lock10
   791  	lock207.Type = syscall.F_UNLCK
   792  	lock207.Pid = 2
   793  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock207)
   794  	if err != nil {
   795  		t.Fatalf("Range test unlock of range 100 - 200 for pid2 should have succeeded, err - %v", err)
   796  	}
   797  
   798  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock30)
   799  	if err != nil {
   800  		t.Fatalf("Range test write lock of range 100 - 200 should have succeeded for pid3 err %v", err)
   801  	}
   802  
   803  	lock301 := lock30
   804  	lock301.Type = syscall.F_UNLCK
   805  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock301)
   806  	if err != nil {
   807  		t.Fatalf("Range test unlock of range 100 - 200 should have succeeded for pid3 err %v", err)
   808  	}
   809  
   810  	lock2u1 := lock201
   811  	lock2u1.Type = syscall.F_UNLCK
   812  	lock2u1.Start = 0
   813  	lock2u1.Len = 150
   814  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock2u1)
   815  	if err != nil {
   816  		t.Fatalf("Range test unlock of range 0 - 150 should have succeeded for pid2 err %v", err)
   817  	}
   818  
   819  	lock2u2 := lock2u1
   820  	lock2u2.Start = 150
   821  	lock2u2.Len = 150
   822  	_, err = testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_SETLK, &lock2u2)
   823  	if err != nil {
   824  		t.Fatalf("Range test unlock of range 150 - 300 should have succeeded for pid2 err %v", err)
   825  	}
   826  
   827  	lock30.Start = 0
   828  	lock30.Len = 250
   829  	lock30.Type = syscall.F_WRLCK
   830  	lockHeld, err := testVolumeStruct.Flock(inode.InodeRootUserID, inode.InodeGroupID(0), nil, lockFileInodeNumber, syscall.F_GETLK, &lock30)
   831  	if err != nil {
   832  		t.Fatalf("Range test GET write lock of range 0 - 250 should have succeeded for pid3 err %v lockHeld %+v", err, lockHeld)
   833  	}
   834  
   835  	if lock30.Type != syscall.F_UNLCK {
   836  		t.Fatalf("GetLock should have succeeded for range 0 - 250 for pid 3, err %v", err)
   837  	}
   838  
   839  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, rootDirInodeNumber, basename)
   840  	if err != nil {
   841  		t.Fatalf("Unlink() %v returned error: %v", basename, err)
   842  	}
   843  
   844  	testTeardown(t)
   845  }
   846  
   847  // Verify that the file system API works correctly with stale inode numbers,
   848  // as can happen if an NFS client cache gets out of sync because another NFS
   849  // client as removed a file or directory.
   850  func TestStaleInodes(t *testing.T) {
   851  	var (
   852  		rootDirInodeNumber   inode.InodeNumber = inode.RootDirInodeNumber
   853  		testDirname          string            = "stale_inodes_test"
   854  		testFileName         string            = "valid_file"
   855  		staleDirName         string            = "dir"
   856  		staleFileName        string            = "file"
   857  		testDirInodeNumber   inode.InodeNumber
   858  		testFileInodeNumber  inode.InodeNumber
   859  		staleDirInodeNumber  inode.InodeNumber
   860  		staleFileInodeNumber inode.InodeNumber
   861  		err                  error
   862  	)
   863  
   864  	testSetup(t, false)
   865  
   866  	// scratchpad directory for testing
   867  	testDirInodeNumber, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   868  		rootDirInodeNumber, testDirname, 0755)
   869  	if nil != err {
   870  		t.Fatalf("Mkdir() '%s' returned error: %v", testDirname, err)
   871  	}
   872  
   873  	// create a valid test file
   874  	testFileInodeNumber, err = testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   875  		testDirInodeNumber, testFileName, 0644)
   876  	if nil != err {
   877  		t.Fatalf("Create() '%s' returned error: %v", testFileName, err)
   878  	}
   879  
   880  	// get an inode number that used to belong to a dirctory
   881  	_, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   882  		testDirInodeNumber, staleDirName, 0755)
   883  	if nil != err {
   884  		t.Fatalf("Mkdir() '%s' returned error: %v", testDirname, err)
   885  	}
   886  	staleDirInodeNumber, err = testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   887  		testDirInodeNumber, staleDirName)
   888  	if err != nil {
   889  		t.Fatalf("Unexpectedly failed to look up of '%s': %v", testDirname, err)
   890  	}
   891  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   892  		testDirInodeNumber, staleDirName)
   893  	if nil != err {
   894  		t.Fatalf("Rmdir() of '%s' returned error: %v", staleDirName, err)
   895  	}
   896  
   897  	// get an inode number that used to belong to a file (it shouldn't
   898  	// really matter which type of file the inode used to be, but it doesn't
   899  	// hurt to have two to play with)
   900  	_, err = testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   901  		testDirInodeNumber, staleFileName, 0644)
   902  	if nil != err {
   903  		t.Fatalf("Mkdir() '%s' returned error: %v", testDirname, err)
   904  	}
   905  	staleFileInodeNumber, err = testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   906  		testDirInodeNumber, staleFileName)
   907  	if err != nil {
   908  		t.Fatalf("Unexpectedly failed to look up of '%s': %v", testDirname, err)
   909  	}
   910  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   911  		testDirInodeNumber, staleFileName)
   912  	if nil != err {
   913  		t.Fatalf("Unlink() of '%s' returned error: %v", staleFileName, err)
   914  	}
   915  
   916  	// Stat
   917  	_, err = testVolumeStruct.Getstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, staleFileInodeNumber)
   918  	if nil == err {
   919  		t.Fatalf("Getstat() should not have returned success")
   920  	}
   921  	if blunder.IsNot(err, blunder.NotFoundError) {
   922  		t.Fatalf("Getstat() should have failed with NotFoundError, instead got: %v", err)
   923  	}
   924  
   925  	// Mkdir
   926  	_, err = testVolumeStruct.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   927  		staleDirInodeNumber, "TestSubDirectory", 0755)
   928  	if nil == err {
   929  		t.Fatalf("Mkdir() should not have returned success")
   930  	}
   931  	if blunder.IsNot(err, blunder.NotFoundError) {
   932  		t.Fatalf("Mkdir() should have failed with NotFoundError, instead got: %v", err)
   933  	}
   934  
   935  	// Rmdir
   936  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   937  		staleDirInodeNumber, "fubar")
   938  	if nil == err {
   939  		t.Fatalf("Rmdir() should not have returned success")
   940  	}
   941  	if blunder.IsNot(err, blunder.NotFoundError) {
   942  		t.Fatalf("Rmdir() should have failed with NotFoundError, instead got: %v", err)
   943  	}
   944  
   945  	// Create
   946  	_, err = testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   947  		staleDirInodeNumber, "fubar", 0644)
   948  	if nil == err {
   949  		t.Fatalf("Create() should not have returned success")
   950  	}
   951  	if blunder.IsNot(err, blunder.NotFoundError) {
   952  		t.Fatalf("Create() should have failed with NotFoundError, instead got: %v", err)
   953  	}
   954  
   955  	// Lookup
   956  	_, err = testVolumeStruct.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   957  		staleDirInodeNumber, "fubar")
   958  	if nil == err {
   959  		t.Fatalf("Lookup() should not have returned success")
   960  	}
   961  	if blunder.IsNot(err, blunder.NotFoundError) {
   962  		t.Fatalf("Lookup() should have failed with NotFoundError, instead got: %v", err)
   963  	}
   964  
   965  	// Write
   966  	bufToWrite := []byte{0x41, 0x42, 0x43}
   967  	_, err = testVolumeStruct.Write(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   968  		staleFileInodeNumber, 0, bufToWrite, nil)
   969  	if nil == err {
   970  		t.Fatalf("Write() should not have returned success")
   971  	}
   972  	if blunder.IsNot(err, blunder.NotFoundError) {
   973  		t.Fatalf("Write() should have failed with NotFoundError, instead got: %v", err)
   974  	}
   975  
   976  	// Read
   977  	_, err = testVolumeStruct.Read(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   978  		staleFileInodeNumber, 0, uint64(len(bufToWrite)), nil)
   979  	if nil == err {
   980  		t.Fatalf("Read() should not have returned success")
   981  	}
   982  	if blunder.IsNot(err, blunder.NotFoundError) {
   983  		t.Fatalf("Read() should have failed with NotFoundError, instead got: %v", err)
   984  	}
   985  
   986  	// Trunc
   987  	err = testVolumeStruct.Resize(inode.InodeRootUserID, inode.InodeGroupID(0), nil, staleFileInodeNumber, 77)
   988  	if nil == err {
   989  		t.Fatalf("Resize() should not have returned success")
   990  	}
   991  	if blunder.IsNot(err, blunder.NotFoundError) {
   992  		t.Fatalf("Resize() should have failed with NotFoundError, instead got: %v", err)
   993  	}
   994  
   995  	// Symlink
   996  	_, err = testVolumeStruct.Symlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
   997  		staleDirInodeNumber, "TestSymlink", "fubar")
   998  	if nil == err {
   999  		t.Fatalf("Symlink() should not have returned success")
  1000  	}
  1001  	if blunder.IsNot(err, blunder.NotFoundError) {
  1002  		t.Fatalf("Symlink() should have failed with NotFoundError, instead got: %v", err)
  1003  	}
  1004  
  1005  	// Readsymlink (that we didn't create)
  1006  	_, err = testVolumeStruct.Readsymlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, staleFileInodeNumber)
  1007  	if nil == err {
  1008  		t.Fatalf("Readsymlink() should not have returned success")
  1009  	}
  1010  	if blunder.IsNot(err, blunder.NotFoundError) {
  1011  		t.Fatalf("Readsymlink() should have failed with NotFoundError, instead got: %v", err)
  1012  	}
  1013  
  1014  	// Readdir
  1015  	_, _, _, err = testVolumeStruct.Readdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, staleDirInodeNumber, 0, "")
  1016  	if nil == err {
  1017  		t.Fatalf("Readdir() should not have returned success")
  1018  	}
  1019  	if blunder.IsNot(err, blunder.NotFoundError) {
  1020  		t.Fatalf("Readdir() should have failed with NotFoundError, instead got: %v", err)
  1021  	}
  1022  
  1023  	// Link -- two cases, one with stale directory and one with stale file
  1024  	err = testVolumeStruct.Link(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1025  		staleDirInodeNumber, "fubar", testFileInodeNumber)
  1026  	if nil == err {
  1027  		t.Fatalf("Link(1) should not have returned success")
  1028  	}
  1029  	if blunder.IsNot(err, blunder.NotFoundError) {
  1030  		t.Fatalf("Link(1) should have failed with NotFoundError, instead got: %v", err)
  1031  	}
  1032  
  1033  	err = testVolumeStruct.Link(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1034  		testDirInodeNumber, testFileName, staleFileInodeNumber)
  1035  	if nil == err {
  1036  		t.Fatalf("Link(2) should not have returned success")
  1037  	}
  1038  	if blunder.IsNot(err, blunder.NotFoundError) {
  1039  		t.Fatalf("Link(2) should have failed with NotFoundError, instead got: %v", err)
  1040  	}
  1041  
  1042  	// Unlink
  1043  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1044  		staleDirInodeNumber, "fubar")
  1045  	if nil == err {
  1046  		t.Fatalf("Unlink() should not have returned success")
  1047  	}
  1048  	if blunder.IsNot(err, blunder.NotFoundError) {
  1049  		t.Fatalf("Unlink() should have failed with NotFoundError, instead got: %v", err)
  1050  	}
  1051  
  1052  	// Rename -- two cases, one with stale src directory and one with stale dest
  1053  	err = testVolumeStruct.Rename(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1054  		testDirInodeNumber, "fubar", staleDirInodeNumber, "barfu")
  1055  	if nil == err {
  1056  		t.Fatalf("Rename(1) should not have returned success")
  1057  	}
  1058  	if blunder.IsNot(err, blunder.NotFoundError) {
  1059  		t.Fatalf("Rename(1) should have failed with NotFoundError, instead got: %v", err)
  1060  	}
  1061  
  1062  	err = testVolumeStruct.Rename(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1063  		staleDirInodeNumber, "fubar", testDirInodeNumber, "barfu")
  1064  	if nil == err {
  1065  		t.Fatalf("Rename(2) should not have returned success")
  1066  	}
  1067  	if blunder.IsNot(err, blunder.NotFoundError) {
  1068  		t.Fatalf("Rename(2) should have failed with NotFoundError, instead got: %v", err)
  1069  	}
  1070  
  1071  	// cleanup test file and directory
  1072  	err = testVolumeStruct.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1073  		testDirInodeNumber, testFileName)
  1074  	if nil != err {
  1075  		t.Fatalf("Unlink() of '%s' returned error: %v", testFileName, err)
  1076  	}
  1077  	err = testVolumeStruct.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil,
  1078  		rootDirInodeNumber, testDirname)
  1079  	if nil != err {
  1080  		t.Fatalf("Rmdir() of '%s' returned error: %v", testDirname, err)
  1081  	}
  1082  
  1083  	testTeardown(t)
  1084  }
  1085  
  1086  func TestMiddlewareGetContainer(t *testing.T) {
  1087  	var ents []ContainerEntry
  1088  	testSetup(t, false)
  1089  
  1090  	testDirInode := createTestDirectory(t, "container")
  1091  
  1092  	marker1 := "a_marker"
  1093  	_, err := testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, marker1, inode.PosixModePerm)
  1094  	if err != nil {
  1095  		t.Fatalf("Create() returned error: %v", err)
  1096  	}
  1097  	marker2 := "b_marker"
  1098  	_, err = testVolumeStruct.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, testDirInode, marker2, inode.PosixModePerm)
  1099  	if err != nil {
  1100  		t.Fatalf("Create() returned error: %v", err)
  1101  	}
  1102  
  1103  	ents, err = testVolumeStruct.MiddlewareGetContainer("container", 10, "a", "", "", "")
  1104  	if nil != err {
  1105  		t.Fatalf("got some error: %v", err)
  1106  	}
  1107  	if 2 != len(ents) {
  1108  		t.Fatalf("marker a gave wrong number of entries: %v", ents)
  1109  	}
  1110  
  1111  	ents, err = testVolumeStruct.MiddlewareGetContainer("container", 10, "b", "", "", "")
  1112  	if nil != err {
  1113  		t.Fatalf("got some error: %v", err)
  1114  	}
  1115  	if 1 != len(ents) {
  1116  		t.Fatalf("marker b gave wrong number of entries: %v", ents)
  1117  	}
  1118  
  1119  	ents, err = testVolumeStruct.MiddlewareGetContainer("container", 10, "a_marker", "", "", "")
  1120  	if nil != err {
  1121  		t.Fatalf("got some error: %v", err)
  1122  	}
  1123  	if 1 != len(ents) {
  1124  		t.Fatalf("marker a_marker gave wrong number of entries: %v", ents)
  1125  	}
  1126  
  1127  	testTeardown(t)
  1128  }
  1129  
  1130  // Verify that the metadata for the object at containerObjPath, as returned by
  1131  // MiddlewareHeadResponse(), matches the metadata in opMetdata.  opMetadata is
  1132  // presumably returned by some middleware operation named opName, but it could
  1133  // come from somewhere else.
  1134  //
  1135  // Throw testing errors if anything doesn't match.  stepName and opName are used
  1136  // in the error strings.
  1137  func verifyMetadata(t *testing.T, containerObjPath string,
  1138  	stepName string, opName string, opMeta *HeadResponse) {
  1139  
  1140  	// how to print time stamps (the date is dropped)
  1141  	timeFormat := "15:04:05.000000000"
  1142  
  1143  	var (
  1144  		headMeta HeadResponse
  1145  		err      error
  1146  	)
  1147  
  1148  	// fetch the current metadata (implicit and explicit)
  1149  	headMeta, err = testVolumeStruct.MiddlewareHeadResponse(containerObjPath)
  1150  	if err != nil {
  1151  		t.Errorf("MiddlewareHeadResponse() for '%s' op %s '%s' failed: %v",
  1152  			containerObjPath, opName, stepName, err)
  1153  		return
  1154  	}
  1155  
  1156  	// validate the "explicit" metadata
  1157  	if !bytes.Equal(opMeta.Metadata, headMeta.Metadata) {
  1158  		t.Errorf("object '%s' op %s '%s' op metadata '%s' does not match stat metadata '%s'",
  1159  			containerObjPath, opName, stepName, opMeta.Metadata, headMeta.Metadata)
  1160  	}
  1161  
  1162  	// check the rest of the attributes and quit after the first mismatch
  1163  	if opMeta.IsDir != headMeta.IsDir {
  1164  		t.Errorf("object '%s' op %s '%s' op IsDir '%v' does not match stat IsDir '%v'",
  1165  			containerObjPath, opName, stepName, opMeta.IsDir, headMeta.IsDir)
  1166  		return
  1167  	}
  1168  	if opMeta.FileSize != headMeta.FileSize {
  1169  		t.Errorf("object '%s' op %s '%s' op FileSize '%d' does not match stat FileSize '%d'",
  1170  			containerObjPath, opName, stepName, opMeta.FileSize, headMeta.FileSize)
  1171  		return
  1172  	}
  1173  	if opMeta.NumWrites != headMeta.NumWrites {
  1174  		t.Errorf("object '%s' op %s '%s' op NumWrites '%d' does not match stat NumWrites '%d'",
  1175  			containerObjPath, opName, stepName, opMeta.NumWrites, headMeta.NumWrites)
  1176  		return
  1177  	}
  1178  	if opMeta.InodeNumber != headMeta.InodeNumber {
  1179  		t.Errorf("object '%s' op %s '%s' op InodeNumber '%d' does not match stat InodeNumber '%d'",
  1180  			containerObjPath, opName, stepName, opMeta.InodeNumber, headMeta.InodeNumber)
  1181  		return
  1182  	}
  1183  
  1184  	if opMeta.AttrChangeTime != headMeta.AttrChangeTime {
  1185  		t.Errorf("object '%s' op %s '%s' op AttrChangeTime '%s' does not match stat AttrChangeTime '%s'",
  1186  			containerObjPath, opName, stepName,
  1187  			time.Unix(0, int64(opMeta.AttrChangeTime)).Format(timeFormat),
  1188  			time.Unix(0, int64(headMeta.AttrChangeTime)).Format(timeFormat))
  1189  		return
  1190  	}
  1191  	if opMeta.ModificationTime != headMeta.ModificationTime {
  1192  		t.Errorf("object '%s' op %s '%s' op ModificationTime '%s' does not match stat ModificationTime '%s'",
  1193  			containerObjPath, opName, stepName,
  1194  			time.Unix(0, int64(opMeta.ModificationTime)).Format(timeFormat),
  1195  			time.Unix(0, int64(headMeta.ModificationTime)).Format(timeFormat))
  1196  		return
  1197  	}
  1198  	return
  1199  }
  1200  
  1201  // Test MiddlewareMkdir() and MiddlewarePutComplete().
  1202  //
  1203  // A Swift PUT operation can create a file or a directory, depending on the
  1204  // arguments.  Further, in Swift a PUT on an object deletes the object and
  1205  // replaces it with a new object with the new metadata.
  1206  //
  1207  // We interpret the "replacement rule" to mean that a PUT can delete a file and
  1208  // replace it with a directory or vice versa, but it cannot cause the delete of
  1209  // a directory that is not empty.  (Its not clear how symlinks figure into this,
  1210  // but they should probably follow the same rule.)
  1211  //
  1212  // The code in pfs_middleware determines whether a PUT request intends to
  1213  // create a file or a directory.
  1214  //
  1215  // Test other behaviors, like automatically creating a path to the specified
  1216  // object.  This does not test concatenating 1 or more objects to make up the
  1217  // contents of the file.
  1218  func TestMiddlewarePuts(t *testing.T) {
  1219  
  1220  	testSetup(t, false)
  1221  	defer testTeardown(t)
  1222  
  1223  	initialMetadata := []byte("initial metadata")
  1224  	updatedMetadata := []byte("updated metadata")
  1225  	updatedMetadata2 := []byte("really new metadata")
  1226  	containerName := "MiddlewarePuts"
  1227  	objectPath := "dir0/dir1/dir2/file0"
  1228  	containerObjectPath := containerName + "/" + objectPath
  1229  
  1230  	var (
  1231  		opMeta HeadResponse
  1232  		err    error
  1233  	)
  1234  
  1235  	// make a container for testing and verify the explicit metadata
  1236  	err = testVolumeStruct.MiddlewarePutContainer(containerName, []byte(""), initialMetadata)
  1237  	if err != nil {
  1238  		t.Fatalf("MiddlewarePutContainer() failed: %v", err)
  1239  	}
  1240  	opMeta, err = testVolumeStruct.MiddlewareHeadResponse(containerName)
  1241  	if err != nil {
  1242  		t.Fatalf("MiddlewareHeadResponse() for container '%s' failed: %v", containerName, err)
  1243  	}
  1244  	if !bytes.Equal(opMeta.Metadata, initialMetadata) {
  1245  		t.Errorf("MiddlewareHeadResponse() for container '%s' metadata '%s' does not match '%s'",
  1246  			containerName, opMeta.Metadata, initialMetadata)
  1247  	}
  1248  
  1249  	// create a file object and then verify the explicit metadata and
  1250  	// returned attributes are correct
  1251  	opMeta.ModificationTime, opMeta.AttrChangeTime, opMeta.InodeNumber, opMeta.NumWrites, err =
  1252  		testVolumeStruct.MiddlewarePutComplete(containerName, objectPath, nil, nil, initialMetadata)
  1253  	if err != nil {
  1254  		t.Errorf("MiddlewarePutComplete() for container '%s' object '%s' failed: %v",
  1255  			containerName, objectPath, err)
  1256  	} else {
  1257  		opMeta.IsDir = false
  1258  		opMeta.FileSize = 0
  1259  		opMeta.Metadata = initialMetadata
  1260  
  1261  		verifyMetadata(t, containerObjectPath, "step 0", "MiddlewarePutComplete", &opMeta)
  1262  	}
  1263  
  1264  	// replace the file object with a directory object then verify the
  1265  	// explicit metadata and returned attributes
  1266  	opMeta.ModificationTime, opMeta.AttrChangeTime, opMeta.InodeNumber, opMeta.NumWrites, err =
  1267  		testVolumeStruct.MiddlewareMkdir(containerName, objectPath, updatedMetadata)
  1268  	if err != nil {
  1269  		t.Errorf("MiddlewareMkdir() for container '%s' object '%s' failed: %v",
  1270  			containerName, objectPath, err)
  1271  	} else {
  1272  		opMeta.IsDir = true
  1273  		opMeta.FileSize = 0
  1274  		opMeta.Metadata = updatedMetadata
  1275  
  1276  		verifyMetadata(t, containerObjectPath, "step 1", "MiddlewareMkdir", &opMeta)
  1277  	}
  1278  
  1279  	// verify the metadata (explicit and implicit) returned by
  1280  	// MiddlewareGetObject() matches MiddlewareHeadResponse() for a
  1281  	// directory
  1282  	opMeta, err = testVolumeStruct.MiddlewareGetObject(containerObjectPath,
  1283  		[]ReadRangeIn{}, &[]inode.ReadPlanStep{})
  1284  	if err != nil {
  1285  		t.Errorf("MiddlewareGetObject() for object '%s' failed: %v", containerObjectPath, err)
  1286  	} else {
  1287  		verifyMetadata(t, containerObjectPath, "step 1.5", "MiddlewareGetObject", &opMeta)
  1288  	}
  1289  
  1290  	// change the directory object back to a file object and verify the
  1291  	// explicit metadata and returned attributes
  1292  	opMeta.ModificationTime, opMeta.AttrChangeTime, opMeta.InodeNumber, opMeta.NumWrites, err =
  1293  		testVolumeStruct.MiddlewarePutComplete(containerName, objectPath, nil, nil, updatedMetadata2)
  1294  	if err != nil {
  1295  		t.Errorf("MiddlewarePutComplete() for container '%s' object '%s' failed: %v",
  1296  			containerName, objectPath, err)
  1297  	} else {
  1298  		opMeta.IsDir = false
  1299  		opMeta.FileSize = 0
  1300  		opMeta.Metadata = updatedMetadata2
  1301  
  1302  		verifyMetadata(t, containerObjectPath, "step 2", "MiddlewarePutComplete", &opMeta)
  1303  	}
  1304  
  1305  	// verify the metadata (explicit and implicit) returned by
  1306  	// MiddlewareGetObject() matches MiddlewareHeadResponse()
  1307  	opMeta, err = testVolumeStruct.MiddlewareGetObject(containerObjectPath,
  1308  		[]ReadRangeIn{}, &[]inode.ReadPlanStep{})
  1309  	if err != nil {
  1310  		t.Errorf("MiddlewareGetObject() for object '%s' failed: %v", containerObjectPath, err)
  1311  	} else {
  1312  		verifyMetadata(t, containerObjectPath, "step 3", "MiddlewareGetObject", &opMeta)
  1313  	}
  1314  
  1315  	// save the file's metadata for later validation
  1316  	file0Meta := opMeta
  1317  
  1318  	// a "file" PUT to a directory object that is not empty should fail
  1319  	// (because we cannot convert a non-empty directory to a file object)
  1320  	pathComponents := strings.Split(objectPath, "/")
  1321  	dirPath := strings.Join(pathComponents[:len(pathComponents)-1], "/")
  1322  	containerDirPath := containerName + "/" + dirPath
  1323  
  1324  	opMeta.ModificationTime, opMeta.AttrChangeTime, opMeta.InodeNumber, opMeta.NumWrites, err =
  1325  		testVolumeStruct.MiddlewarePutComplete(containerName, dirPath, nil, nil, initialMetadata)
  1326  	if err == nil {
  1327  		t.Errorf("MiddlewarePutComplete() for container '%s' non-empty object '%s' should have failed",
  1328  			containerName, objectPath)
  1329  	} else if blunder.IsNot(err, blunder.NotEmptyError) {
  1330  		t.Errorf("MiddlewarePutComplete() for container '%s' non-empty object '%s' should have failed "+
  1331  			"with error '%s' but failed with error '%s' (%s)",
  1332  			containerName, objectPath,
  1333  			blunder.FsError(int(unix.ENOTEMPTY)),
  1334  			blunder.FsError(blunder.Errno(err)),
  1335  			blunder.ErrorString(err))
  1336  	}
  1337  
  1338  	// a "directory PUT" to a directory object that is not empty should
  1339  	// succeed and update the explicit metadata (but should not delete
  1340  	// existing directory entries)
  1341  	opMeta.ModificationTime, opMeta.AttrChangeTime, opMeta.InodeNumber, opMeta.NumWrites, err =
  1342  		testVolumeStruct.MiddlewareMkdir(containerName, dirPath, updatedMetadata)
  1343  	if err != nil {
  1344  		t.Errorf("MiddlewareMkdir() for object '%s' failed: %v", containerDirPath, err)
  1345  	} else {
  1346  		opMeta.IsDir = true
  1347  		opMeta.FileSize = 0
  1348  		opMeta.Metadata = updatedMetadata
  1349  
  1350  		verifyMetadata(t, containerDirPath, "step 4", "MiddlewareMkdir", &opMeta)
  1351  	}
  1352  
  1353  	// verify that the file (child of the directory) is unchanged
  1354  	verifyMetadata(t, containerObjectPath, "step 5", "verify", &file0Meta)
  1355  }