Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(e2e): add CLI test for pkg update #405

Merged
merged 1 commit into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
nderjung marked this conversation as resolved.
Show resolved Hide resolved

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`))
nderjung marked this conversation as resolved.
Show resolved Hide resolved
})
})
})
})
})
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