mirror of
https://github.com/kopia/kopia.git
synced 2026-01-27 15:58:03 -05:00
The dual time measurement is described in https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md The fix is to discard hidden monotonic time component of time.Time by converting to unix time and back. Reviewed usage of clock.Now() and replaced with timetrack.StartTimer() when measuring time. The problem in #1402 was that passage of time was measured using the monotonic time and not wall clock time. When the computer goes to sleep, monotonic time is still monotonic while wall clock time makes a leap when the computer wakes up. This is the behavior that epoch manager (and most other compontents in Kopia) rely upon. Fixes #1402 Co-authored-by: Julio Lopez <julio+gh@kasten.io>
232 lines
6.2 KiB
Go
232 lines
6.2 KiB
Go
// Package testenv contains Environment for use in testing.
|
|
package testenv
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/kopia/kopia/internal/clock"
|
|
"github.com/kopia/kopia/internal/testutil"
|
|
"github.com/kopia/kopia/internal/timetrack"
|
|
)
|
|
|
|
const (
|
|
// TestRepoPassword is a password for repositories created in tests.
|
|
TestRepoPassword = "qWQPJ2hiiLgWRRCr"
|
|
)
|
|
|
|
// CLIRunner encapsulates running kopia subcommands for testing purposes.
|
|
// It supports implementations that use subprocesses or in-process invocations.
|
|
type CLIRunner interface {
|
|
Start(t *testing.T, args []string) (stdout, stderr io.Reader, wait func() error, kill func())
|
|
}
|
|
|
|
// CLITest encapsulates state for a CLI-based test.
|
|
type CLITest struct {
|
|
startTime time.Time
|
|
|
|
RepoDir string
|
|
ConfigDir string
|
|
|
|
Runner CLIRunner
|
|
|
|
fixedArgs []string
|
|
|
|
DefaultRepositoryCreateFlags []string
|
|
}
|
|
|
|
// RepoFormatNotImportant chooses arbitrary format version where it's not important to the test.
|
|
var RepoFormatNotImportant []string
|
|
|
|
// NewCLITest creates a new instance of *CLITest.
|
|
func NewCLITest(t *testing.T, repoCreateFlags []string, runner CLIRunner) *CLITest {
|
|
t.Helper()
|
|
configDir := testutil.TempDirectory(t)
|
|
|
|
fixedArgs := []string{
|
|
// use per-test config file, to avoid clobbering current user's setup.
|
|
"--config-file", filepath.Join(configDir, ".kopia.config"),
|
|
}
|
|
|
|
// disable the use of keyring
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
fixedArgs = append(fixedArgs, "--no-use-keychain")
|
|
case "windows":
|
|
fixedArgs = append(fixedArgs, "--no-use-credential-manager")
|
|
case "linux":
|
|
fixedArgs = append(fixedArgs, "--no-use-keyring")
|
|
}
|
|
|
|
var formatFlags []string
|
|
|
|
formatFlags = append(formatFlags, repoCreateFlags...)
|
|
|
|
if testutil.ShouldReduceTestComplexity() {
|
|
formatFlags = append(formatFlags,
|
|
"--encryption", "CHACHA20-POLY1305-HMAC-SHA256",
|
|
"--block-hash", "BLAKE2S-256")
|
|
}
|
|
|
|
return &CLITest{
|
|
startTime: clock.Now(),
|
|
RepoDir: testutil.TempDirectory(t),
|
|
ConfigDir: configDir,
|
|
fixedArgs: fixedArgs,
|
|
DefaultRepositoryCreateFlags: formatFlags,
|
|
Runner: runner,
|
|
}
|
|
}
|
|
|
|
// RunAndExpectSuccess runs the given command, expects it to succeed and returns its output lines.
|
|
func (e *CLITest) RunAndExpectSuccess(t *testing.T, args ...string) []string {
|
|
t.Helper()
|
|
|
|
stdout, _, err := e.Run(t, false, args...)
|
|
if err != nil {
|
|
t.Fatalf("'kopia %v' failed with %v", strings.Join(args, " "), err)
|
|
}
|
|
|
|
return stdout
|
|
}
|
|
|
|
// RunAndProcessStderr runs the given command, and streams its output line-by-line to a given function until it returns false.
|
|
func (e *CLITest) RunAndProcessStderr(t *testing.T, callback func(line string) bool, args ...string) (kill func()) {
|
|
t.Helper()
|
|
|
|
stdout, stderr, _, kill := e.Runner.Start(t, e.cmdArgs(args))
|
|
go io.Copy(io.Discard, stdout)
|
|
|
|
scanner := bufio.NewScanner(stderr)
|
|
for scanner.Scan() {
|
|
if !callback(scanner.Text()) {
|
|
break
|
|
}
|
|
}
|
|
|
|
// complete the scan in background without processing lines.
|
|
go func() {
|
|
for scanner.Scan() {
|
|
// ignore
|
|
}
|
|
}()
|
|
|
|
return kill
|
|
}
|
|
|
|
// RunAndExpectSuccessWithErrOut runs the given command, expects it to succeed and returns its stdout and stderr lines.
|
|
func (e *CLITest) RunAndExpectSuccessWithErrOut(t *testing.T, args ...string) (stdout, stderr []string) {
|
|
t.Helper()
|
|
|
|
stdout, stderr, err := e.Run(t, false, args...)
|
|
if err != nil {
|
|
t.Fatalf("'kopia %v' failed with %v", strings.Join(args, " "), err)
|
|
}
|
|
|
|
return stdout, stderr
|
|
}
|
|
|
|
// RunAndExpectFailure runs the given command, expects it to fail and returns its output lines.
|
|
func (e *CLITest) RunAndExpectFailure(t *testing.T, args ...string) []string {
|
|
t.Helper()
|
|
|
|
stdout, _, err := e.Run(t, true, args...)
|
|
if err == nil {
|
|
t.Fatalf("'kopia %v' succeeded, but expected failure", strings.Join(args, " "))
|
|
}
|
|
|
|
return stdout
|
|
}
|
|
|
|
// RunAndVerifyOutputLineCount runs the given command and asserts it returns the given number of output lines, then returns them.
|
|
func (e *CLITest) RunAndVerifyOutputLineCount(t *testing.T, wantLines int, args ...string) []string {
|
|
t.Helper()
|
|
|
|
lines := e.RunAndExpectSuccess(t, args...)
|
|
if len(lines) != wantLines {
|
|
t.Fatalf("unexpected list of results of 'kopia %v': %v lines (%v) wanted %v", strings.Join(args, " "), len(lines), lines, wantLines)
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
func (e *CLITest) cmdArgs(args []string) []string {
|
|
var suffix []string
|
|
|
|
// detect repository creation and override DefaultRepositoryCreateFlags for best
|
|
// performance on the current platform.
|
|
if len(args) >= 2 && (args[0] == "repo" && args[1] == "create") {
|
|
suffix = e.DefaultRepositoryCreateFlags
|
|
}
|
|
|
|
return append(append(append([]string(nil), e.fixedArgs...), args...), suffix...)
|
|
}
|
|
|
|
// Run executes kopia with given arguments and returns the output lines.
|
|
func (e *CLITest) Run(t *testing.T, expectedError bool, args ...string) (stdout, stderr []string, err error) {
|
|
t.Helper()
|
|
|
|
args = e.cmdArgs(args)
|
|
t.Logf("running 'kopia %v'", strings.Join(args, " "))
|
|
|
|
timer := timetrack.StartTimer()
|
|
|
|
stdoutReader, stderrReader, wait, _ := e.Runner.Start(t, args)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
scanner := bufio.NewScanner(stdoutReader)
|
|
for scanner.Scan() {
|
|
if os.Getenv("KOPIA_TEST_LOG_OUTPUT") != "" {
|
|
t.Logf("[stdout] %v", scanner.Text())
|
|
}
|
|
|
|
stdout = append(stdout, scanner.Text())
|
|
}
|
|
}()
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
scanner := bufio.NewScanner(stderrReader)
|
|
for scanner.Scan() {
|
|
if os.Getenv("KOPIA_TEST_LOG_OUTPUT") != "" {
|
|
t.Logf("[stderr] %v", scanner.Text())
|
|
}
|
|
|
|
stderr = append(stderr, scanner.Text())
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
gotErr := wait()
|
|
|
|
if expectedError {
|
|
require.Error(t, gotErr, "unexpected success when running 'kopia %v' (stdout:\n%v\nstderr:\n%v", strings.Join(args, " "), strings.Join(stdout, "\n"), strings.Join(stderr, "\n"))
|
|
} else {
|
|
require.NoError(t, gotErr, "unexpected error when running 'kopia %v' (stdout:\n%v\nstderr:\n%v", strings.Join(args, " "), strings.Join(stdout, "\n"), strings.Join(stderr, "\n"))
|
|
}
|
|
|
|
// nolint:forbidigo
|
|
t.Logf("finished in %v: 'kopia %v'", timer.Elapsed().Milliseconds(), strings.Join(args, " "))
|
|
|
|
return stdout, stderr, gotErr
|
|
}
|