gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/filesection_test.go (about)

     1  package proto
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"gitlab.com/NebulousLabs/fastrand"
    12  
    13  	"gitlab.com/SkynetLabs/skyd/build"
    14  )
    15  
    16  // SafeReadAt is a wrapper for ReadAt that recovers from a potential panic and
    17  // returns it as an error.
    18  func (f *fileSection) SafeReadAt(b []byte, off int64) (n int, err error) {
    19  	defer func() {
    20  		if r := recover(); r != nil {
    21  			err = fmt.Errorf("%v", r)
    22  		}
    23  	}()
    24  	return f.ReadAt(b, off)
    25  }
    26  
    27  // SafeTruncate is a wrapper for Truncate that recovers from a potential panic
    28  // and returns it as an error.
    29  func (f *fileSection) SafeTruncate(size int64) (err error) {
    30  	defer func() {
    31  		if r := recover(); r != nil {
    32  			err = fmt.Errorf("%v", r)
    33  		}
    34  	}()
    35  	return f.Truncate(size)
    36  }
    37  
    38  // SafeWriteAt is a wrapper for WriteAt that recovers from a potential panic
    39  // and returns it as an error.
    40  func (f *fileSection) SafeWriteAt(b []byte, off int64) (n int, err error) {
    41  	defer func() {
    42  		if r := recover(); r != nil {
    43  			err = fmt.Errorf("%v", r)
    44  		}
    45  	}()
    46  	return f.WriteAt(b, off)
    47  }
    48  
    49  // TestFileSectionBoundariesValidReadWrites uses valid read and write
    50  // operations on the fileSection to make sure that the data is written to and
    51  // read from the section correctly without corrupting other sections.
    52  func TestFileSectionBoundariesValidReadWrites(t *testing.T) {
    53  	if testing.Short() {
    54  		t.SkipNow()
    55  	}
    56  	testDir := build.TempDir(t.Name())
    57  	if err := os.MkdirAll(testDir, 0700); err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	testFile, err := os.Create(filepath.Join(testDir, "testfile.dat"))
    61  	if err != nil {
    62  		t.Fatal(err)
    63  	}
    64  
    65  	// Create 3 sections.
    66  	s1Size := 100
    67  	s2Size := 100
    68  	s1 := newFileSection(testFile, 0, int64(s1Size))
    69  	s2 := newFileSection(testFile, int64(s1Size), int64(s1Size+s2Size))
    70  	s3 := newFileSection(testFile, int64(s1Size+s2Size), remainingFile)
    71  
    72  	// Write as much data to the sections as they can fit.
    73  	s1Data := fastrand.Bytes(s1Size)
    74  	s2Data := fastrand.Bytes(s2Size)
    75  	s3Data := fastrand.Bytes(s2Size) // s3 has an infinite size so we just write s2Size bytes.
    76  
    77  	n, err := s1.SafeWriteAt(s1Data, 0)
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	if n != len(s1Data) {
    82  		t.Fatalf("expected %v bytes to be written instead of %v", len(s1Data), n)
    83  	}
    84  	n, err = s2.SafeWriteAt(s2Data, 0)
    85  	if err != nil {
    86  		t.Fatal(err)
    87  	}
    88  	if n != len(s2Data) {
    89  		t.Fatalf("expected %v bytes to be written instead of %v", len(s2Data), n)
    90  	}
    91  	n, err = s3.SafeWriteAt(s3Data, 0)
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	if n != len(s3Data) {
    96  		t.Fatalf("expected %v bytes to be written instead of %v", len(s3Data), n)
    97  	}
    98  
    99  	// Read the written data from the file and check if it matches.
   100  	readS1Data := make([]byte, len(s1Data))
   101  	readS2Data := make([]byte, len(s2Data))
   102  	readS3Data := make([]byte, len(s3Data))
   103  	_, err = s1.SafeReadAt(readS1Data, 0)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  	_, err = s2.SafeReadAt(readS2Data, 0)
   108  	if err != nil {
   109  		t.Fatal(err)
   110  	}
   111  	_, err = s3.SafeReadAt(readS3Data, 0)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  	fi, err := testFile.Stat()
   116  	if err != nil {
   117  		t.Fatal(err)
   118  	}
   119  	size := fi.Size()
   120  	size1, err := s1.Size()
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	size2, err := s2.Size()
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	size3, err := s3.Size()
   129  	if err != nil {
   130  		t.Fatal(err)
   131  	}
   132  	if size1 != int64(s1Size) {
   133  		t.Fatalf("expected size to be %v but was %v", s1Size, size1)
   134  	}
   135  	if size2 != int64(s2Size) {
   136  		t.Fatalf("expected size to be %v but was %v", s2Size, size2)
   137  	}
   138  	if size3 != int64(s2Size) {
   139  		t.Fatalf("expected size to be %v but was %v", s2Size, size3)
   140  	}
   141  	if size != size1+size2+size3 {
   142  		t.Fatalf("total size should be %v but was %v", size, size1+size2+size3)
   143  	}
   144  
   145  	if !bytes.Equal(s1Data, readS1Data) {
   146  		t.Fatal("the read data doesn't match the written data")
   147  	}
   148  	if !bytes.Equal(s2Data, readS2Data) {
   149  		t.Fatal("the read data doesn't match the written data")
   150  	}
   151  	if !bytes.Equal(s3Data, readS3Data) {
   152  		t.Fatal("the read data doesn't match the written data")
   153  	}
   154  
   155  	// Read the written data directly from the underlying file and check again.
   156  	_, err = testFile.ReadAt(readS1Data, 0)
   157  	if err != nil {
   158  		t.Fatal(err)
   159  	}
   160  	_, err = testFile.ReadAt(readS2Data, int64(s1Size))
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  	_, err = testFile.ReadAt(readS3Data, int64(s1Size+s2Size))
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  	if !bytes.Equal(s1Data, readS1Data) {
   169  		t.Fatal("the read data doesn't match the written data")
   170  	}
   171  	if !bytes.Equal(s2Data, readS2Data) {
   172  		t.Fatal("the read data doesn't match the written data")
   173  	}
   174  	if !bytes.Equal(s3Data, readS3Data) {
   175  		t.Fatal("the read data doesn't match the written data")
   176  	}
   177  }
   178  
   179  // TestFileSectionBoundariesInvalidReadWrites tries a variation of invalid read
   180  // and write operations on the section to make sure the caller can't write to
   181  // neighboring sections by accident.
   182  func TestFileSectionBoundariesInvalidReadWrites(t *testing.T) {
   183  	if testing.Short() {
   184  		t.SkipNow()
   185  	}
   186  	testDir := build.TempDir(t.Name())
   187  	if err := os.MkdirAll(testDir, 0700); err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	testFile, err := os.Create(filepath.Join(testDir, "testfile.dat"))
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  
   195  	// Create 3 sections.
   196  	s1Size := 100
   197  	s2Size := 100
   198  	s1 := newFileSection(testFile, 0, int64(s1Size))
   199  	s2 := newFileSection(testFile, int64(s1Size), int64(s1Size+s2Size))
   200  
   201  	// Fill the file with some random data
   202  	randomData := fastrand.Bytes(s1Size + s2Size + 100)
   203  	if _, err := testFile.WriteAt(randomData, 0); err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	// Create some random data for the following calls to write. That data
   207  	// should never be written since all the calls should fail.
   208  	data := fastrand.Bytes(1)
   209  
   210  	// Try a number of invalid read and write operations. They should all fail.
   211  	if _, err := s1.SafeWriteAt(data, int64(s1Size)); err == nil {
   212  		t.Fatal("sector shouldn't be able to write data beyond its end boundary")
   213  	}
   214  	if _, err := s2.SafeWriteAt(data, -1); err == nil {
   215  		t.Fatal("sector shouldn't be able to write data below its start boundary")
   216  	}
   217  	if _, err := s1.SafeReadAt(data, int64(s1Size)); err == nil {
   218  		t.Fatal("sector shouldn't be able to read data beyond its end boundary")
   219  	}
   220  	if _, err := s2.SafeReadAt(data, -1); err == nil {
   221  		t.Fatal("sector shouldn't be able to read data below its start boundary")
   222  	}
   223  
   224  	// The file should still have the same random data from the beginning.
   225  	fileData, err := ioutil.ReadAll(testFile)
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	if !bytes.Equal(fileData, randomData) {
   230  		t.Fatal("file data doesn't match the initial data")
   231  	}
   232  }
   233  
   234  // TestFileSectionTruncate checks if file sections without an open end boundary
   235  // can be truncated and makes sure that the last section can't truncate the
   236  // file below its boundary.
   237  func TestFileSectionTruncate(t *testing.T) {
   238  	if testing.Short() {
   239  		t.SkipNow()
   240  	}
   241  	testDir := build.TempDir(t.Name())
   242  	if err := os.MkdirAll(testDir, 0700); err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	testFile, err := os.Create(filepath.Join(testDir, "testfile.dat"))
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  
   250  	// Create 3 sections.
   251  	s1Size := 100
   252  	s2Size := 100
   253  	s1 := newFileSection(testFile, 0, int64(s1Size))
   254  	s2 := newFileSection(testFile, int64(s1Size), int64(s1Size+s2Size))
   255  	s3 := newFileSection(testFile, int64(s1Size+s2Size), remainingFile)
   256  
   257  	// Write as much data to the sections as they can fit.
   258  	s1Data := fastrand.Bytes(s1Size)
   259  	s2Data := fastrand.Bytes(s2Size)
   260  	s3Data := fastrand.Bytes(s2Size) // s3 has an infinite size so we just write s2Size bytes.
   261  
   262  	n, err := s1.SafeWriteAt(s1Data, 0)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	if n != len(s1Data) {
   267  		t.Fatalf("expected %v bytes to be written instead of %v", len(s1Data), n)
   268  	}
   269  	n, err = s2.SafeWriteAt(s2Data, 0)
   270  	if err != nil {
   271  		t.Fatal(err)
   272  	}
   273  	if n != len(s2Data) {
   274  		t.Fatalf("expected %v bytes to be written instead of %v", len(s2Data), n)
   275  	}
   276  	n, err = s3.SafeWriteAt(s3Data, 0)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	if n != len(s3Data) {
   281  		t.Fatalf("expected %v bytes to be written instead of %v", len(s3Data), n)
   282  	}
   283  
   284  	// Try to truncate s1 and s2. That shouldn't be possible.
   285  	if err := s1.SafeTruncate(int64(fastrand.Intn(s1Size + 1))); err == nil {
   286  		t.Fatal("it shouldn't be possible to truncate a section with a fixed end boundary.")
   287  	}
   288  	if err := s2.SafeTruncate(int64(fastrand.Intn(s2Size + 1))); err == nil {
   289  		t.Fatal("it shouldn't be possible to truncate a section with a fixed end boundary.")
   290  	}
   291  
   292  	// Try to truncate s3 to size 0. This should be possible and also reduce
   293  	// the total file size.
   294  	if err := s3.SafeTruncate(0); err != nil {
   295  		t.Fatal("failed to truncate s3", err)
   296  	}
   297  	fi, err := testFile.Stat()
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  	if fi.Size() != int64(s1Size+s2Size) {
   302  		t.Fatalf("expected size after truncate is %v but was %v", s1Size+s2Size, fi.Size())
   303  	}
   304  	size, err := s3.Size()
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	if size != 0 {
   309  		t.Fatalf("size was %v but should be %v", size, 0)
   310  	}
   311  	// Try to truncate s3 below its start. That shouldn't be possible.
   312  	if err := s3.SafeTruncate(-1); err == nil {
   313  		t.Fatal("it shouldn't be possible to truncate a section to a negative size")
   314  	}
   315  }