Repository: kopia/kopia
Pull request #3514 (2023-12-16 11:02:08)

fix(cli): print errors during processing in red
My backup had a fatal error. The end of the log looked like this:

```
Created snapshot with root k5ab05dd5a8aaf9da8a6a822abd0afabb and ID 04caa6e10f4e2866a74492a4162ea943 in 2m44s
WARN Ignored 943 error(s) while snapshotting root@tower:/.
 ERROR Found 1 fatal error(s) while snapshotting root@tower:/.
```

Note that "WARN" is yellow and "ERROR" is red.

Since I got a fatal error, I wanted to check what the fatal error was in the log. Because "ERROR" in the lines above is red, I expected the fatal error in the kopia log to also be red, but it was yellow like the non-fatal errors. This was unexpected to me.

Also note that I have lots of `! Ignored error when processing` in the kopia log because I also backup Docker containers, so right now it is not easy to find the fatal error among the non-fatal errors because non-fatal and fatal errors have the same color.

NB: Not sure if this is a "fix:" or "chore:" in conventional commits. I guess it depends on what you define as the interface.


Patch:
diff --git a/cli/cli_progress.go b/cli/cli_progress.go
index a2b4c377..fa5fda5d 100644
--- a/cli/cli_progress.go
+++ b/cli/cli_progress.go
@@ -94,7 +94,7 @@ func (p *cliProgress) Error(path string, err error, isIgnored bool) {
 		p.output(warningColor, fmt.Sprintf("Ignored error when processing \"%v\": %v\n", path, err))
 	} else {
 		p.fatalErrorCount.Add(1)
-		p.output(warningColor, fmt.Sprintf("Error when processing \"%v\": %v\n", path, err))
+		p.output(errorColor, fmt.Sprintf("Error when processing \"%v\": %v\n", path, err))
 	}
 }
 
diff --git a/internal/epoch/epoch_manager.go b/internal/epoch/epoch_manager.go
index 524521d4..c149c3bf 100644
--- a/internal/epoch/epoch_manager.go
+++ b/internal/epoch/epoch_manager.go
@@ -630,7 +630,7 @@ func (e *Manager) loadUncompactedEpochs(ctx context.Context, min, max int) (map[
 // refreshAttemptLocked attempts to load the committedState of
 // the index and updates `lastKnownState` state atomically when complete.
 func (e *Manager) refreshAttemptLocked(ctx context.Context) error {
-	e.log.Debugf("refreshAttemptLocked")
+	e.log.Debug("refreshAttemptLocked")
 
 	p, perr := e.getParameters()
 	if perr != nil {
@@ -679,7 +679,7 @@ func (e *Manager) refreshAttemptLocked(ctx context.Context) error {
 		cs.ValidUntil.Format(time.RFC3339Nano))
 
 	if !e.st.IsReadOnly() && shouldAdvance(cs.UncompactedEpochSets[cs.WriteEpoch], p.MinEpochDuration, p.EpochAdvanceOnCountThreshold, p.EpochAdvanceOnTotalSizeBytesThreshold) {
-		if err := e.advanceEpoch(ctx, cs); err != nil {
+		if err := e.advanceEpochMarker(ctx, cs); err != nil {
 			return errors.Wrap(err, "error advancing epoch")
 		}
 	}
@@ -703,7 +703,7 @@ func (e *Manager) refreshAttemptLocked(ctx context.Context) error {
 	return nil
 }
 
-func (e *Manager) advanceEpoch(ctx context.Context, cs CurrentSnapshot) error {
+func (e *Manager) advanceEpochMarker(ctx context.Context, cs CurrentSnapshot) error {
 	blobID := blob.ID(fmt.Sprintf("%v%v", string(EpochMarkerIndexBlobPrefix), cs.WriteEpoch+1))
 
 	if err := e.st.PutBlob(ctx, blobID, gather.FromSlice([]byte("epoch-marker")), blob.PutOptions{}); err != nil {
@@ -767,7 +767,7 @@ func (e *Manager) WriteIndex(ctx context.Context, dataShards map[blob.ID]blob.By
 	writtenForEpoch := -1
 
 	for {
-		e.log.Debugf("refreshAttemptLocked")
+		e.log.Debug("WriteIndex")
 
 		p, err := e.getParameters()
 		if err != nil {
diff --git a/internal/faketime/faketime.go b/internal/faketime/faketime.go
index f4b1a8e1..ef18e805 100644
--- a/internal/faketime/faketime.go
+++ b/internal/faketime/faketime.go
@@ -17,11 +17,11 @@ func Frozen(t time.Time) func() time.Time {
 }
 
 // AutoAdvance returns a time source function that returns a time equal to
-// 't + ((n - 1) * dt)' wheren n is the number of serialized invocations of
+// 'start + ((n - 1) * dt)' wheren n is the number of serialized invocations of
 // the returned function. The returned function will generate a time series of
-// the form [t, t+dt, t+2dt, t+3dt, ...].
-func AutoAdvance(t time.Time, dt time.Duration) func() time.Time {
-	return NewTimeAdvance(t, dt).NowFunc()
+// the form [start, start+dt, start+2dt, start+3dt, ...].
+func AutoAdvance(start time.Time, dt time.Duration) func() time.Time {
+	return NewAutoAdvance(start, dt).NowFunc()
 }
 
 // TimeAdvance allows controlling the passage of time. Intended to be used in
@@ -32,8 +32,15 @@ type TimeAdvance struct {
 	base   time.Time
 }
 
-// NewTimeAdvance creates a TimeAdvance with the given start time.
-func NewTimeAdvance(start time.Time, autoDelta time.Duration) *TimeAdvance {
+// NewTimeAdvance creates a TimeAdvance clock with the given start time.
+// The returned clock does not automatically advance time when NowFunc is called.
+func NewTimeAdvance(start time.Time) *TimeAdvance {
+	return NewAutoAdvance(start, 0)
+}
+
+// NewAutoAdvance creates an auto-advancing clock with the given start time and
+// autoDelta automatic time increase en each call to NowFunc().
+func NewAutoAdvance(start time.Time, autoDelta time.Duration) *TimeAdvance {
 	return &TimeAdvance{
 		autoDt: int64(autoDelta),
 		base:   start,
@@ -80,8 +87,8 @@ func (t *ClockTimeWithOffset) NowFunc() func() time.Time {
 	}
 }
 
-// Advance advances t by dt, such that the next call to t.NowFunc()() returns
-// current t + dt.
+// Advance increases the time offset by dt, such that the next call to
+// t.NowFunc()() returns current time + new offset.
 func (t *ClockTimeWithOffset) Advance(dt time.Duration) time.Time {
 	t.mu.Lock()
 	defer t.mu.Unlock()
diff --git a/repo/content/committed_read_manager.go b/repo/content/committed_read_manager.go
index 04baec19..3ac6d67b 100644
--- a/repo/content/committed_read_manager.go
+++ b/repo/content/committed_read_manager.go
@@ -443,7 +443,7 @@ func indexBlobCacheSweepSettings(caching *CachingOptions) cache.SweepSettings {
 	}
 }
 
-func (sm *SharedManager) setupReadManagerCaches(ctx context.Context, caching *CachingOptions, mr *metrics.Registry) error {
+func (sm *SharedManager) setupCachesAndIndexManagers(ctx context.Context, caching *CachingOptions, mr *metrics.Registry) error {
 	dataCache, err := cache.NewContentCache(ctx, sm.st, cache.Options{
 		BaseCacheDirectory: caching.CacheDirectory,
 		CacheSubDir:        "contents",
@@ -642,7 +642,7 @@ func NewSharedManager(ctx context.Context, st blob.Storage, prov format.Provider
 
 	caching = caching.CloneOrDefault()
 
-	if err := sm.setupReadManagerCaches(ctx, caching, mr); err != nil {
+	if err := sm.setupCachesAndIndexManagers(ctx, caching, mr); err != nil {
 		return nil, errors.Wrap(err, "error setting up read manager caches")
 	}
 
diff --git a/repo/content/indexblob/index_blob_manager_v1.go b/repo/content/indexblob/index_blob_manager_v1.go
index 45841ed3..d9a415db 100644
--- a/repo/content/indexblob/index_blob_manager_v1.go
+++ b/repo/content/indexblob/index_blob_manager_v1.go
@@ -25,7 +25,7 @@ type ManagerV1 struct {
 	formattingOptions IndexFormattingOptions
 	log               logging.Logger
 
-	EpochMgr *epoch.Manager
+	epochMgr *epoch.Manager
 }
 
 // ListIndexBlobInfos list active blob info structs.  Also returns time of latest content deletion commit.
@@ -36,7 +36,7 @@ func (m *ManagerV1) ListIndexBlobInfos(ctx context.Context) ([]Metadata, time.Ti
 // ListActiveIndexBlobs lists the metadata for active index blobs and returns the cut-off time
 // before which all deleted index entries should be treated as non-existent.
 func (m *ManagerV1) ListActiveIndexBlobs(ctx context.Context) ([]Metadata, time.Time, error) {
-	active, deletionWatermark, err := m.EpochMgr.GetCompleteIndexSet(ctx, epoch.LatestEpoch)
+	active, deletionWatermark, err := m.epochMgr.GetCompleteIndexSet(ctx, epoch.LatestEpoch)
 	if err != nil {
 		return nil, time.Time{}, errors.Wrap(err, "error getting index set")
 	}
@@ -54,7 +54,7 @@ func (m *ManagerV1) ListActiveIndexBlobs(ctx context.Context) ([]Metadata, time.
 
 // Invalidate clears any read caches.
 func (m *ManagerV1) Invalidate() {
-	m.EpochMgr.Invalidate()
+	m.epochMgr.Invalidate()
 }
 
 // Compact advances the deletion watermark.
@@ -63,7 +63,7 @@ func (m *ManagerV1) Compact(ctx context.Context, opt CompactOptions) error {
 		return nil
 	}
 
-	return errors.Wrap(m.EpochMgr.AdvanceDeletionWatermark(ctx, opt.DropDeletedBefore), "error advancing deletion watermark")
+	return errors.Wrap(m.epochMgr.AdvanceDeletionWatermark(ctx, opt.DropDeletedBefore), "error advancing deletion watermark")
 }
 
 // CompactEpoch compacts the provided index blobs and writes a new set of blobs.
@@ -115,7 +115,7 @@ func (m *ManagerV1) CompactEpoch(ctx context.Context, blobIDs []blob.ID, outputP
 	return nil
 }
 
-// WriteIndexBlobs writes the provided data shards into new index blobs oprionally appending the provided suffix.
+// WriteIndexBlobs writes dataShards into new index blobs with an optional blob name suffix.
 // The writes are atomic in the sense that if any of them fails, the reader will
 // ignore all of the indexes that share the same suffix.
 func (m *ManagerV1) WriteIndexBlobs(ctx context.Context, dataShards []gather.Bytes, suffix blob.ID) ([]blob.Metadata, error) {
@@ -138,12 +138,12 @@ func (m *ManagerV1) WriteIndexBlobs(ctx context.Context, dataShards []gather.Byt
 	}
 
 	//nolint:wrapcheck
-	return m.EpochMgr.WriteIndex(ctx, shards)
+	return m.epochMgr.WriteIndex(ctx, shards)
 }
 
 // EpochManager returns the epoch manager.
 func (m *ManagerV1) EpochManager() *epoch.Manager {
-	return m.EpochMgr
+	return m.epochMgr
 }
 
 // PrepareUpgradeToIndexBlobManagerV1 prepares the repository for migrating to IndexBlobManagerV1.
@@ -182,7 +182,7 @@ func NewManagerV1(
 		log:               log,
 		formattingOptions: formattingOptions,
 
-		EpochMgr: epochMgr,
+		epochMgr: epochMgr,
 	}
 }
 
diff --git a/repo/maintenance/maintenance_run.go b/repo/maintenance/maintenance_run.go
index 455e0da0..794df411 100644
--- a/repo/maintenance/maintenance_run.go
+++ b/repo/maintenance/maintenance_run.go
@@ -299,6 +299,7 @@ func runQuickMaintenance(ctx context.Context, runParams RunParameters, safety Sa
 		return errors.Wrap(err, "error performing index compaction")
 	}
 
+	// clean up logs last
 	if err := runTaskCleanupLogs(ctx, runParams, s); err != nil {
 		return errors.Wrap(err, "error cleaning up logs")
 	}
@@ -448,14 +449,15 @@ func runFullMaintenance(ctx context.Context, runParams RunParameters, safety Saf
 		log(ctx).Debug("Extending object lock retention-period is disabled.")
 	}
 
-	if err := runTaskCleanupLogs(ctx, runParams, s); err != nil {
-		return errors.Wrap(err, "error cleaning up logs")
-	}
-
 	if err := runTaskCleanupEpochManager(ctx, runParams, s); err != nil {
 		return errors.Wrap(err, "error cleaning up epoch manager")
 	}
 
+	// clean up logs last
+	if err := runTaskCleanupLogs(ctx, runParams, s); err != nil {
+		return errors.Wrap(err, "error cleaning up logs")
+	}
+
 	return nil
 }
 


Test patch:
diff --git a/internal/epoch/epoch_advance_test.go b/internal/epoch/epoch_advance_test.go
index 1f113540..bf92f78b 100644
--- a/internal/epoch/epoch_advance_test.go
+++ b/internal/epoch/epoch_advance_test.go
@@ -9,9 +9,8 @@
 	"github.com/kopia/kopia/repo/blob"
 )
 
-var def = DefaultParameters()
-
 func TestShouldAdvanceEpoch(t *testing.T) {
+	def := DefaultParameters()
 	t0 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
 
 	var lotsOfMetadata []blob.Metadata
diff --git a/internal/epoch/epoch_manager_test.go b/internal/epoch/epoch_manager_test.go
index a55bc7bc..06ee0ead 100644
--- a/internal/epoch/epoch_manager_test.go
+++ b/internal/epoch/epoch_manager_test.go
@@ -88,12 +88,10 @@ func newTestEnv(t *testing.T) *epochManagerTestEnv {
 
 	data := blobtesting.DataMap{}
 	ft := faketime.NewClockTimeWithOffset(0)
-	st := blobtesting.NewMapStorage(data, nil, ft.NowFunc())
-	unloggedst := st
-	fs := blobtesting.NewFaultyStorage(st)
-	st = fs
-	st = logging.NewWrapper(st, testlogging.NewTestLogger(t), "[STORAGE] ")
-	te := &epochManagerTestEnv{unloggedst: unloggedst, st: st, ft: ft}
+	ms := blobtesting.NewMapStorage(data, nil, ft.NowFunc())
+	fs := blobtesting.NewFaultyStorage(ms)
+	st := logging.NewWrapper(fs, testlogging.NewTestLogger(t), "[STORAGE] ")
+	te := &epochManagerTestEnv{unloggedst: ms, st: st, ft: ft}
 	m := NewManager(te.st, parameterProvider{&Parameters{
 		Enabled:                 true,
 		EpochRefreshFrequency:   20 * time.Minute,
@@ -392,27 +390,26 @@ func TestIndexEpochManager_NoCompactionInReadOnly(t *testing.T) {
 
 	// Use assert.Eventually here so we'll exit the test early instead of getting
 	// stuck until the timeout.
-	var (
-		loadedDone bool
-		loadedErr  error
-	)
+	loadedDone := &atomic.Bool{}
+
+	var loadedErr atomic.Value
 
 	go func() {
-		defer func() {
-			loadedDone = true
-		}()
+		if err := te2.mgr.Refresh(ctx); err != nil {
+			loadedErr.Store(err)
+		}
 
-		loadedErr = te2.mgr.Refresh(ctx)
 		te2.mgr.backgroundWork.Wait()
+		loadedDone.Store(true)
 	}()
 
-	if !assert.Eventually(t, func() bool { return loadedDone }, time.Second*5, time.Second) {
+	if !assert.Eventually(t, loadedDone.Load, time.Second*5, time.Second) {
 		// Return early so we don't report some odd failure on the error check below
 		// when we just never managed to initialize the epoch manager.
 		return
 	}
 
-	assert.NoError(t, loadedErr, "refreshing read-only index")
+	assert.Nil(t, loadedErr.Load(), "refreshing read-only index")
 }
 
 func TestRefreshRetriesIfTakingTooLong(t *testing.T) {
@@ -826,7 +823,7 @@ func (e *Manager) forceAdvanceEpoch(ctx context.Context) error {
 
 	e.Invalidate()
 
-	if err := e.advanceEpoch(ctx, cs); err != nil {
+	if err := e.advanceEpochMarker(ctx, cs); err != nil {
 		return errors.Wrap(err, "error advancing epoch")
 	}
 
diff --git a/internal/faketime/faketime_test.go b/internal/faketime/faketime_test.go
index 8fd09c1a..3d70fe3f 100644
--- a/internal/faketime/faketime_test.go
+++ b/internal/faketime/faketime_test.go
@@ -78,7 +78,7 @@ func TestAutoAdvance(t *testing.T) {
 
 func TestTimeAdvance(t *testing.T) {
 	startTime := time.Date(2019, 1, 6, 0, 0, 0, 0, time.UTC)
-	ta := NewTimeAdvance(startTime, 0)
+	ta := NewTimeAdvance(startTime)
 	now := ta.NowFunc()
 
 	if got, want := now(), startTime; got != want {
@@ -101,7 +101,7 @@ func TestTimeAdvanceConcurrent(t *testing.T) {
 	)
 
 	startTime := time.Date(2018, 1, 6, 0, 0, 0, 0, time.UTC)
-	ta := NewTimeAdvance(startTime, 3*time.Second)
+	ta := NewAutoAdvance(startTime, 3*time.Second)
 	tchan := make(chan time.Time, 2*parallelism)
 
 	var wg sync.WaitGroup
diff --git a/internal/listcache/listcache_test.go b/internal/listcache/listcache_test.go
index 0dc6be4f..79ef7035 100644
--- a/internal/listcache/listcache_test.go
+++ b/internal/listcache/listcache_test.go
@@ -17,9 +17,9 @@
 var errFake = errors.New("fake")
 
 func TestListCache(t *testing.T) {
-	realStorageTime := faketime.NewTimeAdvance(time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC), 0)
+	realStorageTime := faketime.NewTimeAdvance(time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC))
 	realStorage := blobtesting.NewMapStorage(blobtesting.DataMap{}, nil, realStorageTime.NowFunc())
-	cacheTime := faketime.NewTimeAdvance(time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), 0)
+	cacheTime := faketime.NewTimeAdvance(time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC))
 	cachest := blobtesting.NewMapStorage(blobtesting.DataMap{}, nil, cacheTime.NowFunc())
 
 	lc := NewWrapper(realStorage, cachest, []blob.ID{"n", "xe", "xb"}, []byte("hmac-secret"), 1*time.Minute).(*listCacheStorage)
diff --git a/internal/ownwrites/ownwrites_test.go b/internal/ownwrites/ownwrites_test.go
index fee49e21..0afdc8a2 100644
--- a/internal/ownwrites/ownwrites_test.go
+++ b/internal/ownwrites/ownwrites_test.go
@@ -16,9 +16,9 @@
 const testCacheDuration = 15 * time.Minute
 
 func TestOwnWrites(t *testing.T) {
-	realStorageTime := faketime.NewTimeAdvance(time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC), 0)
+	realStorageTime := faketime.NewTimeAdvance(time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC))
 	realStorage := blobtesting.NewMapStorage(blobtesting.DataMap{}, nil, realStorageTime.NowFunc())
-	cacheTime := faketime.NewTimeAdvance(time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), 0)
+	cacheTime := faketime.NewTimeAdvance(time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC))
 	cachest := blobtesting.NewMapStorage(blobtesting.DataMap{}, nil, cacheTime.NowFunc())
 
 	ec := blobtesting.NewEventuallyConsistentStorage(realStorage, 1*time.Hour, realStorageTime.NowFunc())
diff --git a/internal/repotesting/repotesting_test.go b/internal/repotesting/repotesting_test.go
index 68a53397..355bc9dd 100644
--- a/internal/repotesting/repotesting_test.go
+++ b/internal/repotesting/repotesting_test.go
@@ -19,7 +19,7 @@
 func TestTimeFuncWiring(t *testing.T) {
 	ctx, env := NewEnvironment(t, FormatNotImportant)
 
-	ft := faketime.NewTimeAdvance(time.Date(2018, time.February, 6, 0, 0, 0, 0, time.UTC), 0)
+	ft := faketime.NewTimeAdvance(time.Date(2018, time.February, 6, 0, 0, 0, 0, time.UTC))
 
 	// Re open with injected time
 	rep, err := repo.Open(ctx, env.RepositoryWriter.ConfigFilename(), env.Password, &repo.Options{TimeNowFunc: ft.NowFunc()})
diff --git a/repo/content/content_manager_test.go b/repo/content/content_manager_test.go
index 4f8f734e..fc86242f 100644
--- a/repo/content/content_manager_test.go
+++ b/repo/content/content_manager_test.go
@@ -354,7 +354,7 @@ func (s *contentManagerSuite) TestContentManagerFailedToWritePack(t *testing.T)
 	faulty := blobtesting.NewFaultyStorage(st)
 	st = faulty
 
-	ta := faketime.NewTimeAdvance(fakeTime, 0)
+	ta := faketime.NewTimeAdvance(fakeTime)
 
 	bm, err := NewManagerForTesting(testlogging.Context(t), st, mustCreateFormatProvider(t, &format.ContentFormat{
 		Hash:              "HMAC-SHA256-128",
diff --git a/repo/content/indexblob/index_blob_manager_v0_test.go b/repo/content/indexblob/index_blob_manager_v0_test.go
index 23efad31..0ecebbb8 100644
--- a/repo/content/indexblob/index_blob_manager_v0_test.go
+++ b/repo/content/indexblob/index_blob_manager_v0_test.go
@@ -71,8 +71,8 @@ func TestIndexBlobManager(t *testing.T) {
 			// fake underlying blob store with fake time
 			storageData := blobtesting.DataMap{}
 
-			fakeLocalTime := faketime.NewTimeAdvance(fakeLocalStartTime, 0)
-			fakeStorageTime := faketime.NewTimeAdvance(fakeStoreStartTime, 0)
+			fakeLocalTime := faketime.NewTimeAdvance(fakeLocalStartTime)
+			fakeStorageTime := faketime.NewTimeAdvance(fakeStoreStartTime)
 
 			st := blobtesting.NewMapStorage(storageData, nil, fakeStorageTime.NowFunc())
 			st = blobtesting.NewEventuallyConsistentStorage(st, testEventualConsistencySettleTime, fakeStorageTime.NowFunc())
@@ -280,7 +280,7 @@ func TestIndexBlobManagerPreventsResurrectOfDeletedContents(t *testing.T) {
 func TestCompactionCreatesPreviousIndex(t *testing.T) {
 	storageData := blobtesting.DataMap{}
 
-	fakeTime := faketime.NewTimeAdvance(fakeLocalStartTime, 0)
+	fakeTime := faketime.NewTimeAdvance(fakeLocalStartTime)
 	fakeTimeFunc := fakeTime.NowFunc()
 
 	st := blobtesting.NewMapStorage(storageData, nil, fakeTimeFunc)
@@ -358,7 +358,7 @@ func verifyIndexBlobManagerPreventsResurrectOfDeletedContents(t *testing.T, dela
 
 	storageData := blobtesting.DataMap{}
 
-	fakeTime := faketime.NewTimeAdvance(fakeLocalStartTime, 0)
+	fakeTime := faketime.NewTimeAdvance(fakeLocalStartTime)
 	fakeTimeFunc := fakeTime.NowFunc()
 
 	st := blobtesting.NewMapStorage(storageData, nil, fakeTimeFunc)
diff --git a/repo/format/format_manager_test.go b/repo/format/format_manager_test.go
index 07768cc5..8942cee8 100644
--- a/repo/format/format_manager_test.go
+++ b/repo/format/format_manager_test.go
@@ -52,7 +52,7 @@ func TestFormatManager(t *testing.T) {
 	ctx := testlogging.Context(t)
 
 	startTime := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)
-	ta := faketime.NewTimeAdvance(startTime, 0)
+	ta := faketime.NewTimeAdvance(startTime)
 	nowFunc := ta.NowFunc()
 	blobCache := format.NewMemoryBlobCache(nowFunc)
 
@@ -278,7 +278,7 @@ func TestUpdateRetentionNegativeValue(t *testing.T) {
 	ctx := testlogging.Context(t)
 
 	startTime := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)
-	ta := faketime.NewTimeAdvance(startTime, 0)
+	ta := faketime.NewTimeAdvance(startTime)
 	nowFunc := ta.NowFunc()
 
 	st := blobtesting.NewVersionedMapStorage(nowFunc)
@@ -329,7 +329,7 @@ func TestChangePassword(t *testing.T) {
 	ctx := testlogging.Context(t)
 
 	startTime := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)
-	ta := faketime.NewTimeAdvance(startTime, 0)
+	ta := faketime.NewTimeAdvance(startTime)
 	nowFunc := ta.NowFunc()
 	blobCache := format.NewMemoryBlobCache(nowFunc)
 
@@ -379,7 +379,7 @@ func TestFormatManagerValidDuration(t *testing.T) {
 		ctx := testlogging.Context(t)
 
 		startTime := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)
-		ta := faketime.NewTimeAdvance(startTime, 0)
+		ta := faketime.NewTimeAdvance(startTime)
 		nowFunc := ta.NowFunc()
 		blobCache := format.NewMemoryBlobCache(nowFunc)
 
diff --git a/snapshot/snapshotfs/upload_test.go b/snapshot/snapshotfs/upload_test.go
index 8e86e53a..37b9539a 100644
--- a/snapshot/snapshotfs/upload_test.go
+++ b/snapshot/snapshotfs/upload_test.go
@@ -90,7 +90,7 @@ func newUploadTestHarness(ctx context.Context, t *testing.T) *uploadTestHarness
 		panic("unable to connect to repository: " + conerr.Error())
 	}
 
-	ft := faketime.NewTimeAdvance(time.Date(2018, time.February, 6, 0, 0, 0, 0, time.UTC), 0)
+	ft := faketime.NewTimeAdvance(time.Date(2018, time.February, 6, 0, 0, 0, 0, time.UTC))
 
 	rep, err := repo.Open(ctx, configFile, masterPassword, &repo.Options{
 		TimeNowFunc: ft.NowFunc(),
diff --git a/snapshot/snapshotmaintenance/snapshotmaintenance_test.go b/snapshot/snapshotmaintenance/snapshotmaintenance_test.go
index a52c258d..6bde2200 100644
--- a/snapshot/snapshotmaintenance/snapshotmaintenance_test.go
+++ b/snapshot/snapshotmaintenance/snapshotmaintenance_test.go
@@ -236,7 +236,7 @@ func newTestHarness(t *testing.T, formatVersion format.Version) *testHarness {
 
 	baseTime := time.Date(2020, 9, 10, 0, 0, 0, 0, time.UTC)
 	th := &testHarness{
-		fakeTime:  faketime.NewTimeAdvance(baseTime, time.Second),
+		fakeTime:  faketime.NewAutoAdvance(baseTime, time.Second),
 		sourceDir: mockfs.NewDirectory(),
 	}