Skip to content

Commit

Permalink
test(e2e): add CLI test for pkg update (#405)
Browse files Browse the repository at this point in the history
Reviewed-by: Cezar Craciunoiu <[email protected]>
Reviewed-by: Alexander Jung <[email protected]>
Approved-by: Alexander Jung <[email protected]>
  • Loading branch information
nderjung authored Apr 29, 2023
2 parents 13d4aeb + b45383f commit 9f4ff05
Show file tree
Hide file tree
Showing 10 changed files with 570 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/e2e-cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Install kraft
run: make kraft DOCKER= DISTDIR="$(go env GOPATH)"/bin

- name: Run unit tests
run: ginkgo -v -p -randomize-all ./test/e2e/framework/...

- name: Run e2e tests
env:
KRAFTKIT_NO_CHECK_UPDATES: true
Expand Down
83 changes: 83 additions & 0 deletions test/e2e/cli/pkg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2023, Unikraft GmbH and The KraftKit Authors.
// Licensed under the BSD-3-Clause License (the "License").
// You may not use this file except in compliance with the License.

package cli_test

import (
. "github.com/onsi/ginkgo/v2" //nolint:stylecheck
. "github.com/onsi/gomega" //nolint:stylecheck

"sigs.k8s.io/kustomize/kyaml/yaml"

fcmd "kraftkit.sh/test/e2e/framework/cmd"
fcfg "kraftkit.sh/test/e2e/framework/config"
. "kraftkit.sh/test/e2e/framework/matchers" //nolint:stylecheck
)

var _ = Describe("kraft pkg", func() {
var cmd *fcmd.Cmd

var stdout *fcmd.IOStream
var stderr *fcmd.IOStream

var cfg *fcfg.Config

BeforeEach(func() {
stdout = fcmd.NewIOStream()
stderr = fcmd.NewIOStream()

cfg = fcfg.NewTempConfig()

cmd = fcmd.NewKraft(stdout, stderr, cfg.Path())
cmd.Args = append(cmd.Args, "pkg")
})

_ = Describe("update", func() {
var manifestsPath string

BeforeEach(func() {
cmd.Args = append(cmd.Args, "update")

manifestsPath = yaml.GetValue(cfg.Read("paths", "manifests"))
Expect(manifestsPath).To(SatisfyAny(
Not(BeAnExistingFile()),
BeAnEmptyDirectory(),
), "manifests directory should either be empty or not yet created")
})

Context("implicitly using the default manager type (manifest)", func() {
When("invoked without flags or positional arguments", func() {
It("should retrieve the list of components, libraries and packages", func() {
err := cmd.Run()
Expect(err).ToNot(HaveOccurred())

Expect(stderr.String()).To(BeEmpty())
// The command sends ANSI escape sequences while updating, such as `\e[2K` (erase entire line).
// References:
// https://www.regular-expressions.info/nonprint.html
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
Expect(stdout.String()).To(MatchRegexp(`\x1b\[2K\[\+\] Updating\.\.\. \[\d+\.\d+s\]\r\n`), "Quoted output: %q", stdout)

Expect(manifestsPath).To(ContainFiles("index.yaml", "unikraft.yaml"))
Expect(manifestsPath).To(ContainDirectories("libs"))
})
})

When("invoked with the --help flag", func() {
BeforeEach(func() {
cmd.Args = append(cmd.Args, "--help")
})

It("should print the command's help", func() {
err := cmd.Run()
Expect(err).ToNot(HaveOccurred())

Expect(stderr.String()).To(BeEmpty())
Expect(stdout).To(MatchRegexp(`^Retrieve new lists of Unikraft components, libraries and packages.\n`))
})
})
})
})
})
1 change: 1 addition & 0 deletions test/e2e/cli/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var _ = Describe("kraft version", func() {
// The help subsystem is managed by cobra and fails when top-level flags
// are passed, so we ensure to keep only the command name and subcommand
// from the original cmd.
// Ref. unikraft/kraftkit#430
cmd.Args = []string{cmd.Args[0], cmd.Args[len(cmd.Args)-1], "--help"}
})

Expand Down
51 changes: 51 additions & 0 deletions test/e2e/framework/matchers/be_empty_dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2023, Unikraft GmbH and The KraftKit Authors.
// Licensed under the BSD-3-Clause License (the "License").
// You may not use this file except in compliance with the License.

package matchers

import (
"fmt"
"os"

"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)

// beAnEmptyDirectoryMatcher asserts that an existing directory is empty.
type beAnEmptyDirectoryMatcher struct {
err error
}

var _ types.GomegaMatcher = (*beAnEmptyDirectoryMatcher)(nil)

func (matcher *beAnEmptyDirectoryMatcher) Match(actual any) (success bool, err error) {
actualDirName, ok := actual.(string)
if !ok {
return false, fmt.Errorf("BeAnEmptyDirectory matcher expects a directory path")
}

dirEntries, err := os.ReadDir(actualDirName)
if err != nil {
matcher.err = fmt.Errorf("reading directory entries: %w", err)
return false, nil
}

n := len(dirEntries)
hasEntries := n > 0

if hasEntries {
matcher.err = fmt.Errorf("directory contains %d entries", n)
}

return !hasEntries, nil
}

func (matcher *beAnEmptyDirectoryMatcher) FailureMessage(actual any) string {
return format.Message(actual, fmt.Sprintf("to be an empty directory: %s", matcher.err))
}

func (*beAnEmptyDirectoryMatcher) NegatedFailureMessage(actual any) string {
return format.Message(actual, "not be an empty directory")
}
66 changes: 66 additions & 0 deletions test/e2e/framework/matchers/be_empty_dir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2023, Unikraft GmbH and The KraftKit Authors.
// Licensed under the BSD-3-Clause License (the "License").
// You may not use this file except in compliance with the License.

package matchers_test

import (
"os"
"testing"

"kraftkit.sh/test/e2e/framework/matchers"
)

func TestBeAnEmptyDirectoryMatcher(t *testing.T) {
testCases := []struct {
desc string
files []fileEntry
success bool
}{
{
desc: "Directory is empty",
files: []fileEntry{},
success: true,
}, {
desc: "Directory contains a file",
files: []fileEntry{regular("f.txt")},
success: false,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
d := newDirectory(t, tc.files...)

m := matchers.BeAnEmptyDirectory()
success, err := m.Match(d)
if err != nil {
t.Fatal("Failed to run matcher:", err)
}

if tc.success && !success {
t.Error("Expected the matcher to succeed. Failure was:", m.FailureMessage(d))
} else if !tc.success && success {
t.Error("Expected the matcher to fail")
}
})
}

t.Run("Directory does not exist", func(t *testing.T) {
d := newDirectory(t)
if err := os.RemoveAll(d); err != nil {
t.Fatal("Failed to remove temporary directory:", err)
}

m := matchers.BeAnEmptyDirectory()
success, err := m.Match(d)
if err != nil {
t.Fatal("Failed to run matcher:", err)
}

if success {
t.Error("Expected the matcher to fail")
}
})
}
65 changes: 65 additions & 0 deletions test/e2e/framework/matchers/contain_dirs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2023, Unikraft GmbH and The KraftKit Authors.
// Licensed under the BSD-3-Clause License (the "License").
// You may not use this file except in compliance with the License.

package matchers

import (
"fmt"
"os"
"path/filepath"

"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)

// containDirectoriesMatcher asserts that a directory contains sub-directories
// with provided names.
type containDirectoriesMatcher struct {
dirNames []string
err error
}

var _ types.GomegaMatcher = (*containDirectoriesMatcher)(nil)

func (matcher *containDirectoriesMatcher) Match(actual any) (success bool, err error) {
actualDirName, ok := actual.(string)
if !ok {
return false, fmt.Errorf("ContainFiles matcher expects a directory path")
}

dirEntries, err := os.ReadDir(actualDirName)
if err != nil {
matcher.err = fmt.Errorf("reading directory entries: %w", err)
return false, nil
}

if n, nExpect := len(dirEntries), len(matcher.dirNames); n < nExpect {
matcher.err = fmt.Errorf("directory contains less entries (%d) than provided sub-directories names (%d)", n, nExpect)
return false, nil
}

for _, fn := range matcher.dirNames {
fi, err := os.Stat(filepath.Join(actualDirName, fn))
if err != nil {
matcher.err = fmt.Errorf("reading file info: %w", err)
return false, nil
}

if !fi.IsDir() {
matcher.err = fmt.Errorf("file %q is not a directory (type: %s)", fi.Name(), fi.Mode().Type())
return false, nil
}
}

return true, nil
}

func (matcher *containDirectoriesMatcher) FailureMessage(actual any) string {
return format.Message(actual, fmt.Sprintf("to contain the directories with the provided names: %s", matcher.err))
}

func (*containDirectoriesMatcher) NegatedFailureMessage(actual any) string {
return format.Message(actual, "not contain the directories with the provided names")
}
63 changes: 63 additions & 0 deletions test/e2e/framework/matchers/contain_dirs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2023, Unikraft GmbH and The KraftKit Authors.
// Licensed under the BSD-3-Clause License (the "License").
// You may not use this file except in compliance with the License.

package matchers_test

import (
"testing"

"kraftkit.sh/test/e2e/framework/matchers"
)

func TestContainDirectoriesMatcher(t *testing.T) {
const d1 = "d1"
const d2 = "d2"

testCases := []struct {
desc string
files []fileEntry
success bool
}{
{
desc: "All directories exist",
files: []fileEntry{dir(d1), dir(d2), dir("other")},
success: true,
}, {
desc: "One directory is missing",
files: []fileEntry{dir(d1), dir("other1"), dir("other2")},
success: false,
}, {
desc: "All directories are missing",
files: []fileEntry{dir("other1"), dir("other2")},
success: false,
}, {
desc: "One file is regular",
files: []fileEntry{dir(d1), regular(d2)},
success: false,
}, {
desc: "One file is a symbolic link",
files: []fileEntry{dir(d1), symlink(d2)},
success: false,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
d := newDirectory(t, tc.files...)

m := matchers.ContainDirectories(d1, d2)
success, err := m.Match(d)
if err != nil {
t.Fatal("Failed to run matcher:", err)
}

if tc.success && !success {
t.Error("Expected the matcher to succeed. Failure was:", m.FailureMessage(d))
} else if !tc.success && success {
t.Error("Expected the matcher to fail")
}
})
}
}
Loading

0 comments on commit 9f4ff05

Please sign in to comment.