Files
kopia/tests/robustness/engine/engine.go
Nathan Baulch 657fda216a chore(ci): upgrade to golangci-lint 2.6.1 (#4973)
- upgrade to golangci-lint 2.6.1
- updates for gosec
- updates for govet
- updates for perfsprint
- updates modernize

Leaves out modernize:omitempty due to conflicts with tests
2025-11-11 21:27:10 -08:00

216 lines
4.8 KiB
Go

//go:build darwin || (linux && amd64)
// Package engine provides the framework for a snapshot repository testing engine
package engine
import (
"context"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"sync"
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/tests/robustness"
"github.com/kopia/kopia/tests/robustness/checker"
)
var (
// ErrInvalidArgs is returned if the constructor arguments are incorrect.
ErrInvalidArgs = fmt.Errorf("invalid arguments")
noSpaceOnDeviceMatchStr = "no space left on device"
)
// Args contain the parameters for the engine constructor.
type Args struct {
// Interfaces used by the engine.
MetaStore robustness.Persister
TestRepo robustness.Snapshotter
FileWriter robustness.FileWriter
// WorkingDir is a directory to use for temporary data.
WorkingDir string
// SyncRepositories should be set to true to reconcile differences.
SyncRepositories bool
}
// Validate checks the arguments for correctness.
func (a *Args) Validate() error {
if a.MetaStore == nil || a.TestRepo == nil || a.FileWriter == nil || a.WorkingDir == "" {
return ErrInvalidArgs
}
return nil
}
// New creates an Engine.
func New(args *Args) (*Engine, error) {
if err := args.Validate(); err != nil {
return nil, err
}
var (
e = &Engine{
MetaStore: args.MetaStore,
TestRepo: args.TestRepo,
FileWriter: args.FileWriter,
baseDirPath: args.WorkingDir,
RunStats: Stats{
RunCounter: 1,
CreationTime: clock.Now(),
PerActionStats: make(map[ActionKey]*ActionStats),
},
}
err error
)
if err = e.setupLogging(); err != nil {
e.cleanComponents()
return nil, err
}
e.Checker, err = checker.NewChecker(e.TestRepo, e.MetaStore, e.baseDirPath)
if err != nil {
e.cleanComponents()
return nil, err
}
e.Checker.RecoveryMode = args.SyncRepositories
e.cleanupRoutines = append(e.cleanupRoutines, e.Checker.Cleanup)
return e, nil
}
// Engine is the outer level testing framework for robustness testing.
type Engine struct {
FileWriter robustness.FileWriter
TestRepo robustness.Snapshotter
MetaStore robustness.Persister
Checker *checker.Checker
cleanupRoutines []func()
baseDirPath string
RunStats Stats
CumulativeStats Stats
statsMux sync.RWMutex
EngineLog Log
logMux sync.RWMutex
}
// Shutdown makes a last snapshot then flushes the metadata and prints the final statistics.
func (e *Engine) Shutdown(ctx context.Context) error {
// Perform a snapshot action to capture the state of the data directory
// at the end of the run
lastWriteEntry := e.EngineLog.FindLastThisRun(WriteRandomFilesActionKey)
lastSnapEntry := e.EngineLog.FindLastThisRun(SnapshotDirActionKey)
if lastWriteEntry != nil {
if lastSnapEntry == nil || lastSnapEntry.Idx < lastWriteEntry.Idx {
// Only force a final snapshot if the data tree has been modified since the last snapshot
e.ExecAction(ctx, SnapshotDirActionKey, make(map[string]string)) //nolint:errcheck
}
}
var cleanupSummaryBuilder strings.Builder
cleanupSummaryBuilder.WriteString("\n================\n")
cleanupSummaryBuilder.WriteString("Cleanup Summary:\n\n")
cleanupSummaryBuilder.WriteString(e.Stats())
cleanupSummaryBuilder.WriteString("\n\n")
cleanupSummaryBuilder.WriteString(e.EngineLog.StringThisRun())
cleanupSummaryBuilder.WriteString("\n")
log.Print(cleanupSummaryBuilder.String())
e.RunStats.RunTime = clock.Now().Sub(e.RunStats.CreationTime)
e.CumulativeStats.RunTime += e.RunStats.RunTime
defer e.cleanComponents()
if e.MetaStore != nil {
err := e.saveLog(ctx)
if err != nil {
return err
}
err = e.saveStats(ctx)
if err != nil {
return err
}
err = e.saveSnapIDIndex(ctx)
if err != nil {
return err
}
return e.MetaStore.FlushMetadata()
}
return nil
}
func (e *Engine) setupLogging() error {
dirPath := e.MetaStore.GetPersistDir()
newLogPath := filepath.Join(dirPath, e.formatLogName())
f, err := os.Create(newLogPath)
if err != nil {
return err
}
// Write to both stderr and persistent log file
wrt := io.MultiWriter(os.Stderr, f)
log.SetOutput(wrt)
return nil
}
func (e *Engine) formatLogName() string {
st := e.RunStats.CreationTime
return fmt.Sprintf("Log_%s", st.Format("2006_01_02_15_04_05"))
}
// cleanComponents cleans up each component part of the test engine.
func (e *Engine) cleanComponents() {
for _, f := range e.cleanupRoutines {
if f != nil {
f()
}
}
}
// Init initializes the Engine and performs a consistency check.
func (e *Engine) Init(ctx context.Context) error {
err := e.MetaStore.LoadMetadata()
if err != nil {
return err
}
err = e.loadStats(ctx)
if err != nil {
return err
}
e.CumulativeStats.RunCounter++
err = e.loadLog(ctx)
if err != nil {
return err
}
err = e.loadSnapIDIndex(ctx)
if err != nil {
return err
}
return e.Checker.VerifySnapshotMetadata(ctx)
}