github.com/scottcagno/storage@v1.8.0/pkg/mmap/mmap_test.go (about)

     1  package mmap
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"io/ioutil"
     7  	"math"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"testing"
    12  )
    13  
    14  // testFilePath is the template of the path to the test file.
    15  var testFilePath = filepath.Join(os.TempDir(), "./testfile.txt")
    16  
    17  // testFileIndex is the current index of the test file.
    18  var testFileIndex uint64 = 0
    19  
    20  // testFileMode is the access mode of the test file.
    21  var testFileMode = os.FileMode(0600)
    22  
    23  // testData is the non-zero test data.
    24  var testData = []byte{'H', 'E', 'L', 'L', 'O'}
    25  
    26  // testDataLength is the length of test data.
    27  var testDataLength = len(testData)
    28  
    29  // testZeroData is the zero test data of the same length as test data.
    30  var testZeroData = make([]byte, testDataLength)
    31  
    32  // nextTestFilePath returns the path to the test file.
    33  func nextTestFilePath(t *testing.T) string {
    34  	testFileIndex++
    35  	filePath := testFilePath + "_" + strconv.FormatUint(testFileIndex, 10)
    36  	if _, err := os.Stat(filePath); err != nil {
    37  		if !os.IsNotExist(err) {
    38  			t.Fatal(err)
    39  		}
    40  	} else {
    41  		if err := os.Remove(filePath); err != nil {
    42  			t.Fatal(err)
    43  		}
    44  	}
    45  	return filePath
    46  }
    47  
    48  // openNextTestFile opens and returns the test file.
    49  // if copyPrevious == true previous test file will be copied if exists.
    50  func openNextTestFile(t *testing.T, copyPrevious bool) *os.File {
    51  	filePath := nextTestFilePath(t)
    52  	fileData := testZeroData
    53  	if copyPrevious && testFileIndex > 1 {
    54  		var err error
    55  		fileData, err = ioutil.ReadFile(testFilePath + "_" + strconv.FormatUint(testFileIndex-1, 10))
    56  		if err != nil {
    57  			t.Fatal(err)
    58  		}
    59  	}
    60  	if err := ioutil.WriteFile(filePath, fileData, testFileMode); err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	f, err := os.OpenFile(filePath, os.O_RDWR, testFileMode)
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  	return f
    68  }
    69  
    70  // openTestMapping opens and returns a new mapping of the test file into the memory.
    71  func openTestMapping(t *testing.T, mode Mode) *Mapping {
    72  	f := openNextTestFile(t, false)
    73  	defer closeTestEntity(t, f)
    74  	m, err := Open(f.Fd(), 0, uintptr(testDataLength), mode, 0)
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	return m
    79  }
    80  
    81  // closeTestEntity closes the given entity.
    82  // It ignores ErrClosed error by the reason this error returns when mapping is closed twice.
    83  func closeTestEntity(t *testing.T, closer io.Closer) {
    84  	if err := closer.Close(); err != nil {
    85  		if err != ErrClosed {
    86  			t.Fatal(err)
    87  		}
    88  	}
    89  }
    90  
    91  // TestWithOpenedFile tests the work with the mapping of file which is not closed before closing mapping.
    92  // CASE: The mapping MUST works correctly.
    93  func TestWithOpenedFile(t *testing.T) {
    94  	f := openNextTestFile(t, true)
    95  	defer closeTestEntity(t, f)
    96  	m, err := Open(f.Fd(), 0, uintptr(testDataLength), ModeReadWrite, 0)
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  	defer closeTestEntity(t, m)
   101  	if _, err := m.WriteAt(testData, 0); err != nil {
   102  		t.Fatal(err)
   103  	}
   104  	buf := make([]byte, testDataLength)
   105  	if _, err := m.ReadAt(buf, 0); err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	if bytes.Compare(buf, testData) != 0 {
   109  		t.Fatalf("data must be %q, %v found", testData, buf)
   110  	}
   111  	if err := m.Close(); err != nil {
   112  		t.Fatal(err)
   113  	}
   114  }
   115  
   116  // TestWithClosedFile tests the work with the mapping of file which is closed before closing mapping.
   117  // CASE: The duplication of the file descriptor MUST works correctly.
   118  func TestWithClosedFile(t *testing.T) {
   119  	m := openTestMapping(t, ModeReadWrite)
   120  	defer closeTestEntity(t, m)
   121  	if _, err := m.WriteAt(testData, 0); err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	buf := make([]byte, testDataLength)
   125  	if _, err := m.ReadAt(buf, 0); err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	if bytes.Compare(buf, testData) != 0 {
   129  		t.Fatalf("data must be %q, %v found", testData, buf)
   130  	}
   131  	if err := m.Close(); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  }
   135  
   136  // TestUnalignedOffset tests using the unaligned start address of the mapping memory.
   137  // CASE: The unaligned offset MUST works correctly.
   138  // TODO: This is a strange test...
   139  func TestUnalignedOffset(t *testing.T) {
   140  	f := openNextTestFile(t, false)
   141  	defer closeTestEntity(t, f)
   142  	partialLength := uintptr(testDataLength - 1)
   143  	m, err := Open(f.Fd(), 1, partialLength, ModeReadWrite, 0)
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  	defer closeTestEntity(t, m)
   148  	partialData := make([]byte, partialLength)
   149  	copy(partialData, testData[1:])
   150  	if _, err := m.WriteAt(partialData, 0); err != nil {
   151  		t.Fatal(err)
   152  	}
   153  	buf := make([]byte, partialLength)
   154  	if _, err := m.ReadAt(buf, 0); err != nil {
   155  		t.Fatal(err)
   156  	}
   157  	if bytes.Compare(buf, partialData) != 0 {
   158  		t.Fatalf("data must be %q, %v found", partialData, buf)
   159  	}
   160  }
   161  
   162  // TestSharedSync tests the synchronization of the mapped memory with the underlying file in the shared mode.
   163  // CASE: The data which is read directly from the underlying file MUST be exactly the same
   164  // as the previously written through the mapped memory.
   165  func TestSharedSync(t *testing.T) {
   166  	m := openTestMapping(t, ModeReadWrite)
   167  	defer closeTestEntity(t, m)
   168  	if _, err := m.WriteAt(testData, 0); err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	if err := m.Sync(); err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	f := openNextTestFile(t, true)
   175  	defer closeTestEntity(t, f)
   176  	buf := make([]byte, testDataLength)
   177  	if _, err := f.ReadAt(buf, 0); err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	if bytes.Compare(buf, testData) != 0 {
   181  		t.Fatalf("data must be %q, %v found", testData, buf)
   182  	}
   183  }
   184  
   185  // TestPrivateSync tests the synchronization of the mapped memory with the underlying file in the private mode.
   186  // CASE: The data which is read directly from the underlying file MUST NOT be affected
   187  // by the previous write through the mapped memory.
   188  func TestPrivateSync(t *testing.T) {
   189  	m := openTestMapping(t, ModeWriteCopy)
   190  	defer closeTestEntity(t, m)
   191  	if _, err := m.WriteAt(testData, 0); err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	if err := m.Sync(); err != nil {
   195  		t.Fatal(err)
   196  	}
   197  	f := openNextTestFile(t, true)
   198  	defer closeTestEntity(t, f)
   199  	buf := make([]byte, testDataLength)
   200  	if _, err := f.ReadAt(buf, 0); err != nil {
   201  		t.Fatal(err)
   202  	}
   203  	if bytes.Compare(buf, testZeroData) != 0 {
   204  		t.Fatalf("data must be %v, %v found", testZeroData, buf)
   205  	}
   206  }
   207  
   208  // TestPartialRead tests the reading beyond the mapped memory.
   209  // CASE 1: The ErrOutOfBounds MUST be returned.
   210  // CASE 2: The reading buffer MUST NOT be modified.
   211  func TestPartialRead(t *testing.T) {
   212  	f := openNextTestFile(t, false)
   213  	defer closeTestEntity(t, f)
   214  	partialLength := uintptr(testDataLength - 1)
   215  	m, err := Open(f.Fd(), 0, partialLength, ModeReadWrite, 0)
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	defer closeTestEntity(t, m)
   220  	if _, err := m.WriteAt(testData[:partialLength], 0); err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	buf := make([]byte, testDataLength)
   224  	if _, err := m.ReadAt(buf, 0); err == nil {
   225  		t.Fatal("expected ErrOutOfBounds, no error found")
   226  	} else if err != ErrOutOfBounds {
   227  		t.Fatalf("expected ErrOutOfBounds, [%v] error found", err)
   228  	}
   229  	if bytes.Compare(buf, testZeroData) != 0 {
   230  		t.Fatalf("data must be %v, %v found", testZeroData, buf)
   231  	}
   232  }
   233  
   234  // TestPartialWrite tests the writing beyond the mapped memory.
   235  // CASE 1: The ErrOutOfBounds MUST be returned.
   236  // CASE 2: The mapped memory MUST NOT be modified.
   237  func TestPartialWrite(t *testing.T) {
   238  	f := openNextTestFile(t, false)
   239  	defer closeTestEntity(t, f)
   240  	partialLength := uintptr(testDataLength - 1)
   241  	m, err := Open(f.Fd(), 0, partialLength, ModeReadWrite, 0)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	defer closeTestEntity(t, m)
   246  	if _, err := m.WriteAt(testData, 0); err == nil {
   247  		t.Fatal("expected ErrOutOfBounds, no error found")
   248  	} else if err != ErrOutOfBounds {
   249  		t.Fatalf("expected ErrOutOfBounds, [%v] error found", err)
   250  	}
   251  	buf := make([]byte, partialLength)
   252  	if _, err := m.ReadAt(buf, 0); err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	if bytes.Compare(buf, testZeroData[:partialLength]) != 0 {
   256  		t.Fatalf("data must be %v, %v found", testZeroData, buf)
   257  	}
   258  }
   259  
   260  // TestFileOpening tests the OpenMapping function.
   261  // CASE 1: The initializer must be called once.
   262  // CASE 2: The data read on the second opening must be exactly the same as previously written on the first one.
   263  func TestFileOpening(t *testing.T) {
   264  	initCallCount := 0
   265  	filePath := nextTestFilePath(t)
   266  	open := func() (*Mapping, error) {
   267  		return OpenFileMapping(filePath, testFileMode, uintptr(testDataLength), 0, func(m *Mapping) error {
   268  			initCallCount++
   269  			_, err := m.WriteAt(testData, 0)
   270  			return err
   271  		})
   272  	}
   273  	mf, err := open()
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	closeTestEntity(t, mf)
   278  	mf, err = open()
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  	defer closeTestEntity(t, mf)
   283  	if initCallCount > 1 {
   284  		t.Fatalf("initializer must be called once, %d calls found", initCallCount)
   285  	}
   286  	buf := make([]byte, testDataLength)
   287  	if _, err := mf.ReadAt(buf, 0); err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	if bytes.Compare(buf, testData) != 0 {
   291  		t.Fatalf("data must be %v, %v found", testData, buf)
   292  	}
   293  }
   294  
   295  // TestSegment tests the data segment.
   296  // CASE: The read data must be exactly the same as the previously written unsigned 32-bit integer.
   297  func TestSegment(t *testing.T) {
   298  	m := openTestMapping(t, ModeReadWrite)
   299  	defer closeTestEntity(t, m)
   300  	*m.Segment().Uint32(0) = math.MaxUint32 - 1
   301  	if err := m.Close(); err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	f := openNextTestFile(t, true)
   305  	defer closeTestEntity(t, f)
   306  	buf := make([]byte, 4)
   307  	if _, err := f.ReadAt(buf, 0); err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	uint32Data := []byte{254, 255, 255, 255}
   311  	if bytes.Compare(buf, uint32Data) != 0 {
   312  		t.Fatalf("data must be %v, %v found", uint32Data, buf)
   313  	}
   314  }