test(infra): improved support for in-process testing (#2169)

* feat(infra): improved support for in-process testing

* support for killing of a running server using simulated Ctrl-C
* support for overriding os.Stdin
* migrated many tests from the exe runner to in-process runner

* added required indirection when defining Envar() so we can later override it in tests

* refactored CLI runners by moving environment overrides to CLITestEnv
This commit is contained in:
Jarek Kowalski
2022-07-09 18:22:50 -07:00
committed by GitHub
parent a621cd3fb6
commit 8515d050e5
47 changed files with 283 additions and 203 deletions

View File

@@ -21,6 +21,6 @@ jobs:
with:
fetch-depth: 0
- name: Unit Tests
run: make -j2 test UNIT_TEST_RACE_FLAGS=-race
run: make -j2 test UNIT_TEST_RACE_FLAGS=-race UNIT_TESTS_TIMEOUT=1200s
- name: Integration Tests
run: make -j2 ci-integration-tests INTEGRATION_TEST_RACE_FLAGS=-race

View File

@@ -26,6 +26,7 @@ linters-settings:
- time.Now # do not use outside of 'clock' and 'timetrack' packages use clock.Now or timetrack.StartTimer
- time.Since # use timetrack.Timer.Elapsed()
- time.Until # never use this
- Envar\(\" # do not use envar literals, always wrap with EnvName()
funlen:
lines: 100
statements: 60

View File

@@ -42,7 +42,7 @@ GOTESTSUM_FLAGS=--format=$(GOTESTSUM_FORMAT) --no-summary=skipped
GO_TEST?=$(gotestsum) $(GOTESTSUM_FLAGS) --
LINTER_DEADLINE=600s
UNIT_TESTS_TIMEOUT=300s
UNIT_TESTS_TIMEOUT=600s
ifeq ($(GOARCH),amd64)
PARALLEL=8
@@ -233,11 +233,13 @@ dev-deps:
GO111MODULE=off go get -u github.com/sqs/goreturns
test-with-coverage: export KOPIA_COVERAGE_TEST=1
test-with-coverage: $(gotestsum)
test-with-coverage: export TESTING_ACTION_EXE ?= $(TESTING_ACTION_EXE)
test-with-coverage: $(gotestsum) $(TESTING_ACTION_EXE)
$(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -short -covermode=atomic -coverprofile=coverage.txt --coverpkg $(COVERAGE_PACKAGES) -timeout 300s ./...
test: GOTESTSUM_FLAGS=--format=$(GOTESTSUM_FORMAT) --no-summary=skipped --jsonfile=.tmp.unit-tests.json
test: $(gotestsum)
test: export TESTING_ACTION_EXE ?= $(TESTING_ACTION_EXE)
test: $(gotestsum) $(TESTING_ACTION_EXE)
$(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -timeout $(UNIT_TESTS_TIMEOUT) ./...
-$(gotestsum) tool slowest --jsonfile .tmp.unit-tests.json --threshold 1000ms

View File

@@ -87,6 +87,9 @@ type appServices interface {
getProgress() *cliProgress
stdout() io.Writer
Stderr() io.Writer
stdin() io.Reader
onCtrlC(callback func())
EnvName(s string) string
}
type advancedAppServices interface {
@@ -150,17 +153,24 @@ type App struct {
logs commandLogs
// testability hooks
osExit func(int) // allows replacing os.Exit() with custom code
stdoutWriter io.Writer
stderrWriter io.Writer
rootctx context.Context // nolint:containedctx
loggerFactory logging.LoggerFactory
osExit func(int) // allows replacing os.Exit() with custom code
stdinReader io.Reader
stdoutWriter io.Writer
stderrWriter io.Writer
rootctx context.Context // nolint:containedctx
loggerFactory logging.LoggerFactory
simulatedCtrlC chan bool
envNamePrefix string
}
func (c *App) getProgress() *cliProgress {
return c.progress
}
func (c *App) stdin() io.Reader {
return c.stdinReader
}
func (c *App) stdout() io.Writer {
return c.stdoutWriter
}
@@ -222,21 +232,21 @@ func (c *App) setup(app *kingpin.Application) {
app.Flag("auto-maintenance", "Automatic maintenance").Default("true").Hidden().BoolVar(&c.enableAutomaticMaintenance)
// hidden flags to control auto-update behavior.
app.Flag("initial-update-check-delay", "Initial delay before first time update check").Default("24h").Hidden().Envar("KOPIA_INITIAL_UPDATE_CHECK_DELAY").DurationVar(&c.initialUpdateCheckDelay)
app.Flag("update-check-interval", "Interval between update checks").Default("168h").Hidden().Envar("KOPIA_UPDATE_CHECK_INTERVAL").DurationVar(&c.updateCheckInterval)
app.Flag("update-available-notify-interval", "Interval between update notifications").Default("1h").Hidden().Envar("KOPIA_UPDATE_NOTIFY_INTERVAL").DurationVar(&c.updateAvailableNotifyInterval)
app.Flag("config-file", "Specify the config file to use").Default("repository.config").Envar("KOPIA_CONFIG_PATH").StringVar(&c.configPath)
app.Flag("initial-update-check-delay", "Initial delay before first time update check").Default("24h").Hidden().Envar(c.EnvName("KOPIA_INITIAL_UPDATE_CHECK_DELAY")).DurationVar(&c.initialUpdateCheckDelay)
app.Flag("update-check-interval", "Interval between update checks").Default("168h").Hidden().Envar(c.EnvName("KOPIA_UPDATE_CHECK_INTERVAL")).DurationVar(&c.updateCheckInterval)
app.Flag("update-available-notify-interval", "Interval between update notifications").Default("1h").Hidden().Envar(c.EnvName("KOPIA_UPDATE_NOTIFY_INTERVAL")).DurationVar(&c.updateAvailableNotifyInterval)
app.Flag("config-file", "Specify the config file to use").Default("repository.config").Envar(c.EnvName("KOPIA_CONFIG_PATH")).StringVar(&c.configPath)
app.Flag("trace-storage", "Enables tracing of storage operations.").Default("true").Hidden().BoolVar(&c.traceStorage)
app.Flag("timezone", "Format time according to specified time zone (local, utc, original or time zone name)").Hidden().StringVar(&timeZone)
app.Flag("password", "Repository password.").Envar("KOPIA_PASSWORD").Short('p').StringVar(&c.password)
app.Flag("persist-credentials", "Persist credentials").Default("true").Envar("KOPIA_PERSIST_CREDENTIALS_ON_CONNECT").BoolVar(&c.persistCredentials)
app.Flag("disable-internal-log", "Disable internal log").Hidden().Envar("KOPIA_DISABLE_INTERNAL_LOG").BoolVar(&c.disableInternalLog)
app.Flag("advanced-commands", "Enable advanced (and potentially dangerous) commands.").Hidden().Envar("KOPIA_ADVANCED_COMMANDS").StringVar(&c.AdvancedCommands)
app.Flag("track-releasable", "Enable tracking of releasable resources.").Hidden().Envar("KOPIA_TRACK_RELEASABLE").StringsVar(&c.trackReleasable)
app.Flag("password", "Repository password.").Envar(c.EnvName("KOPIA_PASSWORD")).Short('p').StringVar(&c.password)
app.Flag("persist-credentials", "Persist credentials").Default("true").Envar(c.EnvName("KOPIA_PERSIST_CREDENTIALS_ON_CONNECT")).BoolVar(&c.persistCredentials)
app.Flag("disable-internal-log", "Disable internal log").Hidden().Envar(c.EnvName("KOPIA_DISABLE_INTERNAL_LOG")).BoolVar(&c.disableInternalLog)
app.Flag("advanced-commands", "Enable advanced (and potentially dangerous) commands.").Hidden().Envar(c.EnvName("KOPIA_ADVANCED_COMMANDS")).StringVar(&c.AdvancedCommands)
app.Flag("track-releasable", "Enable tracking of releasable resources.").Hidden().Envar(c.EnvName("KOPIA_TRACK_RELEASABLE")).StringsVar(&c.trackReleasable)
c.observability.setup(app)
c.observability.setup(c, app)
c.setupOSSpecificKeychainFlags(app)
c.setupOSSpecificKeychainFlags(c, app)
_ = app.Flag("caching", "Enables caching of objects (disable with --no-caching)").Default("true").Hidden().Action(
deprecatedFlag(c.stderrWriter, "The '--caching' flag is deprecated and has no effect, use 'kopia cache set' instead."),
@@ -298,10 +308,21 @@ func NewApp() *App {
osExit: os.Exit,
stdoutWriter: colorable.NewColorableStdout(),
stderrWriter: colorable.NewColorableStderr(),
stdinReader: os.Stdin,
rootctx: context.Background(),
}
}
// SetEnvNamePrefixForTesting sets the name prefix to be used for all environment variable names for testing.
func (c *App) SetEnvNamePrefixForTesting(prefix string) {
c.envNamePrefix = prefix
}
// EnvName overrides the provided environment variable name for testability.
func (c *App) EnvName(n string) string {
return c.envNamePrefix + n
}
// Attach attaches the CLI parser to the application.
func (c *App) Attach(app *kingpin.Application) {
c.setup(app)

View File

@@ -26,7 +26,7 @@ func (c *commandDiff) setup(svc appServices, parent commandParent) {
cmd.Arg("object-path1", "First object/path").Required().StringVar(&c.diffFirstObjectPath)
cmd.Arg("object-path2", "Second object/path").Required().StringVar(&c.diffSecondObjectPath)
cmd.Flag("files", "Compare files by launching diff command for all pairs of (old,new)").Short('f').BoolVar(&c.diffCompareFiles)
cmd.Flag("diff-command", "Displays differences between two repository objects (files or directories)").Default(defaultDiffCommand()).Envar("KOPIA_DIFF").StringVar(&c.diffCommandCommand)
cmd.Flag("diff-command", "Displays differences between two repository objects (files or directories)").Default(defaultDiffCommand()).Envar(svc.EnvName("KOPIA_DIFF")).StringVar(&c.diffCommandCommand)
cmd.Action(svc.repositoryReaderAction(c.run))
c.out.setup(svc)

View File

@@ -24,6 +24,8 @@ type commandMount struct {
mountPreferWebDAV bool
maxCachedEntries int
maxCachedDirectories int
svc appServices
}
func (c *commandMount) setup(svc appServices, parent commandParent) {
@@ -41,6 +43,7 @@ func (c *commandMount) setup(svc appServices, parent commandParent) {
cmd.Flag("max-cached-entries", "Limit the number of cached directory entries").Default("100000").IntVar(&c.maxCachedEntries)
cmd.Flag("max-cached-dirs", "Limit the number of cached directories").Default("100").IntVar(&c.maxCachedDirectories)
c.svc = svc
cmd.Action(svc.repositoryReaderAction(c.run))
}
@@ -100,7 +103,7 @@ func (c *commandMount) run(ctx context.Context, rep repo.Repository) error {
// Wait until ctrl-c pressed or until the directory is unmounted.
ctrlCPressed := make(chan bool)
onCtrlC(func() {
c.svc.onCtrlC(func() {
close(ctrlCPressed)
})

View File

@@ -16,7 +16,7 @@ type commandRepositoryChangePassword struct {
func (c *commandRepositoryChangePassword) setup(svc advancedAppServices, parent commandParent) {
cmd := parent.Command("change-password", "Change repository password")
cmd.Flag("new-password", "New password").Envar("KOPIA_NEW_PASSWORD").StringVar(&c.newPassword)
cmd.Flag("new-password", "New password").Envar(svc.EnvName("KOPIA_NEW_PASSWORD")).StringVar(&c.newPassword)
c.svc = svc
cmd.Action(svc.directRepositoryWriteAction(c.run))

View File

@@ -33,14 +33,12 @@ func (s *formatSpecificTestSuite) TestRepositoryChangePassword(t *testing.T) {
// at this point env2 stops working
env2.RunAndExpectFailure(t, "snapshot", "ls")
r3 := testenv.NewInProcRunner(t)
// new connections will fail when using old (default) password
env3 := testenv.NewCLITest(t, s.formatFlags, r3)
env3 := testenv.NewCLITest(t, s.formatFlags, testenv.NewInProcRunner(t))
env3.RunAndExpectFailure(t, "repo", "connect", "filesystem", "--path", env1.RepoDir, "--disable-repository-format-cache")
// new connections will succeed when using new password
r3.RepoPassword = "newPass"
env3.Environment["KOPIA_PASSWORD"] = "newPass"
env3.RunAndExpectSuccess(t, "repo", "connect", "filesystem", "--path", env1.RepoDir, "--disable-repository-format-cache")
}

View File

@@ -22,7 +22,7 @@ type commandRepositoryConnect struct {
func (c *commandRepositoryConnect) setup(svc advancedAppServices, parent commandParent) {
cmd := parent.Command("connect", "Connect to a repository.")
c.co.setup(cmd)
c.co.setup(svc, cmd)
c.server.setup(svc, cmd, &c.co)
for _, prov := range svc.storageProviders() {
@@ -61,16 +61,16 @@ type connectOptions struct {
disableFormatBlobCache bool
}
func (c *connectOptions) setup(cmd *kingpin.CmdClause) {
func (c *connectOptions) setup(svc appServices, cmd *kingpin.CmdClause) {
// Set up flags shared between 'create' and 'connect'. Note that because those flags are used by both command
// we must use *Var() methods, otherwise one of the commands would always get default flag values.
cmd.Flag("cache-directory", "Cache directory").PlaceHolder("PATH").Envar("KOPIA_CACHE_DIRECTORY").StringVar(&c.connectCacheDirectory)
cmd.Flag("cache-directory", "Cache directory").PlaceHolder("PATH").Envar(svc.EnvName("KOPIA_CACHE_DIRECTORY")).StringVar(&c.connectCacheDirectory)
cmd.Flag("content-cache-size-mb", "Size of local content cache").PlaceHolder("MB").Default("5000").Int64Var(&c.connectMaxCacheSizeMB)
cmd.Flag("metadata-cache-size-mb", "Size of local metadata cache").PlaceHolder("MB").Default("5000").Int64Var(&c.connectMaxMetadataCacheSizeMB)
cmd.Flag("max-list-cache-duration", "Duration of index cache").Default("30s").Hidden().DurationVar(&c.connectMaxListCacheDuration)
cmd.Flag("override-hostname", "Override hostname used by this repository connection").Hidden().StringVar(&c.connectHostname)
cmd.Flag("override-username", "Override username used by this repository connection").Hidden().StringVar(&c.connectUsername)
cmd.Flag("check-for-updates", "Periodically check for Kopia updates on GitHub").Default("true").Envar(checkForUpdatesEnvar).BoolVar(&c.connectCheckForUpdates)
cmd.Flag("check-for-updates", "Periodically check for Kopia updates on GitHub").Default("true").Envar(svc.EnvName(checkForUpdatesEnvar)).BoolVar(&c.connectCheckForUpdates)
cmd.Flag("readonly", "Make repository read-only to avoid accidental changes").BoolVar(&c.connectReadonly)
cmd.Flag("description", "Human-readable description of the repository").StringVar(&c.connectDescription)
cmd.Flag("enable-actions", "Allow snapshot actions").BoolVar(&c.connectEnableActions)

View File

@@ -48,7 +48,7 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP
cmd.Flag("retention-mode", "Set the blob retention-mode for supported storage backends.").EnumVar(&c.retentionMode, blob.Governance.String(), blob.Compliance.String())
cmd.Flag("retention-period", "Set the blob retention-period for supported storage backends.").DurationVar(&c.retentionPeriod)
c.co.setup(cmd)
c.co.setup(svc, cmd)
c.svc = svc
c.out.setup(svc)

View File

@@ -127,7 +127,7 @@ func (c *commandRestore) setup(svc appServices, parent commandParent) {
cmd.Flag("overwrite-files", "Specifies whether or not to overwrite already existing files").Default("true").BoolVar(&c.restoreOverwriteFiles)
cmd.Flag("overwrite-symlinks", "Specifies whether or not to overwrite already existing symlinks").Default("true").BoolVar(&c.restoreOverwriteSymlinks)
cmd.Flag("write-sparse-files", "When doing a restore, attempt to write files sparsely-allocating the minimum amount of disk space needed.").Default("false").BoolVar(&c.restoreWriteSparseFiles)
cmd.Flag("consistent-attributes", "When multiple snapshots match, fail if they have inconsistent attributes").Envar("KOPIA_RESTORE_CONSISTENT_ATTRIBUTES").BoolVar(&c.restoreConsistentAttributes)
cmd.Flag("consistent-attributes", "When multiple snapshots match, fail if they have inconsistent attributes").Envar(svc.EnvName("KOPIA_RESTORE_CONSISTENT_ATTRIBUTES")).BoolVar(&c.restoreConsistentAttributes)
cmd.Flag("mode", "Override restore mode").Default(restoreModeAuto).EnumVar(&c.restoreMode, restoreModeAuto, restoreModeLocal, restoreModeZip, restoreModeZipNoCompress, restoreModeTar, restoreModeTgz)
cmd.Flag("parallel", "Restore parallelism (1=disable)").Default("8").IntVar(&c.restoreParallel)
cmd.Flag("skip-owners", "Skip owners during restore").BoolVar(&c.restoreSkipOwners)

View File

@@ -28,10 +28,10 @@ type serverFlags struct {
serverPassword string
}
func (c *serverFlags) setup(cmd *kingpin.CmdClause) {
func (c *serverFlags) setup(svc appServices, cmd *kingpin.CmdClause) {
cmd.Flag("address", "Server address").Default("http://127.0.0.1:51515").StringVar(&c.serverAddress)
cmd.Flag("server-username", "HTTP server username (basic auth)").Envar("KOPIA_SERVER_USERNAME").Default("kopia").StringVar(&c.serverUsername)
cmd.Flag("server-password", "HTTP server password (basic auth)").Envar("KOPIA_SERVER_PASSWORD").StringVar(&c.serverPassword)
cmd.Flag("server-username", "HTTP server username (basic auth)").Envar(svc.EnvName("KOPIA_SERVER_USERNAME")).Default("kopia").StringVar(&c.serverUsername)
cmd.Flag("server-password", "HTTP server password (basic auth)").Envar(svc.EnvName("KOPIA_SERVER_PASSWORD")).StringVar(&c.serverPassword)
}
type serverClientFlags struct {
@@ -41,18 +41,18 @@ type serverClientFlags struct {
serverCertFingerprint string
}
func (c *serverClientFlags) setup(cmd *kingpin.CmdClause) {
func (c *serverClientFlags) setup(svc appServices, cmd *kingpin.CmdClause) {
c.serverUsername = "server-control"
cmd.Flag("address", "Address of the server to connect to").Envar("KOPIA_SERVER_ADDRESS").Default("http://127.0.0.1:51515").StringVar(&c.serverAddress)
cmd.Flag("server-control-username", "Server control username").Envar("KOPIA_SERVER_USERNAME").StringVar(&c.serverUsername)
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar("KOPIA_SERVER_PASSWORD").StringVar(&c.serverPassword)
cmd.Flag("address", "Address of the server to connect to").Envar(svc.EnvName("KOPIA_SERVER_ADDRESS")).Default("http://127.0.0.1:51515").StringVar(&c.serverAddress)
cmd.Flag("server-control-username", "Server control username").Envar(svc.EnvName("KOPIA_SERVER_USERNAME")).StringVar(&c.serverUsername)
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("KOPIA_SERVER_PASSWORD")).StringVar(&c.serverPassword)
// aliases for backwards compat
cmd.Flag("server-username", "Server control username").Hidden().StringVar(&c.serverUsername)
cmd.Flag("server-password", "Server control password").Hidden().StringVar(&c.serverPassword)
cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").PlaceHolder("SHA256-FINGERPRINT").Envar("KOPIA_SERVER_CERT_FINGERPRINT").StringVar(&c.serverCertFingerprint)
cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").PlaceHolder("SHA256-FINGERPRINT").Envar(svc.EnvName("KOPIA_SERVER_CERT_FINGERPRINT")).StringVar(&c.serverCertFingerprint)
}
func (c *commandServer) setup(svc advancedAppServices, parent commandParent) {

View File

@@ -13,7 +13,7 @@ type commandServerFlush struct {
func (c *commandServerFlush) setup(svc appServices, parent commandParent) {
cmd := parent.Command("flush", "Flush the state of Kopia server to persistent storage, etc.")
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
cmd.Action(svc.serverAction(&c.sf, c.run))
}

View File

@@ -13,7 +13,7 @@ type commandServerRefresh struct {
func (c *commandServerRefresh) setup(svc appServices, parent commandParent) {
cmd := parent.Command("refresh", "Refresh the cache in Kopia server to observe new sources, etc.")
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
cmd.Action(svc.serverAction(&c.sf, c.run))
}

View File

@@ -15,7 +15,7 @@ type commandServerShutdown struct {
func (c *commandServerShutdown) setup(svc appServices, parent commandParent) {
cmd := parent.Command("shutdown", "Gracefully shutdown the server")
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
c.out.setup(svc)
cmd.Action(svc.serverAction(&c.sf, c.run))
}

View File

@@ -28,7 +28,7 @@ func (c *commandServerSourceManagerAction) setup(svc appServices, cmd *kingpin.C
cmd.Flag("all", "All paths managed by server").BoolVar(&c.all)
cmd.Arg("source", "Source path managed by server").StringVar(&c.source)
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
c.out.setup(svc)
}

View File

@@ -90,10 +90,10 @@ func (c *commandServerStart) setup(svc advancedAppServices, parent commandParent
cmd.Flag("htpasswd-file", "Path to htpasswd file that contains allowed user@hostname entries").Hidden().ExistingFileVar(&c.serverStartHtpasswdFile)
cmd.Flag("random-server-control-password", "Generate random server control password and print to stderr").Hidden().BoolVar(&c.randomServerControlPassword)
cmd.Flag("server-control-username", "Server control username").Default("server-control").Envar("KOPIA_SERVER_CONTROL_USER").StringVar(&c.serverControlUsername)
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar("KOPIA_SERVER_CONTROL_PASSWORD").StringVar(&c.serverControlPassword)
cmd.Flag("server-control-username", "Server control username").Default("server-control").Envar(svc.EnvName("KOPIA_SERVER_CONTROL_USER")).StringVar(&c.serverControlUsername)
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("KOPIA_SERVER_CONTROL_PASSWORD")).StringVar(&c.serverControlPassword)
cmd.Flag("auth-cookie-signing-key", "Force particular auth cookie signing key").Envar("KOPIA_AUTH_COOKIE_SIGNING_KEY").Hidden().StringVar(&c.serverAuthCookieSingingKey)
cmd.Flag("auth-cookie-signing-key", "Force particular auth cookie signing key").Envar(svc.EnvName("KOPIA_AUTH_COOKIE_SIGNING_KEY")).Hidden().StringVar(&c.serverAuthCookieSingingKey)
cmd.Flag("shutdown-on-stdin", "Shut down the server when stdin handle has closed.").Hidden().BoolVar(&c.serverStartShutdownWhenStdinClosed)
@@ -106,14 +106,14 @@ func (c *commandServerStart) setup(svc advancedAppServices, parent commandParent
cmd.Flag("tls-print-server-cert", "Print server certificate").Hidden().BoolVar(&c.serverStartTLSPrintFullServerCert)
cmd.Flag("async-repo-connect", "Connect to repository asynchronously").Hidden().BoolVar(&c.asyncRepoConnect)
cmd.Flag("ui-title-prefix", "UI title prefix").Hidden().Envar("KOPIA_UI_TITLE_PREFIX").StringVar(&c.uiTitlePrefix)
cmd.Flag("ui-title-prefix", "UI title prefix").Hidden().Envar(svc.EnvName("KOPIA_UI_TITLE_PREFIX")).StringVar(&c.uiTitlePrefix)
cmd.Flag("ui-preferences-file", "Path to JSON file storing UI preferences").StringVar(&c.uiPreferencesFile)
cmd.Flag("log-server-requests", "Log server requests").Hidden().BoolVar(&c.logServerRequests)
cmd.Flag("disable-csrf-token-checks", "Disable CSRF token").Hidden().BoolVar(&c.disableCSRFTokenChecks)
c.sf.setup(cmd)
c.co.setup(cmd)
c.sf.setup(svc, cmd)
c.co.setup(svc, cmd)
c.svc = svc
c.out.setup(svc)
@@ -192,7 +192,7 @@ func (c *commandServerStart) run(ctx context.Context) error {
srv.OnShutdown = httpServer.Shutdown
onCtrlC(func() {
c.svc.onCtrlC(func() {
log(ctx).Infof("Shutting down...")
if err = httpServer.Shutdown(ctx); err != nil {

View File

@@ -22,7 +22,7 @@ func (c *commandServerStatus) setup(svc appServices, parent commandParent) {
cmd.Flag("remote", "Show remote sources").BoolVar(&c.remote)
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
c.out.setup(svc)
cmd.Action(svc.serverAction(&c.sf, c.runServerStatus))

View File

@@ -17,7 +17,7 @@ type commandServerThrottleGet struct {
func (c *commandServerThrottleGet) setup(svc appServices, parent commandParent) {
cmd := parent.Command("get", "Get throttling parameters for a running server")
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
c.ctg.setup(svc, cmd)
cmd.Action(svc.serverAction(&c.sf, c.run))
}

View File

@@ -18,7 +18,7 @@ type commandServerThrottleSet struct {
func (c *commandServerThrottleSet) setup(svc appServices, parent commandParent) {
cmd := parent.Command("set", "Set throttling parameters for a running server")
c.sf.setup(cmd)
c.sf.setup(svc, cmd)
c.cts.setup(cmd)
cmd.Action(svc.serverAction(&c.sf, c.run))

View File

@@ -3,7 +3,6 @@
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
@@ -58,7 +57,7 @@ func (c *commandSnapshotCreate) setup(svc appServices, parent commandParent) {
cmd.Flag("upload-limit-mb", "Stop the backup process after the specified amount of data (in MB) has been uploaded.").PlaceHolder("MB").Default("0").Int64Var(&c.snapshotCreateCheckpointUploadLimitMB)
cmd.Flag("checkpoint-interval", "Frequency for creating periodic checkpoint.").DurationVar(&c.snapshotCreateCheckpointInterval)
cmd.Flag("description", "Free-form snapshot description.").StringVar(&c.snapshotCreateDescription)
cmd.Flag("fail-fast", "Fail fast when creating snapshot.").Envar("KOPIA_SNAPSHOT_FAIL_FAST").BoolVar(&c.snapshotCreateFailFast)
cmd.Flag("fail-fast", "Fail fast when creating snapshot.").Envar(svc.EnvName("KOPIA_SNAPSHOT_FAIL_FAST")).BoolVar(&c.snapshotCreateFailFast)
cmd.Flag("force-hash", "Force hashing of source files for a given percentage of files [0.0 .. 100.0]").Default("0").Float64Var(&c.snapshotCreateForceHash)
cmd.Flag("parallel", "Upload N files in parallel").PlaceHolder("N").Default("0").IntVar(&c.snapshotCreateParallelUploads)
cmd.Flag("start-time", "Override snapshot start timestamp.").StringVar(&c.snapshotCreateStartTime)
@@ -234,7 +233,7 @@ func (c *commandSnapshotCreate) setupUploader(rep repo.RepositoryWriter) *snapsh
u.CheckpointInterval = interval
}
onCtrlC(u.Cancel)
c.svc.onCtrlC(u.Cancel)
u.ForceHashPercentage = c.snapshotCreateForceHash
u.ParallelUploads = c.snapshotCreateParallelUploads
@@ -274,7 +273,7 @@ func (c *commandSnapshotCreate) snapshotSingleSource(ctx context.Context, rep re
// stdin source will be snapshotted using a virtual static root directory with a single streaming file entry
// Create a new static directory with the given name and add a streaming file entry with os.Stdin reader
fsEntry = virtualfs.NewStaticDirectory(sourceInfo.Path, []fs.Entry{
virtualfs.StreamingFileFromReader(c.snapshotCreateStdinFileName, os.Stdin),
virtualfs.StreamingFileFromReader(c.snapshotCreateStdinFileName, c.svc.stdin()),
})
setManual = true
} else {

View File

@@ -68,7 +68,7 @@ func (c *commandSnapshotMigrate) run(ctx context.Context, destRepo repo.Reposito
c.svc.getProgress().StartShared()
onCtrlC(func() {
c.svc.onCtrlC(func() {
mu.Lock()
defer mu.Unlock()

View File

@@ -25,12 +25,20 @@ func deprecatedFlag(w io.Writer, help string) func(_ *kingpin.ParseContext) erro
}
}
func onCtrlC(f func()) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
func (c *App) onCtrlC(f func()) {
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt)
go func() {
<-c
// invoke the function when either real or simulated Ctrl-C signal is delivered
select {
case v := <-c.simulatedCtrlC:
if !v {
return
}
case <-s:
}
f()
}()
}

View File

@@ -7,18 +7,23 @@
"github.com/alecthomas/kingpin"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/releasable"
"github.com/kopia/kopia/repo/logging"
)
// RunSubcommand executes the subcommand asynchronously in current process
// with flags in an isolated CLI environment and returns standard output and standard error.
func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, argsAndFlags []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, stdin io.Reader, argsAndFlags []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
stdoutReader, stdoutWriter := io.Pipe()
stderrReader, stderrWriter := io.Pipe()
c.stdinReader = stdin
c.stdoutWriter = stdoutWriter
c.stderrWriter = stderrWriter
c.rootctx = logging.WithLogger(ctx, logging.ToWriter(stderrWriter))
c.simulatedCtrlC = make(chan bool, 1)
releasable.Created("simulated-ctrl-c", c.simulatedCtrlC)
c.Attach(kpapp)
@@ -31,6 +36,11 @@ func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, arg
}
go func() {
defer func() {
close(c.simulatedCtrlC)
releasable.Released("simulated-ctrl-c", c.simulatedCtrlC)
}()
defer close(resultErr)
defer stderrWriter.Close() //nolint:errcheck
defer stdoutWriter.Close() //nolint:errcheck
@@ -48,6 +58,9 @@ func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, arg
}()
return stdoutReader, stderrReader, func() error {
return <-resultErr
}, func() {}
return <-resultErr
}, func() {
// deliver simulated Ctrl-C to the app.
c.simulatedCtrlC <- true
}
}

View File

@@ -52,19 +52,19 @@ type observabilityFlags struct {
traceProvider *trace.TracerProvider
}
func (c *observabilityFlags) setup(app *kingpin.Application) {
func (c *observabilityFlags) setup(svc appServices, app *kingpin.Application) {
app.Flag("metrics-listen-addr", "Expose Prometheus metrics on a given host:port").Hidden().StringVar(&c.metricsListenAddr)
app.Flag("enable-pprof", "Expose pprof handlers").Hidden().BoolVar(&c.enablePProf)
// push gateway parameters
app.Flag("metrics-push-addr", "Address of push gateway").Envar("KOPIA_METRICS_PUSH_ADDR").Hidden().StringVar(&c.metricsPushAddr)
app.Flag("metrics-push-interval", "Frequency of metrics push").Envar("KOPIA_METRICS_PUSH_INTERVAL").Hidden().Default("5s").DurationVar(&c.metricsPushInterval)
app.Flag("metrics-push-job", "Job ID for to push gateway").Envar("KOPIA_METRICS_JOB").Hidden().Default("kopia").StringVar(&c.metricsJob)
app.Flag("metrics-push-grouping", "Grouping for push gateway").Envar("KOPIA_METRICS_PUSH_GROUPING").Hidden().StringsVar(&c.metricsGroupings)
app.Flag("metrics-push-username", "Username for push gateway").Envar("KOPIA_METRICS_PUSH_USERNAME").Hidden().StringVar(&c.metricsPushUsername)
app.Flag("metrics-push-password", "Password for push gateway").Envar("KOPIA_METRICS_PUSH_PASSWORD").Hidden().StringVar(&c.metricsPushPassword)
app.Flag("metrics-push-addr", "Address of push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_ADDR")).Hidden().StringVar(&c.metricsPushAddr)
app.Flag("metrics-push-interval", "Frequency of metrics push").Envar(svc.EnvName("KOPIA_METRICS_PUSH_INTERVAL")).Hidden().Default("5s").DurationVar(&c.metricsPushInterval)
app.Flag("metrics-push-job", "Job ID for to push gateway").Envar(svc.EnvName("KOPIA_METRICS_JOB")).Hidden().Default("kopia").StringVar(&c.metricsJob)
app.Flag("metrics-push-grouping", "Grouping for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_GROUPING")).Hidden().StringsVar(&c.metricsGroupings)
app.Flag("metrics-push-username", "Username for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_USERNAME")).Hidden().StringVar(&c.metricsPushUsername)
app.Flag("metrics-push-password", "Password for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_PASSWORD")).Hidden().StringVar(&c.metricsPushPassword)
app.Flag("enable-jaeger-collector", "Emit OpenTelemetry traces to Jaeger collector").Hidden().Envar("KOPIA_ENABLE_JAEGER_COLLECTOR").BoolVar(&c.enableJaeger)
app.Flag("enable-jaeger-collector", "Emit OpenTelemetry traces to Jaeger collector").Hidden().Envar(svc.EnvName("KOPIA_ENABLE_JAEGER_COLLECTOR")).BoolVar(&c.enableJaeger)
var formats []string
@@ -74,7 +74,7 @@ func (c *observabilityFlags) setup(app *kingpin.Application) {
sort.Strings(formats)
app.Flag("metrics-push-format", "Format to use for push gateway").Envar("KOPIA_METRICS_FORMAT").Hidden().EnumVar(&c.metricsPushFormat, formats...)
app.Flag("metrics-push-format", "Format to use for push gateway").Envar(svc.EnvName("KOPIA_METRICS_FORMAT")).Hidden().EnumVar(&c.metricsPushFormat, formats...)
}
func (c *observabilityFlags) startMetrics(ctx context.Context) error {

View File

@@ -4,6 +4,6 @@
"github.com/alecthomas/kingpin"
)
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
app.Flag("use-keychain", "Use macOS Keychain for storing repository password.").Default("true").BoolVar(&c.keyRingEnabled)
}

View File

@@ -4,6 +4,6 @@
"github.com/alecthomas/kingpin"
)
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
app.Flag("use-keyring", "Use Gnome Keyring for storing repository password.").Default("false").Envar("KOPIA_USE_KEYRING").BoolVar(&c.keyRingEnabled)
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
app.Flag("use-keyring", "Use Gnome Keyring for storing repository password.").Default("false").Envar(svc.EnvName("KOPIA_USE_KEYRING")).BoolVar(&c.keyRingEnabled)
}

View File

@@ -7,5 +7,5 @@
"github.com/alecthomas/kingpin"
)
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
}

View File

@@ -4,6 +4,6 @@
"github.com/alecthomas/kingpin"
)
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
app.Flag("use-credential-manager", "Use Windows Credential Manager for storing repository password.").Default("true").BoolVar(&c.keyRingEnabled)
}

View File

@@ -13,12 +13,12 @@ type storageAzureFlags struct {
azOptions azure.Options
}
func (c *storageAzureFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageAzureFlags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("container", "Name of the Azure blob container").Required().StringVar(&c.azOptions.Container)
cmd.Flag("storage-account", "Azure storage account name (overrides AZURE_STORAGE_ACCOUNT environment variable)").Required().Envar("AZURE_STORAGE_ACCOUNT").StringVar(&c.azOptions.StorageAccount)
cmd.Flag("storage-key", "Azure storage account key (overrides AZURE_STORAGE_KEY environment variable)").Envar("AZURE_STORAGE_KEY").StringVar(&c.azOptions.StorageKey)
cmd.Flag("storage-domain", "Azure storage domain").Envar("AZURE_STORAGE_DOMAIN").StringVar(&c.azOptions.StorageDomain)
cmd.Flag("sas-token", "Azure SAS Token").Envar("AZURE_STORAGE_SAS_TOKEN").StringVar(&c.azOptions.SASToken)
cmd.Flag("storage-account", "Azure storage account name (overrides AZURE_STORAGE_ACCOUNT environment variable)").Required().Envar(svc.EnvName("AZURE_STORAGE_ACCOUNT")).StringVar(&c.azOptions.StorageAccount)
cmd.Flag("storage-key", "Azure storage account key (overrides AZURE_STORAGE_KEY environment variable)").Envar(svc.EnvName("AZURE_STORAGE_KEY")).StringVar(&c.azOptions.StorageKey)
cmd.Flag("storage-domain", "Azure storage domain").Envar(svc.EnvName("AZURE_STORAGE_DOMAIN")).StringVar(&c.azOptions.StorageDomain)
cmd.Flag("sas-token", "Azure SAS Token").Envar(svc.EnvName("AZURE_STORAGE_SAS_TOKEN")).StringVar(&c.azOptions.SASToken)
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.azOptions.Prefix)
commonThrottlingFlags(cmd, &c.azOptions.Limits)

View File

@@ -13,10 +13,10 @@ type storageB2Flags struct {
b2options b2.Options
}
func (c *storageB2Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageB2Flags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("bucket", "Name of the B2 bucket").Required().StringVar(&c.b2options.BucketName)
cmd.Flag("key-id", "Key ID (overrides B2_KEY_ID environment variable)").Required().Envar("B2_KEY_ID").StringVar(&c.b2options.KeyID)
cmd.Flag("key", "Secret key (overrides B2_KEY environment variable)").Required().Envar("B2_KEY").StringVar(&c.b2options.Key)
cmd.Flag("key-id", "Key ID (overrides B2_KEY_ID environment variable)").Required().Envar(svc.EnvName("B2_KEY_ID")).StringVar(&c.b2options.KeyID)
cmd.Flag("key", "Secret key (overrides B2_KEY environment variable)").Required().Envar(svc.EnvName("B2_KEY")).StringVar(&c.b2options.Key)
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.b2options.Prefix)
commonThrottlingFlags(cmd, &c.b2options.Limits)
}

View File

@@ -12,6 +12,7 @@
// StorageProviderServices is implemented by the cli App that allows the cli
// and tests to mutate the default storage providers.
type StorageProviderServices interface {
EnvName(s string) string
setPasswordFromToken(pwd string)
storageProviders() []StorageProvider
}

View File

@@ -15,13 +15,13 @@ type storageS3Flags struct {
s3options s3.Options
}
func (c *storageS3Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageS3Flags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("bucket", "Name of the S3 bucket").Required().StringVar(&c.s3options.BucketName)
cmd.Flag("endpoint", "Endpoint to use").Default("s3.amazonaws.com").StringVar(&c.s3options.Endpoint)
cmd.Flag("region", "S3 Region").Default("").StringVar(&c.s3options.Region)
cmd.Flag("access-key", "Access key ID (overrides AWS_ACCESS_KEY_ID environment variable)").Required().Envar("AWS_ACCESS_KEY_ID").StringVar(&c.s3options.AccessKeyID)
cmd.Flag("secret-access-key", "Secret access key (overrides AWS_SECRET_ACCESS_KEY environment variable)").Required().Envar("AWS_SECRET_ACCESS_KEY").StringVar(&c.s3options.SecretAccessKey)
cmd.Flag("session-token", "Session token (overrides AWS_SESSION_TOKEN environment variable)").Envar("AWS_SESSION_TOKEN").StringVar(&c.s3options.SessionToken)
cmd.Flag("access-key", "Access key ID (overrides AWS_ACCESS_KEY_ID environment variable)").Required().Envar(svc.EnvName("AWS_ACCESS_KEY_ID")).StringVar(&c.s3options.AccessKeyID)
cmd.Flag("secret-access-key", "Secret access key (overrides AWS_SECRET_ACCESS_KEY environment variable)").Required().Envar(svc.EnvName("AWS_SECRET_ACCESS_KEY")).StringVar(&c.s3options.SecretAccessKey)
cmd.Flag("session-token", "Session token (overrides AWS_SESSION_TOKEN environment variable)").Envar(svc.EnvName("AWS_SESSION_TOKEN")).StringVar(&c.s3options.SessionToken)
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.s3options.Prefix)
cmd.Flag("disable-tls", "Disable TLS security (HTTPS)").BoolVar(&c.s3options.DoNotUseTLS)
cmd.Flag("disable-tls-verification", "Disable TLS (HTTPS) certificate verification").BoolVar(&c.s3options.DoNotVerifyTLS)

View File

@@ -15,11 +15,11 @@ type storageWebDAVFlags struct {
connectFlat bool
}
func (c *storageWebDAVFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageWebDAVFlags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("url", "URL of WebDAV server").Required().StringVar(&c.options.URL)
cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat)
cmd.Flag("webdav-username", "WebDAV username").Envar("KOPIA_WEBDAV_USERNAME").StringVar(&c.options.Username)
cmd.Flag("webdav-password", "WebDAV password").Envar("KOPIA_WEBDAV_PASSWORD").StringVar(&c.options.Password)
cmd.Flag("webdav-username", "WebDAV username").Envar(svc.EnvName("KOPIA_WEBDAV_USERNAME")).StringVar(&c.options.Username)
cmd.Flag("webdav-password", "WebDAV password").Envar(svc.EnvName("KOPIA_WEBDAV_PASSWORD")).StringVar(&c.options.Password)
cmd.Flag("list-parallelism", "Set list parallelism").Hidden().IntVar(&c.options.ListParallelism)
cmd.Flag("atomic-writes", "Assume WebDAV provider implements atomic writes").BoolVar(&c.options.AtomicWrites)

View File

@@ -158,7 +158,7 @@ func verifyGitHubReleaseIsComplete(ctx context.Context, releaseName string) erro
}
func (c *App) maybeCheckForUpdates(ctx context.Context) (string, error) {
if v := os.Getenv(checkForUpdatesEnvar); v != "" {
if v := os.Getenv(c.EnvName(checkForUpdatesEnvar)); v != "" {
// see if environment variable is set to false.
if b, err := strconv.ParseBool(v); err == nil && !b {
return "", errors.Errorf("update check disabled")

View File

@@ -59,23 +59,23 @@ func (c *loggingFlags) setup(cliApp *cli.App, app *kingpin.Application) {
app.Flag("log-file", "Override log file.").StringVar(&c.logFile)
app.Flag("content-log-file", "Override content log file.").Hidden().StringVar(&c.contentLogFile)
app.Flag("log-dir", "Directory where log files should be written.").Envar("KOPIA_LOG_DIR").Default(ospath.LogsDir()).StringVar(&c.logDir)
app.Flag("log-dir-max-files", "Maximum number of log files to retain").Envar("KOPIA_LOG_DIR_MAX_FILES").Default("1000").Hidden().IntVar(&c.logDirMaxFiles)
app.Flag("log-dir-max-age", "Maximum age of log files to retain").Envar("KOPIA_LOG_DIR_MAX_AGE").Hidden().Default("720h").DurationVar(&c.logDirMaxAge)
app.Flag("log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar("KOPIA_LOG_DIR_MAX_SIZE_MB").Hidden().Default("1000").Float64Var(&c.logDirMaxTotalSizeMB)
app.Flag("max-log-file-segment-size", "Maximum size of a single log file segment").Envar("KOPIA_LOG_FILE_MAX_SEGMENT_SIZE").Default("50000000").Hidden().IntVar(&c.logFileMaxSegmentSize)
app.Flag("log-dir", "Directory where log files should be written.").Envar(cliApp.EnvName("KOPIA_LOG_DIR")).Default(ospath.LogsDir()).StringVar(&c.logDir)
app.Flag("log-dir-max-files", "Maximum number of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_FILES")).Default("1000").Hidden().IntVar(&c.logDirMaxFiles)
app.Flag("log-dir-max-age", "Maximum age of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_AGE")).Hidden().Default("720h").DurationVar(&c.logDirMaxAge)
app.Flag("log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.logDirMaxTotalSizeMB)
app.Flag("max-log-file-segment-size", "Maximum size of a single log file segment").Envar(cliApp.EnvName("KOPIA_LOG_FILE_MAX_SEGMENT_SIZE")).Default("50000000").Hidden().IntVar(&c.logFileMaxSegmentSize)
app.Flag("wait-for-log-sweep", "Wait for log sweep before program exit").Default("true").Hidden().BoolVar(&c.waitForLogSweep)
app.Flag("content-log-dir-max-files", "Maximum number of content log files to retain").Envar("KOPIA_CONTENT_LOG_DIR_MAX_FILES").Default("5000").Hidden().IntVar(&c.contentLogDirMaxFiles)
app.Flag("content-log-dir-max-age", "Maximum age of content log files to retain").Envar("KOPIA_CONTENT_LOG_DIR_MAX_AGE").Default("720h").Hidden().DurationVar(&c.contentLogDirMaxAge)
app.Flag("content-log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar("KOPIA_CONTENT_LOG_DIR_MAX_SIZE_MB").Hidden().Default("1000").Float64Var(&c.contentLogDirMaxTotalSizeMB)
app.Flag("content-log-dir-max-files", "Maximum number of content log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_FILES")).Default("5000").Hidden().IntVar(&c.contentLogDirMaxFiles)
app.Flag("content-log-dir-max-age", "Maximum age of content log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_AGE")).Default("720h").Hidden().DurationVar(&c.contentLogDirMaxAge)
app.Flag("content-log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.contentLogDirMaxTotalSizeMB)
app.Flag("log-level", "Console log level").Default("info").EnumVar(&c.logLevel, logLevels...)
app.Flag("json-log-console", "JSON log file").Hidden().BoolVar(&c.jsonLogConsole)
app.Flag("json-log-file", "JSON log file").Hidden().BoolVar(&c.jsonLogFile)
app.Flag("file-log-level", "File log level").Default("debug").EnumVar(&c.fileLogLevel, logLevels...)
app.Flag("file-log-local-tz", "When logging to a file, use local timezone").Hidden().Envar("KOPIA_FILE_LOG_LOCAL_TZ").BoolVar(&c.fileLogLocalTimezone)
app.Flag("force-color", "Force color output").Hidden().Envar("KOPIA_FORCE_COLOR").BoolVar(&c.forceColor)
app.Flag("disable-color", "Disable color output").Hidden().Envar("KOPIA_DISABLE_COLOR").BoolVar(&c.disableColor)
app.Flag("console-timestamps", "Log timestamps to stderr.").Hidden().Default("false").Envar("KOPIA_CONSOLE_TIMESTAMPS").BoolVar(&c.consoleLogTimestamps)
app.Flag("file-log-local-tz", "When logging to a file, use local timezone").Hidden().Envar(cliApp.EnvName("KOPIA_FILE_LOG_LOCAL_TZ")).BoolVar(&c.fileLogLocalTimezone)
app.Flag("force-color", "Force color output").Hidden().Envar(cliApp.EnvName("KOPIA_FORCE_COLOR")).BoolVar(&c.forceColor)
app.Flag("disable-color", "Disable color output").Hidden().Envar(cliApp.EnvName("KOPIA_DISABLE_COLOR")).BoolVar(&c.disableColor)
app.Flag("console-timestamps", "Log timestamps to stderr.").Hidden().Default("false").Envar(cliApp.EnvName("KOPIA_CONSOLE_TIMESTAMPS")).BoolVar(&c.consoleLogTimestamps)
app.PreAction(c.initialize)
c.cliApp = cliApp

View File

@@ -12,7 +12,7 @@
func TestACL(t *testing.T) {
t.Parallel()
serverRunner := testenv.NewExeRunner(t)
serverRunner := testenv.NewInProcRunner(t)
serverEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, serverRunner)
defer serverEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
@@ -42,7 +42,7 @@ func TestACL(t *testing.T) {
var sp testutil.ServerParameters
_, kill := serverEnvironment.RunAndProcessStderr(t, sp.ProcessOutput,
wait, kill := serverEnvironment.RunAndProcessStderr(t, sp.ProcessOutput,
"server", "start",
"--address=localhost:0",
"--server-control-username=admin-user",
@@ -53,14 +53,15 @@ func TestACL(t *testing.T) {
t.Logf("detected server parameters %#v", sp)
defer wait()
defer kill()
fooBarRunner := testenv.NewExeRunner(t)
fooBarRunner := testenv.NewInProcRunner(t)
foobarClientEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, fooBarRunner)
defer foobarClientEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
fooBarRunner.RemoveDefaultPassword()
delete(foobarClientEnvironment.Environment, "KOPIA_PASSWORD")
// connect as foo@bar with password baz
foobarClientEnvironment.RunAndExpectSuccess(t, "repo", "connect", "server",
@@ -71,12 +72,12 @@ func TestACL(t *testing.T) {
"--password", "baz",
)
aliceInWonderlandRunner := testenv.NewExeRunner(t)
aliceInWonderlandRunner := testenv.NewInProcRunner(t)
aliceInWonderlandClientEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, aliceInWonderlandRunner)
defer aliceInWonderlandClientEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
aliceInWonderlandRunner.RemoveDefaultPassword()
delete(aliceInWonderlandClientEnvironment.Environment, "KOPIA_PASSWORD")
// connect as alice@wonderland with password baz
aliceInWonderlandClientEnvironment.RunAndExpectSuccess(t, "repo", "connect", "server",

View File

@@ -211,7 +211,7 @@ func testAPIServerRepository(t *testing.T, serverStartArgs []string, useGRPC, al
// we are providing custom password to connect, make sure we won't be providing
// (different) default password via environment variable, as command-line password
// takes precedence over persisted password.
runner2.RemoveDefaultPassword()
delete(e2.Environment, "KOPIA_PASSWORD")
// should see one snapshot
snapshots := clitestutil.ListSnapshotsAndExpectSuccess(t, e2)

View File

@@ -15,21 +15,21 @@ func TestAutoUpdateEnableTest(t *testing.T) {
cases := []struct {
desc string
extraArgs []string
extraEnv []string
extraEnv map[string]string
wantEnabled bool
wantInitialDelay time.Duration
}{
{desc: "Default", wantEnabled: true, wantInitialDelay: 24 * time.Hour},
{desc: "DisabledByFlag", extraArgs: []string{"--no-check-for-updates"}, wantEnabled: false},
{desc: "DisabledByEnvar-false", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=false"}, wantEnabled: false},
{desc: "DisabledByEnvar-0", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=0"}, wantEnabled: false},
{desc: "DisabledByEnvar-f", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=f"}, wantEnabled: false},
{desc: "DisabledByEnvar-False", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=False"}, wantEnabled: false},
{desc: "DisabledByEnvar-FALSE", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=FALSE"}, wantEnabled: false},
{desc: "DisabledByEnvarOverriddenByFlag", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=false"}, extraArgs: []string{"--check-for-updates"}, wantEnabled: true, wantInitialDelay: 24 * time.Hour},
{desc: "EnabledByEnvarOverriddenByFlag", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=true"}, extraArgs: []string{"--no-check-for-updates"}, wantEnabled: false, wantInitialDelay: 24 * time.Hour},
{desc: "DisabledByEnvar-false", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "false"}, wantEnabled: false},
{desc: "DisabledByEnvar-0", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "0"}, wantEnabled: false},
{desc: "DisabledByEnvar-f", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "f"}, wantEnabled: false},
{desc: "DisabledByEnvar-False", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "False"}, wantEnabled: false},
{desc: "DisabledByEnvar-FALSE", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "FALSE"}, wantEnabled: false},
{desc: "DisabledByEnvarOverriddenByFlag", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "false"}, extraArgs: []string{"--check-for-updates"}, wantEnabled: true, wantInitialDelay: 24 * time.Hour},
{desc: "EnabledByEnvarOverriddenByFlag", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "true"}, extraArgs: []string{"--no-check-for-updates"}, wantEnabled: false, wantInitialDelay: 24 * time.Hour},
{desc: "InitialUpdateCheckIntervalFlag", extraEnv: []string{"KOPIA_INITIAL_UPDATE_CHECK_DELAY=1h"}, wantEnabled: true, wantInitialDelay: 1 * time.Hour},
{desc: "InitialUpdateCheckIntervalFlag", extraEnv: map[string]string{"KOPIA_INITIAL_UPDATE_CHECK_DELAY": "1h"}, wantEnabled: true, wantInitialDelay: 1 * time.Hour},
{desc: "InitialUpdateCheckIntervalEnvar", extraArgs: []string{"--initial-update-check-delay=3h"}, wantEnabled: true, wantInitialDelay: 3 * time.Hour},
}
@@ -40,7 +40,7 @@ func TestAutoUpdateEnableTest(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
runner := testenv.NewExeRunner(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
// create repo
@@ -48,7 +48,10 @@ func TestAutoUpdateEnableTest(t *testing.T) {
"repo", "create", "filesystem", "--path", e.RepoDir,
}, tc.extraArgs...)
runner.Environment = append(runner.Environment, tc.extraEnv...)
for k, v := range tc.extraEnv {
e.Environment[k] = v
}
e.RunAndExpectSuccess(t, args...)
updateInfoFile := filepath.Join(e.ConfigDir, ".kopia.config.update-info.json")

View File

@@ -31,7 +31,7 @@
func TestRestoreFail(t *testing.T) {
t.Parallel()
runner := testenv.NewExeRunner(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
defer e.RunAndExpectSuccess(t, "repo", "disconnect")

View File

@@ -21,12 +21,11 @@
func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
t.Parallel()
th := os.Getenv("TESTING_ACTION_EXE")
if th == "" {
t.Skip("TESTING_ACTION_EXE must be set")
}
th := skipUnlessTestAction(t)
runner := testenv.NewExeRunner(t)
logsDir := testutil.TempLogDirectory(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
@@ -34,7 +33,7 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir, "--override-hostname=foo", "--override-username=foo", "--enable-actions")
e.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir2)
envFile1 := filepath.Join(runner.LogsDir, "env1.txt")
envFile1 := filepath.Join(logsDir, "env1.txt")
// set a action before-snapshot-root that fails and which saves the environment to a file.
e.RunAndExpectSuccess(t,
@@ -45,7 +44,7 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
// this prevents the snapshot from being created
e.RunAndExpectFailure(t, "snapshot", "create", sharedTestDataDir1)
envFile2 := filepath.Join(runner.LogsDir, "env2.txt")
envFile2 := filepath.Join(logsDir, "env2.txt")
// now set a action before-snapshot-root that succeeds and saves environment to a different file
e.RunAndExpectSuccess(t,
@@ -53,7 +52,7 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
"--before-snapshot-root-action",
th+" --save-env="+envFile2)
envFile3 := filepath.Join(runner.LogsDir, "env2.txt")
envFile3 := filepath.Join(logsDir, "env2.txt")
// set a action after-snapshot-root that succeeds and saves environment to a different file
e.RunAndExpectSuccess(t,
@@ -167,12 +166,11 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
func TestSnapshotActionsBeforeAfterFolder(t *testing.T) {
t.Parallel()
th := os.Getenv("TESTING_ACTION_EXE")
if th == "" {
t.Skip("TESTING_ACTION_EXE must be set")
}
th := skipUnlessTestAction(t)
runner := testenv.NewExeRunner(t)
logsDir := testutil.TempLogDirectory(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir, "--enable-actions")
@@ -201,8 +199,8 @@ func TestSnapshotActionsBeforeAfterFolder(t *testing.T) {
actionRanFileBeforeSD2 := filepath.Join(actionRanDir, "before-sd2")
actionRanFileAfterSD2 := filepath.Join(actionRanDir, "before-sd2")
envFile1 := filepath.Join(runner.LogsDir, "env1.txt")
envFile2 := filepath.Join(runner.LogsDir, "env2.txt")
envFile1 := filepath.Join(logsDir, "env1.txt")
envFile2 := filepath.Join(logsDir, "env2.txt")
// setup actions that will write a marker file when the action is executed.
//
@@ -300,10 +298,7 @@ func TestSnapshotActionsEmbeddedScript(t *testing.T) {
func TestSnapshotActionsEnable(t *testing.T) {
t.Parallel()
th := os.Getenv("TESTING_ACTION_EXE")
if th == "" {
t.Skip("TESTING_ACTION_EXE must be set")
}
th := skipUnlessTestAction(t)
cases := []struct {
desc string
@@ -326,14 +321,16 @@ func TestSnapshotActionsEnable(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
runner := testenv.NewExeRunner(t)
logsDir := testutil.TempLogDirectory(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
e.RunAndExpectSuccess(t, append([]string{"repo", "create", "filesystem", "--path", e.RepoDir}, tc.connectFlags...)...)
envFile := filepath.Join(runner.LogsDir, "env1.txt")
envFile := filepath.Join(logsDir, "env1.txt")
// set an action before-snapshot-root that fails and which saves the environment to a file.
e.RunAndExpectSuccess(t,
@@ -411,12 +408,9 @@ func mustReadEnvFile(t *testing.T, fname string) map[string]string {
func TestSnapshotActionsHonorIgnoreRules(t *testing.T) {
t.Parallel()
th := os.Getenv("TESTING_ACTION_EXE")
if th == "" {
t.Skip("TESTING_ACTION_EXE must be set")
}
th := skipUnlessTestAction(t)
runner := testenv.NewExeRunner(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
@@ -450,3 +444,18 @@ func TestSnapshotActionsHonorIgnoreRules(t *testing.T) {
// make sure .kopiaignore was honored
require.NotContains(t, entries, "some-ignored-file")
}
func skipUnlessTestAction(t *testing.T) string {
t.Helper()
th := os.Getenv("TESTING_ACTION_EXE")
if th == "" {
t.Skip("TESTING_ACTION_EXE must be set")
}
if _, err := os.Stat(th); os.IsNotExist(err) {
t.Fatal("TESTING_ACTION_EXE does not exist")
}
return th
}

View File

@@ -561,7 +561,7 @@ func TestSnapshotCreateAllWithManualSnapshot(t *testing.T) {
func TestSnapshotCreateWithStdinStream(t *testing.T) {
t.Parallel()
runner := testenv.NewExeRunner(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
@@ -582,7 +582,8 @@ func TestSnapshotCreateWithStdinStream(t *testing.T) {
w.Close()
streamFileName := "stream-file"
runner.NextCommandStdin = r
runner.SetNextStdin(r)
e.RunAndExpectSuccess(t, "snapshot", "create", "rootdir", "--stdin-file", streamFileName)

View File

@@ -41,7 +41,7 @@ func TestSnapshotFail_Default(t *testing.T) {
func TestSnapshotFail_EnvOverride(t *testing.T) {
t.Parallel()
testSnapshotFail(t, true, nil, []string{"KOPIA_SNAPSHOT_FAIL_FAST=true"})
testSnapshotFail(t, true, nil, map[string]string{"KOPIA_SNAPSHOT_FAIL_FAST": "true"})
}
func TestSnapshotFail_NoFailFast(t *testing.T) {
@@ -70,7 +70,7 @@ func cond(c bool, a, b int) int {
}
// nolint:thelper,cyclop
func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags, snapshotCreateEnv []string) {
func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags []string, snapshotCreateEnv map[string]string) {
if runtime.GOOS == windowsOSName {
t.Skip("this test does not work on Windows")
}
@@ -240,7 +240,7 @@ func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags, snapsh
t.Run(tname, func(t *testing.T) {
t.Parallel()
runner := testenv.NewExeRunner(t)
runner := testenv.NewInProcRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
@@ -260,7 +260,7 @@ func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags, snapsh
e.RunAndExpectSuccess(t, "policy", "set", snapSource, "--ignore-dir-errors", tcIgnoreDirErr, "--ignore-file-errors", tcIgnoreFileErr)
restoreDir := fmt.Sprintf("%s%d_%v_%v", restoreDirPrefix, tcIdx, tcIgnoreDirErr, tcIgnoreFileErr)
testPermissions(t, runner, e, snapSource, modifyEntry, restoreDir, tc.expectSuccess, snapshotCreateFlags, snapshotCreateEnv)
testPermissions(t, e, snapSource, modifyEntry, restoreDir, tc.expectSuccess, snapshotCreateFlags, snapshotCreateEnv)
e.RunAndExpectSuccess(t, "policy", "remove", snapSource)
})
@@ -299,7 +299,7 @@ func createSimplestFileTree(t *testing.T, dirDepth, currDepth int, currPath stri
// against "source" and will test permissions against all entries in "parentDir".
// It returns the number of successful snapshot operations.
// nolint:thelper
func testPermissions(t *testing.T, runner *testenv.CLIExeRunner, e *testenv.CLITest, source, modifyEntry, restoreDir string, expect map[os.FileMode]expectedSnapshotResult, snapshotCreateFlags, snapshotCreateEnv []string) int {
func testPermissions(t *testing.T, e *testenv.CLITest, source, modifyEntry, restoreDir string, expect map[os.FileMode]expectedSnapshotResult, snapshotCreateFlags []string, snapshotCreateEnv map[string]string) int {
var numSuccessfulSnapshots int
changeFile, err := os.Stat(modifyEntry)
@@ -323,10 +323,18 @@ func() {
require.NoError(t, err)
// set up environment for the child process.
oldEnv := runner.Environment
runner.Environment = append(append([]string{}, runner.Environment...), snapshotCreateEnv...)
oldEnv := e.Environment
defer func() { runner.Environment = oldEnv }()
e.Environment = map[string]string{}
for k, v := range oldEnv {
e.Environment[k] = v
}
for k, v := range snapshotCreateEnv {
e.Environment[k] = v
}
defer func() { e.Environment = oldEnv }()
snapshotCreateWithArgs := append([]string{"snapshot", "create", source}, snapshotCreateFlags...)

View File

@@ -77,7 +77,7 @@ func TestEndurance(t *testing.T) {
ft := httptest.NewServer(fts)
defer ft.Close()
runner.Environment = append(runner.Environment, "KOPIA_FAKE_CLOCK_ENDPOINT="+ft.URL)
e.Environment["KOPIA_FAKE_CLOCK_ENDPOINT"] = ft.URL
sts := httptest.NewServer(&webdav.Handler{
FileSystem: webdavDirWithFakeClock{webdav.Dir(tmpDir), fts},
@@ -267,10 +267,8 @@ func enduranceRunner(t *testing.T, runnerID int, fakeTimeServer, webdavServer st
runner := testenv.NewExeRunner(t)
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
runner.Environment = append(runner.Environment,
"KOPIA_FAKE_CLOCK_ENDPOINT="+fakeTimeServer,
"KOPIA_CHECK_FOR_UPDATES=false",
)
e.Environment["KOPIA_FAKE_CLOCK_ENDPOINT"] = fakeTimeServer
e.Environment["KOPIA_CHECK_FOR_UPDATES"] = "false"
e.RunAndExpectSuccess(t, "repo", "connect", "webdav", "--url", webdavServer, "--override-username="+fmt.Sprintf("runner-%v", runnerID))

View File

@@ -5,7 +5,6 @@
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/kopia/kopia/internal/testutil"
@@ -14,21 +13,24 @@
// CLIExeRunner is a CLIExeRunner that invokes the commands via external executable.
type CLIExeRunner struct {
Exe string
Environment []string
PassthroughStderr bool // this is for debugging only
NextCommandStdin io.Reader // this is used for stdin source tests
LogsDir string
}
// Start implements CLIRunner.
func (e *CLIExeRunner) Start(t *testing.T, args []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
func (e *CLIExeRunner) Start(t *testing.T, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, kill func()) {
t.Helper()
c := exec.Command(e.Exe, append([]string{
"--log-dir", e.LogsDir,
}, args...)...)
c.Env = append(os.Environ(), e.Environment...)
c.Env = append(c.Env, os.Environ()...)
for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}
stdoutPipe, err := c.StdoutPipe()
if err != nil {
@@ -52,19 +54,6 @@ func (e *CLIExeRunner) Start(t *testing.T, args []string) (stdout, stderr io.Rea
}
}
// RemoveDefaultPassword prevents KOPIA_PASSWORD from being passed to kopia.
func (e *CLIExeRunner) RemoveDefaultPassword() {
var newEnv []string
for _, s := range e.Environment {
if !strings.HasPrefix(s, "KOPIA_PASSWORD=") {
newEnv = append(newEnv, s)
}
}
e.Environment = newEnv
}
// NewExeRunner returns a CLIRunner that will execute kopia commands by launching subprocesses
// for each. The kopia executable must be passed via KOPIA_EXE environment variable. The test
// will be skipped if it's not provided (unless running inside an IDE in which case system-wide
@@ -96,11 +85,7 @@ func NewExeRunnerWithBinary(t *testing.T, exe string) *CLIExeRunner {
logsDir := testutil.TempLogDirectory(t)
return &CLIExeRunner{
Exe: filepath.FromSlash(exe),
Environment: []string{
"KOPIA_PASSWORD=" + TestRepoPassword,
"KOPIA_ADVANCED_COMMANDS=enabled",
},
Exe: filepath.FromSlash(exe),
LogsDir: logsDir,
}
}

View File

@@ -1,8 +1,11 @@
package testenv
import (
"fmt"
"io"
"os"
"sync"
"sync/atomic"
"testing"
"github.com/alecthomas/kingpin"
@@ -11,15 +14,20 @@
"github.com/kopia/kopia/internal/testlogging"
)
var envPrefixCounter = new(int32)
// CLIInProcRunner is a CLIRunner that invokes provided commands in the current process.
type CLIInProcRunner struct {
RepoPassword string
mu sync.Mutex
// +checklocks:mu
nextCommandStdin io.Reader // this is used for stdin source tests
CustomizeApp func(a *cli.App, kp *kingpin.Application)
}
// Start implements CLIRunner.
func (e *CLIInProcRunner) Start(t *testing.T, args []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
func (e *CLIInProcRunner) Start(t *testing.T, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, kill func()) {
t.Helper()
ctx := testlogging.Context(t)
@@ -27,15 +35,33 @@ func (e *CLIInProcRunner) Start(t *testing.T, args []string) (stdout, stderr io.
a := cli.NewApp()
a.AdvancedCommands = "enabled"
envPrefix := fmt.Sprintf("T%v_", atomic.AddInt32(envPrefixCounter, 1))
a.SetEnvNamePrefixForTesting(envPrefix)
kpapp := kingpin.New("test", "test")
if e.CustomizeApp != nil {
e.CustomizeApp(a, kpapp)
}
return a.RunSubcommand(ctx, kpapp, append([]string{
"--password", e.RepoPassword,
}, args...))
e.mu.Lock()
stdin := e.nextCommandStdin
e.nextCommandStdin = nil
e.mu.Unlock()
for k, v := range env {
os.Setenv(envPrefix+k, v)
}
return a.RunSubcommand(ctx, kpapp, stdin, args)
}
// SetNextStdin sets the stdin to be used on next command execution.
func (e *CLIInProcRunner) SetNextStdin(stdin io.Reader) {
e.mu.Lock()
defer e.mu.Unlock()
e.nextCommandStdin = stdin
}
// NewInProcRunner returns a runner that executes CLI subcommands in the current process using cli.RunSubcommand().
@@ -47,7 +73,6 @@ func NewInProcRunner(t *testing.T) *CLIInProcRunner {
}
return &CLIInProcRunner{
RepoPassword: TestRepoPassword,
CustomizeApp: func(a *cli.App, kp *kingpin.Application) {
a.AddStorageProvider(cli.StorageProvider{
Name: "in-memory",

View File

@@ -27,7 +27,7 @@
// 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())
Start(t *testing.T, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, kill func())
}
// CLITest encapsulates state for a CLI-based test.
@@ -39,7 +39,8 @@ type CLITest struct {
Runner CLIRunner
fixedArgs []string
fixedArgs []string
Environment map[string]string
DefaultRepositoryCreateFlags []string
}
@@ -86,7 +87,10 @@ func NewCLITest(t *testing.T, repoCreateFlags []string, runner CLIRunner) *CLITe
ConfigDir: configDir,
fixedArgs: fixedArgs,
DefaultRepositoryCreateFlags: formatFlags,
Runner: runner,
Environment: map[string]string{
"KOPIA_PASSWORD": TestRepoPassword,
},
Runner: runner,
}
}
@@ -106,7 +110,7 @@ func (e *CLITest) RunAndExpectSuccess(t *testing.T, args ...string) []string {
func (e *CLITest) RunAndProcessStderr(t *testing.T, callback func(line string) bool, args ...string) (wait func() error, kill func()) {
t.Helper()
stdout, stderr, wait, kill := e.Runner.Start(t, e.cmdArgs(args))
stdout, stderr, wait, kill := e.Runner.Start(t, e.cmdArgs(args), e.Environment)
go io.Copy(io.Discard, stdout)
scanner := bufio.NewScanner(stderr)
@@ -179,11 +183,11 @@ func (e *CLITest) Run(t *testing.T, expectedError bool, args ...string) (stdout,
t.Helper()
args = e.cmdArgs(args)
t.Logf("running 'kopia %v'", strings.Join(args, " "))
t.Logf("running 'kopia %v' with %v", strings.Join(args, " "), e.Environment)
timer := timetrack.StartTimer()
stdoutReader, stderrReader, wait, _ := e.Runner.Start(t, args)
stdoutReader, stderrReader, wait, _ := e.Runner.Start(t, args, e.Environment)
var wg sync.WaitGroup