github.com/NebulousLabs/Sia@v1.3.7/modules/renter/files_test.go (about)

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