test(lint): Tests for lint flag --full-path

Related to #12496

Signed-off-by: Bhargav Ravuri <bhargav.ravuri@infracloud.io>
pull/12923/head
Bhargav Ravuri 8 months ago
parent 1ff3fcab80
commit 9252f4e979
No known key found for this signature in database
GPG Key ID: F2D6B63B5A17E057

@ -18,48 +18,153 @@ package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
type lintTestCase struct {
cmdTestCase cmdTestCase
updateFullPathInOutputFile bool
}
func TestLintCmdWithSubchartsFlag(t *testing.T) {
testChart := "testdata/testcharts/chart-with-bad-subcharts"
tests := []cmdTestCase{{
name: "lint good chart with bad subcharts",
cmd: fmt.Sprintf("lint %s", testChart),
golden: "output/lint-chart-with-bad-subcharts.txt",
wantError: true,
}, {
name: "lint good chart with bad subcharts using --with-subcharts flag",
cmd: fmt.Sprintf("lint --with-subcharts %s", testChart),
golden: "output/lint-chart-with-bad-subcharts-with-subcharts.txt",
wantError: true,
}}
runTestCmd(t, tests)
tests := []lintTestCase{
{
cmdTestCase: cmdTestCase{
name: "lint good chart with bad subcharts",
cmd: fmt.Sprintf("lint %s", testChart),
golden: "output/lint-chart-with-bad-subcharts.txt",
wantError: true,
},
},
{
cmdTestCase: cmdTestCase{
name: "lint good chart with bad subcharts using --full-path flag",
cmd: fmt.Sprintf("lint --full-path %s", testChart),
golden: "output/lint-chart-with-bad-subcharts-with-full-paths-enabled.txt",
wantError: true,
},
updateFullPathInOutputFile: true,
},
{
cmdTestCase: cmdTestCase{
name: "lint good chart with bad subcharts using --with-subcharts flag",
cmd: fmt.Sprintf("lint --with-subcharts %s", testChart),
golden: "output/lint-chart-with-bad-subcharts-with-subcharts.txt",
wantError: true,
},
},
{
cmdTestCase: cmdTestCase{
name: "lint good chart with bad subcharts using --with-subcharts and --full-path flags",
cmd: fmt.Sprintf("lint --with-subcharts --full-path %s", testChart),
golden: "output/lint-chart-with-bad-subcharts-with-subcharts-with-full-paths-enabled.txt",
wantError: true,
},
updateFullPathInOutputFile: true,
},
}
workingDir, err := os.Getwd()
if err != nil {
t.Fatalf("failed to determine present working directory: '%v'", err)
}
placeholder := `<full-path>`
testCases := []cmdTestCase{}
for _, test := range tests {
if test.updateFullPathInOutputFile {
// Copy the content of golden file to a temporary file
// and replace the placeholder with working directory's
// path. Replace the golden file's name with it.
test.cmdTestCase.golden = tempFileWithUpdatedPlaceholder(
t,
filepath.Join("testdata", test.cmdTestCase.golden),
placeholder,
workingDir,
)
}
testCases = append(testCases, test.cmdTestCase)
}
runTestCmd(t, testCases)
}
func TestLintCmdWithQuietFlag(t *testing.T) {
testChart1 := "testdata/testcharts/alpine"
testChart2 := "testdata/testcharts/chart-bad-requirements"
tests := []cmdTestCase{{
name: "lint good chart using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s", testChart1),
golden: "output/lint-quiet.txt",
}, {
name: "lint two charts, one with error using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s %s", testChart1, testChart2),
golden: "output/lint-quiet-with-error.txt",
wantError: true,
}, {
name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt",
}, {
name: "lint non-existent chart using --quiet flag",
cmd: "lint --quiet thischartdoesntexist/",
golden: "",
wantError: true,
}}
runTestCmd(t, tests)
tests := []lintTestCase{
{
cmdTestCase: cmdTestCase{
name: "lint good chart using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s", testChart1),
golden: "output/lint-quiet.txt",
},
},
{
cmdTestCase: cmdTestCase{
name: "lint two charts, one with error using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s %s", testChart1, testChart2),
golden: "output/lint-quiet-with-error.txt",
wantError: true,
},
},
{
cmdTestCase: cmdTestCase{
name: "lint two charts, one with error using --quiet & --full-path flags",
cmd: fmt.Sprintf("lint --quiet --full-path %s %s", testChart1, testChart2),
golden: "output/lint-quiet-with-error-with-full-paths.txt",
wantError: true,
},
updateFullPathInOutputFile: true,
},
{
cmdTestCase: cmdTestCase{
name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt",
},
},
{
cmdTestCase: cmdTestCase{
name: "lint non-existent chart using --quiet flag",
cmd: "lint --quiet thischartdoesntexist/",
golden: "",
wantError: true,
},
},
}
workingDir, err := os.Getwd()
if err != nil {
t.Fatalf("failed to determine present working directory: '%v'", err)
}
placeholder := `<full-path>`
testCases := []cmdTestCase{}
for _, test := range tests {
if test.updateFullPathInOutputFile {
// Copy the content of golden file to a temporary file
// and replace the placeholder with working directory's
// path. Replace the golden file's name with it.
test.cmdTestCase.golden = tempFileWithUpdatedPlaceholder(
t,
filepath.Join("testdata", test.cmdTestCase.golden),
placeholder,
workingDir,
)
}
testCases = append(testCases, test.cmdTestCase)
}
runTestCmd(t, testCases)
}
@ -95,3 +200,38 @@ func TestLintFileCompletion(t *testing.T) {
checkFileCompletion(t, "lint", true)
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given
}
// tempFileWithUpdatedPlaceholder creates a temporary file, copies
// the content of source file to it, and replaces the placeholder
// with input value.
//
// The temporary file automatically gets deleted during test clean-up.
func tempFileWithUpdatedPlaceholder(t *testing.T, src string, placeholder,
value string) string {
// Create the temporary file in test's temporary directory.
// This lets the test delete the directory along with file
// during the test clean-up step.
dst, err := os.CreateTemp(t.TempDir(), filepath.Base(src))
if err != nil {
t.Fatalf("failed to create temporary destination file: '%v'", err)
}
// Read source file's content
srcData, err := os.ReadFile(src)
if err != nil {
t.Fatalf("failed to read source file %q: '%v'", src, err)
}
// Replace placeholder with input value
dstData := strings.ReplaceAll(string(srcData), placeholder, value)
// Write to destination (temporary) file
_, err = dst.WriteString(dstData)
if err != nil {
t.Fatalf("failed to write to temporary destination file %q: '%v'",
dst.Name(), err)
}
// Return temporary file's name
return dst.Name()
}

@ -0,0 +1,7 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] <full-path>/testdata/testcharts/chart-with-bad-subcharts/Chart.yaml: icon is recommended
[ERROR] <full-path>/testdata/testcharts/chart-with-bad-subcharts/templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
Error: 1 chart(s) linted, 1 chart(s) failed

@ -0,0 +1,19 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] <full-path>/testdata/testcharts/chart-with-bad-subcharts/Chart.yaml: icon is recommended
[ERROR] <full-path>/testdata/testcharts/chart-with-bad-subcharts/templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart
[ERROR] <full-path>/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml: name is required
[ERROR] <full-path>/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml: apiVersion is required. The value must be either "v1" or "v2"
[ERROR] <full-path>/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml: version is required
[INFO] <full-path>/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml: icon is recommended
[ERROR] <full-path>/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/templates/: validation: chart.metadata.name is required
[ERROR] : unable to load chart
validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart
[INFO] <full-path>/testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart/Chart.yaml: icon is recommended
Error: 3 chart(s) linted, 2 chart(s) failed

@ -0,0 +1,8 @@
==> Linting testdata/testcharts/chart-bad-requirements
[ERROR] <full-path>/testdata/testcharts/chart-bad-requirements/Chart.yaml: unable to parse YAML
error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[ERROR] <full-path>/testdata/testcharts/chart-bad-requirements/templates/: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[ERROR] : unable to load chart
cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
Error: 2 chart(s) linted, 1 chart(s) failed

@ -17,6 +17,11 @@ limitations under the License.
package action
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
@ -30,31 +35,46 @@ var (
)
func TestLintChart(t *testing.T) {
workingDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to determine the current working directory")
}
// Actual directory that `helm lint` uses
helmLintTempDirPrefix := `/tmp/helm-lint`
tests := []struct {
name string
chartPath string
err bool
name string
chartPath string
chartName string
err bool
enableFullPath bool
isCompressedChart bool
}{
{
name: "decompressed-chart",
chartPath: "testdata/charts/decompressedchart/",
},
{
name: "archived-chart-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tgz",
name: "archived-chart-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tgz",
isCompressedChart: true,
},
{
name: "archived-chart-path-with-hyphens",
chartPath: "testdata/charts/compressedchart-with-hyphens-0.1.0.tgz",
name: "archived-chart-path-with-hyphens",
chartPath: "testdata/charts/compressedchart-with-hyphens-0.1.0.tgz",
isCompressedChart: true,
},
{
name: "archived-tar-gz-chart-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tar.gz",
name: "archived-tar-gz-chart-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tar.gz",
isCompressedChart: true,
},
{
name: "invalid-archived-chart-path",
chartPath: "testdata/charts/invalidcompressedchart0.1.0.tgz",
err: true,
name: "invalid-archived-chart-path",
chartPath: "testdata/charts/invalidcompressedchart0.1.0.tgz",
isCompressedChart: true,
err: true,
},
{
name: "chart-missing-manifest",
@ -70,20 +90,106 @@ func TestLintChart(t *testing.T) {
chartPath: "testdata/charts/chart-with-schema-negative",
},
{
name: "pre-release-chart",
chartPath: "testdata/charts/pre-release-chart-0.1.0-alpha.tgz",
name: "pre-release-chart",
chartPath: "testdata/charts/pre-release-chart-0.1.0-alpha.tgz",
isCompressedChart: true,
},
{
name: "decompressed-chart-enable-full-path",
chartPath: "testdata/charts/decompressedchart/",
enableFullPath: true,
},
{
name: "archived-chart-path-enable-full-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tgz",
chartName: "compressedchart",
enableFullPath: true,
isCompressedChart: true,
},
{
name: "archived-chart-path-with-hyphens-enable-full-path",
chartPath: "testdata/charts/compressedchart-with-hyphens-0.1.0.tgz",
chartName: "compressedchart-with-hyphens",
enableFullPath: true,
isCompressedChart: true,
},
{
name: "archived-tar-gz-chart-path-enable-full-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tar.gz",
chartName: "compressedchart",
enableFullPath: true,
isCompressedChart: true,
},
{
name: "invalid-archived-chart-path-enable-full-path",
chartPath: "testdata/charts/invalidcompressedchart0.1.0.tgz",
err: true,
enableFullPath: true,
isCompressedChart: true,
},
{
name: "chart-missing-manifest-enable-full-path",
chartPath: "testdata/charts/chart-missing-manifest",
err: true,
enableFullPath: true,
},
{
name: "chart-with-schema-enable-full-path",
chartPath: "testdata/charts/chart-with-schema",
enableFullPath: true,
},
{
name: "chart-with-schema-negative-enable-full-path",
chartPath: "testdata/charts/chart-with-schema-negative",
enableFullPath: true,
},
{
name: "pre-release-chart-enable-full-path",
chartPath: "testdata/charts/pre-release-chart-0.1.0-alpha.tgz",
chartName: "pre-release-chart",
enableFullPath: true,
isCompressedChart: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil, false)
result, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil, tt.enableFullPath)
switch {
case err != nil && !tt.err:
t.Errorf("%s", err)
case err == nil && tt.err:
t.Errorf("Expected a chart parsing error")
}
// Check for full path if enabled
for _, msg := range result.Messages {
if tt.enableFullPath {
if tt.isCompressedChart {
tempPathPrefixPattern := fmt.Sprintf("^%s[0-9]*/%s/.*",
helmLintTempDirPrefix, tt.chartName)
re, err := regexp.Compile(tempPathPrefixPattern)
if err != nil {
t.Fatalf("Unexpected error parsing regex pattern %q: %v",
tempPathPrefixPattern, err)
}
if !re.Match([]byte(msg.Path)) {
t.Fatalf("Full path is missing or incorrect: %s\nExpected to match pattern: %s",
msg.Path, tempPathPrefixPattern)
}
continue
}
pathPrefix := filepath.Join(workingDir, tt.chartPath)
if !strings.HasPrefix(msg.Path, pathPrefix) {
t.Fatalf("Full path is missing or incorrect: %s\nExpected to have prefix: %s",
msg.Path, pathPrefix)
}
}
}
})
}
}
@ -124,21 +230,103 @@ func TestNonExistentChart(t *testing.T) {
func TestLint_MultipleCharts(t *testing.T) {
testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint}
testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 {
t.Error(result.Errors)
chartFile := "Chart.yaml"
chartFile1FullPath, err := filepath.Abs(
filepath.Join(chart1MultipleChartLint, chartFile))
if err != nil {
t.Fatalf("Failed to determine the full path of %s in %s",
chartFile, chart1MultipleChartLint)
}
chartFile2FullPath, err := filepath.Abs(
filepath.Join(chart2MultipleChartLint, chartFile))
if err != nil {
t.Fatalf("Failed to determine the full path of %s in %s",
chartFile, chart2MultipleChartLint)
}
t.Run("multiple charts", func(t *testing.T) {
testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 {
t.Error(result.Errors)
}
})
t.Run("multiple charts with --full-path enabled", func(t *testing.T) {
testLint := NewLint()
testLint.EnableFullPath = true
result := testLint.Run(testCharts, values)
if len(result.Errors) > 0 {
t.Error(result.Errors)
}
var file1Found, file2Found bool
for _, msg := range result.Messages {
switch msg.Path {
case chartFile1FullPath:
file1Found = true
case chartFile2FullPath:
file2Found = true
default:
t.Errorf("Unexpected path in linter message: %s", msg.Path)
}
}
if !file1Found || !file2Found {
t.Errorf("Missing either of the chart's path (%s, %s)",
chartFile1FullPath, chartFile2FullPath)
}
})
}
func TestLint_EmptyResultErrors(t *testing.T) {
testCharts := []string{chart2MultipleChartLint}
testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 {
t.Error("Expected no error, got more")
chartFile := "Chart.yaml"
chartFileFullPath, err := filepath.Abs(
filepath.Join(chart2MultipleChartLint, chartFile))
if err != nil {
t.Fatalf("Failed to determine the full path of %s in %s",
chartFile, chart2MultipleChartLint)
}
t.Run("empty result errors", func(t *testing.T) {
testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 {
t.Error("Expected no error, got more")
}
})
t.Run("empty result errors with --full-path enabled", func(t *testing.T) {
testLint := NewLint()
testLint.EnableFullPath = true
result := testLint.Run(testCharts, values)
if len(result.Errors) > 0 {
t.Errorf("Incorrect number of linter errors\nExpected: 0\nGot: %d",
len(result.Errors))
}
if len(result.Messages) != 1 {
t.Errorf("Incorrect number of linter messages\nExpected: 1\nGot: %d",
len(result.Messages))
}
if result.Messages[0].Path != chartFileFullPath {
t.Errorf("Mismatch of path in log message\nExpected: %s\nGot: %s",
chartFileFullPath, result.Messages[0].Path)
}
})
}
func TestLint_ChartWithWarnings(t *testing.T) {
valuesFile := "values.yaml"
valuesFileFullPath, err := filepath.Abs(
filepath.Join(chartWithNoTemplatesDir, valuesFile))
if err != nil {
t.Fatalf("Failed to determine the full path of %s in %s",
valuesFile, chartWithNoTemplatesDir)
}
t.Run("should pass when not strict", func(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint()
@ -156,4 +344,46 @@ func TestLint_ChartWithWarnings(t *testing.T) {
t.Error("expected no errors, but got", len(result.Errors))
}
})
t.Run("should pass when not strict with --full-path enabled", func(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint()
testLint.Strict = false
testLint.EnableFullPath = true
result := testLint.Run(testCharts, values)
if len(result.Errors) > 0 {
t.Error("Expected no error, got more")
}
if len(result.Messages) == 0 {
t.Errorf("Missing linter message for values file")
}
if result.Messages[0].Path != valuesFileFullPath {
t.Errorf("Mismatch of path in log message\nExpected: %s\nGot: %s",
valuesFileFullPath, result.Messages[0].Path)
}
})
t.Run("should pass with no errors when strict with --full-path enabled", func(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint()
testLint.Strict = true
testLint.EnableFullPath = true
result := testLint.Run(testCharts, values)
if len(result.Errors) != 0 {
t.Error("expected no errors, but got", len(result.Errors))
}
if len(result.Messages) == 0 {
t.Errorf("Missing linter message for values file")
}
if result.Messages[0].Path != valuesFileFullPath {
t.Errorf("Mismatch of path in log message\nExpected: %s\nGot: %s",
valuesFileFullPath, result.Messages[0].Path)
}
})
}

@ -17,6 +17,7 @@ limitations under the License.
package lint
import (
"path/filepath"
"strings"
"testing"
"time"
@ -29,6 +30,7 @@ var values map[string]interface{}
const namespace = "testNamespace"
const strict = false
const defaultEnableFullPathFlag = false
const badChartDir = "rules/testdata/badchartfile"
const badValuesFileDir = "rules/testdata/badvaluesfile"
@ -38,76 +40,368 @@ const subChartValuesDir = "rules/testdata/withsubchart"
const malformedTemplate = "rules/testdata/malformed-template"
func TestBadChart(t *testing.T) {
m := All(badChartDir, values, namespace, strict, false).Messages
if len(m) != 8 {
t.Errorf("Number of errors %v", len(m))
t.Errorf("All didn't fail with expected errors, got %#v", m)
}
// There should be one INFO, and 2 ERROR messages, check for them
var i, e, e2, e3, e4, e5, e6 bool
for _, msg := range m {
if msg.Severity == support.InfoSev {
if strings.Contains(msg.Err.Error(), "icon is recommended") {
i = true
}
badChartDirFullPath, err := filepath.Abs(badChartDir)
if err != nil {
t.Fatalf("Failed to determine the full path of templates directory")
}
t.Run("bad chart", func(t *testing.T) {
linterMessages := All(badChartDir, values, namespace, strict, defaultEnableFullPathFlag).Messages
if len(linterMessages) != 8 {
t.Errorf("Number of errors %v", len(linterMessages))
t.Errorf("All didn't fail with expected errors, got %#v", linterMessages)
}
if msg.Severity == support.ErrorSev {
if strings.Contains(msg.Err.Error(), "version '0.0.0.0' is not a valid SemVer") {
e = true
}
if strings.Contains(msg.Err.Error(), "name is required") {
e2 = true
}
if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") {
e3 = true
}
type testCase struct {
name string
severity int
matcher func(err error) bool
expectedMatchCount int
}
if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") {
e4 = true
}
tests := []testCase{
{
name: "info: icon is recommended",
severity: support.InfoSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "icon is recommended")
},
expectedMatchCount: 1,
},
{
name: "error: version '0.0.0.0' is not a valid SemVer",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "version '0.0.0.0' is not a valid SemVer")
},
expectedMatchCount: 1,
},
{
name: "error: validation: chart.metadata.name is required and not unable to load chart",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "validation: chart.metadata.name is required") &&
!strings.Contains(e.Error(), "unable to load chart")
},
expectedMatchCount: 1,
},
{
name: `error: apiVersion is required. The value must be either "v1" or "v2"`,
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), `apiVersion is required. The value must be either "v1" or "v2"`)
},
expectedMatchCount: 1,
},
{
name: "error: chart type is not valid in apiVersion",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "chart type is not valid in apiVersion")
},
expectedMatchCount: 1,
},
{
name: "error: dependencies are not valid in the Chart file with apiVersion",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "dependencies are not valid in the Chart file with apiVersion")
},
expectedMatchCount: 1,
},
{
name: "error: unable to load chart",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "unable to load chart")
},
expectedMatchCount: 1,
// This comes from the dependency check, which loads dependency info
// from the Chart.yaml file. Path will be empty for this.
},
}
if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") {
e5 = true
}
// This comes from the dependency check, which loads dependency info from the Chart.yaml
if strings.Contains(msg.Err.Error(), "unable to load chart") {
e6 = true
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matchCount := 0
for _, msg := range linterMessages {
if msg.Severity != tt.severity {
// If severity of the message doesn't match, ignore the matcher
continue
}
if !tt.matcher(msg.Err) {
// If error matcher func doesn't return true, ignore counting current message
continue
}
matchCount++
// Message shouldn't contain full path unless the flag --full-path is enabled
if strings.HasPrefix(msg.Path, badChartDirFullPath) {
t.Errorf("Full path is not expected when --full-path is disabled: %s",
msg.Path)
}
}
// Expected exact match count. Else, update matcher-func or count.
if matchCount < tt.expectedMatchCount {
t.Errorf("Didn't find all the expected errors")
} else if matchCount > tt.expectedMatchCount {
t.Errorf("Too many matches found for the current error-matcher-func: %d", matchCount)
}
})
}
}
if !e || !e2 || !e3 || !e4 || !e5 || !i || !e6 {
t.Errorf("Didn't find all the expected errors, got %#v", m)
}
})
t.Run("bad chart --full-path enabled", func(t *testing.T) {
linterMessages := All(badChartDir, values, namespace, strict, true).Messages
if len(linterMessages) != 8 {
t.Errorf("Number of errors %v", len(linterMessages))
t.Errorf("All didn't fail with expected errors, got %#v", linterMessages)
}
type testCase struct {
name string
severity int
matcher func(err error) bool
expectedMatchCount int
checkFullPath bool
}
tests := []testCase{
{
name: "info: icon is recommended",
severity: support.InfoSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "icon is recommended")
},
expectedMatchCount: 1,
checkFullPath: true,
},
{
name: "error: version '0.0.0.0' is not a valid SemVer",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "version '0.0.0.0' is not a valid SemVer")
},
expectedMatchCount: 1,
checkFullPath: true,
},
{
name: "error: validation: chart.metadata.name is required and not unable to load chart",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "validation: chart.metadata.name is required") &&
!strings.Contains(e.Error(), "unable to load chart")
},
expectedMatchCount: 1,
checkFullPath: true,
},
{
name: `error: apiVersion is required. The value must be either "v1" or "v2"`,
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), `apiVersion is required. The value must be either "v1" or "v2"`)
},
expectedMatchCount: 1,
checkFullPath: true,
},
{
name: "error: chart type is not valid in apiVersion",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "chart type is not valid in apiVersion")
},
expectedMatchCount: 1,
checkFullPath: true,
},
{
name: "error: dependencies are not valid in the Chart file with apiVersion",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "dependencies are not valid in the Chart file with apiVersion")
},
expectedMatchCount: 1,
checkFullPath: true,
},
{
name: "error: unable to load chart",
severity: support.ErrorSev,
matcher: func(e error) bool {
return strings.Contains(e.Error(), "unable to load chart")
},
expectedMatchCount: 1,
// This comes from the dependency check, which loads dependency info
// from the Chart.yaml file. Path will be empty for this.
checkFullPath: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matchCount := 0
for _, msg := range linterMessages {
if msg.Severity != tt.severity {
// If severity of the message doesn't match, ignore the matcher
continue
}
if !tt.matcher(msg.Err) {
// If error matcher func doesn't return true, ignore counting current message
continue
}
matchCount++
if !tt.checkFullPath {
// When check for full path is disable for given matcher
continue
}
if !strings.HasPrefix(msg.Path, badChartDirFullPath) {
// If the message doesn't have chart directory's full path in prefix, fail the sub-test
t.Errorf("Absolute path missing or incorrect: %s: %v\n"+
"Should contain prefix: %s", msg.Path, err, badChartDirFullPath)
}
}
// Expected exact match count. Else, update matcher-func or count.
if matchCount < tt.expectedMatchCount {
t.Errorf("Didn't find all the expected errors")
} else if matchCount > tt.expectedMatchCount {
t.Errorf("Too many matches found for the current error-matcher-func: %d", matchCount)
}
})
}
})
}
func TestInvalidYaml(t *testing.T) {
m := All(badYamlFileDir, values, namespace, strict, false).Messages
if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
chartPath := badYamlFileDir
badYamlFileDirFullPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatalf("Failed to determine the full path of bad YAML file's directory")
}
if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("All didn't have the error for deliberateSyntaxError")
templatesFileFullPath := filepath.Join(badYamlFileDirFullPath, "templates") +
string(filepath.Separator)
tests := []struct {
name string
enableFullPath bool
}{
{
name: "invalid YAML",
enableFullPath: defaultEnableFullPathFlag,
},
{
name: "invalid YAML with --full-path enabled",
enableFullPath: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := All(chartPath, values, namespace, strict, tt.enableFullPath).Messages
if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("All didn't have the error for deliberateSyntaxError")
}
if tt.enableFullPath {
if m[0].Path != templatesFileFullPath {
t.Errorf("Full path is missing or incorrect\nExpected: %s\nGot : %s",
templatesFileFullPath, m[0].Path)
}
}
})
}
}
func TestBadValues(t *testing.T) {
m := All(badValuesFileDir, values, namespace, strict, false).Messages
if len(m) < 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
chartPath := badValuesFileDir
valuesFileFullPath, err := filepath.Abs(filepath.Join(chartPath, "values.yaml"))
if err != nil {
t.Fatalf("Failed to determine the full path of bad values file")
}
if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") {
t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err)
tests := []struct {
name string
enableFullPath bool
}{
{
name: "bad values",
enableFullPath: defaultEnableFullPathFlag,
},
{
name: "bad values with --full-path enabled",
enableFullPath: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := All(chartPath, values, namespace, strict, tt.enableFullPath).Messages
if len(m) < 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") {
t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err)
}
if tt.enableFullPath {
if m[0].Path != valuesFileFullPath {
t.Errorf("Full path is missing or incorrect\nExpected: %s\nGot : %s",
valuesFileFullPath, m[0].Path)
}
}
})
}
}
func TestGoodChart(t *testing.T) {
m := All(goodChartDir, values, namespace, strict, false).Messages
if len(m) != 0 {
t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m {
t.Logf("Message %d: %s", i, msg)
}
chartPath := goodChartDir
goodChartDirFullPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatalf("Failed to determine the full path of good chart")
}
tests := []struct {
name string
enableFullPath bool
}{
{
name: "good chart",
enableFullPath: defaultEnableFullPathFlag,
},
{
name: "good chart with --full-path enabled",
enableFullPath: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := All(chartPath, values, namespace, strict, tt.enableFullPath).Messages
if len(m) != 0 {
t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m {
t.Logf("Message %d: %s", i, msg)
if tt.enableFullPath {
// Check if the message has directory path prefixed, i.e., has full path.
if !strings.HasPrefix(msg.Path, goodChartDirFullPath) {
t.Errorf("Absolute path missing or incorrect: %s\n"+
"Should contain prefix: %s", msg.Path, goodChartDirFullPath)
}
}
}
}
})
}
}
@ -124,50 +418,142 @@ func TestHelmCreateChart(t *testing.T) {
return
}
// Note: we test with strict=true here, even though others have
// strict = false.
m := All(createdChart, values, namespace, true, false).Messages
if ll := len(m); ll != 1 {
t.Errorf("All should have had exactly 1 error. Got %d", ll)
for i, msg := range m {
t.Logf("Message %d: %s", i, msg.Error())
}
} else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") {
t.Errorf("Unexpected lint error: %s", msg)
tests := []struct {
name string
enableFullPath bool
}{
{
name: "create chart and lint",
enableFullPath: defaultEnableFullPathFlag,
},
{
name: "create chart and lint with --full-path enabled",
enableFullPath: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Note: we test with strict=true here, even though others have
// strict = false.
m := All(createdChart, values, namespace, true, tt.enableFullPath).Messages
if ll := len(m); ll != 1 {
t.Errorf("All should have had exactly 1 error. Got %d", ll)
for i, msg := range m {
t.Logf("Message %d: %s", i, msg.Error())
}
} else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") {
t.Errorf("Unexpected lint error: %s", msg)
}
if tt.enableFullPath {
// Check if the message has directory path prefixed, i.e., has full path.
// Note: t.TempDir() returns the absolute path to temporary directory.
if !strings.HasPrefix(m[0].Path, dir) {
t.Errorf("Absolute path missing or incorrect: %s\n"+
"Should contain prefix: %s", m[0].Path, dir)
}
}
})
}
}
// lint ignores import-values
// See https://github.com/helm/helm/issues/9658
func TestSubChartValuesChart(t *testing.T) {
m := All(subChartValuesDir, values, namespace, strict, false).Messages
if len(m) != 0 {
t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m {
t.Logf("Message %d: %s", i, msg)
}
chartPath := subChartValuesDir
subChartValuesDirFullPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatalf("Failed to determine the full path of sub-chart's directory")
}
tests := []struct {
name string
enableFullPath bool
}{
{
name: "sub-chart values",
enableFullPath: defaultEnableFullPathFlag,
},
{
name: "sub-chart values with --full-path enabled",
enableFullPath: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := All(chartPath, values, namespace, strict, tt.enableFullPath).Messages
if len(m) != 0 {
t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m {
t.Logf("Message %d: %s", i, msg)
if tt.enableFullPath {
// Check if the message has directory path prefixed, i.e., has full path.
if !strings.HasPrefix(msg.Path, subChartValuesDirFullPath) {
t.Errorf("Absolute path missing or incorrect: %s\n"+
"Should contain prefix: %s", msg.Path, subChartValuesDirFullPath)
}
}
}
}
})
}
}
// lint stuck with malformed template object
// See https://github.com/helm/helm/issues/11391
func TestMalformedTemplate(t *testing.T) {
c := time.After(3 * time.Second)
ch := make(chan int, 1)
var m []support.Message
go func() {
m = All(malformedTemplate, values, namespace, strict, false).Messages
ch <- 1
}()
select {
case <-c:
t.Fatalf("lint malformed template timeout")
case <-ch:
if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
if !strings.Contains(m[0].Err.Error(), "invalid character '{'") {
t.Errorf("All didn't have the error for invalid character '{'")
}
chartPath := malformedTemplate
malformedTemplateFullPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatalf("Failed to determine the full path of malformed template's directory")
}
tests := []struct {
name string
enableFullPath bool
}{
{
name: "malformed template",
enableFullPath: defaultEnableFullPathFlag,
},
{
name: "malformed template with --full-path enabled",
enableFullPath: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := time.After(3 * time.Second)
ch := make(chan int, 1)
var m []support.Message
go func() {
m = All(chartPath, values, namespace, strict, tt.enableFullPath).Messages
ch <- 1
}()
select {
case <-c:
t.Fatalf("lint malformed template timeout")
case <-ch:
if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
if !strings.Contains(m[0].Err.Error(), "invalid character '{'") {
t.Errorf("All didn't have the error for invalid character '{'")
}
if tt.enableFullPath {
// Check if the message has directory path prefixed, i.e., has full path.
if !strings.HasPrefix(m[0].Path, malformedTemplateFullPath) {
t.Errorf("Absolute path missing or incorrect: %s\n"+
"Should contain prefix: %s", m[0].Path, malformedTemplateFullPath)
}
}
}
})
}
}

@ -229,6 +229,54 @@ func TestChartfile(t *testing.T) {
}
})
t.Run("Chart.yaml basic validity issues with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: badChartDir,
EnableFullPath: true,
}
Chartfile(&linter)
msgs := linter.Messages
expectedNumberOfErrorMessages := 6
if len(msgs) != expectedNumberOfErrorMessages {
t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs))
return
}
if !strings.Contains(msgs[0].Err.Error(), "name is required") {
t.Errorf("Unexpected message 0: %s", msgs[0].Err)
}
if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") {
t.Errorf("Unexpected message 1: %s", msgs[1].Err)
}
if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") {
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
}
if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") {
t.Errorf("Unexpected message 3: %s", msgs[3].Err)
}
if !strings.Contains(msgs[4].Err.Error(), "chart type is not valid in apiVersion") {
t.Errorf("Unexpected message 4: %s", msgs[4].Err)
}
if !strings.Contains(msgs[5].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") {
t.Errorf("Unexpected message 5: %s", msgs[5].Err)
}
// Validate if full paths are returns in linter messages
for _, msg := range msgs {
if !strings.HasPrefix(msg.Path, linter.ChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, linter.ChartDir)
}
}
})
t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) {
linter := support.Linter{ChartDir: anotherBadChartDir}
Chartfile(&linter)
@ -252,4 +300,39 @@ func TestChartfile(t *testing.T) {
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
}
})
t.Run("Chart.yaml validity issues due to type mismatch with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: anotherBadChartDir,
EnableFullPath: true,
}
Chartfile(&linter)
msgs := linter.Messages
expectedNumberOfErrorMessages := 3
if len(msgs) != expectedNumberOfErrorMessages {
t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs))
return
}
if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") {
t.Errorf("Unexpected message 0: %s", msgs[0].Err)
}
if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") {
t.Errorf("Unexpected message 1: %s", msgs[1].Err)
}
if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") {
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
}
// Validate if full paths are returns in linter messages
for _, msg := range msgs {
if !strings.HasPrefix(msg.Path, linter.ChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, linter.ChartDir)
}
}
})
}

@ -53,17 +53,44 @@ const namespace = "testNamespace"
const strict = false
func TestTemplateParsing(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict)
res := linter.Messages
t.Run("template parsing", func(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
t.Fatalf("Expected one error, got %d, %v", len(res), res)
}
if len(res) != 1 {
t.Fatalf("Expected one error, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("Unexpected error: %s", res[0])
}
if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("Unexpected error: %s", res[0])
}
})
t.Run("template parsing with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: templateTestBasedir,
EnableFullPath: true,
}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
t.Fatalf("Expected one error, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("Unexpected error: %s", res[0])
}
// Validate if full paths are returns in linter messages
for _, msg := range linter.Messages {
if !strings.HasPrefix(msg.Path, filepath.Clean(linter.ChartDir)) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, linter.ChartDir)
}
}
})
}
var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml")
@ -72,51 +99,141 @@ var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored"
// Test a template with all the existing features:
// namespaces, partial templates
func TestTemplateIntegrationHappyPath(t *testing.T) {
// Rename file so it gets ignored by the linter
os.Rename(wrongTemplatePath, ignoredTemplatePath)
defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
t.Run("template integration happy path", func(t *testing.T) {
// Rename file so it gets ignored by the linter
os.Rename(wrongTemplatePath, ignoredTemplatePath)
defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict)
res := linter.Messages
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 0 {
t.Fatalf("Expected no error, got %d, %v", len(res), res)
}
if len(res) != 0 {
t.Fatalf("Expected no error, got %d, %v", len(res), res)
}
})
t.Run("template integration happy path with --full-path enabled", func(t *testing.T) {
// Rename file so it gets ignored by the linter
os.Rename(wrongTemplatePath, ignoredTemplatePath)
defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
linter := support.Linter{
ChartDir: templateTestBasedir,
EnableFullPath: true,
}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 0 {
t.Fatalf("Expected no error, got %d, %v", len(res), res)
}
// Validate if full paths are returns in linter messages
cleanedChartDir := filepath.Clean(linter.ChartDir)
for _, msg := range linter.Messages {
if !strings.HasPrefix(msg.Path, cleanedChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, cleanedChartDir)
}
}
})
}
func TestV3Fail(t *testing.T) {
linter := support.Linter{ChartDir: "./testdata/v3-fail"}
Templates(&linter, values, namespace, strict)
res := linter.Messages
t.Run("v3 fail", func(t *testing.T) {
linter := support.Linter{ChartDir: "./testdata/v3-fail"}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 3 {
t.Fatalf("Expected 3 errors, got %d, %v", len(res), res)
}
if len(res) != 3 {
t.Fatalf("Expected 3 errors, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") {
t.Errorf("Unexpected error: %s", res[1].Err)
}
if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") {
t.Errorf("Unexpected error: %s", res[2].Err)
}
if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") {
t.Errorf("Unexpected error: %s", res[1].Err)
}
if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") {
t.Errorf("Unexpected error: %s", res[2].Err)
}
})
t.Run("v3 fail with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: "./testdata/v3-fail",
EnableFullPath: true,
}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 3 {
t.Fatalf("Expected 3 errors, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") {
t.Errorf("Unexpected error: %s", res[1].Err)
}
if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") {
t.Errorf("Unexpected error: %s", res[2].Err)
}
// Validate if full paths are returns in linter messages
cleanedChartDir := filepath.Clean(linter.ChartDir)
for _, msg := range linter.Messages {
if !strings.HasPrefix(msg.Path, cleanedChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, cleanedChartDir)
}
}
})
}
func TestMultiTemplateFail(t *testing.T) {
linter := support.Linter{ChartDir: "./testdata/multi-template-fail"}
Templates(&linter, values, namespace, strict)
res := linter.Messages
t.Run("multiple template fail", func(t *testing.T) {
linter := support.Linter{ChartDir: "./testdata/multi-template-fail"}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
t.Fatalf("Expected 1 error, got %d, %v", len(res), res)
}
if len(res) != 1 {
t.Fatalf("Expected 1 error, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
})
t.Run("multiple template fail with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: "./testdata/multi-template-fail",
EnableFullPath: true,
}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
t.Fatalf("Expected 1 error, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
// Validate if full paths are returns in linter messages
cleanedChartDir := filepath.Clean(linter.ChartDir)
for _, msg := range linter.Messages {
if !strings.HasPrefix(msg.Path, cleanedChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, cleanedChartDir)
}
}
})
}
func TestValidateMetadataName(t *testing.T) {
@ -226,19 +343,49 @@ func TestDeprecatedAPIFails(t *testing.T) {
t.Fatal(err)
}
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l != 1 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
t.Run("deprecated API fails", func(t *testing.T) {
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l != 1 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
}
t.Fatalf("Expected 1 lint error, got %d", l)
}
t.Fatalf("Expected 1 lint error, got %d", l)
}
err := linter.Messages[0].Err.(deprecatedAPIError)
if err.Deprecated != "apps/v1beta1 Deployment" {
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated)
}
err := linter.Messages[0].Err.(deprecatedAPIError)
if err.Deprecated != "apps/v1beta1 Deployment" {
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated)
}
})
t.Run("deprecated API fails with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: filepath.Join(tmpdir, mychart.Name()),
EnableFullPath: true,
}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l != 1 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
}
t.Fatalf("Expected 1 lint error, got %d", l)
}
err := linter.Messages[0].Err.(deprecatedAPIError)
if err.Deprecated != "apps/v1beta1 Deployment" {
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated)
}
// Validate if full paths are returns in linter messages
cleanedChartDir := filepath.Clean(linter.ChartDir)
for _, msg := range linter.Messages {
if !strings.HasPrefix(msg.Path, cleanedChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, cleanedChartDir)
}
}
})
}
const manifest = `apiVersion: v1
@ -280,16 +427,43 @@ func TestStrictTemplateParsingMapError(t *testing.T) {
if err := chartutil.SaveDir(&ch, dir); err != nil {
t.Fatal(err)
}
linter := &support.Linter{
ChartDir: filepath.Join(dir, ch.Metadata.Name),
}
Templates(linter, ch.Values, namespace, strict)
if len(linter.Messages) != 0 {
t.Errorf("expected zero messages, got %d", len(linter.Messages))
for i, msg := range linter.Messages {
t.Logf("Message %d: %q", i, msg)
t.Run("strict template parsing map error", func(t *testing.T) {
linter := &support.Linter{
ChartDir: filepath.Join(dir, ch.Metadata.Name),
}
}
Templates(linter, ch.Values, namespace, strict)
if len(linter.Messages) != 0 {
t.Errorf("expected zero messages, got %d", len(linter.Messages))
for i, msg := range linter.Messages {
t.Logf("Message %d: %q", i, msg)
}
}
})
t.Run("strict template parsing map error with --full-path enabled", func(t *testing.T) {
linter := &support.Linter{
ChartDir: filepath.Join(dir, ch.Metadata.Name),
EnableFullPath: true,
}
cleanedChartDir := filepath.Clean(linter.ChartDir)
Templates(linter, ch.Values, namespace, strict)
if len(linter.Messages) != 0 {
t.Errorf("expected zero messages, got %d", len(linter.Messages))
for i, msg := range linter.Messages {
t.Logf("Message %d: %q", i, msg)
// Validate if full paths are returns in linter messages
if !strings.HasPrefix(msg.Path, cleanedChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, cleanedChartDir)
}
}
}
})
}
func TestValidateMatchSelector(t *testing.T) {
@ -411,14 +585,38 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
t.Fatal(err)
}
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l > 0 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
t.Run("empty manifests", func(t *testing.T) {
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l > 0 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
}
t.Fatalf("Expected 0 lint errors, got %d", l)
}
})
t.Run("empty manifests with --full-path enabled", func(t *testing.T) {
linter := support.Linter{
ChartDir: filepath.Join(tmpdir, mychart.Name()),
EnableFullPath: true,
}
t.Fatalf("Expected 0 lint errors, got %d", l)
}
Templates(&linter, values, namespace, strict)
cleanedChartDir := filepath.Clean(linter.ChartDir)
if l := len(linter.Messages); l > 0 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
// Validate if full paths are returns in linter messages
if !strings.HasPrefix(msg.Path, cleanedChartDir) {
t.Errorf("Full path is missing or incorrect: %s\n"+
"Expected path prefix: %s", msg.Path, cleanedChartDir)
}
}
t.Fatalf("Expected 0 lint errors, got %d", l)
}
})
}
func TestValidateListAnnotations(t *testing.T) {
md := &K8sYamlStruct{

@ -19,11 +19,13 @@ package rules
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/lint/support"
)
var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml")
@ -50,6 +52,10 @@ const testSchema = `
}
}
`
const (
goodChartDir = "testdata/goodone"
badValuesFileDir = "testdata/badvaluesfile"
)
func TestValidateValuesYamlNotDirectory(t *testing.T) {
_ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm)
@ -167,3 +173,117 @@ func createTestingSchema(t *testing.T, dir string) string {
}
return schemafile
}
func TestValuesWithOverrides(t *testing.T) {
tests := []struct {
name string
chartDir string
values map[string]interface{}
enableFullPath bool
pathToErrSubstring map[string]string
}{
{
name: "good chart",
chartDir: goodChartDir,
values: map[string]interface{}{
"name": "not-very-goodone-here",
"tag": "test",
},
enableFullPath: false,
pathToErrSubstring: map[string]string{}},
{
name: "good chart with --full-path enabled",
chartDir: goodChartDir,
values: map[string]interface{}{
"name": "not-very-goodone-here",
"tag": "test",
},
enableFullPath: true,
pathToErrSubstring: map[string]string{},
},
{
name: "chart not found",
chartDir: "some-non-existing-path",
values: map[string]interface{}{
"name": "chart-not-found",
"tag": "test",
},
enableFullPath: false,
pathToErrSubstring: map[string]string{
`values.yaml`: `file does not exist`,
},
},
{
name: "chart not found with --full-path enabled",
chartDir: "some-non-existing-path",
values: map[string]interface{}{
"name": "chart-not-found",
"tag": "test",
},
enableFullPath: true,
pathToErrSubstring: map[string]string{
`some-non-existing-path/values.yaml`: `file does not exist`,
},
},
{
name: "bad values file",
chartDir: badValuesFileDir,
values: map[string]interface{}{
"name": "bad value",
"tag": "test",
},
enableFullPath: false,
pathToErrSubstring: map[string]string{
`values.yaml`: `unable to parse YAML`,
},
},
{
name: "bad values file with --full-path enabled",
chartDir: badValuesFileDir,
values: map[string]interface{}{
"name": "bad value",
"tag": "test",
},
enableFullPath: true,
pathToErrSubstring: map[string]string{
filepath.Join(badValuesFileDir, `values.yaml`): `unable to parse YAML`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
linter := support.Linter{
ChartDir: tt.chartDir,
EnableFullPath: tt.enableFullPath,
}
ValuesWithOverrides(&linter, tt.values)
if len(linter.Messages) != len(tt.pathToErrSubstring) {
for _, msg := range linter.Messages {
t.Log(msg.Path, msg.Err)
}
t.Fatalf("Mismatch of linter messages count\nExpected: %d\nGot: %d",
len(tt.pathToErrSubstring), len(linter.Messages))
}
for path, errMsg := range tt.pathToErrSubstring {
found := false
for _, msg := range linter.Messages {
if msg.Path == path && strings.Contains(msg.Err.Error(), errMsg) {
found = true
break
}
}
if !found {
t.Errorf("Path or error substring not found in linter messages\n"+
"Path: %q\nSubstring: %q\nMessages: %v",
path, errMsg, linter.Messages)
}
}
})
}
}

Loading…
Cancel
Save