mirror of
https://github.com/kopia/kopia.git
synced 2026-03-17 13:46:12 -04:00
* logging: added logger wrappers for Broadcast and Prefix * nit: moved max hash size to a named constant * content: added internal logger * content: replaced context-based logging with explicit Loggers This will capture the logger.Logger associated with the context when the repository is opened and will reuse it for all logs instead of creating new logger for each log message. The new logger will also write logs to the internal logger in addition to writing to a log file/console. * cli: allow decrypting all blobs whose names start with _ * maintenance: added logs cleanup * cli: commands to view logs * cli: log selected command on each write session
280 lines
6.9 KiB
Go
280 lines
6.9 KiB
Go
// Package testenv contains Environment for use in testing.
|
|
package testenv
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/kopia/kopia/internal/clock"
|
|
"github.com/kopia/kopia/internal/testutil"
|
|
)
|
|
|
|
const (
|
|
// TestRepoPassword is a password for repositories created in tests.
|
|
TestRepoPassword = "qWQPJ2hiiLgWRRCr"
|
|
|
|
maxOutputLinesToLog = 4000
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// NewCLITest creates a new instance of *CLITest.
|
|
func NewCLITest(t *testing.T, 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
|
|
|
|
if testutil.ShouldReduceTestComplexity() {
|
|
formatFlags = []string{
|
|
"--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,
|
|
}
|
|
}
|
|
|
|
func dumpLogs(t *testing.T, dirname string) {
|
|
t.Helper()
|
|
|
|
entries, err := ioutil.ReadDir(dirname)
|
|
if err != nil {
|
|
t.Errorf("unable to read %v: %v", dirname, err)
|
|
|
|
return
|
|
}
|
|
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
dumpLogs(t, filepath.Join(dirname, e.Name()))
|
|
continue
|
|
}
|
|
|
|
dumpLogFile(t, filepath.Join(dirname, e.Name()))
|
|
}
|
|
}
|
|
|
|
func dumpLogFile(t *testing.T, fname string) {
|
|
t.Helper()
|
|
|
|
data, err := ioutil.ReadFile(fname)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
t.Logf("LOG FILE: %v %v", fname, trimOutput(string(data)))
|
|
}
|
|
|
|
// 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()
|
|
|
|
t.Logf("running 'kopia %v'", strings.Join(args, " "))
|
|
|
|
args = e.cmdArgs(args)
|
|
t0 := clock.Now()
|
|
|
|
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() {
|
|
stdout = append(stdout, scanner.Text())
|
|
}
|
|
}()
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
scanner := bufio.NewScanner(stderrReader)
|
|
for scanner.Scan() {
|
|
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"))
|
|
}
|
|
|
|
t.Logf("finished in %v: 'kopia %v'", clock.Since(t0).Milliseconds(), strings.Join(args, " "))
|
|
|
|
return stdout, stderr, gotErr
|
|
}
|
|
|
|
func trimOutput(s string) string {
|
|
lines := splitLines(s)
|
|
if len(lines) <= maxOutputLinesToLog {
|
|
return s
|
|
}
|
|
|
|
lines2 := append([]string(nil), lines[0:(maxOutputLinesToLog/2)]...)
|
|
lines2 = append(lines2, fmt.Sprintf("/* %v lines removed */", len(lines)-maxOutputLinesToLog))
|
|
lines2 = append(lines2, lines[len(lines)-(maxOutputLinesToLog/2):]...)
|
|
|
|
return strings.Join(lines2, "\n")
|
|
}
|
|
|
|
func splitLines(s string) []string {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
|
|
var result []string
|
|
for _, l := range strings.Split(s, "\n") {
|
|
result = append(result, strings.TrimRight(l, "\r"))
|
|
}
|
|
|
|
return result
|
|
}
|