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 }