blob: b1a4aeb96a1d0fded143f5ce9473df3ff4d444dd [file] [log] [blame]
package unittest
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/lrills/helm-unittest/unittest/snapshot"
v3chart "helm.sh/helm/v3/pkg/chart"
v3loader "helm.sh/helm/v3/pkg/chart/loader"
v2util "k8s.io/helm/pkg/chartutil"
v2chart "k8s.io/helm/pkg/proto/hapi/chart"
)
// testUnitCounting stores counting numbers of test unit status
type testUnitCounting struct {
passed uint
failed uint
errored uint
}
// sprint returns string of counting result
func (counting testUnitCounting) sprint(printer *Printer) string {
var failedLabel string
if counting.failed > 0 {
failedLabel = printer.danger("%d failed, ", counting.failed)
}
var erroredLabel string
if counting.errored > 0 {
erroredLabel = fmt.Sprintf("%d errored, ", counting.errored)
}
return failedLabel + erroredLabel + fmt.Sprintf(
"%d passed, %d total",
counting.passed,
counting.passed+counting.failed,
)
}
// testUnitCountingWithSnapshotFailed store testUnitCounting with snapshotFailed field
type testUnitCountingWithSnapshotFailed struct {
testUnitCounting
snapshotFailed uint
}
// totalSnapshotCounting store testUnitCounting with snapshotFailed field
type totalSnapshotCounting struct {
testUnitCounting
created uint
vanished uint
}
// TestRunner stores basic settings and testing status for running all tests
type TestRunner struct {
Printer *Printer
Formatter Formatter
Config TestConfig
suiteCounting testUnitCountingWithSnapshotFailed
testCounting testUnitCounting
chartCounting testUnitCounting
snapshotCounting totalSnapshotCounting
testResults []*TestSuiteResult
}
// RunV2 test suites in chart in ChartPaths.
func (tr *TestRunner) RunV2(ChartPaths []string) bool {
allPassed := true
start := time.Now()
for _, chartPath := range ChartPaths {
chart, err := v2util.Load(chartPath)
if err != nil {
tr.printErroredChartHeader(err)
tr.countChart(false, err)
allPassed = false
continue
}
chartRoute := chart.Metadata.Name
testSuites, err := tr.getV2TestSuites(chartPath, chartRoute, chart)
if err != nil {
tr.printErroredChartHeader(err)
tr.countChart(false, err)
allPassed = false
continue
}
tr.printChartHeader(chartRoute, chartPath)
chartPassed := tr.runV2SuitesOfChart(testSuites, chart)
tr.countChart(chartPassed, nil)
allPassed = allPassed && chartPassed
}
err := tr.writeTestOutput()
if err != nil {
tr.printErroredChartHeader(err)
}
tr.printSnapshotSummary()
tr.printSummary(time.Now().Sub(start))
return allPassed
}
// RunV3 test suites in chart in ChartPaths.
func (tr *TestRunner) RunV3(ChartPaths []string) bool {
allPassed := true
start := time.Now()
for _, chartPath := range ChartPaths {
chart, err := v3loader.Load(chartPath)
if err != nil {
tr.printErroredChartHeader(err)
tr.countChart(false, err)
allPassed = false
continue
}
chartRoute := chart.Name()
testSuites, err := tr.getV3TestSuites(chartPath, chartRoute, chart)
if err != nil {
tr.printErroredChartHeader(err)
tr.countChart(false, err)
allPassed = false
continue
}
tr.printChartHeader(chart.Name(), chartPath)
chartPassed := tr.runV3SuitesOfChart(testSuites, chart)
tr.countChart(chartPassed, nil)
allPassed = allPassed && chartPassed
}
err := tr.writeTestOutput()
if err != nil {
tr.printErroredChartHeader(err)
}
tr.printSnapshotSummary()
tr.printSummary(time.Now().Sub(start))
return allPassed
}
func (tr *TestRunner) getTestSuites(chartPath, chartRoute string) ([]*TestSuite, error) {
filesSet := map[string]bool{}
for _, pattern := range tr.Config.TestFiles {
files, err := filepath.Glob(filepath.Join(chartPath, pattern))
if err != nil {
return nil, err
}
for _, file := range files {
filesSet[file] = true
}
}
resultSuites := make([]*TestSuite, 0, len(filesSet))
for file := range filesSet {
suite, err := ParseTestSuiteFile(file, chartRoute)
if err != nil {
tr.handleSuiteResult(&TestSuiteResult{
FilePath: file,
ExecError: err,
})
}
resultSuites = append(resultSuites, suite)
}
return resultSuites, nil
}
// getV2TestSuites return test files of the chart which matched patterns
func (tr *TestRunner) getV2TestSuites(chartPath, chartRoute string, chart *v2chart.Chart) ([]*TestSuite, error) {
resultSuites, err := tr.getTestSuites(chartPath, chartRoute)
if err != nil {
return nil, err
}
if tr.Config.WithSubChart {
for _, subchart := range chart.Dependencies {
subchartSuites, err := tr.getV2TestSuites(
filepath.Join(chartPath, "charts", subchart.Metadata.Name),
filepath.Join(chartRoute, "charts", subchart.Metadata.Name),
subchart,
)
if err != nil {
continue
}
resultSuites = append(resultSuites, subchartSuites...)
}
}
return resultSuites, nil
}
// getV3TestSuites return test files of the chart which matched patterns
func (tr *TestRunner) getV3TestSuites(chartPath, chartRoute string, chart *v3chart.Chart) ([]*TestSuite, error) {
resultSuites, err := tr.getTestSuites(chartPath, chartRoute)
if err != nil {
return nil, err
}
if tr.Config.WithSubChart {
for _, subchart := range chart.Dependencies() {
subchartSuites, err := tr.getV3TestSuites(
filepath.Join(chartPath, "charts", subchart.Metadata.Name),
filepath.Join(chartRoute, "charts", subchart.Metadata.Name),
subchart,
)
if err != nil {
continue
}
resultSuites = append(resultSuites, subchartSuites...)
}
}
return resultSuites, nil
}
// runV2SuitesOfChart runs suite files of the chart and print output
func (tr *TestRunner) runV2SuitesOfChart(suites []*TestSuite, chart *v2chart.Chart) bool {
chartPassed := true
for _, suite := range suites {
snapshotCache, err := snapshot.CreateSnapshotOfSuite(suite.definitionFile, tr.Config.UpdateSnapshot)
if err != nil {
tr.handleSuiteResult(&TestSuiteResult{
FilePath: suite.definitionFile,
ExecError: err,
})
chartPassed = false
continue
}
result := suite.RunV2(chart, snapshotCache, &TestSuiteResult{})
chartPassed = chartPassed && result.Passed
tr.handleSuiteResult(result)
tr.testResults = append(tr.testResults, result)
snapshotCache.StoreToFileIfNeeded()
}
return chartPassed
}
// runV3SuitesOfChart runs suite files of the chart and print output
func (tr *TestRunner) runV3SuitesOfChart(suites []*TestSuite, chart *v3chart.Chart) bool {
chartPassed := true
for _, suite := range suites {
snapshotCache, err := snapshot.CreateSnapshotOfSuite(suite.definitionFile, tr.Config.UpdateSnapshot)
if err != nil {
tr.handleSuiteResult(&TestSuiteResult{
FilePath: suite.definitionFile,
ExecError: err,
})
chartPassed = false
continue
}
result := suite.RunV3(chart, snapshotCache, &TestSuiteResult{})
chartPassed = chartPassed && result.Passed
tr.handleSuiteResult(result)
tr.testResults = append(tr.testResults, result)
snapshotCache.StoreToFileIfNeeded()
}
return chartPassed
}
// handleSuiteResult print suite result and count suites and tests status
func (tr *TestRunner) handleSuiteResult(result *TestSuiteResult) {
result.print(tr.Printer, 0)
tr.countSuite(result)
for _, testsResult := range result.TestsResult {
tr.countTest(testsResult)
}
}
//printSummary print summary footer
func (tr *TestRunner) printSummary(elapsed time.Duration) {
summaryFormat := `
Charts: %s
Test Suites: %s
Tests: %s
Snapshot: %s
Time: %s
`
tr.Printer.println(
fmt.Sprintf(
summaryFormat,
tr.chartCounting.sprint(tr.Printer),
tr.suiteCounting.sprint(tr.Printer),
tr.testCounting.sprint(tr.Printer),
tr.snapshotCounting.sprint(tr.Printer),
elapsed.String(),
),
0,
)
}
// printChartHeader print header before suite result of a chart
func (tr *TestRunner) printChartHeader(chartName, path string) {
headerFormat := `
### Chart [ %s ] %s
`
header := fmt.Sprintf(
headerFormat,
tr.Printer.highlight(chartName),
tr.Printer.faint(path),
)
tr.Printer.println(header, 0)
}
// printErroredChartHeader if chart has exexution error print header with error
func (tr *TestRunner) printErroredChartHeader(err error) {
headerFormat := `
### ` + tr.Printer.danger("Error: ") + ` %s
`
header := fmt.Sprintf(headerFormat, err)
tr.Printer.println(header, 0)
}
// printSnapshotSummary print snapshot summary in footer
func (tr *TestRunner) printSnapshotSummary() {
if tr.snapshotCounting.failed > 0 {
snapshotFormat := `
Snapshot Summary: %s`
summary := tr.Printer.danger("%d snapshot failed", tr.snapshotCounting.failed) +
fmt.Sprintf(" in %d test suite.", tr.suiteCounting.snapshotFailed) +
tr.Printer.faint(" Check changes and use `-u` to update snapshot.")
tr.Printer.println(fmt.Sprintf(snapshotFormat, summary), 0)
}
}
func (tr *TestRunner) countSuite(suite *TestSuiteResult) {
if suite.Passed {
tr.suiteCounting.passed++
} else {
tr.suiteCounting.failed++
if suite.ExecError != nil {
tr.suiteCounting.errored++
}
if suite.SnapshotCounting.Failed > 0 {
tr.suiteCounting.snapshotFailed++
}
}
tr.snapshotCounting.failed += suite.SnapshotCounting.Failed
tr.snapshotCounting.passed += suite.SnapshotCounting.Total - suite.SnapshotCounting.Failed
tr.snapshotCounting.created += suite.SnapshotCounting.Created
tr.snapshotCounting.vanished += suite.SnapshotCounting.Vanished
}
func (tr *TestRunner) countTest(test *TestJobResult) {
if test.Passed {
tr.testCounting.passed++
} else {
tr.testCounting.failed++
if test.ExecError != nil {
tr.testCounting.errored++
}
}
}
func (tr *TestRunner) countChart(passed bool, err error) {
if passed {
tr.chartCounting.passed++
} else {
tr.chartCounting.failed++
if err != nil {
tr.chartCounting.errored++
}
}
}
func (tr *TestRunner) writeTestOutput() error {
// Check if formatter exits to write
if tr.Formatter != nil {
// Create outputfile for testsuite
writer, ferr := os.Create(tr.Config.OutputFile)
if ferr != nil {
return ferr
}
defer writer.Close()
//
jerr := tr.Formatter.WriteTestOutput(tr.testResults, true, writer)
if jerr != nil {
return jerr
}
}
return nil
}