* rework CI validation workflow and makefile * enable push * fix job names * fix license check * fix snapshot builds * fix acceptance tests * fix linting * disable pull request event * rework windows runner caching * disable release pipeline and add issue templates
291 lines
10 KiB
Go
291 lines
10 KiB
Go
package runtime
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
|
|
"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"
|
|
)
|
|
|
|
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: "Image Source: docker://dive-example", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{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: "Image Source: docker://dive-example", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{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: "Image Source: docker://dive-example", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{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 %", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{stdout: " wastedBytes: 32025 bytes (32 kB)", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{stdout: " userWastedPercent: 48.3491 %", 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 %", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{stdout: " wastedBytes: 32025 bytes (32 kB)", stderr: "", errorOnExit: false, errMessage: ""},
|
|
{stdout: " userWastedPercent: 48.3491 %", 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|