github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/inode/file_extent_test.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package inode
     5  
     6  import (
     7  	"bytes"
     8  	cryptoRand "crypto/rand"
     9  	"fmt"
    10  	"math/big"
    11  	mathRand "math/rand"
    12  	"testing"
    13  
    14  	"github.com/swiftstack/sortedmap"
    15  )
    16  
    17  const (
    18  	sizeOfTestFile      uint64 = 0x01000000 // 16 MiB
    19  	maxExtentLength     uint64 = 0x00001000 //  4 KiB
    20  	numExtentOverwrites uint64 = 0x00001000 //  4 Ki
    21  	numZeroLengthReads  uint64 = 0x00001000 //  4 Ki
    22  
    23  	pseudoRandom     = false
    24  	pseudoRandomSeed = int64(0)
    25  )
    26  
    27  var (
    28  	randSource                *mathRand.Rand // A source for pseudo-random numbers (if selected)
    29  	inMemoryFileInodeContents []byte         // A value of 0x00 means the byte has never been written
    30  	byteToWrite               = byte(0x01)   // Incremented for each write, wrapping from 0xFF to 0x01 per above
    31  	curExtentOverwrites       = uint64(0)    // During write phase, loop until == numExtentOverwrites
    32  	curFileSize               = uint64(0)    // Tracks the highest offset actually written
    33  )
    34  
    35  func testChooseUint64(t *testing.T, mustBeLessThan uint64) (u64 uint64) {
    36  	if pseudoRandom {
    37  		if nil == randSource {
    38  			randSource = mathRand.New(mathRand.NewSource(pseudoRandomSeed))
    39  		}
    40  
    41  		u64 = uint64(randSource.Int63n(int64(mustBeLessThan)))
    42  	} else {
    43  		mustBeLessThanBigIntPtr := big.NewInt(int64(mustBeLessThan))
    44  		u64BigIntPtr, err := cryptoRand.Int(cryptoRand.Reader, mustBeLessThanBigIntPtr)
    45  		if nil != err {
    46  			t.Fatalf("rand.Int(rand.Reader, mustBeLessThanBigIntPtr) returned error == \"%v\"", err)
    47  		}
    48  
    49  		u64 = u64BigIntPtr.Uint64()
    50  	}
    51  
    52  	return
    53  }
    54  
    55  func testChooseExtentStart(t *testing.T) (offset uint64) {
    56  	offset = testChooseUint64(t, sizeOfTestFile)
    57  
    58  	return
    59  }
    60  
    61  func testChooseExtentSize(t *testing.T) (length uint64) {
    62  	length = testChooseUint64(t, maxExtentLength) + 1
    63  
    64  	return
    65  }
    66  
    67  func testChooseExtent(t *testing.T) (offset uint64, length uint64) {
    68  	offset = testChooseExtentStart(t)
    69  	length = testChooseExtentSize(t)
    70  
    71  	if (offset + length) > sizeOfTestFile {
    72  		length = sizeOfTestFile - offset
    73  	}
    74  
    75  	return
    76  }
    77  
    78  func testPopulateFileInode(t *testing.T, testVolumeHandle VolumeHandle, fileInodeNumber InodeNumber) {
    79  	for curExtentOverwrites < numExtentOverwrites {
    80  		offset, length := testChooseExtent(t)
    81  
    82  		overwriteOccurred := false
    83  
    84  		for i := offset; i < (offset + length); i++ {
    85  			if byte(0x00) != inMemoryFileInodeContents[i] {
    86  				overwriteOccurred = true
    87  			}
    88  
    89  			inMemoryFileInodeContents[i] = byteToWrite
    90  		}
    91  		if overwriteOccurred {
    92  			curExtentOverwrites++
    93  		}
    94  
    95  		err := testVolumeHandle.Write(fileInodeNumber, offset, inMemoryFileInodeContents[offset:(offset+length)], nil)
    96  		if nil != err {
    97  			t.Fatalf("Write(fileInodeNumber, offset, inMemoryFileInodeContents[offset:(offset+length)]) failed: %v", err)
    98  		}
    99  
   100  		if (offset + length) > curFileSize {
   101  			curFileSize = offset + length
   102  		}
   103  
   104  		if byte(0xFF) == byteToWrite {
   105  			byteToWrite = byte(0x01)
   106  		} else {
   107  			byteToWrite++
   108  		}
   109  	}
   110  }
   111  
   112  func testValidateFileInodeBPlusTree(t *testing.T, testVolumeHandle VolumeHandle, fileInodeNumber InodeNumber) {
   113  	// Fetch actual fileInode
   114  
   115  	fileInode, err := (testVolumeHandle.(*volumeStruct)).fetchInodeType(fileInodeNumber, FileType)
   116  	if nil != err {
   117  		t.Fatalf("testVolumeHandle.fetchInodeType(fileInodeNumber, FileType) failed: %v", err)
   118  	}
   119  
   120  	// Fetch extents B+Tree
   121  
   122  	extents := fileInode.payload.(sortedmap.BPlusTree)
   123  	extentsLen, err := extents.Len()
   124  	if nil != err {
   125  		t.Fatalf("extents.Len() failed: %v", err)
   126  	}
   127  
   128  	// Walk extents B+Tree (by index) ensuring coherence with:
   129  	//   1 - All keys (fileOffset) are monotonically increasing
   130  	//   2 - All keys agree with the fileOffset field of their values
   131  	//   3 - All values (extents) don't overlap
   132  	//   4 - GetByKey(fileOffset) returns the same extent
   133  
   134  	if 0 == extentsLen {
   135  		return
   136  	}
   137  
   138  	var lastExtentTerminationFileOffset uint64
   139  
   140  	for extentIndex := 0; extentIndex < extentsLen; extentIndex++ {
   141  		// Fetch current extent
   142  		keyByIndex, valueByIndex, ok, err := extents.GetByIndex(extentIndex)
   143  		if nil != err {
   144  			t.Fatalf("extents.GetByIndex(extentIndex) failed: %v", err)
   145  		}
   146  		if !ok {
   147  			err = fmt.Errorf("extents.GetByIndex(extentIndex) unexpectedly returned !ok")
   148  			t.Fatalf(err.Error())
   149  		}
   150  		fileOffsetByIndex, ok := keyByIndex.(uint64)
   151  		if !ok {
   152  			err = fmt.Errorf("keyByIndex.(uint64) unexpectedly returned !ok")
   153  			t.Fatalf(err.Error())
   154  		}
   155  		extentByIndex, ok := valueByIndex.(*fileExtentStruct)
   156  		if !ok {
   157  			err = fmt.Errorf("valueByIndex.(*fileExtent) unexpectedly returned !ok")
   158  			t.Fatalf(err.Error())
   159  		}
   160  
   161  		// Validate extent
   162  		if fileOffsetByIndex != extentByIndex.FileOffset {
   163  			err = fmt.Errorf("fileOffsetByIndex != extentByIndex.fileOffset")
   164  			t.Fatalf(err.Error())
   165  		}
   166  		if 0 == extentByIndex.Length {
   167  			err = fmt.Errorf("0 == extentByIndex.length")
   168  			t.Fatalf(err.Error())
   169  		}
   170  
   171  		valueByKey, ok, err := extents.GetByKey(fileOffsetByIndex)
   172  		if nil != err {
   173  			t.Fatalf("extents.GetByKey(fileOffsetByIndex) failed: %v", err)
   174  		}
   175  		if !ok {
   176  			err = fmt.Errorf("extents.GetByKey(fileOffsetByIndex) unexpectedly returned !ok")
   177  			t.Fatalf(err.Error())
   178  		}
   179  		extentByKey, ok := valueByKey.(*fileExtentStruct)
   180  		if !ok {
   181  			err = fmt.Errorf("valueByKey.(*fileExtent) unexpectedly returned !ok")
   182  			t.Fatalf(err.Error())
   183  		}
   184  		if fileOffsetByIndex != extentByKey.FileOffset {
   185  			err = fmt.Errorf("fileOffsetByIndex != extentByKey.fileOffset")
   186  			t.Fatalf(err.Error())
   187  		}
   188  
   189  		if 0 < extentIndex {
   190  			// Ensure this extent strictly follows prior extent
   191  			if lastExtentTerminationFileOffset > fileOffsetByIndex {
   192  				err = fmt.Errorf("(lastExtentFileOffset + lastExtentLength) > fileOffsetByIndex")
   193  				t.Fatalf(err.Error())
   194  			}
   195  		}
   196  
   197  		// Save this extent's lastExtentTerminationFileOffset for next iteration
   198  		lastExtentTerminationFileOffset = fileOffsetByIndex + extentByIndex.Length
   199  	}
   200  
   201  	// If we reach here, extents is valid
   202  }
   203  
   204  func testVerifyFileInodeBPlusTree(t *testing.T, testVolumeHandle VolumeHandle, fileInodeNumber InodeNumber) {
   205  	// Fetch actual fileInode
   206  
   207  	fileInode, err := (testVolumeHandle.(*volumeStruct)).fetchInodeType(fileInodeNumber, FileType)
   208  	if nil != err {
   209  		t.Fatalf("testVolumeHandle.fetchInodeType(fileInodeNumber, FileType) failed: %v", err)
   210  	}
   211  
   212  	// Fetch extents B+Tree
   213  
   214  	extents := fileInode.payload.(sortedmap.BPlusTree)
   215  	extentsLen, err := extents.Len()
   216  	if nil != err {
   217  		t.Fatalf("extents.Len() failed: %v", err)
   218  	}
   219  
   220  	if 0 == extentsLen {
   221  		// Verify entire inMemoryFileInodeContents is all zeroes
   222  		for _, byteValue := range inMemoryFileInodeContents {
   223  			if 0 != byteValue {
   224  				err = fmt.Errorf("0 != byteValue [case 1]")
   225  				t.Fatalf(err.Error())
   226  			}
   227  		}
   228  
   229  		// If we reach here, inMemoryFileInodeContents was all zeroes and (this trivial) extents is verified
   230  		return
   231  	}
   232  
   233  	var lastExtentTerminationFileOffset uint64
   234  
   235  	for extentIndex := 0; extentIndex < extentsLen; extentIndex++ {
   236  		// Fetch current extent
   237  		key, value, ok, err := extents.GetByIndex(extentIndex)
   238  		if nil != err {
   239  			t.Fatalf("extents.GetByIndex(extentIndex) failed: %v", err)
   240  		}
   241  		if !ok {
   242  			err = fmt.Errorf("extents.GetByIndex(extentIndex) unexpectedly returned !ok")
   243  			t.Fatalf(err.Error())
   244  		}
   245  		fileOffset, ok := key.(uint64)
   246  		if !ok {
   247  			err = fmt.Errorf("key.(uint64) unexpectedly returned !ok")
   248  			t.Fatalf(err.Error())
   249  		}
   250  		extent, ok := value.(*fileExtentStruct)
   251  		if !ok {
   252  			err = fmt.Errorf("value.(*fileExtent) unexpectedly returned !ok")
   253  			t.Fatalf(err.Error())
   254  		}
   255  
   256  		// Verify preceeding hole (if any) in extents matches all zeroes in inMemoryFileInodeContents
   257  		for _, byteValue := range inMemoryFileInodeContents[lastExtentTerminationFileOffset:fileOffset] {
   258  			if 0 != byteValue {
   259  				err = fmt.Errorf("0 != byteValue [case 2]")
   260  				t.Fatalf(err.Error())
   261  			}
   262  		}
   263  
   264  		// Update lastExtentTerminationFileOffset for next iteration but used for non-zero check below as well
   265  		lastExtentTerminationFileOffset = fileOffset + extent.Length
   266  
   267  		// Verify extent matches non-zeroes in inMemoryFileInodeContents
   268  		for _, byteValue := range inMemoryFileInodeContents[fileOffset:lastExtentTerminationFileOffset] {
   269  			if 0 == byteValue {
   270  				err = fmt.Errorf("0 == byteValue")
   271  				t.Fatalf(err.Error())
   272  			}
   273  		}
   274  	}
   275  
   276  	// Verify inMemoryFileInodeContents agrees that lastExtentTerminationFileOffset is EOF
   277  	for _, byteValue := range inMemoryFileInodeContents[lastExtentTerminationFileOffset:] {
   278  		if 0 != byteValue {
   279  			err = fmt.Errorf("0 != byteValue [case 3]")
   280  			t.Fatalf(err.Error())
   281  		}
   282  	}
   283  
   284  	// If we reach here, extents is verified
   285  }
   286  
   287  func testCondenseByteSlice(buf []byte) (condensedString string) {
   288  	type countValueTupleStruct struct {
   289  		count uint64
   290  		value byte
   291  	}
   292  
   293  	var countValueTupleSlice []*countValueTupleStruct
   294  
   295  	for _, value := range buf {
   296  		countValueTupleSliceLen := len(countValueTupleSlice)
   297  		if (0 == countValueTupleSliceLen) || (value != countValueTupleSlice[countValueTupleSliceLen-1].value) {
   298  			countValueTupleSlice = append(countValueTupleSlice, &countValueTupleStruct{count: 1, value: value})
   299  		} else {
   300  			countValueTupleSlice[countValueTupleSliceLen-1].count++
   301  		}
   302  	}
   303  
   304  	var condensedByteSlice []byte
   305  
   306  	condensedByteSlice = append(condensedByteSlice, '[')
   307  
   308  	for countValueTupleIndex, countValueTuple := range countValueTupleSlice {
   309  		if 0 < countValueTupleIndex {
   310  			condensedByteSlice = append(condensedByteSlice, ',', ' ')
   311  		}
   312  
   313  		valueAsString := fmt.Sprintf("0x%02x", countValueTuple.value)
   314  
   315  		if 1 == countValueTuple.count {
   316  			condensedByteSlice = append(condensedByteSlice, []byte(valueAsString)...)
   317  		} else {
   318  			countAsString := fmt.Sprintf("0x%x", countValueTuple.count)
   319  			condensedByteSlice = append(condensedByteSlice, []byte(countAsString+"("+valueAsString+")")...)
   320  		}
   321  	}
   322  
   323  	condensedByteSlice = append(condensedByteSlice, ']')
   324  
   325  	condensedString = string(condensedByteSlice[:])
   326  
   327  	return
   328  }
   329  
   330  func testVerifyFileInodeContents(t *testing.T, testVolumeHandle VolumeHandle, fileInodeNumber InodeNumber) {
   331  	// Verify written sections of fileInode match inMemoryFileInodeContents (i.e. non-0x00 bytes all match)
   332  
   333  	offset := uint64(0)
   334  	length := uint64(0)
   335  	currentlyScanningWrittenBytes := false
   336  
   337  	for (offset + length) < curFileSize {
   338  		if currentlyScanningWrittenBytes {
   339  			if byte(0x00) == inMemoryFileInodeContents[offset+length] {
   340  				readBuf, err := testVolumeHandle.Read(fileInodeNumber, offset, length, nil)
   341  				if nil != err {
   342  					t.Fatalf("Read(fileInodeNumber, offset, length) [case 1] failed: %v", err)
   343  				}
   344  
   345  				if bytes.Compare(readBuf, inMemoryFileInodeContents[offset:(offset+length)]) != 0 {
   346  					t.Fatalf("Read(fileInodeNumber, offset, length) [case 1] returned unexpected []byte:\n  expected %v\n       got %v", testCondenseByteSlice(inMemoryFileInodeContents[offset:(offset+length)]), testCondenseByteSlice(readBuf))
   347  				}
   348  
   349  				offset += length
   350  				length = uint64(0)
   351  				currentlyScanningWrittenBytes = false
   352  			} else {
   353  				length++
   354  			}
   355  		} else {
   356  			if byte(0x00) == inMemoryFileInodeContents[offset+length] {
   357  				offset++
   358  			} else {
   359  				length = uint64(1)
   360  				currentlyScanningWrittenBytes = true
   361  			}
   362  		}
   363  	}
   364  
   365  	if currentlyScanningWrittenBytes {
   366  		readBuf, err := testVolumeHandle.Read(fileInodeNumber, offset, length, nil)
   367  		if nil != err {
   368  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 2] failed: %v", err)
   369  		}
   370  
   371  		if 0 != bytes.Compare(readBuf, inMemoryFileInodeContents[offset:(offset+length)]) {
   372  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 2] returned unexpected []byte:\n  expected %v\n       got %v", testCondenseByteSlice(inMemoryFileInodeContents[offset:(offset+length)]), testCondenseByteSlice(readBuf))
   373  		}
   374  	}
   375  
   376  	// Walk through entire fileInode verifying entire inMemoryFileInodeContents
   377  
   378  	offset = uint64(0)
   379  
   380  	for offset < curFileSize {
   381  		length = testChooseExtentSize(t)
   382  
   383  		if offset+length > curFileSize {
   384  			length = curFileSize - offset
   385  		}
   386  
   387  		readBuf, err := testVolumeHandle.Read(fileInodeNumber, offset, length, nil)
   388  		if nil != err {
   389  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 3] failed: %v", err)
   390  		}
   391  
   392  		if 0 != bytes.Compare(readBuf, inMemoryFileInodeContents[offset:(offset+length)]) {
   393  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 3] returned unexpected []byte:\n  expected %v\n       got %v", testCondenseByteSlice(inMemoryFileInodeContents[offset:(offset+length)]), testCondenseByteSlice(readBuf))
   394  		}
   395  
   396  		offset += length
   397  	}
   398  
   399  	// Issue numZeroLengthReads zero-length reads using both Read() & GetReadPlan() for likely valid offsets
   400  
   401  	length = uint64(0)
   402  
   403  	for i := uint64(0); i < numZeroLengthReads; i++ {
   404  		offset = testChooseExtentStart(t)
   405  
   406  		readBuf, err := testVolumeHandle.Read(fileInodeNumber, offset, length, nil)
   407  		if nil != err {
   408  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 4] failed: %v", err)
   409  		}
   410  
   411  		if 0 != len(readBuf) {
   412  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 4] returned unexpected []byte:\n  expected %v\n       got %v", testCondenseByteSlice(inMemoryFileInodeContents[offset:(offset+length)]), testCondenseByteSlice(readBuf))
   413  		}
   414  
   415  		readPlan, err := testVolumeHandle.GetReadPlan(fileInodeNumber, &offset, &length)
   416  		if nil != err {
   417  			t.Fatalf("GetReadPlan(fileInodeNumber, offset, length) [case 4] failed: %v", err)
   418  		}
   419  
   420  		if 0 != len(readPlan) {
   421  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 4] returned unexpected readPlan of length %v", len(readPlan))
   422  		}
   423  	}
   424  
   425  	// Issue numZeroLengthReads zero-length reads using both Read() & GetReadPlan() for definitely invalid offsets
   426  
   427  	length = uint64(0)
   428  
   429  	for i := uint64(0); i < numZeroLengthReads; i++ {
   430  		offset = testChooseExtentStart(t) + sizeOfTestFile
   431  
   432  		readBuf, err := testVolumeHandle.Read(fileInodeNumber, offset, length, nil)
   433  		if nil != err {
   434  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 5] failed: %v", err)
   435  		}
   436  
   437  		if 0 != len(readBuf) {
   438  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 5] returned unexpected []byte:\n  expected %v\n       got %v", testCondenseByteSlice(inMemoryFileInodeContents[offset:(offset+length)]), testCondenseByteSlice(readBuf))
   439  		}
   440  
   441  		readPlan, err := testVolumeHandle.GetReadPlan(fileInodeNumber, &offset, &length)
   442  		if nil != err {
   443  			t.Fatalf("GetReadPlan(fileInodeNumber, offset, length) [case 5] failed: %v", err)
   444  		}
   445  
   446  		if 0 != len(readPlan) {
   447  			t.Fatalf("Read(fileInodeNumber, offset, length) [case 5] returned unexpected readPlan of length %v", len(readPlan))
   448  		}
   449  	}
   450  }
   451  
   452  func TestFileExtents(t *testing.T) {
   453  	testSetup(t, false)
   454  
   455  	testVolumeHandle, err := FetchVolumeHandle("TestVolume")
   456  	if nil != err {
   457  		t.Fatalf("FetchVolumeHandle(\"TestVolume\") should have worked - got error: %v", err)
   458  	}
   459  
   460  	fileInodeNumber, err := testVolumeHandle.CreateFile(PosixModePerm, 0, 0)
   461  	if nil != err {
   462  		t.Fatalf("CreateFile() failed: %v", err)
   463  	}
   464  
   465  	inMemoryFileInodeContents = make([]byte, sizeOfTestFile) // Initially all 0x00... meaning no bytes written
   466  
   467  	testPopulateFileInode(t, testVolumeHandle, fileInodeNumber)
   468  
   469  	testValidateFileInodeBPlusTree(t, testVolumeHandle, fileInodeNumber)
   470  
   471  	testVerifyFileInodeBPlusTree(t, testVolumeHandle, fileInodeNumber)
   472  
   473  	// One might expect to be able to call `testVerifyFileInodeContents` here,
   474  	// but we haven't flushed yet.
   475  
   476  	err = testVolumeHandle.Flush(fileInodeNumber, false)
   477  	if nil != err {
   478  		t.Fatalf("Flush(fileInodeNumber, false) failed: %v", err)
   479  	}
   480  
   481  	testVerifyFileInodeContents(t, testVolumeHandle, fileInodeNumber)
   482  
   483  	err = testVolumeHandle.Purge(fileInodeNumber)
   484  	if nil != err {
   485  		t.Fatalf("Purge(fileInodeNumber) [case one] failed: %v", err)
   486  	}
   487  
   488  	testVerifyFileInodeContents(t, testVolumeHandle, fileInodeNumber)
   489  
   490  	err = testVolumeHandle.Purge(fileInodeNumber)
   491  	if nil != err {
   492  		t.Fatalf("Purge(fileInodeNumber) [case two] failed: %v", err)
   493  	}
   494  
   495  	err = testVolumeHandle.Destroy(fileInodeNumber)
   496  	if nil != err {
   497  		t.Fatalf("Destroy(fileInodeNumber) failed: %v", err)
   498  	}
   499  
   500  	testTeardown(t)
   501  }
   502  
   503  func TestWriteFileExtentAtExtantOffset(t *testing.T) {
   504  	testSetup(t, false)
   505  
   506  	testVolumeHandle, err := FetchVolumeHandle("TestVolume")
   507  	if nil != err {
   508  		t.Fatalf("FetchVolumeHandle(\"TestVolume\") should have worked - got error: %v", err)
   509  	}
   510  
   511  	fileInodeNumber, err := testVolumeHandle.CreateFile(PosixModePerm, 0, 0)
   512  	if nil != err {
   513  		t.Fatalf("CreateFile() failed: %v", err)
   514  	}
   515  
   516  	fileInode, ok, err := (testVolumeHandle.(*volumeStruct)).fetchInode(fileInodeNumber)
   517  	if err != nil {
   518  		t.Fatalf("testVolumeHandle.fetchInode() failed: %v", err)
   519  	}
   520  	if !ok {
   521  		t.Fatalf("testVolumeHandle.fetchInode() returned a free inode")
   522  	}
   523  
   524  	extents := fileInode.payload.(sortedmap.BPlusTree)
   525  
   526  	err = testVolumeHandle.Write(fileInodeNumber, 0, make([]byte, 20), nil)
   527  	if nil != err {
   528  		t.Fatalf("Write(fileInodeNumber, 0, make([]byte, 20)) failed: %v", err)
   529  	}
   530  
   531  	err = testVolumeHandle.Write(fileInodeNumber, 5, []byte("aaaa"), nil) // 4 bytes
   532  	if nil != err {
   533  		t.Fatalf("Write failed: %v", err)
   534  	}
   535  
   536  	// At this point, our file B+-tree should have three extents starting at
   537  	// file offsets 0, 5, and 5+4=9.
   538  
   539  	expectedOffsets := []uint64{0, 5, 9}
   540  	expectedLengths := []uint64{5, 4, 11}
   541  	for i := 0; i < 3; i++ {
   542  		_, value, ok, err := extents.GetByIndex(i)
   543  		if nil != err {
   544  			t.Fatal(err)
   545  		}
   546  		extantExtent := value.(*fileExtentStruct)
   547  		if !ok {
   548  			t.Fatalf("expected to be able to get extent")
   549  		}
   550  		if expectedOffsets[i] != extantExtent.FileOffset {
   551  			t.Fatalf("expected extent to be at offset %v, got %v", expectedOffsets[i], extantExtent.FileOffset)
   552  		}
   553  		if expectedLengths[i] != extantExtent.Length {
   554  			t.Fatalf("expected extent length %v, got %v", expectedLengths[i], extantExtent.Length)
   555  		}
   556  	}
   557  
   558  	err = testVolumeHandle.Write(fileInodeNumber, 9, []byte("bbb"), nil)
   559  	if nil != err {
   560  		t.Fatalf("Overwrite failed: %v", err)
   561  	}
   562  
   563  	err = testVolumeHandle.Flush(fileInodeNumber, false)
   564  	if nil != err {
   565  		t.Fatalf("Flush failed: %v", err)
   566  	}
   567  
   568  	testTeardown(t)
   569  }
   570  
   571  func TestOverwriteIncludesBeginningOfLastExtent(t *testing.T) {
   572  	testSetup(t, false)
   573  
   574  	testVolumeHandle, err := FetchVolumeHandle("TestVolume")
   575  	if nil != err {
   576  		t.Fatalf("FetchVolumeHandle(\"TestVolume\") should have worked - got error: %v", err)
   577  	}
   578  
   579  	fileInodeNumber, err := testVolumeHandle.CreateFile(PosixModePerm, 0, 0)
   580  	if nil != err {
   581  		t.Fatalf("CreateFile() failed: %v", err)
   582  	}
   583  
   584  	err = testVolumeHandle.Write(fileInodeNumber, 0, make([]byte, 20), nil)
   585  	if nil != err {
   586  		t.Fatalf("Write(fileInodeNumber, 0, make([]byte, 20)) failed: %v", err)
   587  	}
   588  
   589  	err = testVolumeHandle.Write(fileInodeNumber, 5, []byte("aaaa"), nil) // 4 bytes
   590  	if nil != err {
   591  		t.Fatalf("Write failed: %v", err)
   592  	}
   593  
   594  	err = testVolumeHandle.Write(fileInodeNumber, 3, []byte("bbbbbbbbbb"), nil)
   595  	if nil != err {
   596  		t.Fatalf("Write failed: %v", err)
   597  	}
   598  
   599  	err = testVolumeHandle.Flush(fileInodeNumber, false)
   600  	if nil != err {
   601  		t.Fatalf("Flush failed: %v", err)
   602  	}
   603  
   604  	testTeardown(t)
   605  }
   606  
   607  func TestReadYourWrite(t *testing.T) {
   608  	testSetup(t, false)
   609  
   610  	testVolumeHandle, err := FetchVolumeHandle("TestVolume")
   611  	if nil != err {
   612  		t.Fatalf("FetchVolumeHandle(\"TestVolume\") should have worked - got error: %v", err)
   613  	}
   614  
   615  	fileInodeNumber, err := testVolumeHandle.CreateFile(PosixModePerm, 0, 0)
   616  	if nil != err {
   617  		t.Fatalf("CreateFile() failed: %v", err)
   618  	}
   619  
   620  	ourBytes := []byte{1, 2, 3, 4, 5, 6, 7, 8}
   621  	err = testVolumeHandle.Write(fileInodeNumber, 0, ourBytes, nil)
   622  	if nil != err {
   623  		t.Fatalf("Write(fileInodeNumber, 0, []byte{1, 2, 3, 4, 5, 6, 7, 8}) failed: %v", err)
   624  	}
   625  	readBuf, err := testVolumeHandle.Read(fileInodeNumber, 0, 8, nil)
   626  	if err != nil {
   627  		t.Fatalf("Read(fileInodeNumber, 0, 8) failed: %v", err)
   628  	}
   629  
   630  	if bytes.Compare(ourBytes, readBuf) != 0 {
   631  		t.Fatalf("read after write didn't work: expected %v, got %v", ourBytes, readBuf)
   632  	}
   633  
   634  	err = testVolumeHandle.Flush(fileInodeNumber, false)
   635  	if nil != err {
   636  		t.Fatalf("Flush failed: %v", err)
   637  	}
   638  
   639  	testTeardown(t)
   640  }