mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-10-21 11:58:41 +00:00
- Add kata-runtime - Add unit test - Add Makefile to build cli Fixes: #33 Signed-off-by: Julio Montes <julio.montes@intel.com> Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com> Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
1116 lines
25 KiB
Go
1116 lines
25 KiB
Go
//
|
|
// Copyright (c) 2017 Intel Corporation
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/dlespiau/covertool/pkg/cover"
|
|
vc "github.com/kata-containers/runtime/virtcontainers"
|
|
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
|
"github.com/kata-containers/runtime/virtcontainers/pkg/vcMock"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
const (
|
|
testDisabledNeedRoot = "Test disabled as requires root user"
|
|
testDisabledNeedNonRoot = "Test disabled as requires non-root user"
|
|
testDirMode = os.FileMode(0750)
|
|
testFileMode = os.FileMode(0640)
|
|
testExeFileMode = os.FileMode(0750)
|
|
|
|
// small docker image used to create root filesystems from
|
|
testDockerImage = "busybox"
|
|
|
|
testPodID = "99999999-9999-9999-99999999999999999"
|
|
testContainerID = "1"
|
|
testBundle = "bundle"
|
|
)
|
|
|
|
var (
|
|
// package variables set by calling TestMain()
|
|
testDir = ""
|
|
testBundleDir = ""
|
|
)
|
|
|
|
// testingImpl is a concrete mock RVC implementation used for testing
|
|
var testingImpl = &vcMock.VCMock{}
|
|
|
|
func init() {
|
|
if version == "" {
|
|
panic("ERROR: invalid build: version not set")
|
|
}
|
|
|
|
if commit == "" {
|
|
panic("ERROR: invalid build: commit not set")
|
|
}
|
|
|
|
if defaultSysConfRuntimeConfiguration == "" {
|
|
panic("ERROR: invalid build: defaultSysConfRuntimeConfiguration not set")
|
|
}
|
|
|
|
if defaultRuntimeConfiguration == "" {
|
|
panic("ERROR: invalid build: defaultRuntimeConfiguration not set")
|
|
}
|
|
|
|
fmt.Printf("INFO: running as actual user %v (effective %v), actual group %v (effective %v)\n",
|
|
os.Getuid(), os.Geteuid(), os.Getgid(), os.Getegid())
|
|
|
|
fmt.Printf("INFO: switching to fake virtcontainers implementation for testing\n")
|
|
vci = testingImpl
|
|
|
|
var err error
|
|
|
|
fmt.Printf("INFO: creating test directory\n")
|
|
testDir, err = ioutil.TempDir("", fmt.Sprintf("%s-", name))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: failed to create test directory: %v", err))
|
|
}
|
|
|
|
fmt.Printf("INFO: test directory is %v\n", testDir)
|
|
|
|
fmt.Printf("INFO: ensuring docker is running\n")
|
|
output, err := runCommandFull([]string{"docker", "version"}, true)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: docker daemon is not installed, not running, or not accessible to current user: %v (error %v)",
|
|
output, err))
|
|
}
|
|
|
|
// Do this now to avoid hitting the test timeout value due to
|
|
// slow network response.
|
|
fmt.Printf("INFO: ensuring required docker image (%v) is available\n", testDockerImage)
|
|
|
|
// Only hit the network if the image doesn't exist locally
|
|
_, err = runCommand([]string{"docker", "image", "inspect", testDockerImage})
|
|
if err == nil {
|
|
fmt.Printf("INFO: docker image %v already exists locally\n", testDockerImage)
|
|
} else {
|
|
_, err = runCommand([]string{"docker", "pull", testDockerImage})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
testBundleDir = filepath.Join(testDir, testBundle)
|
|
err = os.MkdirAll(testBundleDir, testDirMode)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: failed to create bundle directory %v: %v", testBundleDir, err))
|
|
}
|
|
|
|
fmt.Printf("INFO: creating OCI bundle in %v for tests to use\n", testBundleDir)
|
|
err = realMakeOCIBundle(testBundleDir)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ERROR: failed to create OCI bundle: %v", err))
|
|
}
|
|
}
|
|
|
|
// resetCLIGlobals undoes the effects of setCLIGlobals(), restoring the original values
|
|
func resetCLIGlobals() {
|
|
cli.AppHelpTemplate = savedCLIAppHelpTemplate
|
|
cli.VersionPrinter = savedCLIVersionPrinter
|
|
cli.ErrWriter = savedCLIErrWriter
|
|
}
|
|
|
|
func runUnitTests(m *testing.M) {
|
|
ret := m.Run()
|
|
|
|
os.RemoveAll(testDir)
|
|
|
|
os.Exit(ret)
|
|
}
|
|
|
|
// Read fail that should contain a CompatOCISpec and
|
|
// return its JSON representation on success
|
|
func readOCIConfigJSON(configFile string) (string, error) {
|
|
bundlePath := filepath.Dir(configFile)
|
|
ociSpec, err := oci.ParseConfigJSON(bundlePath)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
ociSpecJSON, err := json.Marshal(ociSpec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(ociSpecJSON), err
|
|
}
|
|
|
|
// TestMain is the common main function used by ALL the test functions
|
|
// for this package.
|
|
func TestMain(m *testing.M) {
|
|
// Parse the command line using the stdlib flag package so the flags defined
|
|
// in the testing package get populated.
|
|
cover.ParseAndStripTestFlags()
|
|
|
|
// Make sure we have the opportunity to flush the coverage report to disk when
|
|
// terminating the process.
|
|
atexit(cover.FlushProfiles)
|
|
|
|
// If the test binary name is kata-runtime.coverage, we've are being asked to
|
|
// run the coverage-instrumented kata-runtime.
|
|
if path.Base(os.Args[0]) == name+".coverage" ||
|
|
path.Base(os.Args[0]) == name {
|
|
main()
|
|
exit(0)
|
|
}
|
|
|
|
runUnitTests(m)
|
|
}
|
|
|
|
func createEmptyFile(path string) (err error) {
|
|
return ioutil.WriteFile(path, []byte(""), testFileMode)
|
|
}
|
|
|
|
func grep(pattern, file string) error {
|
|
if file == "" {
|
|
return errors.New("need file")
|
|
}
|
|
|
|
bytes, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
re := regexp.MustCompile(pattern)
|
|
matches := re.FindAllStringSubmatch(string(bytes), -1)
|
|
|
|
if matches == nil {
|
|
return fmt.Errorf("pattern %q not found in file %q", pattern, file)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// newTestHypervisorConfig creaets a new virtcontainers
|
|
// HypervisorConfig, ensuring that the required resources are also
|
|
// created.
|
|
//
|
|
// Note: no parameter validation in case caller wishes to create an invalid
|
|
// object.
|
|
func newTestHypervisorConfig(dir string, create bool) (vc.HypervisorConfig, error) {
|
|
kernelPath := path.Join(dir, "kernel")
|
|
imagePath := path.Join(dir, "image")
|
|
hypervisorPath := path.Join(dir, "hypervisor")
|
|
|
|
if create {
|
|
for _, file := range []string{kernelPath, imagePath, hypervisorPath} {
|
|
err := createEmptyFile(file)
|
|
if err != nil {
|
|
return vc.HypervisorConfig{}, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return vc.HypervisorConfig{
|
|
KernelPath: kernelPath,
|
|
ImagePath: imagePath,
|
|
HypervisorPath: hypervisorPath,
|
|
HypervisorMachineType: "pc-lite",
|
|
}, nil
|
|
}
|
|
|
|
// newTestRuntimeConfig creates a new RuntimeConfig
|
|
func newTestRuntimeConfig(dir, consolePath string, create bool) (oci.RuntimeConfig, error) {
|
|
if dir == "" {
|
|
return oci.RuntimeConfig{}, errors.New("BUG: need directory")
|
|
}
|
|
|
|
hypervisorConfig, err := newTestHypervisorConfig(dir, create)
|
|
if err != nil {
|
|
return oci.RuntimeConfig{}, err
|
|
}
|
|
|
|
return oci.RuntimeConfig{
|
|
HypervisorType: vc.QemuHypervisor,
|
|
HypervisorConfig: hypervisorConfig,
|
|
AgentType: vc.KataContainersAgent,
|
|
ProxyType: vc.CCProxyType,
|
|
ShimType: vc.CCShimType,
|
|
Console: consolePath,
|
|
}, nil
|
|
}
|
|
|
|
// createOCIConfig creates an OCI configuration (spec) file in
|
|
// the bundle directory specified (which must exist).
|
|
func createOCIConfig(bundleDir string) error {
|
|
if bundleDir == "" {
|
|
return errors.New("BUG: Need bundle directory")
|
|
}
|
|
|
|
if !fileExists(bundleDir) {
|
|
return fmt.Errorf("BUG: Bundle directory %s does not exist", bundleDir)
|
|
}
|
|
|
|
var configCmd string
|
|
|
|
// Search for a suitable version of runc to use to generate
|
|
// the OCI config file.
|
|
for _, cmd := range []string{"docker-runc", "runc"} {
|
|
fullPath, err := exec.LookPath(cmd)
|
|
if err == nil {
|
|
configCmd = fullPath
|
|
break
|
|
}
|
|
}
|
|
|
|
if configCmd == "" {
|
|
return errors.New("Cannot find command to generate OCI config file")
|
|
}
|
|
|
|
_, err := runCommand([]string{configCmd, "spec", "--bundle", bundleDir})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
specFile := filepath.Join(bundleDir, specConfig)
|
|
if !fileExists(specFile) {
|
|
return fmt.Errorf("generated OCI config file does not exist: %v", specFile)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createRootfs creates a minimal root filesystem below the specified
|
|
// directory.
|
|
func createRootfs(dir string) error {
|
|
err := os.MkdirAll(dir, testDirMode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
container, err := runCommand([]string{"docker", "create", testDockerImage})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd1 := exec.Command("docker", "export", container)
|
|
cmd2 := exec.Command("tar", "-C", dir, "-xvf", "-")
|
|
|
|
cmd1Stdout, err := cmd1.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd2.Stdin = cmd1Stdout
|
|
|
|
err = cmd2.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cmd1.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cmd2.Wait()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Clean up
|
|
_, err = runCommand([]string{"docker", "rm", container})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// realMakeOCIBundle will create an OCI bundle (including the "config.json"
|
|
// config file) in the directory specified (which must already exist).
|
|
//
|
|
// XXX: Note that tests should *NOT* call this function - they should
|
|
// XXX: instead call makeOCIBundle().
|
|
func realMakeOCIBundle(bundleDir string) error {
|
|
if bundleDir == "" {
|
|
return errors.New("BUG: Need bundle directory")
|
|
}
|
|
|
|
if !fileExists(bundleDir) {
|
|
return fmt.Errorf("BUG: Bundle directory %v does not exist", bundleDir)
|
|
}
|
|
|
|
err := createOCIConfig(bundleDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Note the unusual parameter (a directory, not the config
|
|
// file to parse!)
|
|
spec, err := oci.ParseConfigJSON(bundleDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Determine the rootfs directory name the OCI config refers to
|
|
ociRootPath := spec.Root.Path
|
|
|
|
rootfsDir := filepath.Join(bundleDir, ociRootPath)
|
|
|
|
if strings.HasPrefix(ociRootPath, "/") {
|
|
return fmt.Errorf("Cannot handle absolute rootfs as bundle must be unique to each test")
|
|
}
|
|
|
|
err = createRootfs(rootfsDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Create an OCI bundle in the specified directory.
|
|
//
|
|
// Note that the directory will be created, but it's parent is expected to exist.
|
|
//
|
|
// This function works by copying the already-created test bundle. Ideally,
|
|
// the bundle would be recreated for each test, but createRootfs() uses
|
|
// docker which on some systems is too slow, resulting in the tests timing
|
|
// out.
|
|
func makeOCIBundle(bundleDir string) error {
|
|
from := testBundleDir
|
|
to := bundleDir
|
|
|
|
// only the basename of bundleDir needs to exist as bundleDir
|
|
// will get created by cp(1).
|
|
base := filepath.Dir(bundleDir)
|
|
|
|
for _, dir := range []string{from, base} {
|
|
if !fileExists(dir) {
|
|
return fmt.Errorf("BUG: directory %v should exist", dir)
|
|
}
|
|
}
|
|
|
|
output, err := runCommandFull([]string{"cp", "-a", from, to}, true)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to copy test OCI bundle from %v to %v: %v (output: %v)", from, to, err, output)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// readOCIConfig returns an OCI spec.
|
|
func readOCIConfigFile(configPath string) (oci.CompatOCISpec, error) {
|
|
if configPath == "" {
|
|
return oci.CompatOCISpec{}, errors.New("BUG: need config file path")
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(configPath)
|
|
if err != nil {
|
|
return oci.CompatOCISpec{}, err
|
|
}
|
|
|
|
var ociSpec oci.CompatOCISpec
|
|
if err := json.Unmarshal(data, &ociSpec); err != nil {
|
|
return oci.CompatOCISpec{}, err
|
|
}
|
|
|
|
return ociSpec, nil
|
|
}
|
|
|
|
func writeOCIConfigFile(spec oci.CompatOCISpec, configPath string) error {
|
|
if configPath == "" {
|
|
return errors.New("BUG: need config file path")
|
|
}
|
|
|
|
bytes, err := json.MarshalIndent(spec, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ioutil.WriteFile(configPath, bytes, testFileMode)
|
|
}
|
|
|
|
func newSingleContainerPodStatusList(podID, containerID string, podState, containerState vc.State, annotations map[string]string) []vc.PodStatus {
|
|
return []vc.PodStatus{
|
|
{
|
|
ID: podID,
|
|
State: podState,
|
|
ContainersStatus: []vc.ContainerStatus{
|
|
{
|
|
ID: containerID,
|
|
State: containerState,
|
|
Annotations: annotations,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func execCLICommandFunc(assertHandler *assert.Assertions, cliCommand cli.Command, set *flag.FlagSet, expectedErr bool) {
|
|
app := cli.NewApp()
|
|
ctx := cli.NewContext(app, set, nil)
|
|
app.Name = "foo"
|
|
|
|
fn, ok := cliCommand.Action.(func(context *cli.Context) error)
|
|
assertHandler.True(ok)
|
|
|
|
err := fn(ctx)
|
|
|
|
if expectedErr {
|
|
assertHandler.Error(err)
|
|
} else {
|
|
assertHandler.Nil(err)
|
|
}
|
|
}
|
|
|
|
func TestMakeOCIBundle(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
bundleDir := filepath.Join(tmpdir, "bundle")
|
|
|
|
err = makeOCIBundle(bundleDir)
|
|
assert.NoError(err)
|
|
|
|
specFile := filepath.Join(bundleDir, specConfig)
|
|
assert.True(fileExists(specFile))
|
|
}
|
|
|
|
func TestCreateOCIConfig(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
bundleDir := filepath.Join(tmpdir, "bundle")
|
|
|
|
err = createOCIConfig(bundleDir)
|
|
// ENOENT
|
|
assert.Error(err)
|
|
|
|
err = os.MkdirAll(bundleDir, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
err = createOCIConfig(bundleDir)
|
|
assert.NoError(err)
|
|
|
|
specFile := filepath.Join(bundleDir, specConfig)
|
|
assert.True(fileExists(specFile))
|
|
}
|
|
|
|
func TestCreateRootfs(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
rootfsDir := filepath.Join(tmpdir, "rootfs")
|
|
assert.False(fileExists(rootfsDir))
|
|
|
|
err = createRootfs(rootfsDir)
|
|
assert.NoError(err)
|
|
|
|
// non-comprehensive list of expected directories
|
|
expectedDirs := []string{"bin", "dev", "etc", "usr", "var"}
|
|
|
|
assert.True(fileExists(rootfsDir))
|
|
|
|
for _, dir := range expectedDirs {
|
|
dirPath := filepath.Join(rootfsDir, dir)
|
|
assert.True(fileExists(dirPath))
|
|
}
|
|
}
|
|
|
|
func TestMainUserWantsUsage(t *testing.T) {
|
|
assert := assert.New(t)
|
|
app := cli.NewApp()
|
|
|
|
type testData struct {
|
|
arguments []string
|
|
expectTrue bool
|
|
}
|
|
|
|
data := []testData{
|
|
{[]string{}, true},
|
|
{[]string{"help"}, true},
|
|
{[]string{"version"}, true},
|
|
{[]string{"sub-command", "-h"}, true},
|
|
{[]string{"sub-command", "--help"}, true},
|
|
|
|
{[]string{""}, false},
|
|
{[]string{"sub-command", "--foo"}, false},
|
|
{[]string{"kata-check"}, false},
|
|
{[]string{"haaaalp"}, false},
|
|
{[]string{"wibble"}, false},
|
|
{[]string{"versioned"}, false},
|
|
}
|
|
|
|
for i, d := range data {
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Parse(d.arguments)
|
|
|
|
ctx := cli.NewContext(app, set, nil)
|
|
result := userWantsUsage(ctx)
|
|
|
|
if d.expectTrue {
|
|
assert.True(result, "test %d (%+v)", i, d)
|
|
} else {
|
|
assert.False(result, "test %d (%+v)", i, d)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainBeforeSubCommands(t *testing.T) {
|
|
assert := assert.New(t)
|
|
app := cli.NewApp()
|
|
|
|
type testData struct {
|
|
arguments []string
|
|
expectError bool
|
|
}
|
|
|
|
data := []testData{
|
|
{[]string{}, false},
|
|
{[]string{"help"}, false},
|
|
{[]string{"version"}, false},
|
|
{[]string{"sub-command", "-h"}, false},
|
|
{[]string{"sub-command", "--help"}, false},
|
|
{[]string{"kata-check"}, false},
|
|
}
|
|
|
|
for i, d := range data {
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Parse(d.arguments)
|
|
|
|
ctx := cli.NewContext(app, set, nil)
|
|
err := beforeSubcommands(ctx)
|
|
|
|
if d.expectError {
|
|
assert.Errorf(err, "test %d (%+v)", i, d)
|
|
} else {
|
|
assert.NoError(err, "test %d (%+v)", i, d)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsInvalidLogFile(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
logFile := filepath.Join(tmpdir, "log")
|
|
|
|
// create the file as the wrong type to force a failure
|
|
err = os.MkdirAll(logFile, testDirMode)
|
|
assert.NoError(err)
|
|
|
|
app := cli.NewApp()
|
|
|
|
set := flag.NewFlagSet("", 0)
|
|
set.String("log", logFile, "")
|
|
set.Parse([]string{"create"})
|
|
|
|
ctx := cli.NewContext(app, set, nil)
|
|
|
|
err = beforeSubcommands(ctx)
|
|
assert.Error(err)
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsInvalidLogFormat(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
logFile := filepath.Join(tmpdir, "log")
|
|
|
|
app := cli.NewApp()
|
|
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Bool("debug", true, "")
|
|
set.String("log", logFile, "")
|
|
set.String("log-format", "captain-barnacles", "")
|
|
set.Parse([]string{"create"})
|
|
|
|
logOut := kataLog.Logger.Out
|
|
kataLog.Logger.Out = nil
|
|
|
|
defer func() {
|
|
kataLog.Logger.Out = logOut
|
|
}()
|
|
|
|
ctx := cli.NewContext(app, set, nil)
|
|
|
|
err = beforeSubcommands(ctx)
|
|
assert.Error(err)
|
|
assert.NotNil(kataLog.Logger.Out)
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsLoadConfigurationFail(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
logFile := filepath.Join(tmpdir, "log")
|
|
configFile := filepath.Join(tmpdir, "config")
|
|
|
|
app := cli.NewApp()
|
|
|
|
for _, logFormat := range []string{"json", "text"} {
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Bool("debug", true, "")
|
|
set.String("log", logFile, "")
|
|
set.String("log-format", logFormat, "")
|
|
set.String("kata-config", configFile, "")
|
|
set.Parse([]string{"kata-env"})
|
|
|
|
ctx := cli.NewContext(app, set, nil)
|
|
|
|
savedExitFunc := exitFunc
|
|
|
|
exitStatus := 0
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
defer func() {
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
// calls fatal() so no return
|
|
_ = beforeSubcommands(ctx)
|
|
assert.NotEqual(exitStatus, 0)
|
|
}
|
|
}
|
|
|
|
func TestMainBeforeSubCommandsShowCCConfigPaths(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
app := cli.NewApp()
|
|
|
|
set := flag.NewFlagSet("", 0)
|
|
set.Bool("kata-show-default-config-paths", true, "")
|
|
|
|
ctx := cli.NewContext(app, set, nil)
|
|
|
|
savedExitFunc := exitFunc
|
|
|
|
exitStatus := 99
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
defer func() {
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
savedOutputFile := defaultOutputFile
|
|
|
|
defer func() {
|
|
resetCLIGlobals()
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_SYNC, testFileMode)
|
|
assert.NoError(err)
|
|
defer f.Close()
|
|
|
|
defaultOutputFile = f
|
|
|
|
setCLIGlobals()
|
|
|
|
_ = beforeSubcommands(ctx)
|
|
assert.Equal(exitStatus, 0)
|
|
|
|
text, err := getFileContents(output)
|
|
assert.NoError(err)
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
// Remove last line if empty
|
|
length := len(lines)
|
|
last := lines[length-1]
|
|
if last == "" {
|
|
lines = lines[:length-1]
|
|
}
|
|
|
|
assert.Equal(len(lines), 2)
|
|
|
|
for i, line := range lines {
|
|
switch i {
|
|
case 0:
|
|
assert.Equal(line, defaultSysConfRuntimeConfiguration)
|
|
case 1:
|
|
assert.Equal(line, defaultRuntimeConfiguration)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainFatal(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir(testDir, "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
var exitStatus int
|
|
savedExitFunc := exitFunc
|
|
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
savedErrorFile := defaultErrorFile
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_SYNC, testFileMode)
|
|
assert.NoError(err)
|
|
defaultErrorFile = f
|
|
|
|
defer func() {
|
|
f.Close()
|
|
defaultErrorFile = savedErrorFile
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
exitError := errors.New("hello world")
|
|
|
|
fatal(exitError)
|
|
assert.Equal(exitStatus, 1)
|
|
|
|
text, err := getFileContents(output)
|
|
assert.NoError(err)
|
|
|
|
trimmed := strings.TrimSpace(text)
|
|
assert.Equal(exitError.Error(), trimmed)
|
|
}
|
|
|
|
func testVersionString(assert *assert.Assertions, versionString, expectedVersion, expectedCommit, expectedOCIVersion string) {
|
|
foundVersion := false
|
|
foundCommit := false
|
|
foundOCIVersion := false
|
|
|
|
versionRE := regexp.MustCompile(fmt.Sprintf(`%s\s*:\s*%v`, name, expectedVersion))
|
|
commitRE := regexp.MustCompile(fmt.Sprintf(`%s\s*:\s*%v`, "commit", expectedCommit))
|
|
|
|
ociRE := regexp.MustCompile(fmt.Sprintf(`%s\s*:\s*%v`, "OCI specs", expectedOCIVersion))
|
|
|
|
lines := strings.Split(versionString, "\n")
|
|
assert.True(len(lines) > 0)
|
|
|
|
for _, line := range lines {
|
|
vMatches := versionRE.FindAllStringSubmatch(line, -1)
|
|
if vMatches != nil {
|
|
foundVersion = true
|
|
}
|
|
|
|
cMatches := commitRE.FindAllStringSubmatch(line, -1)
|
|
if cMatches != nil {
|
|
foundCommit = true
|
|
}
|
|
|
|
oMatches := ociRE.FindAllStringSubmatch(line, -1)
|
|
if oMatches != nil {
|
|
foundOCIVersion = true
|
|
}
|
|
}
|
|
|
|
args := fmt.Sprintf("versionString: %q, expectedVersion: %q, expectedCommit: %v, expectedOCIVersion: %v\n",
|
|
versionString, expectedVersion, expectedCommit, expectedOCIVersion)
|
|
|
|
assert.True(foundVersion, args)
|
|
assert.True(foundCommit, args)
|
|
assert.True(foundOCIVersion, args)
|
|
}
|
|
|
|
func TestMainMakeVersionString(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, version, commit, specs.Version)
|
|
}
|
|
|
|
func TestMainMakeVersionStringNoVersion(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedVersion := version
|
|
version = ""
|
|
|
|
defer func() {
|
|
version = savedVersion
|
|
}()
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, unknown, commit, specs.Version)
|
|
}
|
|
|
|
func TestMainMakeVersionStringNoCommit(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedCommit := commit
|
|
commit = ""
|
|
|
|
defer func() {
|
|
commit = savedCommit
|
|
}()
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, version, unknown, specs.Version)
|
|
}
|
|
|
|
func TestMainMakeVersionStringNoOCIVersion(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedVersion := specs.Version
|
|
specs.Version = ""
|
|
|
|
defer func() {
|
|
specs.Version = savedVersion
|
|
}()
|
|
|
|
v := makeVersionString()
|
|
|
|
testVersionString(assert, v, version, commit, unknown)
|
|
}
|
|
|
|
func TestMainCreateRuntimeApp(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedOutputFile := defaultOutputFile
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0640)
|
|
assert.NoError(err)
|
|
defer devNull.Close()
|
|
|
|
defaultOutputFile = devNull
|
|
|
|
setCLIGlobals()
|
|
|
|
defer func() {
|
|
resetCLIGlobals()
|
|
runtimeBeforeSubcommands = savedBefore
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
args := []string{name}
|
|
|
|
err = createRuntimeApp(args)
|
|
assert.NoError(err, "%v", args)
|
|
}
|
|
|
|
func TestMainCreateRuntimeAppInvalidSubCommand(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
exitStatus := 0
|
|
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedExitFunc := exitFunc
|
|
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
defer func() {
|
|
runtimeBeforeSubcommands = savedBefore
|
|
exitFunc = savedExitFunc
|
|
}()
|
|
|
|
// calls fatal() so no return
|
|
_ = createRuntimeApp([]string{name, "i-am-an-invalid-sub-command"})
|
|
|
|
assert.NotEqual(exitStatus, 0)
|
|
}
|
|
|
|
func TestMainCreateRuntime(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
const cmd = "foo"
|
|
const msg = "moo FAILURE"
|
|
|
|
resetCLIGlobals()
|
|
|
|
exitStatus := 0
|
|
|
|
savedOSArgs := os.Args
|
|
savedExitFunc := exitFunc
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedCommands := runtimeCommands
|
|
|
|
os.Args = []string{name, cmd}
|
|
exitFunc = func(status int) { exitStatus = status }
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
// override sub-commands
|
|
runtimeCommands = []cli.Command{
|
|
{
|
|
Name: cmd,
|
|
Action: func(context *cli.Context) error {
|
|
return errors.New(msg)
|
|
},
|
|
},
|
|
}
|
|
|
|
defer func() {
|
|
os.Args = savedOSArgs
|
|
exitFunc = savedExitFunc
|
|
runtimeBeforeSubcommands = savedBefore
|
|
runtimeCommands = savedCommands
|
|
}()
|
|
|
|
assert.Equal(exitStatus, 0)
|
|
createRuntime()
|
|
assert.NotEqual(exitStatus, 0)
|
|
}
|
|
|
|
func TestMainVersionPrinter(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "")
|
|
assert.NoError(err)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
savedOutputFile := defaultOutputFile
|
|
|
|
defer func() {
|
|
resetCLIGlobals()
|
|
defaultOutputFile = savedOutputFile
|
|
}()
|
|
|
|
output := filepath.Join(tmpdir, "output")
|
|
f, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_SYNC, testFileMode)
|
|
assert.NoError(err)
|
|
defer f.Close()
|
|
|
|
defaultOutputFile = f
|
|
|
|
setCLIGlobals()
|
|
|
|
err = createRuntimeApp([]string{name, "--version"})
|
|
assert.NoError(err)
|
|
|
|
err = grep(fmt.Sprintf(`%s\s*:\s*%s`, name, version), output)
|
|
assert.NoError(err)
|
|
}
|
|
|
|
func TestMainFatalWriter(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
const cmd = "foo"
|
|
const msg = "moo FAILURE"
|
|
|
|
// create buffer to save logger output
|
|
buf := &bytes.Buffer{}
|
|
|
|
savedBefore := runtimeBeforeSubcommands
|
|
savedLogOutput := kataLog.Logger.Out
|
|
savedCLIExiter := cli.OsExiter
|
|
savedCommands := runtimeCommands
|
|
|
|
// disable
|
|
runtimeBeforeSubcommands = nil
|
|
|
|
// save all output
|
|
kataLog.Logger.Out = buf
|
|
|
|
cli.OsExiter = func(status int) {}
|
|
|
|
// override sub-commands
|
|
runtimeCommands = []cli.Command{
|
|
{
|
|
Name: cmd,
|
|
Action: func(context *cli.Context) error {
|
|
return cli.NewExitError(msg, 42)
|
|
},
|
|
},
|
|
}
|
|
|
|
defer func() {
|
|
runtimeBeforeSubcommands = savedBefore
|
|
kataLog.Logger.Out = savedLogOutput
|
|
cli.OsExiter = savedCLIExiter
|
|
runtimeCommands = savedCommands
|
|
}()
|
|
|
|
setCLIGlobals()
|
|
|
|
err := createRuntimeApp([]string{name, cmd})
|
|
assert.Error(err)
|
|
|
|
re := regexp.MustCompile(
|
|
fmt.Sprintf(`\blevel\b.*\berror\b.*\b%s\b`, msg))
|
|
matches := re.FindAllStringSubmatch(buf.String(), -1)
|
|
assert.NotEmpty(matches)
|
|
}
|
|
|
|
func TestMainSetCLIGlobals(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
defer resetCLIGlobals()
|
|
|
|
cli.AppHelpTemplate = ""
|
|
cli.VersionPrinter = nil
|
|
cli.ErrWriter = nil
|
|
|
|
setCLIGlobals()
|
|
|
|
assert.NotEqual(cli.AppHelpTemplate, "")
|
|
assert.NotNil(cli.VersionPrinter)
|
|
assert.NotNil(cli.ErrWriter)
|
|
}
|
|
|
|
func TestMainResetCLIGlobals(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
assert.NotEqual(cli.AppHelpTemplate, "")
|
|
assert.NotNil(savedCLIVersionPrinter)
|
|
assert.NotNil(savedCLIErrWriter)
|
|
|
|
cli.AppHelpTemplate = ""
|
|
cli.VersionPrinter = nil
|
|
cli.ErrWriter = nil
|
|
|
|
resetCLIGlobals()
|
|
|
|
assert.Equal(cli.AppHelpTemplate, savedCLIAppHelpTemplate)
|
|
assert.NotNil(cli.VersionPrinter)
|
|
assert.NotNil(savedCLIVersionPrinter)
|
|
}
|