add tests to cover runtime entrypoint
This commit is contained in:
parent
11a2473807
commit
c8ab7098d8
1
go.mod
1
go.mod
@ -26,6 +26,7 @@ require (
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee
|
||||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/afero v1.2.2
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/wagoodman/keybinding v0.0.0-20181213133715-6a824da6df05
|
||||
|
@ -133,21 +133,22 @@ func (ci *CiEvaluator) Evaluate(analysis *image.AnalysisResult) bool {
|
||||
return ci.Pass
|
||||
}
|
||||
|
||||
func (ci *CiEvaluator) Report() {
|
||||
fmt.Println(utils.TitleFormat("Inefficient Files:"))
|
||||
func (ci *CiEvaluator) Report() string {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintln(&sb, utils.TitleFormat("Inefficient Files:"))
|
||||
|
||||
template := "%5s %12s %-s\n"
|
||||
fmt.Printf(template, "Count", "Wasted Space", "File Path")
|
||||
fmt.Fprintf(&sb, template, "Count", "Wasted Space", "File Path")
|
||||
|
||||
if len(ci.InefficientFiles) == 0 {
|
||||
fmt.Println("None")
|
||||
fmt.Fprintln(&sb, "None")
|
||||
} else {
|
||||
for _, file := range ci.InefficientFiles {
|
||||
fmt.Printf(template, strconv.Itoa(file.References), humanize.Bytes(file.SizeBytes), file.Path)
|
||||
fmt.Fprintf(&sb, template, strconv.Itoa(file.References), humanize.Bytes(file.SizeBytes), file.Path)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(utils.TitleFormat("Results:"))
|
||||
fmt.Fprintln(&sb, utils.TitleFormat("Results:"))
|
||||
|
||||
status := "PASS"
|
||||
|
||||
@ -165,23 +166,24 @@ func (ci *CiEvaluator) Report() {
|
||||
result := ci.Results[rule]
|
||||
name := strings.TrimPrefix(rule, "rules.")
|
||||
if result.message != "" {
|
||||
fmt.Printf(" %s: %s: %s\n", result.status.String(), name, result.message)
|
||||
fmt.Fprintf(&sb, " %s: %s: %s\n", result.status.String(), name, result.message)
|
||||
} else {
|
||||
fmt.Printf(" %s: %s\n", result.status.String(), name)
|
||||
fmt.Fprintf(&sb, " %s: %s\n", result.status.String(), name)
|
||||
}
|
||||
}
|
||||
|
||||
if ci.Misconfigured {
|
||||
fmt.Println(aurora.Red("CI Misconfigured"))
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(&sb, aurora.Red("CI Misconfigured"))
|
||||
|
||||
summary := fmt.Sprintf("Result:%s [Total:%d] [Passed:%d] [Failed:%d] [Warn:%d] [Skipped:%d]", status, ci.Tally.Total, ci.Tally.Pass, ci.Tally.Fail, ci.Tally.Warn, ci.Tally.Skip)
|
||||
if ci.Pass {
|
||||
fmt.Println(aurora.Green(summary))
|
||||
} else if ci.Pass && ci.Tally.Warn > 0 {
|
||||
fmt.Println(aurora.Blue(summary))
|
||||
} else {
|
||||
fmt.Println(aurora.Red(summary))
|
||||
summary := fmt.Sprintf("Result:%s [Total:%d] [Passed:%d] [Failed:%d] [Warn:%d] [Skipped:%d]", status, ci.Tally.Total, ci.Tally.Pass, ci.Tally.Fail, ci.Tally.Warn, ci.Tally.Skip)
|
||||
if ci.Pass {
|
||||
fmt.Fprintln(&sb, aurora.Green(summary))
|
||||
} else if ci.Pass && ci.Tally.Warn > 0 {
|
||||
fmt.Fprintln(&sb, aurora.Blue(summary))
|
||||
} else {
|
||||
fmt.Fprintln(&sb, aurora.Red(summary))
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
31
runtime/event.go
Normal file
31
runtime/event.go
Normal file
@ -0,0 +1,31 @@
|
||||
package runtime
|
||||
|
||||
type eventChannel chan event
|
||||
|
||||
type event struct {
|
||||
stdout string
|
||||
stderr string
|
||||
err error
|
||||
errorOnExit bool
|
||||
}
|
||||
|
||||
func (ec eventChannel) message(msg string) {
|
||||
ec <- event{
|
||||
stdout: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (ec eventChannel) exitWithError(err error) {
|
||||
ec <- event{
|
||||
err: err,
|
||||
errorOnExit: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (ec eventChannel) exitWithErrorMessage(msg string, err error) {
|
||||
ec <- event{
|
||||
stderr: msg,
|
||||
err: err,
|
||||
errorOnExit: true,
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ package export
|
||||
import (
|
||||
"encoding/json"
|
||||
diveImage "github.com/wagoodman/dive/dive/image"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type export struct {
|
||||
@ -47,14 +46,6 @@ func NewExport(analysis *diveImage.AnalysisResult) *export {
|
||||
return &data
|
||||
}
|
||||
|
||||
func (exp *export) marshal() ([]byte, error) {
|
||||
func (exp *export) Marshal() ([]byte, error) {
|
||||
return json.MarshalIndent(&exp, "", " ")
|
||||
}
|
||||
|
||||
func (exp *export) ToFile(exportFilePath string) error {
|
||||
payload, err := exp.marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(exportFilePath, payload, 0644)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ func Test_Export(t *testing.T) {
|
||||
result := docker.TestAnalysisFromArchive(t, "../../.data/test-docker-image.tar")
|
||||
|
||||
export := NewExport(result)
|
||||
payload, err := export.marshal()
|
||||
payload, err := export.Marshal()
|
||||
if err != nil {
|
||||
t.Errorf("Test_Export: unable to export analysis: %v", err)
|
||||
}
|
||||
|
189
runtime/run.go
189
runtime/run.go
@ -2,126 +2,149 @@ package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/wagoodman/dive/dive"
|
||||
"github.com/wagoodman/dive/runtime/ci"
|
||||
"github.com/wagoodman/dive/runtime/export"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/wagoodman/dive/dive"
|
||||
"github.com/wagoodman/dive/dive/filetree"
|
||||
"github.com/wagoodman/dive/dive/image"
|
||||
"github.com/wagoodman/dive/runtime/ci"
|
||||
"github.com/wagoodman/dive/runtime/export"
|
||||
"github.com/wagoodman/dive/runtime/ui"
|
||||
"github.com/wagoodman/dive/utils"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func runCi(analysis *image.AnalysisResult, options Options) {
|
||||
|
||||
fmt.Printf(" efficiency: %2.4f %%\n", analysis.Efficiency*100)
|
||||
fmt.Printf(" wastedBytes: %d bytes (%s)\n", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes))
|
||||
fmt.Printf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100)
|
||||
|
||||
evaluator := ci.NewCiEvaluator(options.CiConfig)
|
||||
|
||||
pass := evaluator.Evaluate(analysis)
|
||||
|
||||
// todo: report should return a string?
|
||||
evaluator.Report()
|
||||
|
||||
if pass {
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// todo: give channel of strings which the caller uses for fmt.print? or a more complex type?
|
||||
// todo: return err? or treat like a go routine?
|
||||
// todo: should there be a run() so that Run() can do the above and run() be the go routine? Then we test the behavior of run() (not Run())
|
||||
func Run(options Options) {
|
||||
func run(enableUi bool, options Options, imageResolver image.Resolver, events eventChannel, filesystem afero.Fs) {
|
||||
var img *image.Image
|
||||
var err error
|
||||
defer close(events)
|
||||
|
||||
doExport := options.ExportFile != ""
|
||||
doBuild := len(options.BuildArgs) > 0
|
||||
|
||||
// if an image option was provided, parse it and determine the container image...
|
||||
// otherwise, use the configs default value.
|
||||
|
||||
// if build is given, get the handler based off of either the explicit runtime
|
||||
|
||||
imageResolver, err := dive.GetImageResolver(options.Source)
|
||||
if err != nil {
|
||||
fmt.Printf("cannot determine image provider: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var img *image.Image
|
||||
|
||||
if doBuild {
|
||||
fmt.Println(utils.TitleFormat("Building image..."))
|
||||
events.message(utils.TitleFormat("Building image..."))
|
||||
img, err = imageResolver.Build(options.BuildArgs)
|
||||
if err != nil {
|
||||
fmt.Printf("cannot build image: %v\n", err)
|
||||
os.Exit(1)
|
||||
events.exitWithErrorMessage("cannot build image", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fmt.Println(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)")
|
||||
events.message(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)")
|
||||
img, err = imageResolver.Fetch(options.Image)
|
||||
if err != nil {
|
||||
fmt.Printf("cannot fetch image: %v\n", err)
|
||||
os.Exit(1)
|
||||
events.exitWithErrorMessage("cannot fetch image", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// todo, cleanup on error
|
||||
// todo: image get should return error for cleanup?
|
||||
|
||||
if doExport {
|
||||
fmt.Println(utils.TitleFormat(fmt.Sprintf("Analyzing image... (export to '%s')", options.ExportFile)))
|
||||
} else {
|
||||
fmt.Println(utils.TitleFormat("Analyzing image..."))
|
||||
}
|
||||
|
||||
result, err := img.Analyze()
|
||||
events.message(utils.TitleFormat("Analyzing image..."))
|
||||
analysis, err := img.Analyze()
|
||||
if err != nil {
|
||||
fmt.Printf("cannot analyze image: %v\n", err)
|
||||
os.Exit(1)
|
||||
events.exitWithErrorMessage("cannot analyze image", err)
|
||||
return
|
||||
}
|
||||
|
||||
if doExport {
|
||||
err = export.NewExport(result).ToFile(options.ExportFile)
|
||||
events.message(utils.TitleFormat(fmt.Sprintf("Exporting image to '%s'...", options.ExportFile)))
|
||||
bytes, err := export.NewExport(analysis).Marshal()
|
||||
if err != nil {
|
||||
fmt.Printf("cannot write export file: %v\n", err)
|
||||
os.Exit(1)
|
||||
events.exitWithErrorMessage("cannot marshal export payload", err)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := filesystem.OpenFile(options.ExportFile, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
events.exitWithErrorMessage("cannot open export file", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write(bytes)
|
||||
if err != nil {
|
||||
events.exitWithErrorMessage("cannot write to export file", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if options.Ci {
|
||||
runCi(result, options)
|
||||
} else {
|
||||
if doExport {
|
||||
os.Exit(0)
|
||||
events.message(fmt.Sprintf(" efficiency: %2.4f %%\n", analysis.Efficiency*100))
|
||||
events.message(fmt.Sprintf(" wastedBytes: %d bytes (%s)\n", analysis.WastedBytes, humanize.Bytes(analysis.WastedBytes)))
|
||||
events.message(fmt.Sprintf(" userWastedPercent: %2.4f %%\n", analysis.WastedUserPercent*100))
|
||||
|
||||
evaluator := ci.NewCiEvaluator(options.CiConfig)
|
||||
pass := evaluator.Evaluate(analysis)
|
||||
events.message(evaluator.Report())
|
||||
|
||||
if !pass {
|
||||
events.exitWithError(nil)
|
||||
}
|
||||
|
||||
fmt.Println(utils.TitleFormat("Building cache..."))
|
||||
cache := filetree.NewFileTreeCache(result.RefTrees)
|
||||
return
|
||||
|
||||
} else {
|
||||
events.message(utils.TitleFormat("Building cache..."))
|
||||
cache := filetree.NewFileTreeCache(analysis.RefTrees)
|
||||
err := cache.Build()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
events.exitWithErrorMessage("cannot build cache tree", err)
|
||||
return
|
||||
}
|
||||
|
||||
// it appears there is a race condition where termbox.Init() will
|
||||
// block nearly indefinitely when running as the first process in
|
||||
// a Docker container when started within ~25ms of container startup.
|
||||
// I can't seem to determine the exact root cause, however, a large
|
||||
// enough sleep will prevent this behavior (todo: remove this hack)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if enableUi {
|
||||
// it appears there is a race condition where termbox.Init() will
|
||||
// block nearly indefinitely when running as the first process in
|
||||
// a Docker container when started within ~25ms of container startup.
|
||||
// I can't seem to determine the exact root cause, however, a large
|
||||
// enough sleep will prevent this behavior (todo: remove this hack)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = ui.Run(result, cache)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
err = ui.Run(analysis, cache)
|
||||
if err != nil {
|
||||
events.exitWithErrorMessage("runtime error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func Run(options Options) {
|
||||
var exitCode int
|
||||
var events = make(eventChannel)
|
||||
|
||||
imageResolver, err := dive.GetImageResolver(options.Source)
|
||||
if err != nil {
|
||||
message := "cannot determine image provider"
|
||||
logrus.Error(message)
|
||||
logrus.Error(err)
|
||||
fmt.Fprintf(os.Stderr, "%s: %+v\n", message, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go run(true, options, imageResolver, events, afero.NewOsFs())
|
||||
|
||||
for event := range events {
|
||||
if event.stdout != "" {
|
||||
fmt.Println(event.stdout)
|
||||
}
|
||||
|
||||
if event.stderr != "" {
|
||||
logrus.Error(event.stderr)
|
||||
_, err := fmt.Fprintln(os.Stderr, event.stderr)
|
||||
if err != nil {
|
||||
fmt.Println("error: could not write to buffer:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if event.err != nil {
|
||||
logrus.Error(event.err)
|
||||
}
|
||||
|
||||
if event.errorOnExit {
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
285
runtime/run_test.go
Normal file
285
runtime/run_test.go
Normal file
@ -0,0 +1,285 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wagoodman/dive/dive"
|
||||
"github.com/wagoodman/dive/dive/image"
|
||||
"github.com/wagoodman/dive/dive/image/docker"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type defaultResolver struct{}
|
||||
|
||||
func (r *defaultResolver) Fetch(id string) (*image.Image, error) {
|
||||
archive, err := docker.TestLoadArchive("../.data/test-docker-image.tar")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.ToImage()
|
||||
}
|
||||
|
||||
func (r *defaultResolver) Build(args []string) (*image.Image, error) {
|
||||
return r.Fetch("")
|
||||
}
|
||||
|
||||
type failedBuildResolver struct{}
|
||||
|
||||
func (r *failedBuildResolver) Fetch(id string) (*image.Image, error) {
|
||||
archive, err := docker.TestLoadArchive("../.data/test-docker-image.tar")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.ToImage()
|
||||
}
|
||||
|
||||
func (r *failedBuildResolver) Build(args []string) (*image.Image, error) {
|
||||
return nil, fmt.Errorf("some build failure")
|
||||
}
|
||||
|
||||
type failedFetchResolver struct{}
|
||||
|
||||
func (r *failedFetchResolver) Fetch(id string) (*image.Image, error) {
|
||||
return nil, fmt.Errorf("some fetch failure")
|
||||
}
|
||||
|
||||
func (r *failedFetchResolver) Build(args []string) (*image.Image, error) {
|
||||
return nil, fmt.Errorf("some build failure")
|
||||
}
|
||||
|
||||
// func showEvents(events []testEvent) {
|
||||
// for _, e := range events {
|
||||
// fmt.Printf("{stdout:\"%s\", stderr:\"%s\", errorOnExit: %v, errMessage: \"%s\"},\n",
|
||||
// strings.Replace(vtclean.Clean(e.stdout, false), "\n", "\\n", -1),
|
||||
// strings.Replace(vtclean.Clean(e.stderr, false), "\n", "\\n", -1),
|
||||
// e.errorOnExit,
|
||||
// e.errMessage)
|
||||
// }
|
||||
// }
|
||||
|
||||
type testEvent struct {
|
||||
stdout string
|
||||
stderr string
|
||||
errMessage string
|
||||
errorOnExit bool
|
||||
}
|
||||
|
||||
func newTestEvent(e event) testEvent {
|
||||
var errMsg string
|
||||
if e.err != nil {
|
||||
errMsg = e.err.Error()
|
||||
}
|
||||
return testEvent{
|
||||
stdout: e.stdout,
|
||||
stderr: e.stderr,
|
||||
errMessage: errMsg,
|
||||
errorOnExit: e.errorOnExit,
|
||||
}
|
||||
}
|
||||
|
||||
func configureCi() *viper.Viper {
|
||||
ciConfig := viper.New()
|
||||
ciConfig.SetDefault("rules.lowestEfficiency", "0.9")
|
||||
ciConfig.SetDefault("rules.highestWastedBytes", "1000")
|
||||
ciConfig.SetDefault("rules.highestUserWastedPercent", "0.1")
|
||||
return ciConfig
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
table := map[string]struct {
|
||||
resolver image.Resolver
|
||||
options Options
|
||||
events []testEvent
|
||||
}{
|
||||
"fetch-case": {
|
||||
resolver: &defaultResolver{},
|
||||
options: Options{
|
||||
Ci: false,
|
||||
Image: "dive-example",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: nil,
|
||||
BuildArgs: nil,
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Fetching image... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Building cache...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
},
|
||||
},
|
||||
"fetch-with-no-build-options-case": {
|
||||
resolver: &defaultResolver{},
|
||||
options: Options{
|
||||
Ci: false,
|
||||
Image: "dive-example",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: nil,
|
||||
// note: empty slice is passed
|
||||
BuildArgs: []string{},
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Fetching image... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Building cache...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
},
|
||||
},
|
||||
"build-case": {
|
||||
resolver: &defaultResolver{},
|
||||
options: Options{
|
||||
Ci: false,
|
||||
Image: "dive-example",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: nil,
|
||||
BuildArgs: []string{"an-option"},
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Building image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Building cache...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
},
|
||||
},
|
||||
"failed-fetch": {
|
||||
resolver: &failedFetchResolver{},
|
||||
options: Options{
|
||||
Ci: false,
|
||||
Image: "dive-example",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: nil,
|
||||
BuildArgs: nil,
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Fetching image... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "", stderr: "cannot fetch image", errorOnExit: true, errMessage: "some fetch failure"},
|
||||
},
|
||||
},
|
||||
"failed-build": {
|
||||
resolver: &failedBuildResolver{},
|
||||
options: Options{
|
||||
Ci: false,
|
||||
Image: "doesn't-matter",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: nil,
|
||||
BuildArgs: []string{"an-option"},
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Building image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "", stderr: "cannot build image", errorOnExit: true, errMessage: "some build failure"},
|
||||
},
|
||||
},
|
||||
"ci-go-case": {
|
||||
resolver: &defaultResolver{},
|
||||
options: Options{
|
||||
Ci: true,
|
||||
Image: "doesn't-matter",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: configureCi(),
|
||||
BuildArgs: []string{"an-option"},
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Building image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: " efficiency: 98.4421 %\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: " wastedBytes: 32025 bytes (32 kB)\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: " userWastedPercent: 48.3491 %\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Inefficient Files:\nCount Wasted Space File Path\n 2 13 kB /root/saved.txt\n 2 13 kB /root/example/somefile1.txt\n 2 6.4 kB /root/example/somefile3.txt\nResults:\n FAIL: highestUserWastedPercent: too many bytes wasted, relative to the user bytes added (%-user-wasted-bytes=0.4834911001404049 > threshold=0.1)\n FAIL: highestWastedBytes: too many bytes wasted (wasted-bytes=32025 > threshold=1000)\n PASS: lowestEfficiency\nResult:FAIL [Total:3] [Passed:1] [Failed:2] [Warn:0] [Skipped:0]\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "", stderr: "", errorOnExit: true, errMessage: ""},
|
||||
},
|
||||
},
|
||||
"empty-ci-config-case": {
|
||||
resolver: &defaultResolver{},
|
||||
options: Options{
|
||||
Ci: true,
|
||||
Image: "doesn't-matter",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "",
|
||||
CiConfig: viper.New(),
|
||||
BuildArgs: []string{"an-option"},
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Building image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: " efficiency: 98.4421 %\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: " wastedBytes: 32025 bytes (32 kB)\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: " userWastedPercent: 48.3491 %\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Inefficient Files:\nCount Wasted Space File Path\nNone\nResults:\n MISCONFIGURED: highestUserWastedPercent: invalid config value (''): strconv.ParseFloat: parsing \"\": invalid syntax\n MISCONFIGURED: highestWastedBytes: invalid config value (''): strconv.ParseFloat: parsing \"\": invalid syntax\n MISCONFIGURED: lowestEfficiency: invalid config value (''): strconv.ParseFloat: parsing \"\": invalid syntax\nCI Misconfigured\n", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "", stderr: "", errorOnExit: true, errMessage: ""},
|
||||
},
|
||||
},
|
||||
"export-go-case": {
|
||||
resolver: &defaultResolver{},
|
||||
options: Options{
|
||||
Ci: true,
|
||||
Image: "doesn't-matter",
|
||||
Source: dive.SourceDockerEngine,
|
||||
ExportFile: "some-file.json",
|
||||
CiConfig: configureCi(),
|
||||
BuildArgs: []string{"an-option"},
|
||||
},
|
||||
events: []testEvent{
|
||||
{stdout: "Building image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
{stdout: "Exporting image to 'some-file.json'...", stderr: "", errorOnExit: false, errMessage: ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range table {
|
||||
var ec = make(eventChannel)
|
||||
var events = make([]testEvent, 0)
|
||||
var filesystem = afero.NewMemMapFs()
|
||||
|
||||
go run(false, test.options, test.resolver, ec, filesystem)
|
||||
|
||||
for event := range ec {
|
||||
events = append(events, newTestEvent(event))
|
||||
}
|
||||
|
||||
// fmt.Println(name)
|
||||
// showEvents(events)
|
||||
// fmt.Println()
|
||||
|
||||
if len(test.events) != len(events) {
|
||||
t.Fatalf("%s.%s: expected # events='%v', got '%v'", t.Name(), name, len(test.events), len(events))
|
||||
}
|
||||
|
||||
for idx, actualEvent := range events {
|
||||
expectedEvent := test.events[idx]
|
||||
|
||||
if expectedEvent.errorOnExit != actualEvent.errorOnExit {
|
||||
t.Errorf("%s.%s: expected errorOnExit='%v', got '%v'", t.Name(), name, expectedEvent.errorOnExit, actualEvent.errorOnExit)
|
||||
}
|
||||
|
||||
actualEventStdoutClean := vtclean.Clean(actualEvent.stdout, false)
|
||||
expectedEventStdoutClean := vtclean.Clean(expectedEvent.stdout, false)
|
||||
|
||||
if expectedEventStdoutClean != actualEventStdoutClean {
|
||||
t.Errorf("%s.%s: expected stdout='%v', got '%v'", t.Name(), name, expectedEventStdoutClean, actualEventStdoutClean)
|
||||
}
|
||||
|
||||
actualEventStderrClean := vtclean.Clean(actualEvent.stderr, false)
|
||||
expectedEventStderrClean := vtclean.Clean(expectedEvent.stderr, false)
|
||||
|
||||
if expectedEventStderrClean != actualEventStderrClean {
|
||||
t.Errorf("%s.%s: expected stderr='%v', got '%v'", t.Name(), name, expectedEventStderrClean, actualEventStderrClean)
|
||||
}
|
||||
|
||||
if expectedEvent.errMessage != actualEvent.errMessage {
|
||||
t.Errorf("%s.%s: expected error='%v', got '%v'", t.Name(), name, expectedEvent.errMessage, actualEvent.errMessage)
|
||||
}
|
||||
|
||||
if test.options.ExportFile != "" {
|
||||
if _, err := filesystem.Stat(test.options.ExportFile); os.IsNotExist(err) {
|
||||
t.Errorf("%s.%s: expected export file but did not find one", t.Name(), name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user