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

     1  package renter
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"gitlab.com/NebulousLabs/errors"
    11  	"gitlab.com/NebulousLabs/fastrand"
    12  	"go.sia.tech/siad/crypto"
    13  	"go.sia.tech/siad/persist"
    14  
    15  	"gitlab.com/SkynetLabs/skyd/build"
    16  	"gitlab.com/SkynetLabs/skyd/siatest/dependencies"
    17  	"gitlab.com/SkynetLabs/skyd/skymodules"
    18  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siadir"
    19  )
    20  
    21  // updateFileMetadatas updates the metadata of all siafiles within a dir.
    22  func (rt *renterTester) updateFileMetadatas(dirSiaPath skymodules.SiaPath) error {
    23  	// Get cached offline and goodforrenew maps.
    24  	offlineMap, goodForRenewMap, contracts, used := rt.renter.callRenterContractsAndUtilities()
    25  	return rt.renter.managedUpdateFileMetadatasParams(dirSiaPath, offlineMap, goodForRenewMap, contracts, used)
    26  }
    27  
    28  // openAndUpdateDir is a helper method for updating a siadir metadata
    29  func (rt *renterTester) openAndUpdateDir(siapath skymodules.SiaPath, metadata siadir.Metadata) error {
    30  	siadir, err := rt.renter.staticFileSystem.OpenSiaDir(siapath)
    31  	if err != nil {
    32  		return err
    33  	}
    34  	err = siadir.UpdateMetadata(metadata)
    35  	return errors.Compose(err, siadir.Close())
    36  }
    37  
    38  // TestDirectoryModTime verifies that the last update time of a directory is
    39  // accurately reported
    40  func TestDirectoryModTime(t *testing.T) {
    41  	if testing.Short() {
    42  		t.SkipNow()
    43  	}
    44  	t.Parallel()
    45  
    46  	// Create a test directory with sub folders
    47  	//
    48  	// root/ file
    49  	// root/SubDir1/
    50  	// root/SubDir1/SubDir2/ file
    51  
    52  	// Create test renter
    53  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  	defer func() {
    58  		if err := rt.Close(); err != nil {
    59  			t.Fatal(err)
    60  		}
    61  	}()
    62  
    63  	// Create directory tree
    64  	subDir1, err := skymodules.NewSiaPath("SubDir1")
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	subDir2, err := skymodules.NewSiaPath("SubDir2")
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	if err := rt.renter.CreateDir(subDir1, skymodules.DefaultDirPerm); err != nil {
    73  		t.Fatal(err)
    74  	}
    75  	subDir1_2, err := subDir1.Join(subDir2.String())
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  	if err := rt.renter.CreateDir(subDir1_2, skymodules.DefaultDirPerm); err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	// Call Bubble to update filesystem ModTimes so there are no zero times
    84  	if err := rt.renter.UpdateMetadata(subDir1_2, false); err != nil {
    85  		t.Fatal(err)
    86  	}
    87  	// Sleep for 1 second to allow bubbles to update filesystem. Retry doesn't
    88  	// work here as we are waiting for the ModTime to be fully updated but we
    89  	// don't know what that value will be. We need this value to be updated and
    90  	// static before we create the SiaFiles to be able to ensure the ModTimes of
    91  	// the SiaFiles are the most recent
    92  	time.Sleep(time.Second)
    93  
    94  	// Add files
    95  	sp1 := skymodules.RandomSiaPath()
    96  	rsc, _ := skymodules.NewRSCode(1, 1)
    97  	up := skymodules.FileUploadParams{
    98  		Source:      "",
    99  		SiaPath:     sp1,
   100  		ErasureCode: rsc,
   101  	}
   102  	fileSize := uint64(100)
   103  	f1, err := rt.newTestSiaFile(up.SiaPath, up.Source, up.ErasureCode, fileSize)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  	defer func() {
   108  		if err := f1.Close(); err != nil {
   109  			t.Fatal(err)
   110  		}
   111  	}()
   112  	sp2, err := subDir1_2.Join(hex.EncodeToString(fastrand.Bytes(8)))
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	up.SiaPath = sp2
   117  	f2, err := rt.newTestSiaFile(up.SiaPath, up.Source, up.ErasureCode, fileSize)
   118  	if err != nil {
   119  		t.Fatal(err)
   120  	}
   121  	defer func() {
   122  		if err := f2.Close(); err != nil {
   123  			t.Fatal(err)
   124  		}
   125  	}()
   126  
   127  	// Call bubble on lowest lever and confirm top level reports accurate last
   128  	// update time
   129  	if err := rt.renter.UpdateMetadata(subDir1_2, false); err != nil {
   130  		t.Fatal(err)
   131  	}
   132  	err = build.Retry(100, 100*time.Millisecond, func() error {
   133  		dirInfo, err := rt.renter.staticFileSystem.DirInfo(skymodules.RootSiaPath())
   134  		if err != nil {
   135  			return err
   136  		}
   137  		if dirInfo.MostRecentModTime != f1.ModTime() {
   138  			return fmt.Errorf("MostRecentModTime is incorrect, got %v expected %v", dirInfo.MostRecentModTime, f1.ModTime())
   139  		}
   140  		if dirInfo.AggregateMostRecentModTime != f2.ModTime() {
   141  			return fmt.Errorf("AggregateMostRecentModTime is incorrect, got %v expected %v", dirInfo.AggregateMostRecentModTime, f2.ModTime())
   142  		}
   143  		return nil
   144  	})
   145  	if err != nil {
   146  		t.Fatal(err)
   147  	}
   148  }
   149  
   150  // TestRandomStuckDirectory probes managedStuckDirectory to make sure it
   151  // randomly picks a correct directory
   152  func TestRandomStuckDirectory(t *testing.T) {
   153  	if testing.Short() {
   154  		t.SkipNow()
   155  	}
   156  	t.Parallel()
   157  
   158  	// Create test renter
   159  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	defer func() {
   164  		if err := rt.Close(); err != nil {
   165  			t.Fatal(err)
   166  		}
   167  	}()
   168  
   169  	// Create a test directory with sub folders
   170  	//
   171  	// root/home/siafiles/
   172  	// root/home/siafiles/SubDir1/
   173  	// root/home/siafiles/SubDir1/SubDir2/
   174  	// root/home/siafiles/SubDir2/
   175  	subDir1, err := skymodules.NewSiaPath("SubDir1")
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	subDir2, err := skymodules.NewSiaPath("SubDir2")
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  	if err := rt.renter.CreateDir(subDir1, skymodules.DefaultDirPerm); err != nil {
   184  		t.Fatal(err)
   185  	}
   186  	if err := rt.renter.CreateDir(subDir2, skymodules.DefaultDirPerm); err != nil {
   187  		t.Fatal(err)
   188  	}
   189  	subDir1_2, err := subDir1.Join(subDir2.String())
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  	if err := rt.renter.CreateDir(subDir1_2, skymodules.DefaultDirPerm); err != nil {
   194  		t.Fatal(err)
   195  	}
   196  
   197  	// Add a file to siafiles and SubDir1/SubDir2 and mark the first chunk as
   198  	// stuck in each file
   199  	//
   200  	// This will test the edge case of continuing to find stuck files when a
   201  	// directory has no files only directories
   202  	rsc, _ := skymodules.NewRSCode(1, 1)
   203  	up := skymodules.FileUploadParams{
   204  		Source:      "",
   205  		SiaPath:     skymodules.RandomSiaPath(),
   206  		ErasureCode: rsc,
   207  	}
   208  	f, err := rt.newTestSiaFile(up.SiaPath, up.Source, up.ErasureCode, 100)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	err = rt.renter.SetFileStuck(up.SiaPath, true)
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  	if err = f.SetStuck(uint64(0), true); err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	if err := f.Close(); err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	up.SiaPath, err = subDir1_2.Join(hex.EncodeToString(fastrand.Bytes(8)))
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	f, err = rt.newTestSiaFile(up.SiaPath, up.Source, up.ErasureCode, 100)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	err = f.GrowNumChunks(2)
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  	err = rt.renter.SetFileStuck(up.SiaPath, true)
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	if err = f.SetStuck(uint64(0), true); err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	if err := f.Close(); err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	// Bubble directory information so NumStuckChunks is updated, there should
   246  	// be at least 3 stuck chunks because of the 3 we manually marked as stuck,
   247  	// but the repair loop could have marked the rest as stuck so we just want
   248  	// to ensure that the root directory reflects at least the 3 we marked as
   249  	// stuck
   250  	if err := rt.renter.UpdateMetadata(skymodules.RootSiaPath(), true); err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	err = build.Retry(100, 100*time.Millisecond, func() error {
   254  		// Get Root Directory Metadata
   255  		metadata, err := rt.renter.managedDirectoryMetadata(skymodules.RootSiaPath())
   256  		if err != nil {
   257  			return err
   258  		}
   259  		// Check Aggregate number of stuck chunks
   260  		if metadata.AggregateNumStuckChunks < uint64(3) {
   261  			return fmt.Errorf("Incorrect number of stuck chunks, got %v expected at least 3", metadata.AggregateNumStuckChunks)
   262  		}
   263  		return nil
   264  	})
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  
   269  	// Find a stuck directory randomly, it should never find root/SubDir1 or
   270  	// root/SubDir2 and should find root/SubDir1/SubDir2 more than root
   271  	var count1_2, countRoot, countSiaFiles int
   272  	for i := 0; i < 100; i++ {
   273  		dir, err := rt.renter.managedStuckDirectory()
   274  		if err != nil {
   275  			t.Fatal(err)
   276  		}
   277  		if dir.Equals(subDir1_2) {
   278  			count1_2++
   279  			continue
   280  		}
   281  		if dir.Equals(skymodules.RootSiaPath()) {
   282  			countRoot++
   283  			continue
   284  		}
   285  		if dir.Equals(skymodules.UserFolder) {
   286  			countSiaFiles++
   287  			continue
   288  		}
   289  		t.Fatal("Unstuck dir found", dir.String())
   290  	}
   291  
   292  	// Randomness is weighted so we should always find file 1 more often
   293  	if countRoot > count1_2 {
   294  		t.Log("Should have found root/SubDir1/SubDir2 more than root")
   295  		t.Fatalf("Found root/SubDir1/SubDir2 %v times and root %v times", count1_2, countRoot)
   296  	}
   297  	// If we never find root/SubDir1/SubDir2 then that is a failure
   298  	if count1_2 == 0 {
   299  		t.Fatal("Found root/SubDir1/SubDir2 0 times")
   300  	}
   301  	// If we never find root that is not ideal, Log this error. If it happens
   302  	// a lot then the weighted randomness should be improved
   303  	if countRoot == 0 {
   304  		t.Logf("Found root 0 times. Consider improving the weighted randomness")
   305  	}
   306  	t.Log("Found root/SubDir1/SubDir2", count1_2, "times and root", countRoot, "times")
   307  }
   308  
   309  // TestRandomStuckFile tests that the renter can randomly find stuck files
   310  // weighted by the number of stuck chunks
   311  func TestRandomStuckFile(t *testing.T) {
   312  	if testing.Short() {
   313  		t.SkipNow()
   314  	}
   315  	t.Parallel()
   316  
   317  	// Create Renter
   318  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   319  	if err != nil {
   320  		t.Fatal(err)
   321  	}
   322  	defer func() {
   323  		if err := rt.Close(); err != nil {
   324  			t.Fatal(err)
   325  		}
   326  	}()
   327  
   328  	// Create 3 files at root
   329  	//
   330  	// File 1 will have all chunks stuck
   331  	file1, err := rt.renter.newRenterTestFile()
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	err = file1.GrowNumChunks(3)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  	siaPath1 := rt.renter.staticFileSystem.FileSiaPath(file1)
   340  	err = rt.renter.SetFileStuck(siaPath1, true)
   341  	if err != nil {
   342  		t.Fatal(err)
   343  	}
   344  
   345  	// File 2 will have only 1 chunk stuck
   346  	file2, err := rt.renter.newRenterTestFile()
   347  	if err != nil {
   348  		t.Fatal(err)
   349  	}
   350  	siaPath2 := rt.renter.staticFileSystem.FileSiaPath(file2)
   351  	err = file2.SetStuck(0, true)
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  
   356  	// File 3 will be unstuck
   357  	file3, err := rt.renter.newRenterTestFile()
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  	siaPath3 := rt.renter.staticFileSystem.FileSiaPath(file3)
   362  
   363  	// Since we disabled the health loop for this test, call it manually to
   364  	// update the directory metadata
   365  	if err := rt.renter.UpdateMetadata(skymodules.UserFolder, false); err != nil {
   366  		t.Fatal(err)
   367  	}
   368  	i := 0
   369  	err = build.Retry(100, 100*time.Millisecond, func() error {
   370  		i++
   371  		if i%10 == 0 {
   372  			if err := rt.renter.UpdateMetadata(skymodules.RootSiaPath(), true); err != nil {
   373  				t.Fatal(err)
   374  			}
   375  		}
   376  		// Get Root Directory Metadata
   377  		metadata, err := rt.renter.managedDirectoryMetadata(skymodules.RootSiaPath())
   378  		if err != nil {
   379  			return err
   380  		}
   381  		// Check Aggregate number of stuck chunks
   382  		if metadata.AggregateNumStuckChunks == 0 {
   383  			return errors.New("no stuck chunks found")
   384  		}
   385  		return nil
   386  	})
   387  	if err != nil {
   388  		t.Fatal(err)
   389  	}
   390  	checkFindRandomFile(t, rt.renter, skymodules.RootSiaPath(), siaPath1, siaPath2, siaPath3)
   391  
   392  	// Create a directory
   393  	dir, err := skymodules.NewSiaPath("Dir")
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  	if err := rt.renter.CreateDir(dir, skymodules.DefaultDirPerm); err != nil {
   398  		t.Fatal(err)
   399  	}
   400  
   401  	// Move siafiles to dir
   402  	newSiaPath1, err := dir.Join(siaPath1.String())
   403  	if err != nil {
   404  		t.Fatal(err)
   405  	}
   406  	err = rt.renter.RenameFile(siaPath1, newSiaPath1)
   407  	if err != nil {
   408  		t.Fatal(err)
   409  	}
   410  	newSiaPath2, err := dir.Join(siaPath2.String())
   411  	if err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	err = rt.renter.RenameFile(siaPath2, newSiaPath2)
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  	newSiaPath3, err := dir.Join(siaPath3.String())
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	err = rt.renter.RenameFile(siaPath3, newSiaPath3)
   423  	if err != nil {
   424  		t.Fatal(err)
   425  	}
   426  	// Since we disabled the health loop for this test, call it manually to
   427  	// update the directory metadata
   428  	if err := rt.renter.UpdateMetadata(dir, false); err != nil {
   429  		t.Fatal(err)
   430  	}
   431  	i = 0
   432  	err = build.Retry(100, 100*time.Millisecond, func() error {
   433  		i++
   434  		if i%10 == 0 {
   435  			if err := rt.renter.UpdateMetadata(dir, false); err != nil {
   436  				t.Fatal(err)
   437  			}
   438  		}
   439  		// Get Directory Metadata
   440  		metadata, err := rt.renter.managedDirectoryMetadata(dir)
   441  		if err != nil {
   442  			return err
   443  		}
   444  		// Check Aggregate number of stuck chunks
   445  		if metadata.AggregateNumStuckChunks == 0 {
   446  			return errors.New("no stuck chunks found")
   447  		}
   448  		return nil
   449  	})
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	checkFindRandomFile(t, rt.renter, dir, newSiaPath1, newSiaPath2, newSiaPath3)
   454  }
   455  
   456  // checkFindRandomFile is a helper function that checks the output from
   457  // managedStuckFile in a loop
   458  func checkFindRandomFile(t *testing.T, r *Renter, dir, siaPath1, siaPath2, siaPath3 skymodules.SiaPath) {
   459  	// Find a stuck file randomly, it should never find file 3 and should find
   460  	// file 1 more than file 2.
   461  	var count1, count2 int
   462  	for i := 0; i < 100; i++ {
   463  		siaPath, err := r.managedStuckFile(dir)
   464  		if err != nil {
   465  			t.Fatal(err)
   466  		}
   467  		if siaPath.Equals(siaPath1) {
   468  			count1++
   469  		}
   470  		if siaPath.Equals(siaPath2) {
   471  			count2++
   472  		}
   473  		if siaPath.Equals(siaPath3) {
   474  			t.Fatal("Unstuck file 3 found")
   475  		}
   476  	}
   477  
   478  	// Randomness is weighted so we should always find file 1 more often
   479  	if count2 > count1 {
   480  		t.Log("Should have found file 1 more than file 2")
   481  		t.Fatalf("Found file 1 %v times and file 2 %v times", count1, count2)
   482  	}
   483  	// If we never find file 1 then that is a failure
   484  	if count1 == 0 {
   485  		t.Fatal("Found file 1 0 times")
   486  	}
   487  	// If we never find file 2 that is not ideal, Log this error. If it happens
   488  	// a lot then the weighted randomness should be improved
   489  	if count2 == 0 {
   490  		t.Logf("Found file 2 0 times. Consider improving the weighted randomness")
   491  	}
   492  	t.Log("Found file1", count1, "times and file2", count2, "times")
   493  }
   494  
   495  // TestCalculateFileMetadata checks that the values returned from
   496  // managedCalculateFileMetadata make sense
   497  func TestCalculateFileMetadata(t *testing.T) {
   498  	if testing.Short() {
   499  		t.SkipNow()
   500  	}
   501  	t.Parallel()
   502  
   503  	// Create renter
   504  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   505  	if err != nil {
   506  		t.Fatal(err)
   507  	}
   508  	defer func() {
   509  		if err := rt.Close(); err != nil {
   510  			t.Fatal(err)
   511  		}
   512  	}()
   513  
   514  	// Create a file at root with a skylink
   515  	rsc, _ := skymodules.NewRSCode(1, 1)
   516  	siaPath, err := skymodules.NewSiaPath("rootFile")
   517  	if err != nil {
   518  		t.Fatal(err)
   519  	}
   520  	up := skymodules.FileUploadParams{
   521  		Source:      "",
   522  		SiaPath:     siaPath,
   523  		ErasureCode: rsc,
   524  	}
   525  	fileSize := uint64(100)
   526  	err = rt.renter.staticFileSystem.NewSiaFile(up.SiaPath, up.Source, up.ErasureCode, crypto.GenerateSiaKey(crypto.RandomCipherType()), fileSize, persist.DefaultDiskPermissionsTest)
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  	sf, err := rt.renter.staticFileSystem.OpenSiaFile(up.SiaPath)
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	defer func() {
   535  		if err := sf.Close(); err != nil {
   536  			t.Fatal(err)
   537  		}
   538  	}()
   539  	var skylink skymodules.Skylink
   540  	err = sf.AddSkylink(skylink)
   541  	if err != nil {
   542  		t.Fatal(err)
   543  	}
   544  
   545  	// Grab initial metadata values
   546  	rt.renter.managedUpdateRenterContractsAndUtilities()
   547  	offline, goodForRenew, _, _ := rt.renter.callRenterContractsAndUtilities()
   548  	health, stuckHealth, _, _, numStuckChunks, repairBytes, stuckBytes := sf.Health(offline, goodForRenew)
   549  	redundancy, _, err := sf.Redundancy(offline, goodForRenew)
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  	lastHealthCheckTime := sf.LastHealthCheckTime()
   554  	modTime := sf.ModTime()
   555  
   556  	// Update the file metadata.
   557  	err = rt.updateFileMetadatas(skymodules.RootSiaPath())
   558  	if err != nil {
   559  		t.Fatal(err)
   560  	}
   561  
   562  	// Check calculated metadata
   563  	bubbledMetadatas, err := rt.renter.managedCachedFileMetadatas([]skymodules.SiaPath{up.SiaPath})
   564  	if err != nil {
   565  		t.Fatal(err)
   566  	}
   567  	fileMetadata := bubbledMetadatas[0].bm
   568  
   569  	// Check siafile calculated metadata
   570  	if fileMetadata.Health != health {
   571  		t.Fatalf("health incorrect, expected %v got %v", health, fileMetadata.Health)
   572  	}
   573  	if fileMetadata.StuckHealth != stuckHealth {
   574  		t.Fatalf("stuckHealth incorrect, expected %v got %v", stuckHealth, fileMetadata.StuckHealth)
   575  	}
   576  	if fileMetadata.Redundancy != redundancy {
   577  		t.Fatalf("redundancy incorrect, expected %v got %v", redundancy, fileMetadata.Redundancy)
   578  	}
   579  	if fileMetadata.RepairBytes != repairBytes {
   580  		t.Fatalf("RepairBytes incorrect, expected %v got %v", repairBytes, fileMetadata.RepairBytes)
   581  	}
   582  	if fileMetadata.StuckBytes != stuckBytes {
   583  		t.Fatalf("StuckBytes incorrect, expected %v got %v", stuckBytes, fileMetadata.StuckBytes)
   584  	}
   585  	if fileMetadata.Size != fileSize {
   586  		t.Fatalf("size incorrect, expected %v got %v", fileSize, fileMetadata.Size)
   587  	}
   588  	if fileMetadata.NumStuckChunks != numStuckChunks {
   589  		t.Fatalf("numstuckchunks incorrect, expected %v got %v", numStuckChunks, fileMetadata.NumStuckChunks)
   590  	}
   591  	if fileMetadata.LastHealthCheckTime.Equal(lastHealthCheckTime) || fileMetadata.LastHealthCheckTime.IsZero() {
   592  		t.Log("Initial lasthealthchecktime", lastHealthCheckTime)
   593  		t.Log("Calculated lasthealthchecktime", fileMetadata.LastHealthCheckTime)
   594  		t.Fatal("Expected lasthealthchecktime to have updated and be non zero")
   595  	}
   596  	if !fileMetadata.ModTime.Equal(modTime) {
   597  		t.Fatalf("Unexpected modtime, expected %v got %v", modTime, fileMetadata.ModTime)
   598  	}
   599  	if fileMetadata.NumSkylinks != 1 {
   600  		t.Fatalf("NumSkylinks incorrect, expected %v got %v", 1, fileMetadata.NumSkylinks)
   601  	}
   602  	if fileMetadata.Lost {
   603  		t.Fatal("expected file not to be lost")
   604  	}
   605  }
   606  
   607  // TestCreateMissingSiaDir confirms that the repair code creates a siadir file
   608  // if one is not found
   609  func TestCreateMissingSiaDir(t *testing.T) {
   610  	if testing.Short() {
   611  		t.SkipNow()
   612  	}
   613  	t.Parallel()
   614  
   615  	// Create test renter
   616  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   617  	if err != nil {
   618  		t.Fatal(err)
   619  	}
   620  	defer func() {
   621  		if err := rt.Close(); err != nil {
   622  			t.Fatal(err)
   623  		}
   624  	}()
   625  
   626  	// Confirm the siadir file is on disk
   627  	siaDirPath := skymodules.RootSiaPath().SiaDirMetadataSysPath(rt.renter.staticFileSystem.Root())
   628  	_, err = os.Stat(siaDirPath)
   629  	if err != nil {
   630  		t.Fatal(err)
   631  	}
   632  
   633  	// Remove .siadir file on disk
   634  	err = os.Remove(siaDirPath)
   635  	if err != nil {
   636  		t.Fatal(err)
   637  	}
   638  
   639  	// Confirm siadir is gone
   640  	_, err = os.Stat(siaDirPath)
   641  	if !os.IsNotExist(err) {
   642  		t.Fatal("Err should have been IsNotExist", err)
   643  	}
   644  
   645  	// Create siadir file with managedDirectoryMetadata
   646  	_, err = rt.renter.managedDirectoryMetadata(skymodules.RootSiaPath())
   647  	if err != nil {
   648  		t.Fatal(err)
   649  	}
   650  
   651  	// Confirm it is on disk
   652  	_, err = os.Stat(siaDirPath)
   653  	if err != nil {
   654  		t.Fatal(err)
   655  	}
   656  }
   657  
   658  // TestAddStuckChunksToHeap probes the managedAddStuckChunksToHeap method
   659  func TestAddStuckChunksToHeap(t *testing.T) {
   660  	if testing.Short() {
   661  		t.SkipNow()
   662  	}
   663  	t.Parallel()
   664  
   665  	// create renter with dependencies, first to disable the background health,
   666  	// repair, and stuck loops from running, then update it to bypass the worker
   667  	// pool length check in managedBuildUnfinishedChunks
   668  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   669  	if err != nil {
   670  		t.Fatal(err)
   671  	}
   672  	defer func() {
   673  		if err := rt.Close(); err != nil {
   674  			t.Fatal(err)
   675  		}
   676  	}()
   677  
   678  	// create file with no stuck chunks
   679  	rsc, _ := skymodules.NewRSCode(1, 1)
   680  	up := skymodules.FileUploadParams{
   681  		Source:      "",
   682  		SiaPath:     skymodules.RandomSiaPath(),
   683  		ErasureCode: rsc,
   684  	}
   685  	f, err := rt.newTestSiaFile(up.SiaPath, up.Source, up.ErasureCode, 100)
   686  	if err != nil {
   687  		t.Fatal(err)
   688  	}
   689  
   690  	// Create maps for method inputs
   691  	hosts := make(map[string]struct{})
   692  	offline := make(map[string]bool)
   693  	goodForRenew := make(map[string]bool)
   694  
   695  	// Manually add workers to worker pool
   696  	rt.renter.staticWorkerPool.mu.Lock()
   697  	for i := 0; i < int(f.NumChunks()); i++ {
   698  		rt.renter.staticWorkerPool.workers[fmt.Sprint(i)] = &worker{
   699  			wakeChan: make(chan struct{}, 1),
   700  		}
   701  	}
   702  	rt.renter.staticWorkerPool.mu.Unlock()
   703  
   704  	// call managedAddStuckChunksToHeap, no chunks should be added
   705  	err = rt.renter.managedAddStuckChunksToHeap(up.SiaPath, hosts, offline, goodForRenew)
   706  	if !errors.Contains(err, errNoStuckChunks) {
   707  		t.Fatal(err)
   708  	}
   709  	if rt.renter.staticUploadHeap.managedLen() != 0 {
   710  		t.Fatal("Expected uploadHeap to be of length 0 got", rt.renter.staticUploadHeap.managedLen())
   711  	}
   712  
   713  	// make chunk stuck
   714  	if err = f.SetStuck(uint64(0), true); err != nil {
   715  		t.Fatal(err)
   716  	}
   717  
   718  	// call managedAddStuckChunksToHeap, chunk should be added to heap
   719  	err = rt.renter.managedAddStuckChunksToHeap(up.SiaPath, hosts, offline, goodForRenew)
   720  	if err != nil {
   721  		t.Fatal(err)
   722  	}
   723  	if rt.renter.staticUploadHeap.managedLen() != 1 {
   724  		t.Fatal("Expected uploadHeap to be of length 1 got", rt.renter.staticUploadHeap.managedLen())
   725  	}
   726  
   727  	// Pop chunk, chunk should be marked as fileRecentlySuccessful true
   728  	chunk := rt.renter.staticUploadHeap.managedPop()
   729  	if !chunk.fileRecentlySuccessful {
   730  		t.Fatal("chunk not marked as fileRecentlySuccessful true")
   731  	}
   732  }
   733  
   734  // TestRandomStuckFileRegression tests an edge case where no siapath was being
   735  // returned from managedStuckFile.
   736  func TestRandomStuckFileRegression(t *testing.T) {
   737  	if testing.Short() {
   738  		t.SkipNow()
   739  	}
   740  	t.Parallel()
   741  
   742  	// Create Renter
   743  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   744  	if err != nil {
   745  		t.Fatal(err)
   746  	}
   747  	defer func() {
   748  		if err := rt.Close(); err != nil {
   749  			t.Fatal(err)
   750  		}
   751  	}()
   752  
   753  	// Create 1 file at root with all chunks stuck
   754  	file, err := rt.renter.newRenterTestFile()
   755  	if err != nil {
   756  		t.Fatal(err)
   757  	}
   758  	siaPath := rt.renter.staticFileSystem.FileSiaPath(file)
   759  	err = rt.renter.SetFileStuck(siaPath, true)
   760  	if err != nil {
   761  		t.Fatal(err)
   762  	}
   763  
   764  	// Set the root directories metadata to have a large number of aggregate
   765  	// stuck chunks. Since there is only 1 stuck chunk this was causing the
   766  	// likelihood of the stuck file being chosen to be very low.
   767  	rootDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath())
   768  	if err != nil {
   769  		t.Fatal(err)
   770  	}
   771  	md, err := rootDir.Metadata()
   772  	if err != nil {
   773  		t.Fatal(err)
   774  	}
   775  	md.AggregateNumStuckChunks = 50000
   776  	md.NumStuckChunks = 1
   777  	md.NumFiles = 1
   778  	err = rootDir.UpdateMetadata(md)
   779  	if err != nil {
   780  		t.Fatal(err)
   781  	}
   782  
   783  	stuckSiaPath, err := rt.renter.managedStuckFile(skymodules.RootSiaPath())
   784  	if err != nil {
   785  		t.Fatal(err)
   786  	}
   787  	if !stuckSiaPath.Equals(siaPath) {
   788  		t.Fatalf("Stuck siapath should have been the one file in the directory, expected %v got %v", siaPath, stuckSiaPath)
   789  	}
   790  }