github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/files_test.go (about)

     1  package renter
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"testing"
     7  
     8  	"github.com/Synthesix/Sia/modules"
     9  	"github.com/Synthesix/Sia/types"
    10  )
    11  
    12  // TestFileNumChunks checks the numChunks method of the file type.
    13  func TestFileNumChunks(t *testing.T) {
    14  	tests := []struct {
    15  		size           uint64
    16  		pieceSize      uint64
    17  		piecesPerChunk int
    18  		expNumChunks   uint64
    19  	}{
    20  		{100, 10, 1, 10}, // evenly divides
    21  		{100, 10, 2, 5},  // evenly divides
    22  
    23  		{101, 10, 1, 11}, // padded
    24  		{101, 10, 2, 6},  // padded
    25  
    26  		{10, 100, 1, 1}, // larger piece than file
    27  		{0, 10, 1, 1},   // 0-length
    28  	}
    29  
    30  	for _, test := range tests {
    31  		rsc, _ := NewRSCode(test.piecesPerChunk, 1) // can't use 0
    32  		f := &file{size: test.size, erasureCode: rsc, pieceSize: test.pieceSize}
    33  		if f.numChunks() != test.expNumChunks {
    34  			t.Errorf("Test %v: expected %v, got %v", test, test.expNumChunks, f.numChunks())
    35  		}
    36  	}
    37  }
    38  
    39  // TestFileAvailable probes the available method of the file type.
    40  func TestFileAvailable(t *testing.T) {
    41  	rsc, _ := NewRSCode(1, 10)
    42  	f := &file{
    43  		size:        1000,
    44  		erasureCode: rsc,
    45  		pieceSize:   100,
    46  	}
    47  	neverOffline := func(types.FileContractID) (bool, bool) {
    48  		return false, true
    49  	}
    50  
    51  	if f.available(neverOffline) {
    52  		t.Error("file should not be available")
    53  	}
    54  
    55  	var fc fileContract
    56  	for i := uint64(0); i < f.numChunks(); i++ {
    57  		fc.Pieces = append(fc.Pieces, pieceData{Chunk: i, Piece: 0})
    58  	}
    59  	f.contracts = map[types.FileContractID]fileContract{{}: fc}
    60  
    61  	if !f.available(neverOffline) {
    62  		t.Error("file should be available")
    63  	}
    64  
    65  	specificOffline := func(fcid types.FileContractID) (bool, bool) {
    66  		return fcid == fc.ID, true
    67  	}
    68  	if f.available(specificOffline) {
    69  		t.Error("file should not be available")
    70  	}
    71  }
    72  
    73  // TestFileUploadedBytes tests that uploadedBytes() returns a value equal to
    74  // the number of sectors stored via contract times the size of each sector.
    75  func TestFileUploadedBytes(t *testing.T) {
    76  	f := &file{}
    77  	// ensure that a piece fits within a sector
    78  	f.pieceSize = modules.SectorSize / 2
    79  	f.contracts = make(map[types.FileContractID]fileContract)
    80  	f.contracts[types.FileContractID{}] = fileContract{
    81  		ID:     types.FileContractID{},
    82  		IP:     modules.NetAddress(""),
    83  		Pieces: make([]pieceData, 4),
    84  	}
    85  	if f.uploadedBytes() != 4*modules.SectorSize {
    86  		t.Errorf("expected uploadedBytes to be 8, got %v", f.uploadedBytes())
    87  	}
    88  }
    89  
    90  // TestFileUploadProgressPinning verifies that uploadProgress() returns at most
    91  // 100%, even if more pieces have been uploaded,
    92  func TestFileUploadProgressPinning(t *testing.T) {
    93  	f := &file{}
    94  	f.pieceSize = 2
    95  	f.contracts = make(map[types.FileContractID]fileContract)
    96  	f.contracts[types.FileContractID{}] = fileContract{
    97  		ID:     types.FileContractID{},
    98  		IP:     modules.NetAddress(""),
    99  		Pieces: make([]pieceData, 4),
   100  	}
   101  	rsc, _ := NewRSCode(1, 1)
   102  	f.erasureCode = rsc
   103  	if f.uploadProgress() != 100 {
   104  		t.Fatal("expected uploadProgress to report 100%")
   105  	}
   106  }
   107  
   108  // TestFileRedundancy tests that redundancy is correctly calculated for files
   109  // with varying number of filecontracts and erasure code settings.
   110  func TestFileRedundancy(t *testing.T) {
   111  	nDatas := []int{1, 2, 10}
   112  	neverOffline := func(types.FileContractID) (bool, bool) {
   113  		return false, true
   114  	}
   115  	for _, nData := range nDatas {
   116  		rsc, _ := NewRSCode(nData, 10)
   117  		f := &file{
   118  			size:        1000,
   119  			pieceSize:   100,
   120  			contracts:   make(map[types.FileContractID]fileContract),
   121  			erasureCode: rsc,
   122  		}
   123  		// Test that an empty file has 0 redundancy.
   124  		if r := f.redundancy(neverOffline); r != 0 {
   125  			t.Error("expected 0 redundancy, got", r)
   126  		}
   127  		// Test that a file with 1 filecontract that has a piece for every chunk but
   128  		// one chunk still has a redundancy of 0.
   129  		fc := fileContract{
   130  			ID: types.FileContractID{0},
   131  		}
   132  		for i := uint64(0); i < f.numChunks()-1; i++ {
   133  			pd := pieceData{
   134  				Chunk: i,
   135  				Piece: 0,
   136  			}
   137  			fc.Pieces = append(fc.Pieces, pd)
   138  		}
   139  		f.contracts[fc.ID] = fc
   140  		if r := f.redundancy(neverOffline); r != 0 {
   141  			t.Error("expected 0 redundancy, got", r)
   142  		}
   143  		// Test that adding another filecontract with a piece for every chunk but one
   144  		// chunk still results in a file with redundancy 0.
   145  		fc = fileContract{
   146  			ID: types.FileContractID{1},
   147  		}
   148  		for i := uint64(0); i < f.numChunks()-1; i++ {
   149  			pd := pieceData{
   150  				Chunk: i,
   151  				Piece: 1,
   152  			}
   153  			fc.Pieces = append(fc.Pieces, pd)
   154  		}
   155  		f.contracts[fc.ID] = fc
   156  		if r := f.redundancy(neverOffline); r != 0 {
   157  			t.Error("expected 0 redundancy, got", r)
   158  		}
   159  		// Test that adding a file contract with a piece for the missing chunk
   160  		// results in a file with redundancy > 0 && <= 1.
   161  		fc = fileContract{
   162  			ID: types.FileContractID{2},
   163  		}
   164  		pd := pieceData{
   165  			Chunk: f.numChunks() - 1,
   166  			Piece: 0,
   167  		}
   168  		fc.Pieces = append(fc.Pieces, pd)
   169  		f.contracts[fc.ID] = fc
   170  		// 1.0 / MinPieces because the chunk with the least number of pieces has 1 piece.
   171  		expectedR := 1.0 / float64(f.erasureCode.MinPieces())
   172  		if r := f.redundancy(neverOffline); r != expectedR {
   173  			t.Errorf("expected %f redundancy, got %f", expectedR, r)
   174  		}
   175  		// Test that adding a file contract that has erasureCode.MinPieces() pieces
   176  		// per chunk for all chunks results in a file with redundancy > 1.
   177  		fc = fileContract{
   178  			ID: types.FileContractID{3},
   179  		}
   180  		for iChunk := uint64(0); iChunk < f.numChunks(); iChunk++ {
   181  			for iPiece := uint64(0); iPiece < uint64(f.erasureCode.MinPieces()); iPiece++ {
   182  				fc.Pieces = append(fc.Pieces, pieceData{
   183  					Chunk: iChunk,
   184  					Piece: iPiece,
   185  				})
   186  			}
   187  		}
   188  		f.contracts[fc.ID] = fc
   189  		// 1+MinPieces / MinPieces because the chunk with the least number of pieces has 1+MinPieces pieces.
   190  		expectedR = float64(1+f.erasureCode.MinPieces()) / float64(f.erasureCode.MinPieces())
   191  		if r := f.redundancy(neverOffline); r != expectedR {
   192  			t.Errorf("expected %f redundancy, got %f", expectedR, r)
   193  		}
   194  
   195  		// verify offline file contracts are not counted in the redundancy
   196  		fc = fileContract{
   197  			ID: types.FileContractID{4},
   198  		}
   199  		for iChunk := uint64(0); iChunk < f.numChunks(); iChunk++ {
   200  			for iPiece := uint64(0); iPiece < uint64(f.erasureCode.MinPieces()); iPiece++ {
   201  				fc.Pieces = append(fc.Pieces, pieceData{
   202  					Chunk: iChunk,
   203  					Piece: iPiece,
   204  				})
   205  			}
   206  		}
   207  		f.contracts[fc.ID] = fc
   208  		specificOffline := func(fcid types.FileContractID) (bool, bool) {
   209  			return fcid == fc.ID, true
   210  		}
   211  		if r := f.redundancy(specificOffline); r != expectedR {
   212  			t.Errorf("expected redundancy to ignore offline file contracts, wanted %f got %f", expectedR, r)
   213  		}
   214  	}
   215  }
   216  
   217  // TestFileExpiration probes the expiration method of the file type.
   218  func TestFileExpiration(t *testing.T) {
   219  	f := &file{
   220  		contracts: make(map[types.FileContractID]fileContract),
   221  	}
   222  
   223  	if f.expiration() != 0 {
   224  		t.Error("file with no pieces should report as having no time remaining")
   225  	}
   226  
   227  	// Add a contract.
   228  	fc := fileContract{}
   229  	fc.WindowStart = 100
   230  	f.contracts[types.FileContractID{0}] = fc
   231  	if f.expiration() != 100 {
   232  		t.Error("file did not report lowest WindowStart")
   233  	}
   234  
   235  	// Add a contract with a lower WindowStart.
   236  	fc.WindowStart = 50
   237  	f.contracts[types.FileContractID{1}] = fc
   238  	if f.expiration() != 50 {
   239  		t.Error("file did not report lowest WindowStart")
   240  	}
   241  
   242  	// Add a contract with a higher WindowStart.
   243  	fc.WindowStart = 75
   244  	f.contracts[types.FileContractID{2}] = fc
   245  	if f.expiration() != 50 {
   246  		t.Error("file did not report lowest WindowStart")
   247  	}
   248  }
   249  
   250  // TestRenterFileListLocalPath verifies that FileList() returns the correct
   251  // local path information for an uploaded file.
   252  func TestRenterFileListLocalPath(t *testing.T) {
   253  	if testing.Short() {
   254  		t.SkipNow()
   255  	}
   256  	rt, err := newRenterTester(t.Name())
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  	defer rt.Close()
   261  	id := rt.renter.mu.Lock()
   262  	f := newTestingFile()
   263  	f.name = "testname"
   264  	rt.renter.files["test"] = f
   265  	rt.renter.tracking[f.name] = trackedFile{
   266  		RepairPath: "TestPath",
   267  	}
   268  	rt.renter.mu.Unlock(id)
   269  	files := rt.renter.FileList()
   270  	if len(files) != 1 {
   271  		t.Fatal("wrong number of files, got", len(files), "wanted one")
   272  	}
   273  	if files[0].LocalPath != "TestPath" {
   274  		t.Fatal("file had wrong LocalPath: got", files[0].LocalPath, "wanted TestPath")
   275  	}
   276  }
   277  
   278  // TestRenterDeleteFile probes the DeleteFile method of the renter type.
   279  func TestRenterDeleteFile(t *testing.T) {
   280  	if testing.Short() {
   281  		t.SkipNow()
   282  	}
   283  	rt, err := newRenterTester(t.Name())
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	defer rt.Close()
   288  
   289  	// Delete a file from an empty renter.
   290  	err = rt.renter.DeleteFile("dne")
   291  	if err != ErrUnknownPath {
   292  		t.Error("Expected ErrUnknownPath:", err)
   293  	}
   294  
   295  	// Put a file in the renter.
   296  	rt.renter.files["1"] = &file{
   297  		name: "one",
   298  	}
   299  	// Delete a different file.
   300  	err = rt.renter.DeleteFile("one")
   301  	if err != ErrUnknownPath {
   302  		t.Error("Expected ErrUnknownPath, got", err)
   303  	}
   304  	// Delete the file.
   305  	err = rt.renter.DeleteFile("1")
   306  	if err != nil {
   307  		t.Error(err)
   308  	}
   309  	if len(rt.renter.FileList()) != 0 {
   310  		t.Error("file was deleted, but is still reported in FileList")
   311  	}
   312  
   313  	// Put a file in the renter, then rename it.
   314  	f := newTestingFile()
   315  	f.name = "1"
   316  	rt.renter.files[f.name] = f
   317  	rt.renter.RenameFile(f.name, "one")
   318  	// Call delete on the previous name.
   319  	err = rt.renter.DeleteFile("1")
   320  	if err != ErrUnknownPath {
   321  		t.Error("Expected ErrUnknownPath, got", err)
   322  	}
   323  	// Call delete on the new name.
   324  	err = rt.renter.DeleteFile("one")
   325  	if err != nil {
   326  		t.Error(err)
   327  	}
   328  
   329  	// Check that all .sia files have been deleted.
   330  	var walkStr string
   331  	filepath.Walk(rt.renter.persistDir, func(path string, _ os.FileInfo, _ error) error {
   332  		// capture only .sia files
   333  		if filepath.Ext(path) == ".sia" {
   334  			rel, _ := filepath.Rel(rt.renter.persistDir, path) // strip testdir prefix
   335  			walkStr += rel
   336  		}
   337  		return nil
   338  	})
   339  	expWalkStr := ""
   340  	if walkStr != expWalkStr {
   341  		t.Fatalf("Bad walk string: expected %q, got %q", expWalkStr, walkStr)
   342  	}
   343  }
   344  
   345  // TestRenterFileList probes the FileList method of the renter type.
   346  func TestRenterFileList(t *testing.T) {
   347  	if testing.Short() {
   348  		t.SkipNow()
   349  	}
   350  	rt, err := newRenterTester(t.Name())
   351  	if err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	defer rt.Close()
   355  
   356  	// Get the file list of an empty renter.
   357  	if len(rt.renter.FileList()) != 0 {
   358  		t.Error("FileList has non-zero length for empty renter?")
   359  	}
   360  
   361  	// Put a file in the renter.
   362  	rsc, _ := NewRSCode(1, 1)
   363  	rt.renter.files["1"] = &file{
   364  		name:        "one",
   365  		erasureCode: rsc,
   366  		pieceSize:   1,
   367  	}
   368  	if len(rt.renter.FileList()) != 1 {
   369  		t.Error("FileList is not returning the only file in the renter")
   370  	}
   371  	if rt.renter.FileList()[0].SiaPath != "one" {
   372  		t.Error("FileList is not returning the correct filename for the only file")
   373  	}
   374  
   375  	// Put multiple files in the renter.
   376  	rt.renter.files["2"] = &file{
   377  		name:        "two",
   378  		erasureCode: rsc,
   379  		pieceSize:   1,
   380  	}
   381  	if len(rt.renter.FileList()) != 2 {
   382  		t.Error("FileList is not returning both files in the renter")
   383  	}
   384  	files := rt.renter.FileList()
   385  	if !((files[0].SiaPath == "one" || files[0].SiaPath == "two") &&
   386  		(files[1].SiaPath == "one" || files[1].SiaPath == "two") &&
   387  		(files[0].SiaPath != files[1].SiaPath)) {
   388  		t.Error("FileList is returning wrong names for the files:", files[0].SiaPath, files[1].SiaPath)
   389  	}
   390  }
   391  
   392  // TestRenterRenameFile probes the rename method of the renter.
   393  func TestRenterRenameFile(t *testing.T) {
   394  	if testing.Short() {
   395  		t.SkipNow()
   396  	}
   397  	rt, err := newRenterTester(t.Name())
   398  	if err != nil {
   399  		t.Fatal(err)
   400  	}
   401  	defer rt.Close()
   402  
   403  	// Rename a file that doesn't exist.
   404  	err = rt.renter.RenameFile("1", "1a")
   405  	if err != ErrUnknownPath {
   406  		t.Error("Expecting ErrUnknownPath:", err)
   407  	}
   408  
   409  	// Rename a file that does exist.
   410  	f := newTestingFile()
   411  	f.name = "1"
   412  	rt.renter.files["1"] = f
   413  	err = rt.renter.RenameFile("1", "1a")
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  	files := rt.renter.FileList()
   418  	if len(files) != 1 {
   419  		t.Fatal("FileList has unexpected number of files:", len(files))
   420  	}
   421  	if files[0].SiaPath != "1a" {
   422  		t.Errorf("RenameFile failed: expected 1a, got %v", files[0].SiaPath)
   423  	}
   424  
   425  	// Rename a file to an existing name.
   426  	f2 := newTestingFile()
   427  	f2.name = "1"
   428  	rt.renter.files["1"] = f2
   429  	err = rt.renter.RenameFile("1", "1a")
   430  	if err != ErrPathOverload {
   431  		t.Error("Expecting ErrPathOverload, got", err)
   432  	}
   433  
   434  	// Rename a file to the same name.
   435  	err = rt.renter.RenameFile("1", "1")
   436  	if err != ErrPathOverload {
   437  		t.Error("Expecting ErrPathOverload, got", err)
   438  	}
   439  
   440  	// Renaming should also update the tracking set
   441  	rt.renter.tracking["1"] = trackedFile{"foo"}
   442  	err = rt.renter.RenameFile("1", "1b")
   443  	if err != nil {
   444  		t.Fatal(err)
   445  	}
   446  	_, oldexists := rt.renter.tracking["1"]
   447  	_, newexists := rt.renter.tracking["1b"]
   448  	if oldexists || !newexists {
   449  		t.Error("renaming should have updated the entry in the tracking set")
   450  	}
   451  }