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 }