From 4fe691214366c08ea846bdc6233dd592da0d4769 Mon Sep 17 00:00:00 2001 From: Alexey Palazhchenko Date: Thu, 20 May 2021 08:50:05 +0300 Subject: [PATCH] test: better `talosctl ls` tests Refs #3018. Signed-off-by: Alexey Palazhchenko --- hack/test/e2e-docker.sh | 5 +- hack/test/e2e.sh | 2 +- hack/test/provision-tests.sh | 2 +- internal/integration/base/cli.go | 6 +- internal/integration/base/run.go | 56 +++++++------ internal/integration/cli/list.go | 83 +++++++++++++++++++ pkg/archiver/walker_test.go | 59 ++++++++++--- pkg/provision/providers/qemu/launch.go | 2 +- .../Guides/configuring-pull-through-cache.md | 2 +- 9 files changed, 170 insertions(+), 47 deletions(-) diff --git a/hack/test/e2e-docker.sh b/hack/test/e2e-docker.sh index ae5a0babe8..2be0d9dde1 100755 --- a/hack/test/e2e-docker.sh +++ b/hack/test/e2e-docker.sh @@ -29,7 +29,7 @@ function create_cluster { } function destroy_cluster() { - "${TALOSCTL}" cluster destroy --name "${CLUSTER_NAME}" + "${TALOSCTL}" cluster destroy --name "${CLUSTER_NAME}" --provisioner "${PROVISIONER}" } create_cluster @@ -37,3 +37,6 @@ get_kubeconfig ${KUBECTL} config set-cluster e2e-docker --server https://10.5.0.2:6443 run_talos_integration_test_docker run_kubernetes_integration_test + +# Unlike other local e2e tests, we don't destroy the cluster there as it is used by CAPI and AWS/GCP e2e tests later. +# destroy_cluster diff --git a/hack/test/e2e.sh b/hack/test/e2e.sh index 2f4bfb2680..ce280ef0a9 100755 --- a/hack/test/e2e.sh +++ b/hack/test/e2e.sh @@ -218,7 +218,7 @@ function build_registry_mirrors { if [[ "${CI:-false}" == "true" ]]; then REGISTRY_MIRROR_FLAGS= - for registry in docker.io ghcr.io k8s.gcr.io quay.io gcr.io registry.dev.talos-systems.io; do + for registry in docker.io k8s.gcr.io quay.io gcr.io ghcr.io registry.dev.talos-systems.io; do local service="registry-${registry//./-}.ci.svc" local addr=`python3 -c "import socket; print(socket.gethostbyname('${service}'))"` diff --git a/hack/test/provision-tests.sh b/hack/test/provision-tests.sh index d88b6121e3..dd3816c6cc 100755 --- a/hack/test/provision-tests.sh +++ b/hack/test/provision-tests.sh @@ -6,7 +6,7 @@ case "${CI:-false}" in true) mirror_flag="" - for registry in docker.io ghcr.io k8s.gcr.io quay.io gcr.io registry.dev.talos-systems.io; do + for registry in docker.io k8s.gcr.io quay.io gcr.io ghcr.io registry.dev.talos-systems.io; do service="registry-${registry//./-}.ci.svc" addr=`python3 -c "import socket; print(socket.gethostbyname('${service}'))"` diff --git a/internal/integration/base/cli.go b/internal/integration/base/cli.go index c31517e61a..95a0741d03 100644 --- a/internal/integration/base/cli.go +++ b/internal/integration/base/cli.go @@ -103,14 +103,14 @@ func (cliSuite *CLISuite) buildCLICmd(args []string) *exec.Cmd { } // RunCLI runs talosctl binary with the options provided. -func (cliSuite *CLISuite) RunCLI(args []string, options ...RunOption) { - Run(&cliSuite.Suite, cliSuite.buildCLICmd(args), options...) +func (cliSuite *CLISuite) RunCLI(args []string, options ...RunOption) (stdout string) { + return Run(&cliSuite.Suite, cliSuite.buildCLICmd(args), options...) } // RunAndWaitForMatch retries command until output matches. func (cliSuite *CLISuite) RunAndWaitForMatch(args []string, regex *regexp.Regexp, duration time.Duration, options ...retry.Option) { cliSuite.Assert().NoError(retry.Constant(duration, options...).Retry(func() error { - stdout, _, err := RunAndWait(&cliSuite.Suite, cliSuite.buildCLICmd(args)) + stdout, _, err := runAndWait(&cliSuite.Suite, cliSuite.buildCLICmd(args)) if err != nil { return retry.UnexpectedError(err) } diff --git a/internal/integration/base/run.go b/internal/integration/base/run.go index c0fde9186b..5d1e2406dd 100644 --- a/internal/integration/base/run.go +++ b/internal/integration/base/run.go @@ -44,13 +44,6 @@ func ShouldFail() RunOption { } } -// ShouldSucceed tells Run command should succeed (that is default). -func ShouldSucceed() RunOption { - return func(opts *runOptions) { - opts.shouldFail = true - } -} - // StderrNotEmpty tells run that stderr of the command should not be empty. func StderrNotEmpty() RunOption { return func(opts *runOptions) { @@ -107,10 +100,10 @@ func StderrMatchFunc(f MatchFunc) RunOption { } } -// RunAndWait launches the command and waits for completion. +// runAndWait launches the command and waits for completion. // -// RunAndWait doesn't do any assertions on result. -func RunAndWait(suite *suite.Suite, cmd *exec.Cmd) (stdoutBuf, stderrBuf *bytes.Buffer, err error) { +// runAndWait doesn't do any assertions on result. +func runAndWait(suite *suite.Suite, cmd *exec.Cmd) (stdoutBuf, stderrBuf *bytes.Buffer, err error) { var stdout, stderr bytes.Buffer cmd.Stdin = nil @@ -142,17 +135,26 @@ func RunAndWait(suite *suite.Suite, cmd *exec.Cmd) (stdoutBuf, stderrBuf *bytes. return &stdout, &stderr, err } -// Run executes command and asserts on its exit status/output. +// Run executes command, asserts on its exit status/output, and returns stdout. // -//nolint:gocyclo -func Run(suite *suite.Suite, cmd *exec.Cmd, options ...RunOption) { +//nolint:gocyclo,nakedret +func Run(suite *suite.Suite, cmd *exec.Cmd, options ...RunOption) (stdout string) { var opts runOptions for _, o := range options { o(&opts) } - stdout, stderr, err := RunAndWait(suite, cmd) + stdoutBuf, stderrBuf, err := runAndWait(suite, cmd) + + if stdoutBuf != nil { + stdout = stdoutBuf.String() + } + + var stderr string + if stderrBuf != nil { + stderr = stderrBuf.String() + } if err == nil { if opts.shouldFail { @@ -169,39 +171,41 @@ func Run(suite *suite.Suite, cmd *exec.Cmd, options ...RunOption) { } } - if opts.stderrNotEmpty { - suite.Assert().NotEmpty(stderr.String(), "stderr should be not empty") + if opts.stdoutEmpty { + suite.Assert().Empty(stdout, "stdout should be empty") } else { - suite.Assert().Empty(stderr.String(), "stderr should be empty") + suite.Assert().NotEmpty(stdout, "stdout should be not empty") } - if opts.stdoutEmpty { - suite.Assert().Empty(stdout.String(), "stdout should be empty") + if opts.stderrNotEmpty { + suite.Assert().NotEmpty(stderr, "stderr should be not empty") } else { - suite.Assert().NotEmpty(stdout.String(), "stdout should be not empty") + suite.Assert().Empty(stderr, "stderr should be empty") } for _, rx := range opts.stdoutRegexps { - suite.Assert().Regexp(rx, stdout.String()) + suite.Assert().Regexp(rx, stdout) } for _, rx := range opts.stderrRegexps { - suite.Assert().Regexp(rx, stderr.String()) + suite.Assert().Regexp(rx, stderr) } for _, rx := range opts.stdoutNegativeRegexps { - suite.Assert().NotRegexp(rx, stdout.String()) + suite.Assert().NotRegexp(rx, stdout) } for _, rx := range opts.stderrNegativeRegexps { - suite.Assert().NotRegexp(rx, stderr.String()) + suite.Assert().NotRegexp(rx, stderr) } for _, f := range opts.stdoutMatchers { - suite.Assert().NoError(f(stdout.String()), "stdout match: %q", stdout.String()) + suite.Assert().NoError(f(stdout), "stdout match: %q", stdout) } for _, f := range opts.stderrMatchers { - suite.Assert().NoError(f(stderr.String()), "stderr match: %q", stderr.String()) + suite.Assert().NoError(f(stderr), "stderr match: %q", stderr) } + + return } diff --git a/internal/integration/cli/list.go b/internal/integration/cli/list.go index beb32aeeea..1510b76da4 100644 --- a/internal/integration/cli/list.go +++ b/internal/integration/cli/list.go @@ -7,9 +7,16 @@ package cli import ( + "fmt" + "os" "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" "github.com/talos-systems/talos/internal/integration/base" + "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine" ) // ListSuite verifies dmesg command. @@ -31,6 +38,82 @@ func (suite *ListSuite) TestSuccess() { base.StdoutShouldNotMatch(regexp.MustCompile(`os-release`))) } +// TestDepth tests various combinations of --recurse and --depth flags. +func (suite *ListSuite) TestDepth() { + suite.T().Parallel() + + node := suite.RandomDiscoveredNode(machine.TypeControlPlane) + + // checks that enough separators are encountered in the output + runAndCheck := func(t *testing.T, expectedSeparators int, flags ...string) { + args := append([]string{"list", "--nodes", node, "/system"}, flags...) + stdout := suite.RunCLI(args) + + lines := strings.Split(strings.TrimSpace(stdout), "\n") + assert.Greater(t, len(lines), 2) + assert.Equal(t, []string{"NODE", "NAME"}, strings.Fields(lines[0])) + assert.Equal(t, []string{"."}, strings.Fields(lines[1])[1:]) + + var maxActualSeparators int + + for _, line := range lines[2:] { + actualSeparators := strings.Count(strings.Fields(line)[1], string(os.PathSeparator)) + + msg := fmt.Sprintf( + "too many separators (actualSeparators = %d, expectedSeparators = %d)\nflags: %s\nlines:\n%s", + actualSeparators, expectedSeparators, strings.Join(flags, " "), strings.Join(lines, "\n"), + ) + if !assert.LessOrEqual(t, actualSeparators, expectedSeparators, msg) { + return + } + + if maxActualSeparators < actualSeparators { + maxActualSeparators = actualSeparators + } + } + + msg := fmt.Sprintf( + "not enough separators (maxActualSeparators = %d, expectedSeparators = %d)\nflags: %s\nlines:\n%s", + maxActualSeparators, expectedSeparators, strings.Join(flags, " "), strings.Join(lines, "\n"), + ) + assert.Equal(t, maxActualSeparators, expectedSeparators, msg) + } + + for _, test := range []struct { + separators int + flags []string + }{ + {separators: 0}, + + {separators: 0, flags: []string{"--recurse=false"}}, + {separators: 5, flags: []string{"--recurse=true"}}, + + {separators: 0, flags: []string{"--depth=-1"}}, + {separators: 0, flags: []string{"--depth=0"}}, + {separators: 0, flags: []string{"--depth=1"}}, + {separators: 0, flags: []string{"--depth=2"}}, + {separators: 0, flags: []string{"--depth=3"}}, + + {separators: 0, flags: []string{"--recurse=false", "--depth=-1"}}, + {separators: 0, flags: []string{"--recurse=false", "--depth=0"}}, + {separators: 0, flags: []string{"--recurse=false", "--depth=1"}}, + {separators: 0, flags: []string{"--recurse=false", "--depth=2"}}, + {separators: 0, flags: []string{"--recurse=false", "--depth=3"}}, + + {separators: 5, flags: []string{"--recurse=true", "--depth=-1"}}, + {separators: 5, flags: []string{"--recurse=true", "--depth=0"}}, + {separators: 0, flags: []string{"--recurse=true", "--depth=1"}}, + {separators: 1, flags: []string{"--recurse=true", "--depth=2"}}, + {separators: 2, flags: []string{"--recurse=true", "--depth=3"}}, + } { + test := test + suite.Run(strings.Join(test.flags, ","), func() { + suite.T().Parallel() + runAndCheck(suite.T(), test.separators, test.flags...) + }) + } +} + func init() { allSuites = append(allSuites, new(ListSuite)) } diff --git a/pkg/archiver/walker_test.go b/pkg/archiver/walker_test.go index 793c50f9ae..f7dba963ec 100644 --- a/pkg/archiver/walker_test.go +++ b/pkg/archiver/walker_test.go @@ -6,6 +6,7 @@ package archiver_test import ( "context" + "fmt" "io/ioutil" "os" "path/filepath" @@ -67,20 +68,52 @@ func (suite *WalkerSuite) TestIterationFilter() { } func (suite *WalkerSuite) TestIterationMaxRecurseDepth() { - ch, err := archiver.Walker(context.Background(), suite.tmpDir, archiver.WithMaxRecurseDepth(1)) - suite.Require().NoError(err) - - relPaths := []string(nil) - - for fi := range ch { - suite.Require().NoError(fi.Error) - relPaths = append(relPaths, fi.RelPath) + for _, test := range []struct { + maxDepth int + result []string + }{ + { + maxDepth: -1, + result: []string{".", "dev", "dev/random", "etc", "etc/certs", "etc/certs/ca.crt", "etc/hostname", "lib", "lib/dynalib.so", "usr", "usr/bin", "usr/bin/cp", "usr/bin/mv"}, + }, + { + // confusing case + maxDepth: 0, + result: []string{".", "dev", "etc", "lib", "usr"}, + }, + { + maxDepth: 1, + result: []string{".", "dev", "etc", "lib", "usr"}, + }, + { + maxDepth: 2, + result: []string{".", "dev", "dev/random", "etc", "etc/certs", "etc/hostname", "lib", "lib/dynalib.so", "usr", "usr/bin"}, + }, + { + maxDepth: 3, + result: []string{".", "dev", "dev/random", "etc", "etc/certs", "etc/certs/ca.crt", "etc/hostname", "lib", "lib/dynalib.so", "usr", "usr/bin", "usr/bin/cp", "usr/bin/mv"}, + }, + { + maxDepth: 4, + result: []string{".", "dev", "dev/random", "etc", "etc/certs", "etc/certs/ca.crt", "etc/hostname", "lib", "lib/dynalib.so", "usr", "usr/bin", "usr/bin/cp", "usr/bin/mv"}, + }, + } { + test := test + suite.Run(fmt.Sprint(test.maxDepth), func() { + suite.T().Parallel() + + ch, err := archiver.Walker(context.Background(), suite.tmpDir, archiver.WithMaxRecurseDepth(test.maxDepth)) + suite.Require().NoError(err) + + var result []string + for fi := range ch { + suite.Require().NoError(fi.Error) + result = append(result, fi.RelPath) + } + + suite.Equal(test.result, result) + }) } - - suite.Assert().Equal([]string{ - ".", "dev", "etc", "lib", "usr", - }, - relPaths) } func (suite *WalkerSuite) TestIterationFile() { diff --git a/pkg/provision/providers/qemu/launch.go b/pkg/provision/providers/qemu/launch.go index 20b1f3e0a1..0a9973fb76 100644 --- a/pkg/provision/providers/qemu/launch.go +++ b/pkg/provision/providers/qemu/launch.go @@ -284,7 +284,7 @@ func launchVM(config *LaunchConfig) error { } } - fmt.Fprintf(os.Stderr, "starting qemu with args:\n%s\n", strings.Join(args, " ")) + fmt.Fprintf(os.Stderr, "starting %s with args:\n%s\n", config.QemuExecutable, strings.Join(args, " ")) cmd := exec.Command( config.QemuExecutable, args..., diff --git a/website/content/docs/v0.11/Guides/configuring-pull-through-cache.md b/website/content/docs/v0.11/Guides/configuring-pull-through-cache.md index 5502f20f7d..3b2e05fbcf 100644 --- a/website/content/docs/v0.11/Guides/configuring-pull-through-cache.md +++ b/website/content/docs/v0.11/Guides/configuring-pull-through-cache.md @@ -24,7 +24,7 @@ The follow are requirements for creating the set of caching proxies: ## Launch the Caching Docker Registry Proxies -Talos pulls from `docker.io`, `k8s.gcr.io`, `gcr.io`, `ghcr.io` and `quay.io` by default. +Talos pulls from `docker.io`, `k8s.gcr.io`, `quay.io`, `gcr.io`, and `ghcr.io` by default. If your configuration is different, you might need to modify the commands below: ```bash