Merge branch 'main' into main

Signed-off-by: yeomin4242 <111717431+yeomin4242@users.noreply.github.com>
pull/32155/head
yeomin4242 2 months ago committed by GitHub
commit 1de11e668c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -37,9 +37,17 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
groups:
github-actions:
patterns:
- "*"
- # Keep dev-v3 GitHub Actions up to date, while Helm v3 is within support - # Keep dev-v3 GitHub Actions up to date, while Helm v3 is within support
package-ecosystem: "github-actions" package-ecosystem: "github-actions"
target-branch: "dev-v3" target-branch: "dev-v3"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
groups:
github-actions:
patterns:
- "*"

@ -0,0 +1,2 @@
"v4.x":
- base-branch: "main"

@ -18,11 +18,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -44,11 +44,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -59,7 +59,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -73,4 +73,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3

@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -19,13 +19,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- name: Add variables to environment file - name: Add variables to environment file
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -0,0 +1,16 @@
name: Label PRs
on:
pull_request_target:
branches: [main]
types: [opened, reopened, edited, synchronize]
permissions: {}
jobs:
label:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # pin@v7.0.0
with:
sync-labels: true

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest-16-cores runs-on: ubuntu-latest-16-cores
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1
with: with:
fetch-depth: 0 fetch-depth: 0
@ -28,7 +28,7 @@ jobs:
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true
@ -85,7 +85,7 @@ jobs:
if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm'
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1
with: with:
fetch-depth: 0 fetch-depth: 0
@ -93,7 +93,7 @@ jobs:
run: cat ".github/env" >> "$GITHUB_ENV" run: cat ".github/env" >> "$GITHUB_ENV"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0
with: with:
go-version: '${{ env.GOLANG_VERSION }}' go-version: '${{ env.GOLANG_VERSION }}'
check-latest: true check-latest: true

@ -28,12 +28,12 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@ -64,6 +64,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional). # Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard # Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif

@ -74,9 +74,9 @@ linters:
errorlint: errorlint:
# Check for plain type assertions and type switches. # Check for plain type assertions and type switches.
asserts: false asserts: true
# Check for plain error comparisons. # Check for plain error comparisons.
comparison: false comparison: true
exhaustive: exhaustive:
default-signifies-exhaustive: true default-signifies-exhaustive: true

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main // import "helm.sh/helm/v4/cmd/helm" package main
import ( import (
"errors" "errors"

@ -39,14 +39,14 @@ require (
golang.org/x/term v0.45.0 golang.org/x/term v0.45.0
golang.org/x/text v0.40.0 golang.org/x/text v0.40.0
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.36.2 k8s.io/api v0.36.3
k8s.io/apiextensions-apiserver v0.36.2 k8s.io/apiextensions-apiserver v0.36.3
k8s.io/apimachinery v0.36.2 k8s.io/apimachinery v0.36.3
k8s.io/apiserver v0.36.2 k8s.io/apiserver v0.36.3
k8s.io/cli-runtime v0.36.2 k8s.io/cli-runtime v0.36.3
k8s.io/client-go v0.36.2 k8s.io/client-go v0.36.3
k8s.io/klog/v2 v2.140.0 k8s.io/klog/v2 v2.140.0
k8s.io/kubectl v0.36.2 k8s.io/kubectl v0.36.3
oras.land/oras-go/v2 v2.6.2 oras.land/oras-go/v2 v2.6.2
sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/kustomize/kyaml v0.21.1
@ -165,18 +165,18 @@ require (
golang.org/x/sys v0.47.0 // indirect golang.org/x/sys v0.47.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.47.0 // indirect golang.org/x/tools v0.47.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/grpc v1.80.0 // indirect google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/component-base v0.36.2 // indirect k8s.io/component-base v0.36.3 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect
) )

@ -465,12 +465,12 @@ golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@ -487,26 +487,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w=
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg=
k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0=
k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4=
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM=
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE=
k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= k8s.io/apiserver v0.36.3 h1:MGSg2SkdfuytiDEcRylT5mQFmmSsbx90XFUO67Y4bsQ=
k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= k8s.io/apiserver v0.36.3/go.mod h1:fVH7zv9EUNUA7Fl7LtDKh8aB9W7u1VQPSGtWV5SjUxg=
k8s.io/cli-runtime v0.36.2 h1:CconTvEeV4DJs4ZX3HQKCFbFRGsm6OtuBM9yjmMP2VM= k8s.io/cli-runtime v0.36.3 h1:g+eJ+M1sYpnNYp/q5fzaw2KejIL0Q7DH+xFl6YVoL4U=
k8s.io/cli-runtime v0.36.2/go.mod h1:LddcjiMf4YlnHO7c1Y7rEtDqL84FyiYVLco7V679GUU= k8s.io/cli-runtime v0.36.3/go.mod h1:hZpAqK8nSFXvvLaVCbzUPVp8e9TRLSTCfpNzMt7s3tE=
k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg=
k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30=
k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY=
k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= k8s.io/kubectl v0.36.3 h1:TesKp+XYQEjPYoFvuobcVnuvira2+/xAVlq//+kksaI=
k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= k8s.io/kubectl v0.36.3/go.mod h1:W+NEb1CzBGmoaI1Nrpn2ETo9omNBl0AsyxnnMT40N6E=
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc=
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo=
@ -521,7 +521,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

@ -17,6 +17,8 @@ package v3
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestValidateDependency(t *testing.T) { func TestValidateDependency(t *testing.T) {
@ -35,10 +37,10 @@ func TestValidateDependency(t *testing.T) {
} { } {
dep.Alias = value dep.Alias = value
res := dep.Validate() res := dep.Validate()
if res != nil && !shouldFail { if shouldFail {
t.Errorf("Failed on case %q", dep.Alias) assert.Errorf(t, res, "Expected failure for %q", dep.Alias)
} else if res == nil && shouldFail { } else {
t.Errorf("Expected failure for %q", dep.Alias) assert.NoErrorf(t, res, "Failed on case %q", dep.Alias)
} }
} }
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package lint // import "helm.sh/helm/v4/internal/chart/v3/lint" package lint
import ( import (
"path/filepath" "path/filepath"

@ -114,7 +114,7 @@ func TestBadValuesV3(t *testing.T) {
func TestBadCrdFileV3(t *testing.T) { func TestBadCrdFileV3(t *testing.T) {
var values map[string]any var values map[string]any
m := RunAll(badCrdFileDir, values, namespace).Messages m := RunAll(badCrdFileDir, values, namespace).Messages
assert.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) require.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m)
require.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") require.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'")
assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'") assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'")
} }
@ -122,8 +122,7 @@ func TestBadCrdFileV3(t *testing.T) {
func TestGoodChart(t *testing.T) { func TestGoodChart(t *testing.T) {
var values map[string]any var values map[string]any
m := RunAll(goodChartDir, values, namespace).Messages m := RunAll(goodChartDir, values, namespace).Messages
if len(m) != 0 { if !assert.Empty(t, m, "All returned linter messages when it shouldn't have") {
t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m { for i, msg := range m {
t.Logf("Message %d: %s", i, msg) t.Logf("Message %d: %s", i, msg)
} }
@ -143,13 +142,12 @@ func TestHelmCreateChart(t *testing.T) {
// Note: we test with strict=true here, even though others have // Note: we test with strict=true here, even though others have
// strict = false. // strict = false.
m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages
if ll := len(m); ll != 1 { if !assert.Len(t, m, 1, "All should have had exactly 1 error") {
t.Errorf("All should have had exactly 1 error. Got %d", ll)
for i, msg := range m { for i, msg := range m {
t.Logf("Message %d: %s", i, msg.Error()) t.Logf("Message %d: %s", i, msg.Error())
} }
} else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { } else {
t.Errorf("Unexpected lint error: %s", msg) assert.ErrorContains(t, m[0].Err, "icon is recommended")
} }
} }
@ -192,8 +190,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) {
func TestSubChartValuesChart(t *testing.T) { func TestSubChartValuesChart(t *testing.T) {
var values map[string]any var values map[string]any
m := RunAll(subChartValuesDir, values, namespace).Messages m := RunAll(subChartValuesDir, values, namespace).Messages
if len(m) != 0 { if !assert.Empty(t, m, "All returned linter messages when it shouldn't have") {
t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m { for i, msg := range m {
t.Logf("Message %d: %s", i, msg) t.Logf("Message %d: %s", i, msg)
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"errors" "errors"

@ -52,24 +52,19 @@ func TestValidateChartYamlNotDirectory(t *testing.T) {
_ = os.Mkdir(nonExistingChartFilePath, os.ModePerm) _ = os.Mkdir(nonExistingChartFilePath, os.ModePerm)
defer os.Remove(nonExistingChartFilePath) defer os.Remove(nonExistingChartFilePath)
err := validateChartYamlNotDirectory(nonExistingChartFilePath) assert.Error(t, validateChartYamlNotDirectory(nonExistingChartFilePath), "validateChartYamlNotDirectory to return a linter error, got no error")
assert.Error(t, err, "validateChartYamlNotDirectory to return a linter error, got no error")
} }
func TestValidateChartYamlFormat(t *testing.T) { func TestValidateChartYamlFormat(t *testing.T) {
err := validateChartYamlFormat(errors.New("Read error")) require.Error(t, validateChartYamlFormat(errors.New("Read error")), "validateChartYamlFormat to return a linter error, got no error")
require.Error(t, err, "validateChartYamlFormat to return a linter error, got no error")
err = validateChartYamlFormat(nil) assert.NoError(t, validateChartYamlFormat(nil), "validateChartYamlFormat to return no error, got a linter error")
assert.NoError(t, err, "validateChartYamlFormat to return no error, got a linter error")
} }
func TestValidateChartName(t *testing.T) { func TestValidateChartName(t *testing.T) {
err := validateChartName(badChart) require.Error(t, validateChartName(badChart), "validateChartName to return a linter error, got no error")
require.Error(t, err, "validateChartName to return a linter error, got no error")
err = validateChartName(badChartName) assert.Error(t, validateChartName(badChartName), "expected validateChartName to return a linter error for an invalid name, got no error")
assert.Error(t, err, "expected validateChartName to return a linter error for an invalid name, got no error")
} }
func TestValidateChartVersion(t *testing.T) { func TestValidateChartVersion(t *testing.T) {
@ -90,15 +85,13 @@ func TestValidateChartVersion(t *testing.T) {
for i, test := range failTest { for i, test := range failTest {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
badChart.Version = test.Version badChart.Version = test.Version
err := validateChartVersion(badChart) require.ErrorContains(t, validateChartVersion(badChart), test.ErrorMsg, "validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg)
require.ErrorContains(t, err, test.ErrorMsg, "validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg)
}) })
} }
for _, version := range successTest { for _, version := range successTest {
badChart.Version = version badChart.Version = version
err := validateChartVersion(badChart) assert.NoError(t, validateChartVersion(badChart), "validateChartVersion(%s) to return no error, got a linter error", version)
assert.NoError(t, err, "validateChartVersion(%s) to return no error, got a linter error", version)
} }
} }
@ -124,24 +117,20 @@ func TestValidateChartMaintainer(t *testing.T) {
for _, test := range failTest { for _, test := range failTest {
t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) { t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) {
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(badChart) require.ErrorContains(t, validateChartMaintainer(badChart), test.ErrorMsg, "validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg)
require.ErrorContains(t, err, test.ErrorMsg, "validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg)
}) })
} }
for _, test := range successTest { for _, test := range successTest {
t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) { t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) {
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(badChart) require.NoError(t, validateChartMaintainer(badChart), "validateChartMaintainer(%s, %s) to return no error", test.Name, test.Email)
require.NoError(t, err, "validateChartMaintainer(%s, %s) to return no error", test.Name, test.Email)
}) })
} }
// Testing for an empty maintainer // Testing for an empty maintainer
badChart.Maintainers = []*chart.Maintainer{nil} badChart.Maintainers = []*chart.Maintainer{nil}
err := validateChartMaintainer(badChart) assert.EqualError(t, validateChartMaintainer(badChart), "a maintainer entry is empty")
require.Error(t, err, "validateChartMaintainer did not return error for nil maintainer as expected")
assert.EqualError(t, err, "a maintainer entry is empty", "validateChartMaintainer returned unexpected error for nil maintainer")
} }
func TestValidateChartSources(t *testing.T) { func TestValidateChartSources(t *testing.T) {
@ -150,15 +139,13 @@ func TestValidateChartSources(t *testing.T) {
for _, test := range failTest { for _, test := range failTest {
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
badChart.Sources = []string{test} badChart.Sources = []string{test}
err := validateChartSources(badChart) require.ErrorContains(t, validateChartSources(badChart), "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test)
require.ErrorContains(t, err, "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test)
}) })
} }
for _, test := range successTest { for _, test := range successTest {
badChart.Sources = []string{test} badChart.Sources = []string{test}
err := validateChartSources(badChart) assert.NoError(t, validateChartSources(badChart), "validateChartSources(%s) to return no error", test)
assert.NoError(t, err, "validateChartSources(%s) to return no error", test)
} }
} }
@ -168,19 +155,13 @@ func TestValidateChartIconPresence(t *testing.T) {
Icon: "", Icon: "",
} }
err := validateChartIconPresence(testChart) assert.ErrorContains(t, validateChartIconPresence(testChart), "icon is recommended", "expected %q", "icon is recommended")
require.Error(t, err, "validateChartIconPresence to return a linter error, got no error")
assert.ErrorContains(t, err, "icon is recommended", "expected %q", "icon is recommended")
}) })
t.Run("Icon present", func(t *testing.T) { t.Run("Icon present", func(t *testing.T) {
testChart := &chart.Metadata{ testChart := &chart.Metadata{
Icon: "http://example.org/icon.png", Icon: "http://example.org/icon.png",
} }
assert.NoError(t, validateChartIconPresence(testChart))
err := validateChartIconPresence(testChart)
assert.NoError(t, err, "Unexpected error")
}) })
} }
@ -190,15 +171,13 @@ func TestValidateChartIconURL(t *testing.T) {
for _, test := range failTest { for _, test := range failTest {
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
badChart.Icon = test badChart.Icon = test
err := validateChartIconURL(badChart) require.ErrorContains(t, validateChartIconURL(badChart), "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test)
require.ErrorContains(t, err, "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test)
}) })
} }
for _, test := range successTest { for _, test := range successTest {
badChart.Icon = test badChart.Icon = test
err := validateChartIconURL(badChart) assert.NoError(t, validateChartIconURL(badChart), "validateChartIconURL(%s) to return no error", test)
assert.NoError(t, err, "validateChartIconURL(%s) to return no error", test)
} }
} }

@ -22,6 +22,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/chart/v3/lint/support" "helm.sh/helm/v4/internal/chart/v3/lint/support"
) )
@ -33,7 +34,7 @@ func TestInvalidCrdsDir(t *testing.T) {
Crds(&linter) Crds(&linter)
res := linter.Messages res := linter.Messages
assert.Len(t, res, 1) require.Len(t, res, 1)
assert.ErrorContains(t, res[0].Err, "not a directory") assert.ErrorContains(t, res[0].Err, "not a directory")
} }

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" package rules
import ( import (
"testing" "testing"
@ -29,14 +29,12 @@ func TestValidateNoDeprecations(t *testing.T) {
Kind: "Deployment", Kind: "Deployment",
} }
err := validateNoDeprecations(deprecated, nil) err := validateNoDeprecations(deprecated, nil)
var depErr deprecatedAPIError
require.Error(t, err, "Expected deprecated extension to be flagged") require.Error(t, err, "Expected deprecated extension to be flagged")
var depErr deprecatedAPIError
require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError") require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError")
require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank: %v", err) require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank")
assert.NoError(t, validateNoDeprecations(&k8sYamlStruct{
err = validateNoDeprecations(&k8sYamlStruct{
APIVersion: "v1", APIVersion: "v1",
Kind: "Pod", Kind: "Pod",
}, nil) }, nil), "Expected a v1 Pod to not be deprecated")
assert.NoError(t, err, "Expected a v1 Pod to not be deprecated")
} }

@ -37,13 +37,11 @@ const templateTestBasedir = "./testdata/albatross"
func TestValidateAllowedExtension(t *testing.T) { func TestValidateAllowedExtension(t *testing.T) {
var failTest = []string{"/foo", "/test.toml"} var failTest = []string{"/foo", "/test.toml"}
for _, test := range failTest { for _, test := range failTest {
err := validateAllowedExtension(test) require.ErrorContains(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test)
require.ErrorContains(t, err, "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test)
} }
var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"}
for _, test := range successTest { for _, test := range successTest {
err := validateAllowedExtension(test) assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test)
assert.NoError(t, err, "validateAllowedExtension('%s') to return no error", test)
} }
} }
@ -58,8 +56,7 @@ func TestTemplateParsing(t *testing.T) {
res := linter.Messages res := linter.Messages
require.Len(t, res, 1, "Expected one error, got %d, %v", len(res), res) require.Len(t, res, 1, "Expected one error, got %d, %v", len(res), res)
assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError")
assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError", "Unexpected error: %s", res[0])
} }
var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml")
@ -86,7 +83,7 @@ func TestMultiTemplateFail(t *testing.T) {
require.Len(t, res, 1, "Expected 1 error, got %d, %v", len(res), res) require.Len(t, res, 1, "Expected 1 error, got %d, %v", len(res), res)
assert.ErrorContains(t, res[0].Err, "object name does not conform to Kubernetes naming requirements", "Unexpected error: %s", res[0].Err) assert.ErrorContains(t, res[0].Err, "object name does not conform to Kubernetes naming requirements")
} }
func TestValidateMetadataName(t *testing.T) { func TestValidateMetadataName(t *testing.T) {
@ -202,12 +199,12 @@ func TestDeprecatedAPIFails(t *testing.T) {
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict) Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l != 1 { if !assert.Len(t, linter.Messages, 1) {
for i, msg := range linter.Messages { for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg) t.Logf("Message %d: %s", i, msg)
} }
t.Fatalf("Expected 1 lint error, got %d", l)
} }
require.Len(t, linter.Messages, 1, "Expected 1 lint error")
var err deprecatedAPIError var err deprecatedAPIError
require.ErrorAs(t, linter.Messages[0].Err, &err, "Expected error to be of type deprecatedAPIError") require.ErrorAs(t, linter.Messages[0].Err, &err, "Expected error to be of type deprecatedAPIError")
@ -255,8 +252,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) {
ChartDir: filepath.Join(dir, ch.Metadata.Name), ChartDir: filepath.Join(dir, ch.Metadata.Name),
} }
Templates(linter, ch.Values, namespace, strict) Templates(linter, ch.Values, namespace, strict)
if len(linter.Messages) != 0 { if !assert.Empty(t, linter.Messages, "expected zero messages") {
t.Errorf("expected zero messages, got %d", len(linter.Messages))
for i, msg := range linter.Messages { for i, msg := range linter.Messages {
t.Logf("Message %d: %q", i, msg) t.Logf("Message %d: %q", i, msg)
} }
@ -292,8 +288,7 @@ spec:
- name: nginx - name: nginx
image: nginx:1.14.2 image: nginx:1.14.2
` `
err := validateMatchSelector(md, manifest) require.NoError(t, validateMatchSelector(md, manifest))
require.NoError(t, err)
manifest = ` manifest = `
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
@ -315,8 +310,7 @@ spec:
- name: nginx - name: nginx
image: nginx:1.14.2 image: nginx:1.14.2
` `
err = validateMatchSelector(md, manifest) require.NoError(t, validateMatchSelector(md, manifest))
require.NoError(t, err)
manifest = ` manifest = `
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
@ -335,8 +329,7 @@ spec:
- name: nginx - name: nginx
image: nginx:1.14.2 image: nginx:1.14.2
` `
err = validateMatchSelector(md, manifest) assert.Error(t, validateMatchSelector(md, manifest), "expected Deployment with no selector to fail")
assert.Error(t, err, "expected Deployment with no selector to fail")
} }
func TestValidateTopIndentLevel(t *testing.T) { func TestValidateTopIndentLevel(t *testing.T) {
@ -349,8 +342,11 @@ func TestValidateTopIndentLevel(t *testing.T) {
" apiVersion:foo": true, " apiVersion:foo": true,
"\n\n apiVersion:foo\n\n": true, "\n\n apiVersion:foo\n\n": true,
} { } {
if err := validateTopIndentLevel(doc); (err == nil) == shouldFail { err := validateTopIndentLevel(doc)
t.Errorf("Expected %t for %q", shouldFail, doc) if shouldFail {
assert.Errorf(t, err, "Expected %t for %q", shouldFail, doc)
} else {
assert.NoErrorf(t, err, "Expected %t for %q", shouldFail, doc)
} }
} }
} }
@ -379,12 +375,12 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict) Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l > 0 { if !assert.Empty(t, linter.Messages) {
for i, msg := range linter.Messages { for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg) t.Logf("Message %d: %s", i, msg)
} }
t.Fatalf("Expected 0 lint errors, got %d", l)
} }
require.Empty(t, linter.Messages, "Expected 0 lint errors")
} }
func TestValidateListAnnotations(t *testing.T) { func TestValidateListAnnotations(t *testing.T) {
md := &k8sYamlStruct{ md := &k8sYamlStruct{

@ -56,8 +56,7 @@ func TestValidateValuesYamlNotDirectory(t *testing.T) {
_ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm) _ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm)
defer os.Remove(nonExistingValuesFilePath) defer os.Remove(nonExistingValuesFilePath)
err := validateValuesFileExistence(nonExistingValuesFilePath) assert.Error(t, validateValuesFileExistence(nonExistingValuesFilePath), "validateValuesFileExistence to return a linter error, got no error")
assert.Error(t, err, "validateValuesFileExistence to return a linter error, got no error")
} }
func TestValidateValuesFileWellFormed(t *testing.T) { func TestValidateValuesFileWellFormed(t *testing.T) {
@ -150,7 +149,7 @@ func TestValidateValuesFile(t *testing.T) {
require.NoError(t, err, "Failed validation") require.NoError(t, err, "Failed validation")
} else { } else {
require.Error(t, err, "expected values file to fail parsing") require.Error(t, err, "expected values file to fail parsing")
assert.ErrorContains(t, err, tt.errorMessage, "Failed with unexpected error") assert.ErrorContains(t, err, tt.errorMessage)
} }
}) })
} }

@ -20,4 +20,4 @@ Package support contains tools for linting charts.
Linting is the process of testing charts for errors or warnings regarding Linting is the process of testing charts for errors or warnings regarding
formatting, compilation, or standards compliance. formatting, compilation, or standards compliance.
*/ */
package support // import "helm.sh/helm/v4/internal/chart/v3/lint/support" package support

@ -19,6 +19,9 @@ package support
import ( import (
"errors" "errors"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var errLint = errors.New("lint failed") var errLint = errors.New("lint failed")
@ -47,33 +50,19 @@ func TestRunLinterRule(t *testing.T) {
linter := Linter{} linter := Linter{}
for _, test := range tests { for _, test := range tests {
isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError) isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError)
if len(linter.Messages) != test.ExpectedMessages { assert.Lenf(t, linter.Messages, test.ExpectedMessages, "RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages))
t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) assert.Equalf(t, test.ExpectedHighestSeverity, linter.HighestSeverity, "RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity)
} assert.Equalf(t, test.ExpectedReturn, isValid, "RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid)
if linter.HighestSeverity != test.ExpectedHighestSeverity {
t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity)
}
if isValid != test.ExpectedReturn {
t.Errorf("RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid)
}
} }
} }
func TestMessage(t *testing.T) { func TestMessage(t *testing.T) {
m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")}
if m.Error() != "[ERROR] Chart.yaml: Foo" { require.EqualError(t, m, "[ERROR] Chart.yaml: Foo")
t.Errorf("Unexpected output: %s", m.Error())
}
m = Message{WarningSev, "templates/", errors.New("Bar")} m = Message{WarningSev, "templates/", errors.New("Bar")}
if m.Error() != "[WARNING] templates/: Bar" { require.EqualError(t, m, "[WARNING] templates/: Bar")
t.Errorf("Unexpected output: %s", m.Error())
}
m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")}
if m.Error() != "[INFO] templates/rc.yaml: FooBar" { assert.EqualError(t, m, "[INFO] templates/rc.yaml: FooBar")
t.Errorf("Unexpected output: %s", m.Error())
}
} }

@ -25,7 +25,6 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@ -41,13 +40,9 @@ import (
func TestLoadDir(t *testing.T) { func TestLoadDir(t *testing.T) {
l, err := Loader("testdata/frobnitz") l, err := Loader("testdata/frobnitz")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load() c, err := l.Load()
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyFrobnitz(t, c) verifyFrobnitz(t, c)
verifyChart(t, c) verifyChart(t, c)
verifyDependencies(t, c) verifyDependencies(t, c)
@ -60,33 +55,24 @@ func TestLoadDirWithDevNull(t *testing.T) {
} }
l, err := Loader("testdata/frobnitz_with_dev_null") l, err := Loader("testdata/frobnitz_with_dev_null")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err) _, err = l.Load()
} assert.Error(t, err, "packages with an irregular file (/dev/null) should not load")
if _, err := l.Load(); err == nil {
t.Error("packages with an irregular file (/dev/null) should not load")
}
} }
func TestLoadDirWithSymlink(t *testing.T) { func TestLoadDirWithSymlink(t *testing.T) {
sym := filepath.Join("..", "LICENSE") sym := filepath.Join("..", "LICENSE")
link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE") link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE")
if err := os.Symlink(sym, link); err != nil { require.NoError(t, os.Symlink(sym, link))
t.Fatal(err)
}
defer os.Remove(link) defer os.Remove(link)
l, err := Loader("testdata/frobnitz_with_symlink") l, err := Loader("testdata/frobnitz_with_symlink")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load() c, err := l.Load()
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyFrobnitz(t, c) verifyFrobnitz(t, c)
verifyChart(t, c) verifyChart(t, c)
verifyDependencies(t, c) verifyDependencies(t, c)
@ -109,8 +95,7 @@ func TestBomTestData(t *testing.T) {
defer unzipped.Close() defer unzipped.Close()
for _, testFile := range testFiles { for _, testFile := range testFiles {
data := make([]byte, 3) data := make([]byte, 3)
err := unzipped.Reset(bytes.NewReader(archive)) require.NoErrorf(t, unzipped.Reset(bytes.NewReader(archive)), "Error reading archive frobnitz_with_bom.tgz")
require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz")
tr := tar.NewReader(unzipped) tr := tar.NewReader(unzipped)
for { for {
file, err := tr.Next() file, err := tr.Next()
@ -132,13 +117,9 @@ func TestBomTestData(t *testing.T) {
func TestLoadDirWithUTFBOM(t *testing.T) { func TestLoadDirWithUTFBOM(t *testing.T) {
l, err := Loader("testdata/frobnitz_with_bom") l, err := Loader("testdata/frobnitz_with_bom")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load() c, err := l.Load()
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyFrobnitz(t, c) verifyFrobnitz(t, c)
verifyChart(t, c) verifyChart(t, c)
verifyDependencies(t, c) verifyDependencies(t, c)
@ -148,13 +129,9 @@ func TestLoadDirWithUTFBOM(t *testing.T) {
func TestLoadArchiveWithUTFBOM(t *testing.T) { func TestLoadArchiveWithUTFBOM(t *testing.T) {
l, err := Loader("testdata/frobnitz_with_bom.tgz") l, err := Loader("testdata/frobnitz_with_bom.tgz")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load() c, err := l.Load()
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyFrobnitz(t, c) verifyFrobnitz(t, c)
verifyChart(t, c) verifyChart(t, c)
verifyDependencies(t, c) verifyDependencies(t, c)
@ -164,13 +141,9 @@ func TestLoadArchiveWithUTFBOM(t *testing.T) {
func TestLoadFile(t *testing.T) { func TestLoadFile(t *testing.T) {
l, err := Loader("testdata/frobnitz-1.2.3.tgz") l, err := Loader("testdata/frobnitz-1.2.3.tgz")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load() c, err := l.Load()
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyFrobnitz(t, c) verifyFrobnitz(t, c)
verifyChart(t, c) verifyChart(t, c)
verifyDependencies(t, c) verifyDependencies(t, c)
@ -224,36 +197,16 @@ icon: https://example.com/64x64.png
} }
c, err := LoadFiles(goodFiles) c, err := LoadFiles(goodFiles)
if err != nil { require.NoError(t, err, "Expected good files to be loaded")
t.Errorf("Expected good files to be loaded, got %v", err) assert.Equal(t, "frobnitz", c.Name(), "Expected chart name to be 'frobnitz', got %s", c.Name())
} assert.Equal(t, "some values", c.Values["var"], "Expected chart values to be populated with default values")
assert.Len(t, c.Raw, 5, "Expected 5 files")
if c.Name() != "frobnitz" { assert.True(t, bytes.Equal(c.Schema, []byte("type: Values")), "Expected chart schema to be populated with default values")
t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name()) assert.Len(t, c.Templates, 2, "Expected 2 templates")
}
_, err = LoadFiles([]*archive.BufferedFile{})
if c.Values["var"] != "some values" { require.Error(t, err, "Expected err to be non-nil")
t.Error("Expected chart values to be populated with default values") assert.EqualError(t, err, "Chart.yaml file is missing", "Expected chart metadata missing error, got '%s'", err.Error())
}
if len(c.Raw) != 5 {
t.Errorf("Expected %d files, got %d", 5, len(c.Raw))
}
if !bytes.Equal(c.Schema, []byte("type: Values")) {
t.Error("Expected chart schema to be populated with default values")
}
if len(c.Templates) != 2 {
t.Errorf("Expected number of templates == 2, got %d", len(c.Templates))
}
if _, err = LoadFiles([]*archive.BufferedFile{}); err == nil {
t.Fatal("Expected err to be non-nil")
}
if err.Error() != "Chart.yaml file is missing" {
t.Errorf("Expected chart metadata missing error, got '%s'", err.Error())
}
} }
// Test the order of file loading. The Chart.yaml file needs to come first for // Test the order of file loading. The Chart.yaml file needs to come first for
@ -309,9 +262,7 @@ icon: https://example.com/64x64.png
// Capture stderr to make sure message about Chart.yaml handle dependencies // Capture stderr to make sure message about Chart.yaml handle dependencies
// is not present // is not present
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { require.NoError(t, err, "Unable to create pipe")
t.Fatalf("Unable to create pipe: %s", err)
}
stderr := log.Writer() stderr := log.Writer()
log.SetOutput(w) log.SetOutput(w)
defer func() { defer func() {
@ -319,25 +270,19 @@ icon: https://example.com/64x64.png
}() }()
_, err = LoadFiles(goodFiles) _, err = LoadFiles(goodFiles)
if err != nil { require.NoError(t, err, "Expected good files to be loaded")
t.Errorf("Expected good files to be loaded, got %v", err)
}
w.Close() w.Close()
var text bytes.Buffer var text bytes.Buffer
io.Copy(&text, r) io.Copy(&text, r)
if text.String() != "" { assert.Empty(t, text.String(), "Expected no message to Stderr, got %s", text.String())
t.Errorf("Expected no message to Stderr, got %s", text.String())
}
} }
// Packaging the chart on a Windows machine will produce an // Packaging the chart on a Windows machine will produce an
// archive that has \\ as delimiters. Test that we support these archives // archive that has \\ as delimiters. Test that we support these archives
func TestLoadFileBackslash(t *testing.T) { func TestLoadFileBackslash(t *testing.T) {
c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz") c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyChartFileAndTemplate(t, c, "frobnitz_backslash") verifyChartFileAndTemplate(t, c, "frobnitz_backslash")
verifyChart(t, c) verifyChart(t, c)
verifyDependencies(t, c) verifyDependencies(t, c)
@ -345,13 +290,9 @@ func TestLoadFileBackslash(t *testing.T) {
func TestLoadV3WithReqs(t *testing.T) { func TestLoadV3WithReqs(t *testing.T) {
l, err := Loader("testdata/frobnitz.v3.reqs") l, err := Loader("testdata/frobnitz.v3.reqs")
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
c, err := l.Load() c, err := l.Load()
if err != nil { require.NoError(t, err, "Failed to load testdata")
t.Fatalf("Failed to load testdata: %s", err)
}
verifyDependencies(t, c) verifyDependencies(t, c)
verifyDependenciesLock(t, c) verifyDependenciesLock(t, c)
} }
@ -361,9 +302,7 @@ func TestLoadInvalidArchive(t *testing.T) {
writeTar := func(filename, internalPath string, body []byte) { writeTar := func(filename, internalPath string, body []byte) {
dest, err := os.Create(filename) dest, err := os.Create(filename)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
zipper := gzip.NewWriter(dest) zipper := gzip.NewWriter(dest)
tw := tar.NewWriter(zipper) tw := tar.NewWriter(zipper)
@ -373,12 +312,9 @@ func TestLoadInvalidArchive(t *testing.T) {
Size: int64(len(body)), Size: int64(len(body)),
ModTime: time.Now(), ModTime: time.Now(),
} }
if err := tw.WriteHeader(h); err != nil { require.NoError(t, tw.WriteHeader(h))
t.Fatal(err) _, err = tw.Write(body)
} require.NoError(t, err)
if _, err := tw.Write(body); err != nil {
t.Fatal(err)
}
tw.Close() tw.Close()
zipper.Close() zipper.Close()
dest.Close() dest.Close()
@ -410,37 +346,27 @@ func TestLoadInvalidArchive(t *testing.T) {
illegalChart := filepath.Join(tmpdir, tt.chartname) illegalChart := filepath.Join(tmpdir, tt.chartname)
writeTar(illegalChart, tt.internal, []byte("hello: world")) writeTar(illegalChart, tt.internal, []byte("hello: world"))
_, err := Load(illegalChart) _, err := Load(illegalChart)
if err == nil { require.Error(t, err, "expected error when unpacking illegal files")
t.Fatal("expected error when unpacking illegal files") require.ErrorContains(t, err, tt.expectError, "Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname)
}
if !strings.Contains(err.Error(), tt.expectError) {
t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname)
}
} }
// Make sure that absolute path gets interpreted as relative // Make sure that absolute path gets interpreted as relative
illegalChart := filepath.Join(tmpdir, "abs-path.tgz") illegalChart := filepath.Join(tmpdir, "abs-path.tgz")
writeTar(illegalChart, "/Chart.yaml", []byte("hello: world")) writeTar(illegalChart, "/Chart.yaml", []byte("hello: world"))
_, err := Load(illegalChart) _, err := Load(illegalChart)
if err.Error() != "validation: chart.metadata.name is required" { require.EqualError(t, err, "validation: chart.metadata.name is required")
t.Error(err)
}
// And just to validate that the above was not spurious // And just to validate that the above was not spurious
illegalChart = filepath.Join(tmpdir, "abs-path2.tgz") illegalChart = filepath.Join(tmpdir, "abs-path2.tgz")
writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world")) writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world"))
_, err = Load(illegalChart) _, err = Load(illegalChart)
if err.Error() != "Chart.yaml file is missing" { require.EqualError(t, err, "Chart.yaml file is missing")
t.Errorf("Unexpected error message: %s", err)
}
// Finally, test that drive letter gets stripped off on Windows // Finally, test that drive letter gets stripped off on Windows
illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz") illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz")
writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world")) writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world"))
_, err = Load(illegalChart) _, err = Load(illegalChart)
if err.Error() != "validation: chart.metadata.name is required" { assert.EqualError(t, err, "validation: chart.metadata.name is required")
t.Error(err)
}
} }
func TestLoadValues(t *testing.T) { func TestLoadValues(t *testing.T) {
@ -487,12 +413,8 @@ foo:
for testName, testCase := range testCases { for testName, testCase := range testCases {
t.Run(testName, func(tt *testing.T) { t.Run(testName, func(tt *testing.T) {
values, err := LoadValues(bytes.NewReader(testCase.data)) values, err := LoadValues(bytes.NewReader(testCase.data))
if err != nil { require.NoError(tt, err)
tt.Fatal(err) assert.Equalf(tt, testCase.expctedValues, values, "Expected values: %v, got %v", testCase.expctedValues, values)
}
if !reflect.DeepEqual(values, testCase.expctedValues) {
tt.Errorf("Expected values: %v, got %v", testCase.expctedValues, values)
}
}) })
} }
} }
@ -520,22 +442,13 @@ func TestMergeValuesV3(t *testing.T) {
} }
testMap := MergeMaps(flatMap, nestedMap) testMap := MergeMaps(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap) assert.Equal(t, testMap, nestedMap, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = MergeMaps(nestedMap, flatMap) testMap = MergeMaps(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap) assert.Equal(t, testMap, flatMap, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = MergeMaps(nestedMap, anotherNestedMap) testMap = MergeMaps(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap) assert.Equal(t, testMap, anotherNestedMap, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = MergeMaps(anotherFlatMap, anotherNestedMap) testMap = MergeMaps(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]any{ expectedMap := map[string]any{
@ -546,32 +459,23 @@ func TestMergeValuesV3(t *testing.T) {
"awesome": "stuff", "awesome": "stuff",
}, },
} }
equal = reflect.DeepEqual(testMap, expectedMap) assert.Equal(t, expectedMap, testMap, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
} }
func verifyChart(t *testing.T, c *chart.Chart) { func verifyChart(t *testing.T, c *chart.Chart) {
t.Helper() t.Helper()
if c.Name() == "" { require.NotEmpty(t, c.Name(), "No chart metadata found on %v", c)
t.Fatalf("No chart metadata found on %v", c)
}
t.Logf("Verifying chart %s", c.Name()) t.Logf("Verifying chart %s", c.Name())
if len(c.Templates) != 1 { assert.Len(t, c.Templates, 1, "Expected 1 template")
t.Errorf("Expected 1 template, got %d", len(c.Templates))
}
numfiles := 6 numfiles := 6
if len(c.Files) != numfiles { if !assert.Len(t, c.Files, numfiles, "Expected %d extra files", numfiles) {
t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files))
for _, n := range c.Files { for _, n := range c.Files {
t.Logf("\t%s", n.Name) t.Logf("\t%s", n.Name)
} }
} }
if len(c.Dependencies()) != 2 { if !assert.Len(t, c.Dependencies(), 2, "Expected 2 dependencies") {
t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies())
for _, d := range c.Dependencies() { for _, d := range c.Dependencies() {
t.Logf("\tSubchart: %s\n", d.Name()) t.Logf("\tSubchart: %s\n", d.Name())
} }
@ -587,62 +491,40 @@ func verifyChart(t *testing.T, c *chart.Chart) {
} }
for _, dep := range c.Dependencies() { for _, dep := range c.Dependencies() {
if dep.Metadata == nil { require.NotNil(t, dep.Metadata, "expected metadata on dependency: %v", dep)
t.Fatalf("expected metadata on dependency: %v", dep)
}
exp, ok := expect[dep.Name()] exp, ok := expect[dep.Name()]
if !ok { require.True(t, ok, "Unknown dependency %s", dep.Name())
t.Fatalf("Unknown dependency %s", dep.Name()) assert.Equal(t, exp["version"], dep.Metadata.Version, "Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
}
if exp["version"] != dep.Metadata.Version {
t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
}
} }
} }
func verifyDependencies(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) {
t.Helper() t.Helper()
if len(c.Metadata.Dependencies) != 2 { assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies")
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
}
tests := []*chart.Dependency{ tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
} }
for i, tt := range tests { for i, tt := range tests {
d := c.Metadata.Dependencies[i] d := c.Metadata.Dependencies[i]
if d.Name != tt.Name { assert.Equal(t, tt.Name, d.Name, "Expected dependency named %q, got %q", tt.Name, d.Name)
t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) assert.Equal(t, tt.Version, d.Version, "Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
} assert.Equal(t, tt.Repository, d.Repository, "Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
if d.Version != tt.Version {
t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
}
if d.Repository != tt.Repository {
t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
}
} }
} }
func verifyDependenciesLock(t *testing.T, c *chart.Chart) { func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
t.Helper() t.Helper()
if len(c.Metadata.Dependencies) != 2 { assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
}
tests := []*chart.Dependency{ tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
} }
for i, tt := range tests { for i, tt := range tests {
d := c.Metadata.Dependencies[i] d := c.Metadata.Dependencies[i]
if d.Name != tt.Name { assert.Equal(t, tt.Name, d.Name, "Expected dependency named %q, got %q", tt.Name, d.Name)
t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) assert.Equal(t, tt.Version, d.Version, "Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
} assert.Equal(t, tt.Repository, d.Repository, "Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
if d.Version != tt.Version {
t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
}
if d.Repository != tt.Repository {
t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
}
} }
} }
@ -653,51 +535,25 @@ func verifyFrobnitz(t *testing.T, c *chart.Chart) {
func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
t.Helper() t.Helper()
if c.Metadata == nil { require.NotNil(t, c.Metadata, "Metadata is nil")
t.Fatal("Metadata is nil") assert.Equal(t, name, c.Name(), "Expected %s, got %s", name, c.Name())
} require.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates))
if c.Name() != name { assert.Equal(t, "templates/template.tpl", c.Templates[0].Name, "Unexpected template: %s", c.Templates[0].Name)
t.Errorf("Expected %s, got %s", name, c.Name()) assert.NotEmpty(t, c.Templates[0].Data, "No template data.")
} require.Len(t, c.Files, 6, "Expected 6 Files, got %d", len(c.Files))
if len(c.Templates) != 1 { require.Len(t, c.Dependencies(), 2, "Expected 2 Dependency, got %d", len(c.Dependencies()))
t.Fatalf("Expected 1 template, got %d", len(c.Templates)) require.Len(t, c.Metadata.Dependencies, 2, "Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies))
} require.Len(t, c.Lock.Dependencies, 2, "Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies))
if c.Templates[0].Name != "templates/template.tpl" {
t.Errorf("Unexpected template: %s", c.Templates[0].Name)
}
if len(c.Templates[0].Data) == 0 {
t.Error("No template data.")
}
if len(c.Files) != 6 {
t.Fatalf("Expected 6 Files, got %d", len(c.Files))
}
if len(c.Dependencies()) != 2 {
t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies()))
}
if len(c.Metadata.Dependencies) != 2 {
t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies))
}
if len(c.Lock.Dependencies) != 2 {
t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies))
}
for _, dep := range c.Dependencies() { for _, dep := range c.Dependencies() {
switch dep.Name() { switch dep.Name() {
case "mariner": case "mariner":
case "alpine": case "alpine":
if len(dep.Templates) != 1 { require.Len(t, dep.Templates, 1, "Expected 1 template, got %d", len(dep.Templates))
t.Fatalf("Expected 1 template, got %d", len(dep.Templates)) assert.Equal(t, "templates/alpine-pod.yaml", dep.Templates[0].Name, "Unexpected template: %s", dep.Templates[0].Name)
}
if dep.Templates[0].Name != "templates/alpine-pod.yaml" {
t.Errorf("Unexpected template: %s", dep.Templates[0].Name)
}
assert.NotEmpty(t, dep.Templates[0].Data, "No template data.") assert.NotEmpty(t, dep.Templates[0].Data, "No template data.")
if len(dep.Files) != 1 { require.Len(t, dep.Files, 1, "Expected 1 Files, got %d", len(dep.Files))
t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) require.Len(t, dep.Dependencies(), 2, "Expected 2 Dependency, got %d", len(dep.Dependencies()))
}
if len(dep.Dependencies()) != 2 {
t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies()))
}
default: default:
t.Errorf("Unexpected dependency %s", dep.Name()) t.Errorf("Unexpected dependency %s", dep.Name())
} }
@ -707,8 +563,6 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
func verifyBomStripped(t *testing.T, files []*common.File) { func verifyBomStripped(t *testing.T, files []*common.File) {
t.Helper() t.Helper()
for _, file := range files { for _, file := range files {
if bytes.HasPrefix(file.Data, utf8bom) { assert.Falsef(t, bytes.HasPrefix(file.Data, utf8bom), "Byte Order Mark still present in processed file %s", file.Name)
t.Errorf("Byte Order Mark still present in processed file %s", file.Name)
}
} }
} }

@ -29,48 +29,28 @@ const testfile = "testdata/chartfiletest.yaml"
func TestLoadChartfile(t *testing.T) { func TestLoadChartfile(t *testing.T) {
f, err := LoadChartfile(testfile) f, err := LoadChartfile(testfile)
if err != nil { require.NoErrorf(t, err, "Failed to open %s", testfile)
t.Errorf("Failed to open %s: %s", testfile, err)
return
}
verifyChartfile(t, f, "frobnitz") verifyChartfile(t, f, "frobnitz")
} }
func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
t.Helper() t.Helper()
require.NotNil(t, f, "Failed verifyChartfile because f is nil") require.NotNil(t, f, "Failed verifyChartfile because f is nil")
assert.Equal(t, name, f.Name, "Expected %s, got %s", name, f.Name) assert.Equal(t, name, f.Name, "Expected %s, got %s", name, f.Name)
assert.Equal(t, "This is a frobnitz.", f.Description, "Unexpected description %q", f.Description) assert.Equal(t, "This is a frobnitz.", f.Description, "Unexpected description %q", f.Description)
assert.Equal(t, "1.2.3", f.Version, "Unexpected version %q", f.Version) assert.Equal(t, "1.2.3", f.Version, "Unexpected version %q", f.Version)
assert.Len(t, f.Maintainers, 2, "Expected 2 maintainers, got %d", len(f.Maintainers)) assert.Len(t, f.Maintainers, 2, "Expected 2 maintainers, got %d", len(f.Maintainers))
assert.Equal(t, "The Helm Team", f.Maintainers[0].Name, "Unexpected maintainer name.") assert.Equal(t, "The Helm Team", f.Maintainers[0].Name, "Unexpected maintainer name.")
assert.Equal(t, "nobody@example.com", f.Maintainers[1].Email, "Unexpected maintainer email.") assert.Equal(t, "nobody@example.com", f.Maintainers[1].Email, "Unexpected maintainer email.")
require.Len(t, f.Sources, 1, "Unexpected number of sources") require.Len(t, f.Sources, 1, "Unexpected number of sources")
assert.Equal(t, "https://example.com/foo/bar", f.Sources[0], "Expected https://example.com/foo/bar, got %s", f.Sources) assert.Equal(t, "https://example.com/foo/bar", f.Sources[0], "Expected https://example.com/foo/bar, got %s", f.Sources)
assert.Equal(t, "http://example.com", f.Home, "Unexpected home.") assert.Equal(t, "http://example.com", f.Home, "Unexpected home.")
assert.Equal(t, "https://example.com/64x64.png", f.Icon, "Unexpected icon: %q", f.Icon) assert.Equal(t, "https://example.com/64x64.png", f.Icon, "Unexpected icon: %q", f.Icon)
require.Len(t, f.Keywords, 3, "Unexpected keywords") require.Len(t, f.Keywords, 3, "Unexpected keywords")
require.Len(t, f.Annotations, 2, "Unexpected annotations") require.Len(t, f.Annotations, 2, "Unexpected annotations")
if want, got := "extravalue", f.Annotations["extrakey"]; want != got { assert.Equal(t, "extravalue", f.Annotations["extrakey"])
t.Errorf("Want %q, but got %q", want, got) assert.Equal(t, "anothervalue", f.Annotations["anotherkey"])
}
if want, got := "anothervalue", f.Annotations["anotherkey"]; want != got {
t.Errorf("Want %q, but got %q", want, got)
}
kk := []string{"frobnitz", "sprocket", "dodad"} kk := []string{"frobnitz", "sprocket", "dodad"}
for i, k := range f.Keywords { for i, k := range f.Keywords {
@ -80,13 +60,9 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
func TestIsChartDir(t *testing.T) { func TestIsChartDir(t *testing.T) {
validChartDir, err := IsChartDir("testdata/frobnitz") validChartDir, err := IsChartDir("testdata/frobnitz")
if !validChartDir { require.NoError(t, err, "while reading chart-directory")
t.Errorf("unexpected error while reading chart-directory: (%v)", err) require.True(t, validChartDir, "expected valid chart directory")
return
}
validChartDir, err = IsChartDir("testdata") validChartDir, err = IsChartDir("testdata")
if validChartDir || err == nil { require.Error(t, err)
t.Error("expected error but did not get any") require.False(t, validChartDir, "expected invalid chart directory")
return
}
} }

@ -38,7 +38,7 @@ func TestCreate(t *testing.T) {
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
mychart, err := loader.LoadDir(c) mychart, err := loader.LoadDir(c)
require.NoError(t, err, "Failed to load newly created chart %q: %s", c, err) require.NoError(t, err, "Failed to load newly created chart %q", c)
assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name())
@ -55,9 +55,8 @@ func TestCreate(t *testing.T) {
TestConnectionName, TestConnectionName,
ValuesfileName, ValuesfileName,
} { } {
if _, err := os.Stat(filepath.Join(dir, f)); err != nil { _, err := os.Stat(filepath.Join(dir, f))
t.Errorf("Expected %s file: %s", f, err) assert.NoErrorf(t, err, "Expected %s file", f)
}
} }
} }
@ -71,14 +70,12 @@ func TestCreateFrom(t *testing.T) {
} }
srcdir := "./testdata/frobnitz/charts/mariner" srcdir := "./testdata/frobnitz/charts/mariner"
if err := CreateFrom(cf, tdir, srcdir); err != nil { require.NoError(t, CreateFrom(cf, tdir, srcdir))
t.Fatal(err)
}
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
c := filepath.Join(tdir, cf.Name) c := filepath.Join(tdir, cf.Name)
mychart, err := loader.LoadDir(c) mychart, err := loader.LoadDir(c)
require.NoError(t, err, "Failed to load newly created chart %q: %s", c, err) require.NoError(t, err, "Failed to load newly created chart %q", c)
assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name())
@ -87,16 +84,13 @@ func TestCreateFrom(t *testing.T) {
ValuesfileName, ValuesfileName,
filepath.Join(TemplatesDir, "placeholder.tpl"), filepath.Join(TemplatesDir, "placeholder.tpl"),
} { } {
if _, err := os.Stat(filepath.Join(dir, f)); err != nil { _, err := os.Stat(filepath.Join(dir, f))
t.Errorf("Expected %s file: %s", f, err) require.NoErrorf(t, err, "Expected %s file", f)
}
// Check each file to make sure <CHARTNAME> has been replaced // Check each file to make sure <CHARTNAME> has been replaced
b, err := os.ReadFile(filepath.Join(dir, f)) b, err := os.ReadFile(filepath.Join(dir, f))
require.NoError(t, err, "Unable to read file %s: %s", f, err) require.NoError(t, err, "Unable to read file %s", f)
if bytes.Contains(b, []byte("<CHARTNAME>")) { assert.Falsef(t, bytes.Contains(b, []byte("<CHARTNAME>")), "File %s contains <CHARTNAME>", f)
t.Errorf("File %s contains <CHARTNAME>", f)
}
} }
} }
@ -106,9 +100,8 @@ func TestCreate_Overwrite(t *testing.T) {
var errlog bytes.Buffer var errlog bytes.Buffer
if _, err := Create("foo", tdir); err != nil { _, err := Create("foo", tdir)
t.Fatal(err) require.NoError(t, err)
}
dir := filepath.Join(tdir, "foo") dir := filepath.Join(tdir, "foo")
@ -117,9 +110,8 @@ func TestCreate_Overwrite(t *testing.T) {
// Now re-run the create // Now re-run the create
Stderr = &errlog Stderr = &errlog
if _, err := Create("foo", tdir); err != nil { _, err = Create("foo", tdir)
t.Fatal(err) require.NoError(t, err)
}
data, err := os.ReadFile(tplname) data, err := os.ReadFile(tplname)
require.NoError(t, err) require.NoError(t, err)
@ -150,8 +142,11 @@ func TestValidateChartName(t *testing.T) {
"abcdefghijklmnopqrstuvwxyz-_." + "abcdefghijklmnopqrstuvwxyz-_." +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false, "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false,
} { } {
if err := validateChartName(name); (err != nil) == shouldPass { err := validateChartName(name)
t.Errorf("test for %q failed", name) if shouldPass {
assert.NoErrorf(t, err, "Expected chart name %q to pass validation", name)
} else {
assert.Errorf(t, err, "Expected chart name %q to fail validation, but it passed", name)
} }
} }
} }

@ -369,8 +369,7 @@ func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) {
require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
err := processDependencyEnabled(c, c.Values, "") require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors")
require.NoError(t, err, "expected no errors")
require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies")
} }
@ -381,8 +380,7 @@ func TestDependentChartWithSubChartsHelmignore(t *testing.T) {
func TestDependentChartsWithSubChartsSymlink(t *testing.T) { func TestDependentChartsWithSubChartsSymlink(t *testing.T) {
joonix := filepath.Join("testdata", "joonix") joonix := filepath.Join("testdata", "joonix")
err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")) require.NoError(t, os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")), "failed to create symlink")
require.NoError(t, err, "failed to create symlink")
defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz")) defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz"))
c := loadChart(t, joonix) c := loadChart(t, joonix)
@ -394,12 +392,8 @@ func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) {
c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml") c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml")
require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors")
err := processDependencyEnabled(c, c.Values, "")
require.NoError(t, err, "expected no errors")
require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies")
require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies()))
} }

@ -47,19 +47,19 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff
fStat, err := f.Stat() fStat, err := f.Stat()
require.NoError(t, err) require.NoError(t, err)
err = tw.WriteHeader(&tar.Header{ require.NoError(t, tw.WriteHeader(&tar.Header{
Name: filepath.Join(chartName, relPath), Name: filepath.Join(chartName, relPath),
Mode: int64(fStat.Mode()), Mode: int64(fStat.Mode()),
Size: fStat.Size(), Size: fStat.Size(),
}) }))
require.NoError(t, err)
data, err := fs.ReadFile(dir, relPath) data, err := fs.ReadFile(dir, relPath)
require.NoError(t, err) require.NoError(t, err)
tw.Write(data) _, err = tw.Write(data)
require.NoError(t, err)
} }
err := fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { require.NoError(t, fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil { if walkErr != nil {
return walkErr return walkErr
} }
@ -71,13 +71,10 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff
writeFile(path) writeFile(path)
return nil return nil
}) }))
require.NoError(t, err)
err = tw.Close() require.NoError(t, tw.Close())
require.NoError(t, err) require.NoError(t, gw.Close())
err = gw.Close()
require.NoError(t, err)
return &result return &result
} }
@ -87,26 +84,24 @@ func TestExpand(t *testing.T) {
reader, err := os.Open("testdata/frobnitz-1.2.3.tgz") reader, err := os.Open("testdata/frobnitz-1.2.3.tgz")
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { assert.NoError(t, reader.Close()) })
if err := Expand(dest, reader); err != nil { require.NoError(t, Expand(dest, reader))
t.Fatal(err)
}
expectedChartPath := filepath.Join(dest, "frobnitz") expectedChartPath := filepath.Join(dest, "frobnitz")
fi, err := os.Stat(expectedChartPath) fi, err := os.Stat(expectedChartPath)
require.NoError(t, err) require.NoError(t, err)
if !fi.IsDir() { require.Truef(t, fi.IsDir(), "expected a chart directory at %s", expectedChartPath)
t.Fatalf("expected a chart directory at %s", expectedChartPath)
}
dir, err := os.Open(expectedChartPath) dir, err := os.Open(expectedChartPath)
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { assert.NoError(t, dir.Close()) })
fis, err := dir.Readdir(0) fis, err := dir.Readdir(0)
require.NoError(t, err) require.NoError(t, err)
expectLen := 11 expectLen := 11
assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) assert.Len(t, fis, expectLen, "Expected %d files", expectLen)
for _, fi := range fis { for _, fi := range fis {
expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name()))
@ -137,8 +132,7 @@ func TestExpandError(t *testing.T) {
archive := makeTestChartArchive(t, tt.chartName, tt.chartDir) archive := makeTestChartArchive(t, tt.chartName, tt.chartDir)
dest := t.TempDir() dest := t.TempDir()
err := Expand(dest, archive) assert.ErrorContains(t, Expand(dest, archive), tt.wantErr)
assert.ErrorContains(t, err, tt.wantErr)
}) })
} }
} }
@ -146,16 +140,12 @@ func TestExpandError(t *testing.T) {
func TestExpandFile(t *testing.T) { func TestExpandFile(t *testing.T) {
dest := t.TempDir() dest := t.TempDir()
if err := ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"); err != nil { require.NoError(t, ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"))
t.Fatal(err)
}
expectedChartPath := filepath.Join(dest, "frobnitz") expectedChartPath := filepath.Join(dest, "frobnitz")
fi, err := os.Stat(expectedChartPath) fi, err := os.Stat(expectedChartPath)
require.NoError(t, err) require.NoError(t, err)
if !fi.IsDir() { require.Truef(t, fi.IsDir(), "expected a chart directory at %s", expectedChartPath)
t.Fatalf("expected a chart directory at %s", expectedChartPath)
}
dir, err := os.Open(expectedChartPath) dir, err := os.Open(expectedChartPath)
require.NoError(t, err) require.NoError(t, err)
@ -164,7 +154,7 @@ func TestExpandFile(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
expectLen := 11 expectLen := 11
assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) assert.Len(t, fis, expectLen, "Expected %d files", expectLen)
for _, fi := range fis { for _, fi := range fis {
expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name()))

@ -62,20 +62,15 @@ func TestSave(t *testing.T) {
chartWithInvalidJSON := withSchema(*c, []byte("{")) chartWithInvalidJSON := withSchema(*c, []byte("{"))
where, err := Save(c, dest) where, err := Save(c, dest)
require.NoError(t, err, "Failed to save: %s", err) require.NoError(t, err, "Failed to save")
if !strings.HasPrefix(where, dest) { require.Truef(t, strings.HasPrefix(where, dest), "Expected %q to start with %q", where, dest)
t.Fatalf("Expected %q to start with %q", where, dest) require.Truef(t, strings.HasSuffix(where, ".tgz"), "Expected %q to end with .tgz", where)
}
if !strings.HasSuffix(where, ".tgz") {
t.Fatalf("Expected %q to end with .tgz", where)
}
c2, err := loader.LoadFile(where) c2, err := loader.LoadFile(where)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name()) require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name())
if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { require.Len(t, c2.Files, 1, "Files data did not match")
t.Fatal("Files data did not match") require.Equal(t, "scheherazade/shahryar.txt", c2.Files[0].Name, "Files data did not match")
}
if !bytes.Equal(c.Schema, c2.Schema) { if !bytes.Equal(c.Schema, c2.Schema) {
indentation := 4 indentation := 4
@ -83,13 +78,12 @@ func TestSave(t *testing.T) {
formattedActual := Indent(indentation, string(c2.Schema)) formattedActual := Indent(indentation, string(c2.Schema))
t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual)
} }
if _, err := Save(&chartWithInvalidJSON, dest); err == nil { _, err = Save(&chartWithInvalidJSON, dest)
t.Fatal("Invalid JSON was not caught while saving chart") require.Error(t, err, "Invalid JSON was not caught while saving chart")
}
c.Metadata.APIVersion = chart.APIVersionV3 c.Metadata.APIVersion = chart.APIVersionV3
where, err = Save(c, dest) where, err = Save(c, dest)
require.NoError(t, err, "Failed to save: %s", err) require.NoError(t, err, "Failed to save")
c2, err = loader.LoadFile(where) c2, err = loader.LoadFile(where)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, c2.Lock, "Expected v3 chart archive to contain a Chart.lock file") require.NotNil(t, c2.Lock, "Expected v3 chart archive to contain a Chart.lock file")
@ -294,16 +288,14 @@ func TestSavePreservesTimestamps(t *testing.T) {
} }
where, err := Save(c, tmp) where, err := Save(c, tmp)
require.NoError(t, err, "Failed to save: %s", err) require.NoError(t, err, "Failed to save")
allHeaders, err := retrieveAllHeadersFromTar(where) allHeaders, err := retrieveAllHeadersFromTar(where)
require.NoError(t, err, "Failed to parse tar: %v", err) require.NoError(t, err, "Failed to parse tar")
roundedTime := initialCreateTime.Round(time.Second) roundedTime := initialCreateTime.Round(time.Second)
for _, header := range allHeaders { for _, header := range allHeaders {
if !header.ModTime.Equal(roundedTime) { require.Truef(t, header.ModTime.Equal(roundedTime), "File timestamp not preserved: %v", header.ModTime)
t.Fatalf("File timestamp not preserved: %v", header.ModTime)
}
} }
} }
@ -377,8 +369,7 @@ func TestSaveDir(t *testing.T) {
pth := filepath.Join(tmp2, "tmpcharts") pth := filepath.Join(tmp2, "tmpcharts")
require.NoError(t, os.MkdirAll(filepath.Join(pth), 0o755), "Failed to create directory") require.NoError(t, os.MkdirAll(filepath.Join(pth), 0o755), "Failed to create directory")
err = SaveDir(c, pth) assert.EqualError(t, SaveDir(c, pth), "\"../ahab\" is not a valid chart name", "Did not get expected error for chart named %q", c.Name())
assert.EqualError(t, err, "\"../ahab\" is not a valid chart name", "Did not get expected error for chart named %q", c.Name())
} }
func TestRepeatableSave(t *testing.T) { func TestRepeatableSave(t *testing.T) {
@ -439,10 +430,10 @@ func TestRepeatableSave(t *testing.T) {
// create package // create package
dest := path.Join(tmp, "newdir") dest := path.Join(tmp, "newdir")
where, err := Save(test.chart, dest) where, err := Save(test.chart, dest)
require.NoError(t, err, "Failed to save: %s", err) require.NoError(t, err, "Failed to save")
// get shasum for package // get shasum for package
result, err := sha256Sum(where) result, err := sha256Sum(where)
require.NoError(t, err, "Failed to check shasum: %s", err) require.NoError(t, err, "Failed to check shasum")
// assert that the package SHA is what we wanted. // assert that the package SHA is what we wanted.
assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want) assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want)
}) })

@ -16,7 +16,11 @@ limitations under the License.
package util package util
import "testing" import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestValidateReleaseName is a regression test for ValidateName // TestValidateReleaseName is a regression test for ValidateName
// //
@ -46,12 +50,11 @@ func TestValidateReleaseName(t *testing.T) {
"a1111111111111111111111111111111111111111111111111111111111z": false, "a1111111111111111111111111111111111111111111111111111111111z": false,
} }
for input, expectPass := range names { for input, expectPass := range names {
if err := ValidateReleaseName(input); (err == nil) != expectPass { err := ValidateReleaseName(input)
st := "fail" if expectPass {
if expectPass { assert.NoErrorf(t, err, "Expected release name %q to pass validation", input)
st = "succeed" } else {
} assert.Errorf(t, err, "Expected release name %q to fail validation, but it passed", input)
t.Errorf("Expected %q to %s", input, st)
} }
} }
} }
@ -80,12 +83,11 @@ func TestValidateMetadataName(t *testing.T) {
"a1111111111111111111111111111111111111111111111111111111111z": false, "a1111111111111111111111111111111111111111111111111111111111z": false,
} }
for input, expectPass := range names { for input, expectPass := range names {
if err := ValidateMetadataName(input); (err == nil) != expectPass { err := ValidateMetadataName(input)
st := "fail" if expectPass {
if expectPass { assert.NoError(t, err, "Expected %q to succeed", input)
st = "succeed" } else {
} assert.Error(t, err, "Expected %q to fail", input)
t.Errorf("Expected %q to %s", input, st)
} }
} }
} }

@ -37,8 +37,7 @@ func TestAtomicWriteFile(t *testing.T) {
reader := bytes.NewReader([]byte(stringContent)) reader := bytes.NewReader([]byte(stringContent))
mode := os.FileMode(0o644) mode := os.FileMode(0o644)
err := AtomicWriteFile(testpath, reader, mode) require.NoError(t, AtomicWriteFile(testpath, reader, mode))
require.NoError(t, err)
got, err := os.ReadFile(testpath) got, err := os.ReadFile(testpath)
require.NoError(t, err) require.NoError(t, err)
@ -58,8 +57,7 @@ func TestAtomicWriteFile_CreateTempError(t *testing.T) {
reader := bytes.NewReader([]byte("test content")) reader := bytes.NewReader([]byte("test content"))
mode := os.FileMode(0o644) mode := os.FileMode(0o644)
err := AtomicWriteFile(invalidPath, reader, mode) assert.Error(t, AtomicWriteFile(invalidPath, reader, mode), "Expected error when CreateTemp fails")
assert.Error(t, err, "Expected error when CreateTemp fails")
} }
// TestAtomicWriteFile_EmptyContent tests with empty content // TestAtomicWriteFile_EmptyContent tests with empty content
@ -70,8 +68,7 @@ func TestAtomicWriteFile_EmptyContent(t *testing.T) {
reader := bytes.NewReader([]byte("")) reader := bytes.NewReader([]byte(""))
mode := os.FileMode(0o644) mode := os.FileMode(0o644)
err := AtomicWriteFile(testpath, reader, mode) require.NoError(t, AtomicWriteFile(testpath, reader, mode), "AtomicWriteFile error with empty content")
require.NoError(t, err, "AtomicWriteFile error with empty content")
got, err := os.ReadFile(testpath) got, err := os.ReadFile(testpath)
require.NoError(t, err) require.NoError(t, err)
@ -89,8 +86,7 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) {
reader := bytes.NewReader([]byte(largeContent)) reader := bytes.NewReader([]byte(largeContent))
mode := os.FileMode(0o644) mode := os.FileMode(0o644)
err := AtomicWriteFile(testpath, reader, mode) require.NoError(t, AtomicWriteFile(testpath, reader, mode), "AtomicWriteFile error with large content")
require.NoError(t, err, "AtomicWriteFile error with large content")
got, err := os.ReadFile(testpath) got, err := os.ReadFile(testpath)
require.NoError(t, err) require.NoError(t, err)

@ -197,9 +197,8 @@ func TestDebugCheckHandler_Handle(t *testing.T) {
} }
record := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0) record := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0)
err := handler.Handle(t.Context(), record)
require.NoError(t, err) require.NoError(t, handler.Handle(t.Context(), record))
assert.Contains(t, buf.String(), "test message") assert.Contains(t, buf.String(), "test message")
}) })
@ -214,9 +213,8 @@ func TestDebugCheckHandler_Handle(t *testing.T) {
type testKey string type testKey string
ctx := context.WithValue(t.Context(), testKey("test"), "value") ctx := context.WithValue(t.Context(), testKey("test"), "value")
record := slog.NewRecord(time.Now(), slog.LevelInfo, "context test", 0) record := slog.NewRecord(time.Now(), slog.LevelInfo, "context test", 0)
err := handler.Handle(ctx, record)
require.NoError(t, err) require.NoError(t, handler.Handle(ctx, record))
assert.Contains(t, buf.String(), "context test") assert.Contains(t, buf.String(), "context test")
}) })
} }

@ -14,7 +14,7 @@ limitations under the License.
*/ */
// Package cache provides a key generator for vcs urls. // Package cache provides a key generator for vcs urls.
package cache // import "helm.sh/helm/v4/internal/plugin/cache" package cache
import ( import (
"net/url" "net/url"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"path/filepath" "path/filepath"

@ -11,10 +11,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestPath(t *testing.T) { func TestPath(t *testing.T) {
@ -38,8 +40,6 @@ func TestPath(t *testing.T) {
t.Setenv("HELM_PLUGINS", tt.helmPluginsDir) t.Setenv("HELM_PLUGINS", tt.helmPluginsDir)
baseIns := newBase(tt.source) baseIns := newBase(tt.source)
baseInsPath := baseIns.Path() baseInsPath := baseIns.Path()
if baseInsPath != tt.expectPath { assert.Equal(t, tt.expectPath, baseInsPath, "expected name %s, got %s", tt.expectPath, baseInsPath)
t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath)
}
} }
} }

@ -14,4 +14,4 @@ limitations under the License.
*/ */
// Package installer provides an interface for installing Helm plugins. // Package installer provides an interface for installing Helm plugins.
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"bytes" "bytes"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"
@ -27,10 +27,14 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
@ -52,18 +56,10 @@ func (t *TestHTTPGetter) Get(_ string, _ ...getter.Option) (*bytes.Buffer, error
var fakePluginB64 = "H4sIAAAAAAAAA+3SQUvDMBgG4Jz7K0LwapdvSxrwJig6mCKC5xHabBaXdDSt4L+3cQ56mV42ZPg+lw+SF5LwZmXf3OV206/rMGEnIgdG6zTJaDmee4y01FOlZpqGHJGZSsb1qS401sfOtpyz0FTup9xv+2dqNep/N/IP6zdHPSMVXCh1sH8yhtGMDBUFFTL1r4iIcXnUWxzwz/sP1rsrLkbfQGTvro11E4ZlmcucRNZHu04py1OO73OVi2Vbb7td9vp7nXevtvsKRpGVjfc2VMP2xf3t4mH5tHi5mz8ub+bPk9JXIvvr5wMAAAAAAAAAAAAAAAAAAAAAnLVPqwHcXQAoAAA=" var fakePluginB64 = "H4sIAAAAAAAAA+3SQUvDMBgG4Jz7K0LwapdvSxrwJig6mCKC5xHabBaXdDSt4L+3cQ56mV42ZPg+lw+SF5LwZmXf3OV206/rMGEnIgdG6zTJaDmee4y01FOlZpqGHJGZSsb1qS401sfOtpyz0FTup9xv+2dqNep/N/IP6zdHPSMVXCh1sH8yhtGMDBUFFTL1r4iIcXnUWxzwz/sP1rsrLkbfQGTvro11E4ZlmcucRNZHu04py1OO73OVi2Vbb7td9vp7nXevtvsKRpGVjfc2VMP2xf3t4mH5tHi5mz8ub+bPk9JXIvvr5wMAAAAAAAAAAAAAAAAAAAAAnLVPqwHcXQAoAAA="
func TestStripName(t *testing.T) { func TestStripName(t *testing.T) {
if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin-0.0.1.tar.gz"), "name does not match expected value")
t.Error("name does not match expected value") assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin-0.0.1.tgz"), "name does not match expected value")
} assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin.tgz"), "name does not match expected value")
if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin.tar.gz"), "name does not match expected value")
t.Error("name does not match expected value")
}
if stripPluginName("fake-plugin.tgz") != "fake-plugin" {
t.Error("name does not match expected value")
}
if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" {
t.Error("name does not match expected value")
}
} }
func mockArchiveServer() *httptest.Server { func mockArchiveServer() *httptest.Server {
@ -71,10 +67,10 @@ func mockArchiveServer() *httptest.Server {
if !strings.HasSuffix(r.URL.Path, ".tar.gz") { if !strings.HasSuffix(r.URL.Path, ".tar.gz") {
w.Header().Add("Content-Type", "text/html") w.Header().Add("Content-Type", "text/html")
fmt.Fprintln(w, "broken") fmt.Fprintln(w, "broken")
return } else {
w.Header().Add("Content-Type", "application/gzip")
fmt.Fprintln(w, "test")
} }
w.Header().Add("Content-Type", "application/gzip")
fmt.Fprintln(w, "test")
})) }))
} }
@ -85,45 +81,29 @@ func TestHTTPInstaller(t *testing.T) {
defer srv.Close() defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins"))
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
}
i, err := NewForSource(source, "0.0.1") i, err := NewForSource(source, "0.0.1")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// ensure a HTTPInstaller was returned // ensure a HTTPInstaller was returned
httpInstaller, ok := i.(*HTTPInstaller) httpInstaller, ok := i.(*HTTPInstaller)
if !ok { require.True(t, ok, "expected a HTTPInstaller")
t.Fatal("expected a HTTPInstaller")
}
// inject fake http client responding with minimal plugin tarball // inject fake http client responding with minimal plugin tarball
mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
if err != nil { require.NoError(t, err, "Could not decode fake tgz plugin")
t.Fatalf("Could not decode fake tgz plugin: %s", err)
}
httpInstaller.getter = &TestHTTPGetter{ httpInstaller.getter = &TestHTTPGetter{
MockResponse: bytes.NewBuffer(mockTgz), MockResponse: bytes.NewBuffer(mockTgz),
} }
// install the plugin // install the plugin
if err := Install(i); err != nil { require.NoError(t, Install(i))
t.Fatal(err) require.Equal(t, helmpath.DataPath("plugins", "fake-plugin"), i.Path(), "expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
}
if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
}
// Install again to test plugin exists error // Install again to test plugin exists error
if err := Install(i); err == nil { require.EqualErrorf(t, Install(i), "plugin already exists", "expected error for plugin exists")
t.Fatal("expected error for plugin exists, got none")
} else if err.Error() != "plugin already exists" {
t.Fatalf("expected error for plugin exists, got (%v)", err)
}
} }
func TestHTTPInstallerNonExistentVersion(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) {
@ -132,20 +112,14 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
defer srv.Close() defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins"))
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
}
i, err := NewForSource(source, "0.0.2") i, err := NewForSource(source, "0.0.2")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// ensure a HTTPInstaller was returned // ensure a HTTPInstaller was returned
httpInstaller, ok := i.(*HTTPInstaller) httpInstaller, ok := i.(*HTTPInstaller)
if !ok { require.True(t, ok, "expected a HTTPInstaller")
t.Fatal("expected a HTTPInstaller")
}
// inject fake http client responding with error // inject fake http client responding with error
httpInstaller.getter = &TestHTTPGetter{ httpInstaller.getter = &TestHTTPGetter{
@ -153,9 +127,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
} }
// attempt to install the plugin // attempt to install the plugin
if err := Install(i); err == nil { require.Error(t, Install(i), "expected error from http client")
t.Fatal("expected error from http client")
}
} }
func TestHTTPInstallerUpdate(t *testing.T) { func TestHTTPInstallerUpdate(t *testing.T) {
@ -164,43 +136,29 @@ func TestHTTPInstallerUpdate(t *testing.T) {
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
ensure.HelmHome(t) ensure.HelmHome(t)
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins"))
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
}
i, err := NewForSource(source, "0.0.1") i, err := NewForSource(source, "0.0.1")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// ensure a HTTPInstaller was returned // ensure a HTTPInstaller was returned
httpInstaller, ok := i.(*HTTPInstaller) httpInstaller, ok := i.(*HTTPInstaller)
if !ok { require.True(t, ok, "expected a HTTPInstaller")
t.Fatal("expected a HTTPInstaller")
}
// inject fake http client responding with minimal plugin tarball // inject fake http client responding with minimal plugin tarball
mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
if err != nil { require.NoError(t, err, "Could not decode fake tgz plugin")
t.Fatalf("Could not decode fake tgz plugin: %s", err)
}
httpInstaller.getter = &TestHTTPGetter{ httpInstaller.getter = &TestHTTPGetter{
MockResponse: bytes.NewBuffer(mockTgz), MockResponse: bytes.NewBuffer(mockTgz),
} }
// install the plugin before updating // install the plugin before updating
if err := Install(i); err != nil { require.NoError(t, Install(i))
t.Fatal(err) require.Equal(t, helmpath.DataPath("plugins", "fake-plugin"), i.Path(), "expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
}
if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
}
// Update plugin, should fail because it is not implemented // Update plugin, should fail because it is not implemented
if err := Update(i); err == nil { require.Error(t, Update(i), "update method not implemented for http installer")
t.Fatal("update method not implemented for http installer")
}
} }
func TestExtract(t *testing.T) { func TestExtract(t *testing.T) {
@ -229,12 +187,9 @@ func TestExtract(t *testing.T) {
Mode: file.Mode, Mode: file.Mode,
Size: int64(len(file.Body)), Size: int64(len(file.Body)),
} }
if err := tw.WriteHeader(hdr); err != nil { require.NoError(t, tw.WriteHeader(hdr))
t.Fatal(err) _, err := tw.Write([]byte(file.Body))
} require.NoError(t, err)
if _, err := tw.Write([]byte(file.Body)); err != nil {
t.Fatal(err)
}
} }
// Add pax global headers. This should be ignored. // Add pax global headers. This should be ignored.
@ -242,59 +197,46 @@ func TestExtract(t *testing.T) {
// Details are in the internal Go function for the tar packaged named // Details are in the internal Go function for the tar packaged named
// allowedFormats. For a TypeXHeader it will return a message stating // allowedFormats. For a TypeXHeader it will return a message stating
// "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers" // "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"
if err := tw.WriteHeader(&tar.Header{ require.NoError(t, tw.WriteHeader(&tar.Header{
Name: "pax_global_header", Name: "pax_global_header",
Typeflag: tar.TypeXGlobalHeader, Typeflag: tar.TypeXGlobalHeader,
}); err != nil { }))
t.Fatal(err)
}
if err := tw.Close(); err != nil { require.NoError(t, tw.Close())
t.Fatal(err)
}
var buf bytes.Buffer var buf bytes.Buffer
gz := gzip.NewWriter(&buf) gz := gzip.NewWriter(&buf)
if _, err := gz.Write(tarbuf.Bytes()); err != nil { _, err := gz.Write(tarbuf.Bytes())
t.Fatal(err) require.NoError(t, err)
}
gz.Close() gz.Close()
// END tarball creation // END tarball creation
extractor, err := NewExtractor(source) extractor, err := NewExtractor(source)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if err = extractor.Extract(&buf, tempDir); err != nil { require.NoErrorf(t, extractor.Extract(&buf, tempDir), "Did not expect error")
t.Fatalf("Did not expect error but got error: %v", err)
}
// Calculate expected permissions after umask is applied // Calculate expected permissions after umask is applied
expectedPluginYAMLPerm := os.FileMode(0o600 &^ currentUmask) expectedPluginYAMLPerm := os.FileMode(0o600 &^ currentUmask)
expectedReadmePerm := os.FileMode(0o777 &^ currentUmask) expectedReadmePerm := os.FileMode(0o777 &^ currentUmask)
pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml")
if info, err := os.Stat(pluginYAMLFullPath); err != nil { info, err := os.Stat(pluginYAMLFullPath)
if errors.Is(err, fs.ErrNotExist) { if err != nil {
t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath) require.NotErrorIs(t, err, fs.ErrNotExist, "Expected %s to exist but doesn't", pluginYAMLFullPath)
}
t.Fatal(err)
} else if info.Mode().Perm() != expectedPluginYAMLPerm {
t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)",
pluginYAMLFullPath, expectedPluginYAMLPerm, info.Mode().Perm(), currentUmask)
} }
require.NoError(t, err)
require.Equalf(t, expectedPluginYAMLPerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)",
pluginYAMLFullPath, expectedPluginYAMLPerm, info.Mode().Perm(), currentUmask)
readmeFullPath := filepath.Join(tempDir, "README.md") readmeFullPath := filepath.Join(tempDir, "README.md")
if info, err := os.Stat(readmeFullPath); err != nil { info, err = os.Stat(readmeFullPath)
if errors.Is(err, fs.ErrNotExist) { if err != nil {
t.Fatalf("Expected %s to exist but doesn't", readmeFullPath) require.NotErrorIs(t, err, fs.ErrNotExist, "Expected %s to exist but doesn't", readmeFullPath)
}
t.Fatal(err)
} else if info.Mode().Perm() != expectedReadmePerm {
t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)",
readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask)
} }
require.NoError(t, err)
require.Equalf(t, expectedReadmePerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)",
readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask)
} }
func TestCleanJoin(t *testing.T) { func TestCleanJoin(t *testing.T) {
@ -313,16 +255,15 @@ func TestCleanJoin(t *testing.T) {
{"foo\\bar.txt", "/tmp/foo/bar.txt", false}, {"foo\\bar.txt", "/tmp/foo/bar.txt", false},
{"c:\\foo\\bar.txt", "", true}, {"c:\\foo\\bar.txt", "", true},
} { } {
out, err := cleanJoin("/tmp", fixture.path) t.Run(strconv.Itoa(i), func(t *testing.T) {
if err != nil { out, err := cleanJoin("/tmp", fixture.path)
if !fixture.expectError { if fixture.expectError {
t.Errorf("Test %d: Path was not cleaned: %s", i, err) require.Error(t, err, "Test %d: Path was not cleaned", i)
} else {
require.NoError(t, err)
assert.Equal(t, fixture.expect, out, "Test %d: Expected %q but got %q", i, fixture.expect, out)
} }
continue })
}
if fixture.expect != out {
t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out)
}
} }
} }
@ -336,14 +277,11 @@ func TestMediaTypeToExtension(t *testing.T) {
"application/json": false, "application/json": false,
} { } {
ext, ok := mediaTypeToExtension(mt) ext, ok := mediaTypeToExtension(mt)
if ok != shouldPass { assert.Equal(t, shouldPass, ok, "Media type %q failed test", mt)
t.Errorf("Media type %q failed test", mt) if shouldPass {
} assert.NotEmpty(t, ext, "Expected an extension but got empty string for media type %q", mt)
if shouldPass && ext == "" { } else {
t.Error("Expected an extension but got empty string") assert.Empty(t, ext, "Expected extension to be empty for unrecognized media type %q", mt)
}
if !shouldPass && ext != "" {
t.Error("Expected extension to be empty for unrecognized type")
} }
} }
} }
@ -377,55 +315,41 @@ func TestExtractWithNestedDirectories(t *testing.T) {
Mode: file.Mode, Mode: file.Mode,
Size: int64(len(file.Body)), Size: int64(len(file.Body)),
} }
if err := tw.WriteHeader(hdr); err != nil { require.NoError(t, tw.WriteHeader(hdr))
t.Fatal(err)
}
if file.TypeFlag == tar.TypeReg { if file.TypeFlag == tar.TypeReg {
if _, err := tw.Write([]byte(file.Body)); err != nil { _, err := tw.Write([]byte(file.Body))
t.Fatal(err) require.NoError(t, err)
}
} }
} }
if err := tw.Close(); err != nil { require.NoError(t, tw.Close())
t.Fatal(err)
}
var buf bytes.Buffer var buf bytes.Buffer
gz := gzip.NewWriter(&buf) gz := gzip.NewWriter(&buf)
if _, err := gz.Write(tarbuf.Bytes()); err != nil { _, err := gz.Write(tarbuf.Bytes())
t.Fatal(err) require.NoError(t, err)
}
gz.Close() gz.Close()
extractor, err := NewExtractor(source) extractor, err := NewExtractor(source)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// First extraction // First extraction
if err = extractor.Extract(&buf, tempDir); err != nil { require.NoError(t, extractor.Extract(&buf, tempDir), "First extraction failed")
t.Fatalf("First extraction failed: %v", err)
}
// Verify nested structure was created // Verify nested structure was created
nestedFile := filepath.Join(tempDir, "docs", "examples", "example1.yaml") nestedFile := filepath.Join(tempDir, "docs", "examples", "example1.yaml")
if _, err := os.Stat(nestedFile); err != nil { _, err = os.Stat(nestedFile)
t.Fatalf("Expected nested file %s to exist but got error: %v", nestedFile, err) require.NoErrorf(t, err, "Expected nested file %s to exist", nestedFile)
}
// Reset buffer for second extraction // Reset buffer for second extraction
buf.Reset() buf.Reset()
gz = gzip.NewWriter(&buf) gz = gzip.NewWriter(&buf)
if _, err := gz.Write(tarbuf.Bytes()); err != nil { _, err = gz.Write(tarbuf.Bytes())
t.Fatal(err) require.NoError(t, err)
}
gz.Close() gz.Close()
// Second extraction to same directory (should not fail) // Second extraction to same directory (should not fail)
if err = extractor.Extract(&buf, tempDir); err != nil { require.NoErrorf(t, extractor.Extract(&buf, tempDir), "Second extraction to existing directory failed")
t.Fatalf("Second extraction to existing directory failed: %v", err)
}
} }
func TestExtractWithExistingDirectory(t *testing.T) { func TestExtractWithExistingDirectory(t *testing.T) {
@ -434,15 +358,11 @@ func TestExtractWithExistingDirectory(t *testing.T) {
// Pre-create the cache directory structure // Pre-create the cache directory structure
cacheDir := filepath.Join(tempDir, "cache") cacheDir := filepath.Join(tempDir, "cache")
if err := os.MkdirAll(filepath.Join(cacheDir, "existing", "dir"), 0o755); err != nil { require.NoError(t, os.MkdirAll(filepath.Join(cacheDir, "existing", "dir"), 0o755))
t.Fatal(err)
}
// Create a file in the existing directory // Create a file in the existing directory
existingFile := filepath.Join(cacheDir, "existing", "file.txt") existingFile := filepath.Join(cacheDir, "existing", "file.txt")
if err := os.WriteFile(existingFile, []byte("existing content"), 0o644); err != nil { require.NoError(t, os.WriteFile(existingFile, []byte("existing content"), 0o644))
t.Fatal(err)
}
// Write a tarball // Write a tarball
var tarbuf bytes.Buffer var tarbuf bytes.Buffer
@ -466,47 +386,35 @@ func TestExtractWithExistingDirectory(t *testing.T) {
Mode: file.Mode, Mode: file.Mode,
Size: int64(len(file.Body)), Size: int64(len(file.Body)),
} }
if err := tw.WriteHeader(hdr); err != nil { require.NoError(t, tw.WriteHeader(hdr))
t.Fatal(err)
}
if file.TypeFlag == tar.TypeReg { if file.TypeFlag == tar.TypeReg {
if _, err := tw.Write([]byte(file.Body)); err != nil { _, err := tw.Write([]byte(file.Body))
t.Fatal(err) require.NoError(t, err)
}
} }
} }
if err := tw.Close(); err != nil { require.NoError(t, tw.Close())
t.Fatal(err)
}
var buf bytes.Buffer var buf bytes.Buffer
gz := gzip.NewWriter(&buf) gz := gzip.NewWriter(&buf)
if _, err := gz.Write(tarbuf.Bytes()); err != nil { _, err := gz.Write(tarbuf.Bytes())
t.Fatal(err) require.NoError(t, err)
}
gz.Close() gz.Close()
extractor, err := NewExtractor(source) extractor, err := NewExtractor(source)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// Extract to directory with existing content // Extract to directory with existing content
if err = extractor.Extract(&buf, cacheDir); err != nil { require.NoErrorf(t, extractor.Extract(&buf, cacheDir), "Extraction to directory with existing content failed")
t.Fatalf("Extraction to directory with existing content failed: %v", err)
}
// Verify new file was created // Verify new file was created
newFile := filepath.Join(cacheDir, "existing", "dir", "newfile.txt") newFile := filepath.Join(cacheDir, "existing", "dir", "newfile.txt")
if _, err := os.Stat(newFile); err != nil { _, err = os.Stat(newFile)
t.Fatalf("Expected new file %s to exist but got error: %v", newFile, err) require.NoErrorf(t, err, "Expected new file %s to exist but got error", newFile)
}
// Verify existing file is still there // Verify existing file is still there
if _, err := os.Stat(existingFile); err != nil { _, err = os.Stat(existingFile)
t.Fatalf("Expected existing file %s to still exist but got error: %v", existingFile, err) require.NoErrorf(t, err, "Expected existing file %s to still exist", existingFile)
}
} }
func TestExtractPluginInSubdirectory(t *testing.T) { func TestExtractPluginInSubdirectory(t *testing.T) {
@ -536,25 +444,19 @@ func TestExtractPluginInSubdirectory(t *testing.T) {
Mode: file.Mode, Mode: file.Mode,
Size: int64(len(file.Body)), Size: int64(len(file.Body)),
} }
if err := tw.WriteHeader(hdr); err != nil { require.NoError(t, tw.WriteHeader(hdr))
t.Fatal(err)
}
if file.TypeFlag == tar.TypeReg { if file.TypeFlag == tar.TypeReg {
if _, err := tw.Write([]byte(file.Body)); err != nil { _, err := tw.Write([]byte(file.Body))
t.Fatal(err) require.NoError(t, err)
}
} }
} }
if err := tw.Close(); err != nil { require.NoError(t, tw.Close())
t.Fatal(err)
}
var buf bytes.Buffer var buf bytes.Buffer
gz := gzip.NewWriter(&buf) gz := gzip.NewWriter(&buf)
if _, err := gz.Write(tarbuf.Bytes()); err != nil { _, err := gz.Write(tarbuf.Bytes())
t.Fatal(err) require.NoError(t, err)
}
gz.Close() gz.Close()
// Test the installer // Test the installer
@ -573,24 +475,16 @@ func TestExtractPluginInSubdirectory(t *testing.T) {
// Ensure the destination directory doesn't exist // Ensure the destination directory doesn't exist
// (In a real scenario, this is handled by installer.Install() wrapper) // (In a real scenario, this is handled by installer.Install() wrapper)
destPath := installer.Path() destPath := installer.Path()
if err := os.RemoveAll(destPath); err != nil { require.NoErrorf(t, os.RemoveAll(destPath), "Failed to clean destination path")
t.Fatalf("Failed to clean destination path: %v", err)
}
// Install should handle the subdirectory correctly // Install should handle the subdirectory correctly
if err := installer.Install(); err != nil { require.NoErrorf(t, installer.Install(), "Failed to install plugin with subdirectory")
t.Fatalf("Failed to install plugin with subdirectory: %v", err)
}
// The plugin should be installed from the subdirectory // The plugin should be installed from the subdirectory
// Check that detectPluginRoot found the correct location // Check that detectPluginRoot found the correct location
pluginRoot, err := detectPluginRoot(tempDir) pluginRoot, err := detectPluginRoot(tempDir)
if err != nil { require.NoError(t, err, "Failed to detect plugin root")
t.Fatalf("Failed to detect plugin root: %v", err)
}
expectedRoot := filepath.Join(tempDir, "my-plugin") expectedRoot := filepath.Join(tempDir, "my-plugin")
if pluginRoot != expectedRoot { assert.Equal(t, expectedRoot, pluginRoot, "Expected plugin root to be %s but got %s", expectedRoot, pluginRoot)
t.Errorf("Expected plugin root to be %s but got %s", expectedRoot, pluginRoot)
}
} }

@ -15,33 +15,25 @@ limitations under the License.
package installer package installer
import "testing" import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsRemoteHTTPArchive(t *testing.T) { func TestIsRemoteHTTPArchive(t *testing.T) {
srv := mockArchiveServer() srv := mockArchiveServer()
defer srv.Close() defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if isRemoteHTTPArchive("/not/a/URL") { assert.False(t, isRemoteHTTPArchive("/not/a/URL"), "Expected non-URL to return false")
t.Error("Expected non-URL to return false")
}
// URLs with valid archive extensions are considered valid archives // URLs with valid archive extensions are considered valid archives
// even if the server is unreachable (optimization to avoid unnecessary HTTP requests) // even if the server is unreachable (optimization to avoid unnecessary HTTP requests)
if !isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") { assert.True(t, isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz"), "URL with .tgz extension should be considered a valid archive")
t.Error("URL with .tgz extension should be considered a valid archive")
}
// Test with invalid extension and unreachable server // Test with invalid extension and unreachable server
if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.notanarchive") { assert.False(t, isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.notanarchive"), "Bad URL without valid extension should not succeed")
t.Error("Bad URL without valid extension should not succeed") assert.True(t, isRemoteHTTPArchive(source), "Expected %q to be a valid archive URL", source)
} assert.False(t, isRemoteHTTPArchive(source+"-not-an-extension"), "Expected media type match to fail")
if !isRemoteHTTPArchive(source) {
t.Errorf("Expected %q to be a valid archive URL", source)
}
if isRemoteHTTPArchive(source + "-not-an-extension") {
t.Error("Expected media type match to fail")
}
} }

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"bytes" "bytes"

@ -13,17 +13,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"errors"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
) )
@ -34,40 +35,23 @@ func TestLocalInstaller(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
// Make a temp dir // Make a temp dir
tdir := t.TempDir() tdir := t.TempDir()
if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0o644); err != nil { require.NoError(t, os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0o644))
t.Fatal(err)
}
source := "../testdata/plugdir/good/echo-v1" source := "../testdata/plugdir/good/echo-v1"
i, err := NewForSource(source, "") i, err := NewForSource(source, "")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
if err := Install(i); err != nil { require.NoError(t, Install(i))
t.Fatal(err)
}
if i.Path() != helmpath.DataPath("plugins", "echo-v1") { require.Equal(t, helmpath.DataPath("plugins", "echo-v1"), i.Path(), "expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
}
os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm
} }
func TestLocalInstallerNotAFolder(t *testing.T) { func TestLocalInstallerNotAFolder(t *testing.T) {
source := "../testdata/plugdir/good/echo-v1/plugin.yaml" source := "../testdata/plugdir/good/echo-v1/plugin.yaml"
i, err := NewForSource(source, "") i, err := NewForSource(source, "")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err) require.ErrorIs(t, Install(i), ErrPluginNotADirectory)
}
err = Install(i)
if err == nil {
t.Fatal("expected error")
}
if !errors.Is(err, ErrPluginNotADirectory) {
t.Fatalf("expected error to equal: %q", err)
}
} }
func TestLocalInstallerTarball(t *testing.T) { func TestLocalInstallerTarball(t *testing.T) {
@ -97,53 +81,31 @@ func TestLocalInstallerTarball(t *testing.T) {
Mode: file.Mode, Mode: file.Mode,
Size: int64(len(file.Body)), Size: int64(len(file.Body)),
} }
if err := tw.WriteHeader(hdr); err != nil { require.NoError(t, tw.WriteHeader(hdr))
t.Fatal(err) _, err := tw.Write([]byte(file.Body))
} require.NoError(t, err)
if _, err := tw.Write([]byte(file.Body)); err != nil {
t.Fatal(err)
}
} }
if err := tw.Close(); err != nil { require.NoError(t, tw.Close())
t.Fatal(err) require.NoError(t, gw.Close())
}
if err := gw.Close(); err != nil {
t.Fatal(err)
}
// Write tarball to file // Write tarball to file
if err := os.WriteFile(tarballPath, buf.Bytes(), 0o644); err != nil { require.NoError(t, os.WriteFile(tarballPath, buf.Bytes(), 0o644))
t.Fatal(err)
}
// Test installation // Test installation
i, err := NewForSource(tarballPath, "") i, err := NewForSource(tarballPath, "")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// Verify it's detected as LocalInstaller // Verify it's detected as LocalInstaller
localInstaller, ok := i.(*LocalInstaller) localInstaller, ok := i.(*LocalInstaller)
if !ok { require.True(t, ok, "expected LocalInstaller")
t.Fatal("expected LocalInstaller") require.True(t, localInstaller.isArchive, "expected isArchive to be true")
} require.NoError(t, Install(i))
if !localInstaller.isArchive {
t.Fatal("expected isArchive to be true")
}
if err := Install(i); err != nil {
t.Fatal(err)
}
expectedPath := helmpath.DataPath("plugins", "test-plugin") expectedPath := helmpath.DataPath("plugins", "test-plugin")
if i.Path() != expectedPath { require.Equal(t, expectedPath, i.Path(), "expected path %q, got %q", expectedPath, i.Path())
t.Fatalf("expected path %q, got %q", expectedPath, i.Path())
}
// Verify plugin was installed // Verify plugin was installed
if _, err := os.Stat(i.Path()); err != nil { _, err = os.Stat(i.Path())
t.Fatalf("plugin not found at %s: %v", i.Path(), err) require.NoErrorf(t, err, "plugin not found at %s", i.Path())
}
} }

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"archive/tar" "archive/tar"
@ -64,12 +64,9 @@ command: "$HELM_PLUGIN_DIR/bin/%s"
Size: int64(len(pluginYAML)), Size: int64(len(pluginYAML)),
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
} }
if err := tarWriter.WriteHeader(header); err != nil { require.NoError(t, tarWriter.WriteHeader(header))
t.Fatal(err) _, err := tarWriter.Write([]byte(pluginYAML))
} require.NoError(t, err)
if _, err := tarWriter.Write([]byte(pluginYAML)); err != nil {
t.Fatal(err)
}
// Add bin directory // Add bin directory
dirHeader := &tar.Header{ dirHeader := &tar.Header{
@ -77,9 +74,7 @@ command: "$HELM_PLUGIN_DIR/bin/%s"
Mode: 0o755, Mode: 0o755,
Typeflag: tar.TypeDir, Typeflag: tar.TypeDir,
} }
if err := tarWriter.WriteHeader(dirHeader); err != nil { require.NoError(t, tarWriter.WriteHeader(dirHeader))
t.Fatal(err)
}
// Add executable // Add executable
execContent := fmt.Sprintf("#!/bin/sh\necho '%s test plugin'", pluginName) execContent := fmt.Sprintf("#!/bin/sh\necho '%s test plugin'", pluginName)
@ -89,12 +84,9 @@ command: "$HELM_PLUGIN_DIR/bin/%s"
Size: int64(len(execContent)), Size: int64(len(execContent)),
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
} }
if err := tarWriter.WriteHeader(execHeader); err != nil { require.NoError(t, tarWriter.WriteHeader(execHeader))
t.Fatal(err) _, err = tarWriter.Write([]byte(execContent))
} require.NoError(t, err)
if _, err := tarWriter.Write([]byte(execContent)); err != nil {
t.Fatal(err)
}
tarWriter.Close() tarWriter.Close()
gzWriter.Close() gzWriter.Close()
@ -135,9 +127,7 @@ func mockOCIRegistryWithArtifactType(t *testing.T, pluginName string) (*httptest
} }
manifestData, err := json.Marshal(manifest) manifestData, err := json.Marshal(manifest)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
manifestDigest := fmt.Sprintf("sha256:%x", sha256Sum(manifestData)) manifestDigest := fmt.Sprintf("sha256:%x", sha256Sum(manifestData))
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -175,9 +165,7 @@ func mockOCIRegistryWithArtifactType(t *testing.T, pluginName string) (*httptest
// Parse server URL to get host:port format for OCI reference // Parse server URL to get host:port format for OCI reference
serverURL, err := url.Parse(server.URL) serverURL, err := url.Parse(server.URL)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
registryHost := serverURL.Host registryHost := serverURL.Host
return server, registryHost return server, registryHost
@ -240,42 +228,20 @@ func TestNewOCIInstaller(t *testing.T) {
installer, err := NewOCIInstaller(tt.source) installer, err := NewOCIInstaller(tt.source)
if tt.expectError { if tt.expectError {
if err == nil { require.Error(t, err)
t.Error("expected error but got none") } else {
} require.NoError(t, err)
return
} // Check all fields thoroughly
assert.Equal(t, tt.expectName, installer.PluginName, "expected plugin name %s, got %s", tt.expectName, installer.PluginName)
if err != nil { assert.Equal(t, tt.source, installer.Source, "expected source %s, got %s", tt.source, installer.Source)
t.Errorf("unexpected error: %v", err) assert.NotEmpty(t, installer.CacheDir, "expected non-empty cache directory")
return assert.Contains(t, installer.CacheDir, "plugins", "expected cache directory to contain 'plugins', got %s", installer.CacheDir)
} assert.NotNil(t, installer.settings, "expected settings to be initialized")
// Check all fields thoroughly // Check that Path() method works
if installer.PluginName != tt.expectName { expectedPath := helmpath.DataPath("plugins", tt.expectName)
t.Errorf("expected plugin name %s, got %s", tt.expectName, installer.PluginName) assert.Equal(t, expectedPath, installer.Path(), "expected path %s, got %s", expectedPath, installer.Path())
}
if installer.Source != tt.source {
t.Errorf("expected source %s, got %s", tt.source, installer.Source)
}
if installer.CacheDir == "" {
t.Error("expected non-empty cache directory")
}
if !strings.Contains(installer.CacheDir, "plugins") {
t.Errorf("expected cache directory to contain 'plugins', got %s", installer.CacheDir)
}
if installer.settings == nil {
t.Error("expected settings to be initialized")
}
// Check that Path() method works
expectedPath := helmpath.DataPath("plugins", tt.expectName)
if installer.Path() != expectedPath {
t.Errorf("expected path %s, got %s", expectedPath, installer.Path())
} }
}) })
} }
@ -311,9 +277,7 @@ func TestOCIInstaller_Path(t *testing.T) {
} }
path := installer.Path() path := installer.Path()
if path != tt.expectPath { assert.Equal(t, tt.expectPath, path, "expected path %s, got %s", tt.expectPath, path)
t.Errorf("expected path %s, got %s", tt.expectPath, path)
}
}) })
} }
} }
@ -331,39 +295,30 @@ func TestOCIInstaller_Install(t *testing.T) {
// Test with plain HTTP (since test server uses HTTP) // Test with plain HTTP (since test server uses HTTP)
installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true))
if err != nil { require.NoError(t, err)
t.Fatalf("Expected no error, got %v", err)
}
// The OCI installer uses helmpath.DataPath, which is isolated by ensure.HelmHome(t) // The OCI installer uses helmpath.DataPath, which is isolated by ensure.HelmHome(t)
actualPath := installer.Path() actualPath := installer.Path()
t.Logf("Installer will use path: %s", actualPath) t.Logf("Installer will use path: %s", actualPath)
// Install the plugin // Install the plugin
if err := Install(installer); err != nil { require.NoErrorf(t, Install(installer), "Expected installation to succeed")
t.Fatalf("Expected installation to succeed, got error: %v", err)
}
// Verify plugin was installed to the correct location // Verify plugin was installed to the correct location
if !isPlugin(actualPath) { assert.Truef(t, isPlugin(actualPath), "Expected plugin directory %s to contain plugin.yaml", actualPath)
t.Errorf("Expected plugin directory %s to contain plugin.yaml", actualPath)
}
// Debug: list what was actually created // Debug: list what was actually created
if entries, err := os.ReadDir(actualPath); err != nil { entries, err := os.ReadDir(actualPath)
t.Fatalf("Could not read plugin directory %s: %v", actualPath, err) require.NoError(t, err, "Could not read plugin directory %s", actualPath)
} else { t.Logf("Plugin directory %s contains:", actualPath)
t.Logf("Plugin directory %s contains:", actualPath) for _, entry := range entries {
for _, entry := range entries { t.Logf(" - %s", entry.Name())
t.Logf(" - %s", entry.Name())
}
} }
// Verify the plugin.yaml file exists and is valid // Verify the plugin.yaml file exists and is valid
pluginFile := filepath.Join(actualPath, "plugin.yaml") pluginFile := filepath.Join(actualPath, "plugin.yaml")
if _, err := os.Stat(pluginFile); err != nil { _, err = os.Stat(pluginFile)
t.Errorf("Expected plugin.yaml to exist, got error: %v", err) assert.NoErrorf(t, err, "Expected plugin.yaml to exist")
}
} }
func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) { func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) {
@ -404,11 +359,9 @@ func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) {
source := fmt.Sprintf("oci://%s/%s:latest", registryHost, tc.pluginName) source := fmt.Sprintf("oci://%s/%s:latest", registryHost, tc.pluginName)
installer, err := NewOCIInstaller(source, tc.options...) installer, err := NewOCIInstaller(source, tc.options...)
if err != nil {
if !tc.wantErr { if !tc.wantErr {
t.Fatalf("Expected no error creating installer, got %v", err) require.NoError(t, err, "Expected no error creating installer")
}
return
} }
// The installer now uses our isolated test directory // The installer now uses our isolated test directory
@ -419,7 +372,7 @@ func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) {
if tc.wantErr { if tc.wantErr {
require.Error(t, err, "Expected installation to fail, but it succeeded") require.Error(t, err, "Expected installation to fail, but it succeeded")
} else { } else {
require.NoError(t, err, "Expected installation to succeed, got error: %v", err) require.NoError(t, err, "Expected installation to succeed")
// Verify plugin was installed to the actual path // Verify plugin was installed to the actual path
assert.True(t, isPlugin(actualPath), "Expected plugin directory %s to contain plugin.yaml", actualPath) assert.True(t, isPlugin(actualPath), "Expected plugin directory %s to contain plugin.yaml", actualPath)
} }
@ -437,27 +390,16 @@ func TestOCIInstaller_Install_AlreadyExists(t *testing.T) {
source := fmt.Sprintf("oci://%s/%s:latest", registryHost, pluginName) source := fmt.Sprintf("oci://%s/%s:latest", registryHost, pluginName)
installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true))
if err != nil { require.NoError(t, err)
t.Fatalf("Expected no error, got %v", err)
}
// First install should succeed // First install should succeed
if err := Install(installer); err != nil { require.NoErrorf(t, Install(installer), "Expected first installation to succeed")
t.Fatalf("Expected first installation to succeed, got error: %v", err)
}
// Verify plugin was installed // Verify plugin was installed
if !isPlugin(installer.Path()) { assert.Truef(t, isPlugin(installer.Path()), "Expected plugin directory %s to contain plugin.yaml", installer.Path())
t.Errorf("Expected plugin directory %s to contain plugin.yaml", installer.Path())
}
// Second install should fail with "plugin already exists" // Second install should fail with "plugin already exists"
err = Install(installer) assert.ErrorContains(t, Install(installer), "plugin already exists")
if err == nil {
t.Error("Expected error when installing plugin that already exists")
} else if !strings.Contains(err.Error(), "plugin already exists") {
t.Errorf("Expected 'plugin already exists' error, got: %v", err)
}
} }
func TestOCIInstaller_Update(t *testing.T) { func TestOCIInstaller_Update(t *testing.T) {
@ -470,38 +412,23 @@ func TestOCIInstaller_Update(t *testing.T) {
source := fmt.Sprintf("oci://%s/%s:latest", registryHost, pluginName) source := fmt.Sprintf("oci://%s/%s:latest", registryHost, pluginName)
installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true))
if err != nil { require.NoError(t, err)
t.Fatalf("Expected no error, got %v", err)
}
// Test update when plugin does not exist - should fail // Test update when plugin does not exist - should fail
err = Update(installer) require.ErrorContains(t, Update(installer), "plugin does not exist")
if err == nil {
t.Error("Expected error when updating plugin that does not exist")
} else if !strings.Contains(err.Error(), "plugin does not exist") {
t.Errorf("Expected 'plugin does not exist' error, got: %v", err)
}
// Install plugin first // Install plugin first
if err := Install(installer); err != nil { require.NoErrorf(t, Install(installer), "Expected installation to succeed")
t.Fatalf("Expected installation to succeed, got error: %v", err)
}
// Verify plugin was installed // Verify plugin was installed
if !isPlugin(installer.Path()) { assert.Truef(t, isPlugin(installer.Path()), "Expected plugin directory %s to contain plugin.yaml", installer.Path())
t.Errorf("Expected plugin directory %s to contain plugin.yaml", installer.Path())
}
// Test update when plugin exists - should succeed // Test update when plugin exists - should succeed
// For OCI, Update() removes old version and reinstalls // For OCI, Update() removes old version and reinstalls
if err := Update(installer); err != nil { require.NoErrorf(t, Update(installer), "Expected update to succeed")
t.Errorf("Expected update to succeed, got error: %v", err)
}
// Verify plugin is still installed after update // Verify plugin is still installed after update
if !isPlugin(installer.Path()) { assert.Truef(t, isPlugin(installer.Path()), "Expected plugin directory %s to contain plugin.yaml after update", installer.Path())
t.Errorf("Expected plugin directory %s to contain plugin.yaml after update", installer.Path())
}
} }
func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) { func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) {
@ -513,35 +440,26 @@ func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) {
pluginData := createTestPluginTarGz(t, pluginName) pluginData := createTestPluginTarGz(t, pluginName)
// Test extraction // Test extraction
err := extractTarGz(bytes.NewReader(pluginData), tempDir) require.NoError(t, extractTarGz(bytes.NewReader(pluginData), tempDir), "Failed to extract plugin")
if err != nil {
t.Fatalf("Failed to extract plugin: %v", err)
}
// Verify plugin.yaml exists // Verify plugin.yaml exists
pluginYAMLPath := filepath.Join(tempDir, "plugin.yaml") pluginYAMLPath := filepath.Join(tempDir, "plugin.yaml")
if _, err := os.Stat(pluginYAMLPath); os.IsNotExist(err) { _, err := os.Stat(pluginYAMLPath)
t.Error("plugin.yaml not found after extraction") assert.False(t, os.IsNotExist(err), "plugin.yaml not found after extraction")
}
// Verify bin directory exists // Verify bin directory exists
binPath := filepath.Join(tempDir, "bin") binPath := filepath.Join(tempDir, "bin")
if _, err := os.Stat(binPath); os.IsNotExist(err) { _, err = os.Stat(binPath)
t.Error("bin directory not found after extraction") assert.False(t, os.IsNotExist(err), "bin directory not found after extraction")
}
// Verify executable exists and has correct permissions // Verify executable exists and has correct permissions
execPath := filepath.Join(tempDir, "bin", pluginName) execPath := filepath.Join(tempDir, "bin", pluginName)
if info, err := os.Stat(execPath); err != nil { info, err := os.Stat(execPath)
t.Errorf("executable not found: %v", err) require.NoError(t, err, "executable not found")
} else if info.Mode()&0o111 == 0 { assert.NotEqual(t, 0, info.Mode()&0o111, "file is not executable")
t.Error("file is not executable")
}
// Verify this would be recognized as a plugin // Verify this would be recognized as a plugin
if !isPlugin(tempDir) { assert.True(t, isPlugin(tempDir), "extracted directory is not a valid plugin")
t.Error("extracted directory is not a valid plugin")
}
} }
func TestExtractTarGz(t *testing.T) { func TestExtractTarGz(t *testing.T) {
@ -561,13 +479,10 @@ func TestExtractTarGz(t *testing.T) {
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
} }
if err := tarWriter.WriteHeader(header); err != nil { require.NoError(t, tarWriter.WriteHeader(header))
t.Fatal(err)
}
if _, err := tarWriter.Write([]byte(testContent)); err != nil { _, err := tarWriter.Write([]byte(testContent))
t.Fatal(err) require.NoError(t, err)
}
// Add a test directory // Add a test directory
dirHeader := &tar.Header{ dirHeader := &tar.Header{
@ -576,35 +491,25 @@ func TestExtractTarGz(t *testing.T) {
Typeflag: tar.TypeDir, Typeflag: tar.TypeDir,
} }
if err := tarWriter.WriteHeader(dirHeader); err != nil { require.NoError(t, tarWriter.WriteHeader(dirHeader))
t.Fatal(err)
}
tarWriter.Close() tarWriter.Close()
gzWriter.Close() gzWriter.Close()
// Test extraction // Test extraction
err := extractTarGz(bytes.NewReader(buf.Bytes()), tempDir) require.NoError(t, extractTarGz(bytes.NewReader(buf.Bytes()), tempDir), "extractTarGz failed")
if err != nil {
t.Errorf("extractTarGz failed: %v", err)
}
// Verify extracted file // Verify extracted file
extractedFile := filepath.Join(tempDir, "test-file.txt") extractedFile := filepath.Join(tempDir, "test-file.txt")
content, err := os.ReadFile(extractedFile) content, err := os.ReadFile(extractedFile)
if err != nil { require.NoError(t, err, "failed to read extracted file")
t.Errorf("failed to read extracted file: %v", err)
}
if string(content) != testContent { assert.Equal(t, testContent, string(content), "expected content %s, got %s", testContent, string(content))
t.Errorf("expected content %s, got %s", testContent, string(content))
}
// Verify extracted directory // Verify extracted directory
extractedDir := filepath.Join(tempDir, "test-dir") extractedDir := filepath.Join(tempDir, "test-dir")
if _, err := os.Stat(extractedDir); os.IsNotExist(err) { _, err = os.Stat(extractedDir)
t.Errorf("extracted directory does not exist: %s", extractedDir) assert.Falsef(t, os.IsNotExist(err), "extracted directory does not exist: %s", extractedDir)
}
} }
func TestExtractTarGz_InvalidGzip(t *testing.T) { func TestExtractTarGz_InvalidGzip(t *testing.T) {
@ -612,10 +517,7 @@ func TestExtractTarGz_InvalidGzip(t *testing.T) {
// Test with invalid gzip data // Test with invalid gzip data
invalidGzipData := []byte("not gzip data") invalidGzipData := []byte("not gzip data")
err := extractTarGz(bytes.NewReader(invalidGzipData), tempDir) assert.Error(t, extractTarGz(bytes.NewReader(invalidGzipData), tempDir), "expected error for invalid gzip data")
if err == nil {
t.Error("expected error for invalid gzip data")
}
} }
func TestExtractTar_UnknownFileType(t *testing.T) { func TestExtractTar_UnknownFileType(t *testing.T) {
@ -634,13 +536,10 @@ func TestExtractTar_UnknownFileType(t *testing.T) {
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
} }
if err := tarWriter.WriteHeader(header); err != nil { require.NoError(t, tarWriter.WriteHeader(header))
t.Fatal(err)
}
if _, err := tarWriter.Write([]byte(testContent)); err != nil { _, err := tarWriter.Write([]byte(testContent))
t.Fatal(err) require.NoError(t, err)
}
// Test unknown file type // Test unknown file type
unknownHeader := &tar.Header{ unknownHeader := &tar.Header{
@ -649,21 +548,12 @@ func TestExtractTar_UnknownFileType(t *testing.T) {
Typeflag: tar.TypeSymlink, // Use a type that's not handled Typeflag: tar.TypeSymlink, // Use a type that's not handled
} }
if err := tarWriter.WriteHeader(unknownHeader); err != nil { require.NoError(t, tarWriter.WriteHeader(unknownHeader))
t.Fatal(err)
}
tarWriter.Close() tarWriter.Close()
// Test extraction - should fail due to unknown type // Test extraction - should fail due to unknown type
err := extractTar(bytes.NewReader(buf.Bytes()), tempDir) assert.ErrorContains(t, extractTar(bytes.NewReader(buf.Bytes()), tempDir), "unknown type")
if err == nil {
t.Error("expected error for unknown tar file type")
}
if !strings.Contains(err.Error(), "unknown type") {
t.Errorf("expected 'unknown type' error, got: %v", err)
}
} }
func TestExtractTar_SuccessfulExtraction(t *testing.T) { func TestExtractTar_SuccessfulExtraction(t *testing.T) {
@ -686,32 +576,22 @@ func TestExtractTar_SuccessfulExtraction(t *testing.T) {
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
} }
if err := tarWriter.WriteHeader(header); err != nil { require.NoError(t, tarWriter.WriteHeader(header))
t.Fatal(err)
}
if _, err := tarWriter.Write([]byte(testContent)); err != nil { _, err := tarWriter.Write([]byte(testContent))
t.Fatal(err) require.NoError(t, err)
}
tarWriter.Close() tarWriter.Close()
// Test extraction // Test extraction
err := extractTar(bytes.NewReader(buf.Bytes()), tempDir) require.NoError(t, extractTar(bytes.NewReader(buf.Bytes()), tempDir), "extractTar failed")
if err != nil {
t.Errorf("extractTar failed: %v", err)
}
// Verify the regular file was extracted // Verify the regular file was extracted
extractedFile := filepath.Join(tempDir, "test-file.txt") extractedFile := filepath.Join(tempDir, "test-file.txt")
content, err := os.ReadFile(extractedFile) content, err := os.ReadFile(extractedFile)
if err != nil { require.NoError(t, err, "failed to read extracted file")
t.Errorf("failed to read extracted file: %v", err)
}
if string(content) != testContent { assert.Equal(t, testContent, string(content), "expected content %s, got %s", testContent, string(content))
t.Errorf("expected content %s, got %s", testContent, string(content))
}
} }
func TestOCIInstaller_Install_PlainHTTPOption(t *testing.T) { func TestOCIInstaller_Install_PlainHTTPOption(t *testing.T) {
@ -720,41 +600,25 @@ func TestOCIInstaller_Install_PlainHTTPOption(t *testing.T) {
// Test with PlainHTTP=false (default) // Test with PlainHTTP=false (default)
installer1, err := NewOCIInstaller(source) installer1, err := NewOCIInstaller(source)
if err != nil { require.NoError(t, err, "failed to create installer")
t.Fatalf("failed to create installer: %v", err) assert.NotNil(t, installer1.getter, "getter should be initialized")
}
if installer1.getter == nil {
t.Error("getter should be initialized")
}
// Test with PlainHTTP=true // Test with PlainHTTP=true
installer2, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) installer2, err := NewOCIInstaller(source, getter.WithPlainHTTP(true))
if err != nil { require.NoError(t, err, "failed to create installer with PlainHTTP=true")
t.Fatalf("failed to create installer with PlainHTTP=true: %v", err) assert.NotNil(t, installer2.getter, "getter should be initialized with PlainHTTP=true")
}
if installer2.getter == nil {
t.Error("getter should be initialized with PlainHTTP=true")
}
// Both installers should have the same basic properties // Both installers should have the same basic properties
if installer1.PluginName != installer2.PluginName { assert.Equal(t, installer2.PluginName, installer1.PluginName, "plugin names should match")
t.Error("plugin names should match") assert.Equal(t, installer2.Source, installer1.Source, "sources should match")
}
if installer1.Source != installer2.Source {
t.Error("sources should match")
}
// Test with multiple options // Test with multiple options
installer3, err := NewOCIInstaller(source, installer3, err := NewOCIInstaller(source,
getter.WithPlainHTTP(true), getter.WithPlainHTTP(true),
getter.WithBasicAuth("user", "pass"), getter.WithBasicAuth("user", "pass"),
) )
if err != nil { require.NoError(t, err, "failed to create installer with multiple options")
t.Fatalf("failed to create installer with multiple options: %v", err) assert.NotNil(t, installer3.getter, "getter should be initialized with multiple options")
}
if installer3.getter == nil {
t.Error("getter should be initialized with multiple options")
}
} }
func TestOCIInstaller_Install_ValidationErrors(t *testing.T) { func TestOCIInstaller_Install_ValidationErrors(t *testing.T) {
@ -789,12 +653,8 @@ func TestOCIInstaller_Install_ValidationErrors(t *testing.T) {
// Test the gzip validation logic that's used in the Install method // Test the gzip validation logic that's used in the Install method
if len(tt.layerData) < 2 || tt.layerData[0] != 0x1f || tt.layerData[1] != 0x8b { if len(tt.layerData) < 2 || tt.layerData[0] != 0x1f || tt.layerData[1] != 0x8b {
// This matches the validation in the Install method // This matches the validation in the Install method
if !tt.expectError { assert.True(t, tt.expectError, "expected valid gzip data")
t.Error("expected valid gzip data") assert.Contains(t, tt.errorMsg, "is not a gzip compressed archive")
}
if !strings.Contains(tt.errorMsg, "is not a gzip compressed archive") {
t.Error("expected error message to contain 'is not a gzip compressed archive'")
}
} }
}) })
} }

@ -19,6 +19,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDetectPluginRoot(t *testing.T) { func TestDetectPluginRoot(t *testing.T) {
@ -73,26 +76,18 @@ func TestDetectPluginRoot(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
if err := tt.setup(dir); err != nil { require.NoErrorf(t, tt.setup(dir), "Setup failed")
t.Fatalf("Setup failed: %v", err)
}
root, err := detectPluginRoot(dir) root, err := detectPluginRoot(dir)
if tt.expectError { if tt.expectError {
if err == nil { assert.Error(t, err)
t.Error("Expected error but got none")
}
} else { } else {
if err != nil { require.NoError(t, err)
t.Errorf("Unexpected error: %v", err)
}
expectedPath := dir expectedPath := dir
if tt.expectRoot != "." { if tt.expectRoot != "." {
expectedPath = filepath.Join(dir, tt.expectRoot) expectedPath = filepath.Join(dir, tt.expectRoot)
} }
if root != expectedPath { assert.Equal(t, expectedPath, root, "Expected root %s but got %s", expectedPath, root)
t.Errorf("Expected root %s but got %s", expectedPath, root)
}
} }
}) })
} }
@ -145,20 +140,14 @@ description: test`
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
if err := tt.setup(dir); err != nil { require.NoErrorf(t, tt.setup(dir), "Setup failed")
t.Fatalf("Setup failed: %v", err)
}
pluginRoot := filepath.Join(dir, tt.pluginRoot) pluginRoot := filepath.Join(dir, tt.pluginRoot)
err := validatePluginName(pluginRoot, tt.expectedName) err := validatePluginName(pluginRoot, tt.expectedName)
if tt.expectError { if tt.expectError {
if err == nil { assert.Error(t, err)
t.Error("Expected error but got none")
}
} else { } else {
if err != nil { assert.NoError(t, err)
t.Errorf("Unexpected error: %v", err)
}
} }
}) })
} }

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"errors" "errors"

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package installer // import "helm.sh/helm/v4/internal/plugin/installer" package installer
import ( import (
"fmt" "fmt"
@ -23,6 +23,7 @@ import (
"testing" "testing"
"github.com/Masterminds/vcs" "github.com/Masterminds/vcs"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
@ -52,9 +53,7 @@ func (r *testRepo) UpdateVersion(version string) error {
func TestVCSInstaller(t *testing.T) { func TestVCSInstaller(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins"))
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
}
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo-v1") testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo-v1")
@ -64,43 +63,27 @@ func TestVCSInstaller(t *testing.T) {
} }
i, err := NewForSource(source, "~0.1.0") i, err := NewForSource(source, "~0.1.0")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// ensure a VCSInstaller was returned // ensure a VCSInstaller was returned
vcsInstaller, ok := i.(*VCSInstaller) vcsInstaller, ok := i.(*VCSInstaller)
if !ok { require.True(t, ok, "expected a VCSInstaller")
t.Fatal("expected a VCSInstaller")
}
// set the testRepo in the VCSInstaller // set the testRepo in the VCSInstaller
vcsInstaller.Repo = repo vcsInstaller.Repo = repo
if err := Install(i); err != nil { require.NoError(t, Install(i))
t.Fatal(err) require.Equal(t, "0.1.1", repo.current, "expected version '0.1.1', got %q", repo.current)
}
if repo.current != "0.1.1" {
t.Fatalf("expected version '0.1.1', got %q", repo.current)
}
expectedPath := helmpath.DataPath("plugins", "helm-env") expectedPath := helmpath.DataPath("plugins", "helm-env")
if i.Path() != expectedPath { require.Equal(t, expectedPath, i.Path(), "expected path %q, got %q", expectedPath, i.Path())
t.Fatalf("expected path %q, got %q", expectedPath, i.Path())
}
// Install again to test plugin exists error // Install again to test plugin exists error
if err := Install(i); err == nil { require.EqualErrorf(t, Install(i), "plugin already exists", "expected error for plugin exists")
t.Fatal("expected error for plugin exists, got none")
} else if err.Error() != "plugin already exists" {
t.Fatalf("expected error for plugin exists, got (%v)", err)
}
// Testing FindSource method, expect error because plugin code is not a cloned repository // Testing FindSource method, expect error because plugin code is not a cloned repository
if _, err := FindSource(i.Path()); err == nil { _, err = FindSource(i.Path())
t.Fatal("expected error for inability to find plugin source, got none") require.Error(t, err, "expected error for inability to find plugin source, got none")
} else if err.Error() != "cannot get information about plugin source" { require.EqualErrorf(t, err, "cannot get information about plugin source", "expected error for inability to find plugin source")
t.Fatalf("expected error for inability to find plugin source, got (%v)", err)
}
} }
func TestVCSInstallerNonExistentVersion(t *testing.T) { func TestVCSInstallerNonExistentVersion(t *testing.T) {
@ -110,22 +93,17 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
version := "0.2.0" version := "0.2.0"
i, err := NewForSource(source, version) i, err := NewForSource(source, version)
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// ensure a VCSInstaller was returned // ensure a VCSInstaller was returned
if _, ok := i.(*VCSInstaller); !ok { require.IsType(t, &VCSInstaller{}, i, "expected a VCSInstaller")
t.Fatal("expected a VCSInstaller")
}
if err := Install(i); err == nil { err = Install(i)
t.Fatal("expected error for version does not exists, got none") require.Error(t, err, "expected error for version does not exists, got none")
} else if strings.Contains(err.Error(), "Could not resolve host: github.com") { if strings.Contains(err.Error(), "Could not resolve host: github.com") {
t.Skip("Unable to run test without Internet access") t.Skip("Unable to run test without Internet access")
} else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) {
t.Fatalf("expected error for version does not exists, got (%v)", err)
} }
require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists")
} }
func TestVCSInstallerUpdate(t *testing.T) { func TestVCSInstallerUpdate(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
@ -133,56 +111,34 @@ func TestVCSInstallerUpdate(t *testing.T) {
source := "https://github.com/adamreese/helm-env" source := "https://github.com/adamreese/helm-env"
i, err := NewForSource(source, "") i, err := NewForSource(source, "")
if err != nil { require.NoError(t, err)
t.Fatalf("unexpected error: %s", err)
}
// ensure a VCSInstaller was returned // ensure a VCSInstaller was returned
if _, ok := i.(*VCSInstaller); !ok { require.IsType(t, &VCSInstaller{}, i, "expected a VCSInstaller")
t.Fatal("expected a VCSInstaller")
}
if err := Update(i); err == nil { require.EqualErrorf(t, Update(i), "plugin does not exist", "expected error for plugin does not exist")
t.Fatal("expected error for plugin does not exist, got none")
} else if err.Error() != "plugin does not exist" {
t.Fatalf("expected error for plugin does not exist, got (%v)", err)
}
// Install plugin before update // Install plugin before update
if err := Install(i); err != nil { err = Install(i)
if strings.Contains(err.Error(), "Could not resolve host: github.com") { if err != nil && strings.Contains(err.Error(), "Could not resolve host: github.com") {
t.Skip("Unable to run test without Internet access") t.Skip("Unable to run test without Internet access")
} else {
t.Fatal(err)
}
} }
require.NoError(t, err)
// Test FindSource method for positive result // Test FindSource method for positive result
pluginInfo, err := FindSource(i.Path()) pluginInfo, err := FindSource(i.Path())
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
vcsInstaller := pluginInfo.(*VCSInstaller) vcsInstaller := pluginInfo.(*VCSInstaller)
repoRemote := vcsInstaller.Repo.Remote() repoRemote := vcsInstaller.Repo.Remote()
if repoRemote != source { require.Equal(t, source, repoRemote, "invalid source found, expected %q got %q", source, repoRemote)
t.Fatalf("invalid source found, expected %q got %q", source, repoRemote)
}
// Update plugin // Update plugin
if err := Update(i); err != nil { require.NoError(t, Update(i))
t.Fatal(err)
}
// Test update failure // Test update failure
if err := os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")); err != nil { require.NoError(t, os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")))
t.Fatal(err)
}
// Testing update for error // Testing update for error
if err := Update(vcsInstaller); err == nil { require.EqualErrorf(t, Update(vcsInstaller), "plugin repo was modified", "expected error for plugin modified")
t.Fatal("expected error for plugin modified, got none")
} else if err.Error() != "plugin repo was modified" {
t.Fatalf("expected error for plugin modified, got (%v)", err)
}
} }

@ -23,6 +23,9 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/internal/plugin"
"helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
) )
@ -37,29 +40,20 @@ func TestInstallWithOptions_VerifyMissingProvenance(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification enabled should fail when .prov is missing // Install with verification enabled should fail when .prov is missing
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"})
// Should fail with a missing provenance error // Should fail with a missing provenance error
if err == nil { require.Error(t, err, "Expected installation to fail when .prov file is missing and verification is enabled")
t.Fatal("Expected installation to fail when .prov file is missing and verification is enabled") require.ErrorContains(t, err, "no provenance file")
} assert.Nil(t, result, "Expected nil verification result when .prov file is missing, got: %+v", result)
if !strings.Contains(err.Error(), "no provenance file") {
t.Errorf("Expected 'no provenance file' in error message, got: %v", err)
}
if result != nil {
t.Errorf("Expected nil verification result when .prov file is missing, got: %+v", result)
}
// Plugin should NOT be installed // Plugin should NOT be installed
if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { _, err = os.Stat(installer.Path())
t.Error("Plugin should not be installed when verification fails due to missing .prov") assert.True(t, os.IsNotExist(err), "Plugin should not be installed when verification fails due to missing .prov")
}
} }
func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) { func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) {
@ -72,25 +66,18 @@ func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification explicitly disabled should succeed without .prov // Install with verification explicitly disabled should succeed without .prov
result, err := InstallWithOptions(installer, Options{Verify: false}) result, err := InstallWithOptions(installer, Options{Verify: false})
if err != nil { require.NoError(t, err, "Expected installation to succeed with --verify=false")
t.Fatalf("Expected installation to succeed with --verify=false, got error: %v", err) assert.Nil(t, result, "Expected nil verification result when verification is disabled, got: %+v", result)
}
if result != nil {
t.Errorf("Expected nil verification result when verification is disabled, got: %+v", result)
}
// Plugin should be installed // Plugin should be installed
if _, err := os.Stat(installer.Path()); os.IsNotExist(err) { _, err = os.Stat(installer.Path())
t.Errorf("Plugin should be installed at %s", installer.Path()) assert.Falsef(t, os.IsNotExist(err), "Plugin should be installed at %s", installer.Path())
}
} }
func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) { func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) {
@ -110,9 +97,7 @@ func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification enabled // Install with verification enabled
@ -120,20 +105,13 @@ func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) {
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring})
// Should fail due to invalid signature (empty keyring) but we test that it gets past the hash check // Should fail due to invalid signature (empty keyring) but we test that it gets past the hash check
if err == nil { require.Error(t, err, "Expected installation to fail with empty keyring")
t.Fatal("Expected installation to fail with empty keyring") require.ErrorContains(t, err, "plugin verification failed")
} assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result)
if !strings.Contains(err.Error(), "plugin verification failed") {
t.Errorf("Expected plugin verification failed error, got: %v", err)
}
if result != nil {
t.Errorf("Expected nil verification result when verification fails, got: %+v", result)
}
// Plugin should not be installed due to verification failure // Plugin should not be installed due to verification failure
if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { _, err = os.Stat(installer.Path())
t.Error("Plugin should not be installed when verification fails") assert.True(t, os.IsNotExist(err), "Plugin should not be installed when verification fails")
}
} }
func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) { func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) {
@ -154,32 +132,23 @@ func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification enabled (should fail) // Install with verification enabled (should fail)
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring})
// Should fail with verification error // Should fail with verification error
if err == nil { require.Error(t, err, "Expected installation with invalid .prov file to fail")
t.Fatal("Expected installation with invalid .prov file to fail") assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result)
}
if result != nil {
t.Errorf("Expected nil verification result when verification fails, got: %+v", result)
}
// Should contain verification failure message // Should contain verification failure message
expectedError := "plugin verification failed" expectedError := "plugin verification failed"
if !strings.Contains(err.Error(), expectedError) { require.ErrorContains(t, err, expectedError)
t.Errorf("Expected error message '%s', got: %s", expectedError, err.Error())
}
// Plugin should not be installed // Plugin should not be installed
if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { _, err = os.Stat(installer.Path())
t.Error("Plugin should not be installed when verification fails") assert.True(t, os.IsNotExist(err), "Plugin should not be installed when verification fails")
}
} }
func TestInstallWithOptions_NoVerifyRequested(t *testing.T) { func TestInstallWithOptions_NoVerifyRequested(t *testing.T) {
@ -192,26 +161,19 @@ func TestInstallWithOptions_NoVerifyRequested(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install without verification (should succeed without any verification) // Install without verification (should succeed without any verification)
result, err := InstallWithOptions(installer, Options{Verify: false}) result, err := InstallWithOptions(installer, Options{Verify: false})
// Should succeed with no verification // Should succeed with no verification
if err != nil { require.NoError(t, err, "Expected installation without verification to succeed")
t.Fatalf("Expected installation without verification to succeed, got error: %v", err) assert.Nil(t, result, "Expected nil verification result when verification is disabled, got: %+v", result)
}
if result != nil {
t.Errorf("Expected nil verification result when verification is disabled, got: %+v", result)
}
// Plugin should be installed // Plugin should be installed
if _, err := os.Stat(installer.Path()); os.IsNotExist(err) { _, err = os.Stat(installer.Path())
t.Errorf("Plugin should be installed at %s", installer.Path()) assert.Falsef(t, os.IsNotExist(err), "Plugin should be installed at %s", installer.Path())
}
} }
func TestInstallWithOptions_VerifyDirectoryNotSupported(t *testing.T) { func TestInstallWithOptions_VerifyDirectoryNotSupported(t *testing.T) {
@ -222,24 +184,16 @@ func TestInstallWithOptions_VerifyDirectoryNotSupported(t *testing.T) {
// Create local installer for directory // Create local installer for directory
installer, err := NewLocalInstaller(pluginDir) installer, err := NewLocalInstaller(pluginDir)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification should fail (directories don't support verification) // Install with verification should fail (directories don't support verification)
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"})
// Should fail with verification not supported error // Should fail with verification not supported error
if err == nil { require.Error(t, err, "Expected installation to fail with verification not supported error")
t.Fatal("Expected installation to fail with verification not supported error") require.ErrorContains(t, err, "--verify is only supported for plugin tarballs")
} assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result)
if !strings.Contains(err.Error(), "--verify is only supported for plugin tarballs") {
t.Errorf("Expected verification not supported error, got: %v", err)
}
if result != nil {
t.Errorf("Expected nil verification result when verification fails, got: %+v", result)
}
} }
func TestInstallWithOptions_VerifyMismatchedProvenance(t *testing.T) { func TestInstallWithOptions_VerifyMismatchedProvenance(t *testing.T) {
@ -261,24 +215,16 @@ func TestInstallWithOptions_VerifyMismatchedProvenance(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification should fail due to hash mismatch // Install with verification should fail due to hash mismatch
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring})
// Should fail with verification error // Should fail with verification error
if err == nil { require.Error(t, err, "Expected installation to fail with hash mismatch")
t.Fatal("Expected installation to fail with hash mismatch") require.ErrorContains(t, err, "plugin verification failed")
} assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result)
if !strings.Contains(err.Error(), "plugin verification failed") {
t.Errorf("Expected plugin verification failed error, got: %v", err)
}
if result != nil {
t.Errorf("Expected nil verification result when verification fails, got: %+v", result)
}
} }
func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) { func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) {
@ -291,9 +237,7 @@ func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) {
// Create a .prov file but make it inaccessible (simulate permission error) // Create a .prov file but make it inaccessible (simulate permission error)
provFile := pluginTgz + ".prov" provFile := pluginTgz + ".prov"
if err := os.WriteFile(provFile, []byte("test"), 0o000); err != nil { require.NoErrorf(t, os.WriteFile(provFile, []byte("test"), 0o000), "Failed to create inaccessible provenance file")
t.Fatalf("Failed to create inaccessible provenance file: %v", err)
}
defer os.Remove(provFile) defer os.Remove(provFile)
// Create keyring // Create keyring
@ -302,27 +246,21 @@ func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) {
// Create local installer // Create local installer
installer, err := NewLocalInstaller(pluginTgz) installer, err := NewLocalInstaller(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to create installer")
t.Fatalf("Failed to create installer: %v", err)
}
defer os.RemoveAll(installer.Path()) defer os.RemoveAll(installer.Path())
// Install with verification should fail due to access error // Install with verification should fail due to access error
result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring})
// Should fail with access error (either at stat level or during verification) // Should fail with access error (either at stat level or during verification)
if err == nil { require.Error(t, err, "Expected installation to fail with provenance file access error")
t.Fatal("Expected installation to fail with provenance file access error")
}
// The error could be either "failed to access provenance file" or "plugin verification failed" // The error could be either "failed to access provenance file" or "plugin verification failed"
// depending on when the permission error occurs // depending on when the permission error occurs
if !strings.Contains(err.Error(), "failed to access provenance file") && if !strings.Contains(err.Error(), "failed to access provenance file") &&
!strings.Contains(err.Error(), "plugin verification failed") { !strings.Contains(err.Error(), "plugin verification failed") {
t.Errorf("Expected provenance file access or verification error, got: %v", err) t.Errorf("Expected provenance file access or verification error, got: %v", err)
} }
if result != nil { assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result)
t.Errorf("Expected nil verification result when verification fails, got: %+v", result)
}
} }
// Helper functions for test setup // Helper functions for test setup
@ -333,9 +271,7 @@ func createTestPluginDir(t *testing.T) string {
// Create temporary directory with plugin structure // Create temporary directory with plugin structure
tmpDir := t.TempDir() tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "test-plugin") pluginDir := filepath.Join(tmpDir, "test-plugin")
if err := os.MkdirAll(pluginDir, 0o755); err != nil { require.NoErrorf(t, os.MkdirAll(pluginDir, 0o755), "Failed to create plugin directory")
t.Fatalf("Failed to create plugin directory: %v", err)
}
// Create plugin.yaml using the standardized v1 format // Create plugin.yaml using the standardized v1 format
pluginYaml := `apiVersion: v1 pluginYaml := `apiVersion: v1
@ -346,9 +282,7 @@ version: 1.0.0
runtimeConfig: runtimeConfig:
platformCommand: platformCommand:
- command: echo` - command: echo`
if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYaml), 0o644); err != nil { require.NoErrorf(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYaml), 0o644), "Failed to create plugin.yaml")
t.Fatalf("Failed to create plugin.yaml: %v", err)
}
return pluginDir return pluginDir
} }
@ -360,15 +294,10 @@ func createTarballFromPluginDir(t *testing.T, pluginDir string) string {
tmpDir := filepath.Dir(pluginDir) tmpDir := filepath.Dir(pluginDir)
tgzPath := filepath.Join(tmpDir, "test-plugin-1.0.0.tgz") tgzPath := filepath.Join(tmpDir, "test-plugin-1.0.0.tgz")
tarFile, err := os.Create(tgzPath) tarFile, err := os.Create(tgzPath)
if err != nil { require.NoError(t, err, "Failed to create tarball file")
t.Fatalf("Failed to create tarball file: %v", err)
}
defer tarFile.Close() defer tarFile.Close()
if err := plugin.CreatePluginTarball(pluginDir, "test-plugin", tarFile); err != nil { require.NoErrorf(t, plugin.CreatePluginTarball(pluginDir, "test-plugin", tarFile), "Failed to create tarball")
t.Fatalf("Failed to create tarball: %v", err)
}
return tgzPath return tgzPath
} }
@ -379,9 +308,7 @@ func createProvFile(t *testing.T, provFile, pluginTgz, hash string) {
if hash == "" { if hash == "" {
// Calculate actual hash of the tarball for realistic testing // Calculate actual hash of the tarball for realistic testing
data, err := os.ReadFile(pluginTgz) data, err := os.ReadFile(pluginTgz)
if err != nil { require.NoError(t, err, "Failed to read tarball for hashing")
t.Fatalf("Failed to read tarball for hashing: %v", err)
}
hashSum := sha256.Sum256(data) hashSum := sha256.Sum256(data)
hashStr = fmt.Sprintf("sha256:%x", hashSum) hashStr = fmt.Sprintf("sha256:%x", hashSum)
} else { } else {
@ -404,9 +331,7 @@ Version: GnuPG v1
iQEcBAEBCAAGBQJktest... iQEcBAEBCAAGBQJktest...
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----
`, hashStr) `, hashStr)
if err := os.WriteFile(provFile, []byte(provContent), 0o644); err != nil { require.NoErrorf(t, os.WriteFile(provFile, []byte(provContent), 0o644), "Failed to create provenance file")
t.Fatalf("Failed to create provenance file: %v", err)
}
} }
func createProvFileInvalidFormat(t *testing.T, provFile string) { func createProvFileInvalidFormat(t *testing.T, provFile string) {
@ -414,9 +339,7 @@ func createProvFileInvalidFormat(t *testing.T, provFile string) {
// Create an invalid provenance file (not PGP signed format) // Create an invalid provenance file (not PGP signed format)
invalidProv := "This is not a valid PGP signed message" invalidProv := "This is not a valid PGP signed message"
if err := os.WriteFile(provFile, []byte(invalidProv), 0o644); err != nil { require.NoErrorf(t, os.WriteFile(provFile, []byte(invalidProv), 0o644), "Failed to create invalid provenance file")
t.Fatalf("Failed to create invalid provenance file: %v", err)
}
} }
func createTestKeyring(t *testing.T) string { func createTestKeyring(t *testing.T) string {
@ -427,9 +350,7 @@ func createTestKeyring(t *testing.T) string {
keyringPath := filepath.Join(tmpDir, "pubring.gpg") keyringPath := filepath.Join(tmpDir, "pubring.gpg")
// Create empty keyring for testing // Create empty keyring for testing
if err := os.WriteFile(keyringPath, []byte{}, 0o644); err != nil { require.NoErrorf(t, os.WriteFile(keyringPath, []byte{}, 0o644), "Failed to create test keyring")
t.Fatalf("Failed to create test keyring: %v", err)
}
return keyringPath return keyringPath
} }

@ -194,13 +194,9 @@ func TestDetectDuplicates(t *testing.T) {
mockSubprocessCLIPlugin(t, "foo"), mockSubprocessCLIPlugin(t, "foo"),
mockSubprocessCLIPlugin(t, "bar"), mockSubprocessCLIPlugin(t, "bar"),
} }
if err := detectDuplicates(plugs); err != nil { require.NoError(t, detectDuplicates(plugs), "no duplicates in the first set")
t.Error("no duplicates in the first set")
}
plugs = append(plugs, mockSubprocessCLIPlugin(t, "foo")) plugs = append(plugs, mockSubprocessCLIPlugin(t, "foo"))
if err := detectDuplicates(plugs); err == nil { assert.Error(t, detectDuplicates(plugs), "duplicates in the second set")
t.Error("duplicates in the second set")
}
} }
func TestLoadAllDir_Empty(t *testing.T) { func TestLoadAllDir_Empty(t *testing.T) {

@ -66,7 +66,7 @@ func TestValidatePluginData(t *testing.T) {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
err := item.plug.Metadata().Validate() err := item.plug.Metadata().Validate()
if item.pass { if item.pass {
require.NoError(t, err, "failed to validate case %d: %s", i, err) require.NoError(t, err, "failed to validate case %d", i)
} else { } else {
require.Error(t, err, "expected case %d to fail", i) require.Error(t, err, "expected case %d to fail", i)
assert.ErrorContains(t, err, item.errString, "expected case %d error to contain %q", i, item.errString) assert.ErrorContains(t, err, item.errString, "expected case %d error to contain %q", i, item.errString)
@ -97,8 +97,7 @@ func TestMetadataValidateVersion(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
m := mockSubprocessCLIPlugin(t, "testplugin") m := mockSubprocessCLIPlugin(t, "testplugin")
m.metadata.Version = tc.version m.metadata.Version = tc.version
err := m.Metadata().Validate() assert.NoError(t, m.Metadata().Validate())
assert.NoError(t, err)
}) })
} }
@ -106,8 +105,7 @@ func TestMetadataValidateVersion(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
m := mockSubprocessCLIPlugin(t, "testplugin") m := mockSubprocessCLIPlugin(t, "testplugin")
m.metadata.Version = tc.version m.metadata.Version = tc.version
err := m.Metadata().Validate() assert.ErrorContains(t, m.Metadata().Validate(), "invalid plugin version")
assert.ErrorContains(t, err, "invalid plugin version")
}) })
} }
} }
@ -139,7 +137,7 @@ func TestMetadataValidateMultipleErrors(t *testing.T) {
} }
for _, expectedErr := range expectedErrors { for _, expectedErr := range expectedErrors {
assert.Contains(t, errStr, expectedErr, "expected error to contain %q, but got: %v", expectedErr, errStr) require.ErrorContains(t, err, expectedErr)
} }
// Verify that the error contains the correct number of error messages // Verify that the error contains the correct number of error messages

@ -77,8 +77,7 @@ func TestMetadataV1ValidateVersion(t *testing.T) {
t.Run("invalid/"+name, func(t *testing.T) { t.Run("invalid/"+name, func(t *testing.T) {
m := base() m := base()
m.Version = tc.version m.Version = tc.version
err := m.Validate() assert.ErrorContains(t, m.Validate(), tc.errMsg)
assert.ErrorContains(t, err, tc.errMsg)
}) })
} }
} }

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package plugin // import "helm.sh/helm/v4/internal/plugin" package plugin
import ( import (
"context" "context"

@ -18,6 +18,8 @@ package plugin
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v4/internal/plugin/schema" "helm.sh/helm/v4/internal/plugin/schema"
) )
@ -34,9 +36,7 @@ func TestValidPluginName(t *testing.T) {
for name, pluginName := range validNames { for name, pluginName := range validNames {
t.Run("valid/"+name, func(t *testing.T) { t.Run("valid/"+name, func(t *testing.T) {
if !validPluginName.MatchString(pluginName) { assert.Truef(t, validPluginName.MatchString(pluginName), "expected %q to match validPluginName regex", pluginName)
t.Errorf("expected %q to match validPluginName regex", pluginName)
}
}) })
} }
@ -52,9 +52,7 @@ func TestValidPluginName(t *testing.T) {
for name, pluginName := range invalidNames { for name, pluginName := range invalidNames {
t.Run("invalid/"+name, func(t *testing.T) { t.Run("invalid/"+name, func(t *testing.T) {
if validPluginName.MatchString(pluginName) { assert.Falsef(t, validPluginName.MatchString(pluginName), "expected %q to not match validPluginName regex", pluginName)
t.Errorf("expected %q to not match validPluginName regex", pluginName)
}
}) })
} }
} }

@ -59,8 +59,7 @@ func buildLoadExtismPlugin(t *testing.T, dir string) pluginRaw {
func TestRuntimeConfigExtismV1Validate(t *testing.T) { func TestRuntimeConfigExtismV1Validate(t *testing.T) {
rc := RuntimeConfigExtismV1{} rc := RuntimeConfigExtismV1{}
err := rc.Validate() assert.NoError(t, rc.Validate(), "expected no error for empty RuntimeConfigExtismV1")
assert.NoError(t, err, "expected no error for empty RuntimeConfigExtismV1")
} }
func TestRuntimeExtismV1InvokePlugin(t *testing.T) { func TestRuntimeExtismV1InvokePlugin(t *testing.T) {

@ -18,6 +18,7 @@ package plugin
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
@ -152,7 +153,8 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error {
slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String()))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { var eerr *exec.ExitError
if errors.As(err, &eerr) {
os.Stderr.Write(eerr.Stderr) os.Stderr.Write(eerr.Stderr)
return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name) return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name)
} }
@ -166,7 +168,8 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error {
// then replace the other three with a call to this func // then replace the other three with a call to this func
func executeCmd(prog *exec.Cmd, pluginName string) error { func executeCmd(prog *exec.Cmd, pluginName string) error {
if err := prog.Run(); err != nil { if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { var eerr *exec.ExitError
if errors.As(err, &eerr) {
slog.Debug( slog.Debug(
"plugin execution failed", "plugin execution failed",
slog.String("pluginName", pluginName), slog.String("pluginName", pluginName),

@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package plugin // import "helm.sh/helm/v4/internal/plugin" package plugin
// Types of hooks // Types of hooks
const ( const (

@ -16,7 +16,6 @@ limitations under the License.
package plugin package plugin
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -78,8 +77,7 @@ func TestSubprocessPluginRuntime(t *testing.T) {
require.Error(t, err) require.Error(t, err)
ieerr := &InvokeExecError{} ieerr := &InvokeExecError{}
ok := errors.As(err, &ieerr) require.ErrorAs(t, err, &ieerr, "expected InvokeExecError")
require.True(t, ok, "expected InvokeExecError, got %T", err)
assert.Equal(t, 56, ieerr.ExitCode) assert.Equal(t, 56, ieerr.ExitCode)
assert.Nil(t, output) assert.Nil(t, output)

@ -16,7 +16,6 @@ limitations under the License.
package plugin package plugin
import ( import (
"reflect"
"runtime" "runtime"
"testing" "testing"
@ -39,9 +38,7 @@ func TestPrepareCommand(t *testing.T) {
cmd, args, err := PrepareCommands(platformCommand, true, []string{}, env) cmd, args, err := PrepareCommands(platformCommand, true, []string{}, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, cmdArgs) { require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args)
t.Fatalf("Expected %v, got %v", cmdArgs, args)
}
} }
func TestPrepareCommandExtraArgs(t *testing.T) { func TestPrepareCommandExtraArgs(t *testing.T) {
@ -113,9 +110,7 @@ func TestPrepareCommands(t *testing.T) {
cmd, args, err := PrepareCommands(cmds, true, []string{}, env) cmd, args, err := PrepareCommands(cmds, true, []string{}, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, cmdArgs) { require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args)
t.Fatalf("Expected %v, got %v", cmdArgs, args)
}
} }
func TestPrepareCommandsExtraArgs(t *testing.T) { func TestPrepareCommandsExtraArgs(t *testing.T) {
@ -136,9 +131,7 @@ func TestPrepareCommandsExtraArgs(t *testing.T) {
cmd, args, err := PrepareCommands(cmds, true, extraArgs, env) cmd, args, err := PrepareCommands(cmds, true, extraArgs, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, expectedArgs) { require.Equalf(t, expectedArgs, args, "Expected %v, got %v", expectedArgs, args)
t.Fatalf("Expected %v, got %v", expectedArgs, args)
}
} }
func TestPrepareCommandsNoArch(t *testing.T) { func TestPrepareCommandsNoArch(t *testing.T) {
@ -155,9 +148,7 @@ func TestPrepareCommandsNoArch(t *testing.T) {
cmd, args, err := PrepareCommands(cmds, true, []string{}, env) cmd, args, err := PrepareCommands(cmds, true, []string{}, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, cmdArgs) { require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args)
t.Fatalf("Expected %v, got %v", cmdArgs, args)
}
} }
func TestPrepareCommandsNoOsNoArch(t *testing.T) { func TestPrepareCommandsNoOsNoArch(t *testing.T) {
@ -174,9 +165,7 @@ func TestPrepareCommandsNoOsNoArch(t *testing.T) {
cmd, args, err := PrepareCommands(cmds, true, []string{}, env) cmd, args, err := PrepareCommands(cmds, true, []string{}, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, cmdArgs) { require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args)
t.Fatalf("Expected %v, got %v", cmdArgs, args)
}
} }
func TestPrepareCommandsNoMatch(t *testing.T) { func TestPrepareCommandsNoMatch(t *testing.T) {
@ -216,9 +205,7 @@ func TestPrepareCommandsExpand(t *testing.T) {
cmd, args, err := PrepareCommands(cmds, true, []string{}, env) cmd, args, err := PrepareCommands(cmds, true, []string{}, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, expectedArgs) { require.Equalf(t, expectedArgs, args, "Expected %v, got %v", expectedArgs, args)
t.Fatalf("Expected %v, got %v", expectedArgs, args)
}
} }
func TestPrepareCommandsNoExpand(t *testing.T) { func TestPrepareCommandsNoExpand(t *testing.T) {
@ -235,7 +222,5 @@ func TestPrepareCommandsNoExpand(t *testing.T) {
cmd, args, err := PrepareCommands(cmds, false, []string{}, env) cmd, args, err := PrepareCommands(cmds, false, []string{}, env)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd)
if !reflect.DeepEqual(args, cmdArgs) { require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args)
t.Fatalf("Expected %v, got %v", cmdArgs, args)
}
} }

@ -44,11 +44,9 @@ func TestVerifyPlugin(t *testing.T) {
// Create plugin directory // Create plugin directory
pluginDir := filepath.Join(tempDir, "verify-test-plugin") pluginDir := filepath.Join(tempDir, "verify-test-plugin")
err := os.MkdirAll(pluginDir, 0o755) require.NoError(t, os.MkdirAll(pluginDir, 0o755))
require.NoError(t, err)
err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644) require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644))
require.NoError(t, err)
// Create tarball // Create tarball
tarballPath := filepath.Join(tempDir, "verify-test-plugin.tar.gz") tarballPath := filepath.Join(tempDir, "verify-test-plugin.tar.gz")
@ -64,10 +62,9 @@ func TestVerifyPlugin(t *testing.T) {
// Sign the plugin with source directory // Sign the plugin with source directory
signer, err := provenance.NewFromKeyring(testKeyFile, "helm-test") signer, err := provenance.NewFromKeyring(testKeyFile, "helm-test")
require.NoError(t, err) require.NoError(t, err)
err = signer.DecryptKey(func(_ string) ([]byte, error) { require.NoError(t, signer.DecryptKey(func(_ string) ([]byte, error) {
return []byte(""), nil return []byte(""), nil
}) }))
require.NoError(t, err)
// Read the tarball data // Read the tarball data
tarballData, err := os.ReadFile(tarballPath) tarballData, err := os.ReadFile(tarballPath)
@ -78,8 +75,7 @@ func TestVerifyPlugin(t *testing.T) {
// Write the signature to .prov file // Write the signature to .prov file
provFile := tarballPath + ".prov" provFile := tarballPath + ".prov"
err = os.WriteFile(provFile, []byte(sig), 0o644) require.NoError(t, os.WriteFile(provFile, []byte(sig), 0o644))
require.NoError(t, err)
// Read the files for verification // Read the files for verification
archiveData, err := os.ReadFile(tarballPath) archiveData, err := os.ReadFile(tarballPath)
@ -105,11 +101,9 @@ func TestVerifyPluginBadSignature(t *testing.T) {
// Create a plugin tarball // Create a plugin tarball
pluginDir := filepath.Join(tempDir, "bad-plugin") pluginDir := filepath.Join(tempDir, "bad-plugin")
err := os.MkdirAll(pluginDir, 0o755) require.NoError(t, os.MkdirAll(pluginDir, 0o755))
require.NoError(t, err)
err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644) require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644))
require.NoError(t, err)
tarballPath := filepath.Join(tempDir, "bad-plugin.tar.gz") tarballPath := filepath.Join(tempDir, "bad-plugin.tar.gz")
tarFile, err := os.Create(tarballPath) tarFile, err := os.Create(tarballPath)
@ -133,8 +127,7 @@ InvalidSignatureData
-----END PGP SIGNATURE-----` -----END PGP SIGNATURE-----`
provFile := tarballPath + ".prov" provFile := tarballPath + ".prov"
err = os.WriteFile(provFile, []byte(badSig), 0o644) require.NoError(t, os.WriteFile(provFile, []byte(badSig), 0o644))
require.NoError(t, err)
// Read the files // Read the files
archiveData, err := os.ReadFile(tarballPath) archiveData, err := os.ReadFile(tarballPath)
@ -153,8 +146,7 @@ func TestVerifyPluginMissingProvenance(t *testing.T) {
tarballPath := filepath.Join(tempDir, "no-prov.tar.gz") tarballPath := filepath.Join(tempDir, "no-prov.tar.gz")
// Create a minimal tarball // Create a minimal tarball
err := os.WriteFile(tarballPath, []byte("dummy"), 0o644) require.NoError(t, os.WriteFile(tarballPath, []byte("dummy"), 0o644))
require.NoError(t, err)
// Read the tarball data // Read the tarball data
archiveData, err := os.ReadFile(tarballPath) archiveData, err := os.ReadFile(tarballPath)

@ -170,12 +170,12 @@ func TestHookExecutionUnmarshalJSON(t *testing.T) {
err := json.Unmarshal([]byte(tt.input), &exec) err := json.Unmarshal([]byte(tt.input), &exec)
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
return } else {
require.NoError(t, err)
assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix())
assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix())
assert.Equal(t, tt.expected.Phase, exec.Phase)
} }
require.NoError(t, err)
assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix())
assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix())
assert.Equal(t, tt.expected.Phase, exec.Phase)
}) })
} }
} }
@ -194,8 +194,7 @@ func TestHookExecutionRoundTrip(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
var decoded HookExecution var decoded HookExecution
err = json.Unmarshal(data, &decoded) require.NoError(t, json.Unmarshal(data, &decoded))
require.NoError(t, err)
assert.Equal(t, original.StartedAt.Unix(), decoded.StartedAt.Unix()) assert.Equal(t, original.StartedAt.Unix(), decoded.StartedAt.Unix())
assert.Equal(t, original.CompletedAt.Unix(), decoded.CompletedAt.Unix()) assert.Equal(t, original.CompletedAt.Unix(), decoded.CompletedAt.Unix())
@ -208,12 +207,11 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) {
input := `{"started_at":"","completed_at":"","phase":"Succeeded"}` input := `{"started_at":"","completed_at":"","phase":"Succeeded"}`
var exec HookExecution var exec HookExecution
err := json.Unmarshal([]byte(input), &exec) require.NoError(t, json.Unmarshal([]byte(input), &exec))
require.NoError(t, err)
// Verify time fields are zero values // Verify time fields are zero values
assert.True(t, exec.StartedAt.IsZero()) assert.Zero(t, exec.StartedAt)
assert.True(t, exec.CompletedAt.IsZero()) assert.Zero(t, exec.CompletedAt)
assert.Equal(t, HookPhaseSucceeded, exec.Phase) assert.Equal(t, HookPhaseSucceeded, exec.Phase)
// Marshal back and verify empty time fields are omitted // Marshal back and verify empty time fields are omitted
@ -221,8 +219,7 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
var result map[string]any var result map[string]any
err = json.Unmarshal(data, &result) require.NoError(t, json.Unmarshal(data, &result))
require.NoError(t, err)
// Zero time values should be omitted // Zero time values should be omitted
assert.NotContains(t, result, "started_at") assert.NotContains(t, result, "started_at")

@ -253,17 +253,17 @@ func TestInfoUnmarshalJSON(t *testing.T) {
err := json.Unmarshal([]byte(tt.input), &info) err := json.Unmarshal([]byte(tt.input), &info)
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
return } else {
require.NoError(t, err)
assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix())
assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix())
assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix())
assert.Equal(t, tt.expected.Description, info.Description)
assert.Equal(t, tt.expected.Status, info.Status)
assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision)
assert.Equal(t, tt.expected.Notes, info.Notes)
assert.Equal(t, tt.expected.Resources, info.Resources)
} }
require.NoError(t, err)
assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix())
assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix())
assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix())
assert.Equal(t, tt.expected.Description, info.Description)
assert.Equal(t, tt.expected.Status, info.Status)
assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision)
assert.Equal(t, tt.expected.Notes, info.Notes)
assert.Equal(t, tt.expected.Resources, info.Resources)
}) })
} }
} }
@ -284,9 +284,7 @@ func TestInfoRoundTrip(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
var decoded Info var decoded Info
err = json.Unmarshal(data, &decoded) require.NoError(t, json.Unmarshal(data, &decoded))
require.NoError(t, err)
assert.Equal(t, original.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) assert.Equal(t, original.FirstDeployed.Unix(), decoded.FirstDeployed.Unix())
assert.Equal(t, original.LastDeployed.Unix(), decoded.LastDeployed.Unix()) assert.Equal(t, original.LastDeployed.Unix(), decoded.LastDeployed.Unix())
assert.Equal(t, original.Deleted.Unix(), decoded.Deleted.Unix()) assert.Equal(t, original.Deleted.Unix(), decoded.Deleted.Unix())
@ -330,8 +328,7 @@ func TestInfoRollbackRevisionRoundTrip(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
var decoded Info var decoded Info
err = json.Unmarshal(data, &decoded) require.NoError(t, json.Unmarshal(data, &decoded))
require.NoError(t, err)
assert.Equal(t, tt.info.RollbackRevision, decoded.RollbackRevision) assert.Equal(t, tt.info.RollbackRevision, decoded.RollbackRevision)
assert.Equal(t, tt.info.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) assert.Equal(t, tt.info.FirstDeployed.Unix(), decoded.FirstDeployed.Unix())
@ -342,8 +339,7 @@ func TestInfoRollbackRevisionRoundTrip(t *testing.T) {
// Verify omitempty behavior: zero rollback_revision should not appear in JSON // Verify omitempty behavior: zero rollback_revision should not appear in JSON
if tt.info.RollbackRevision == 0 { if tt.info.RollbackRevision == 0 {
var raw map[string]any var raw map[string]any
err = json.Unmarshal(data, &raw) require.NoError(t, json.Unmarshal(data, &raw))
require.NoError(t, err)
assert.NotContains(t, raw, "rollback_revision") assert.NotContains(t, raw, "rollback_revision")
} }
}) })
@ -356,13 +352,12 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) {
input := `{"first_deployed":"","last_deployed":"","deleted":"","status":"deployed","description":"test"}` input := `{"first_deployed":"","last_deployed":"","deleted":"","status":"deployed","description":"test"}`
var info Info var info Info
err := json.Unmarshal([]byte(input), &info) require.NoError(t, json.Unmarshal([]byte(input), &info))
require.NoError(t, err)
// Verify time fields are zero values // Verify time fields are zero values
assert.True(t, info.FirstDeployed.IsZero()) assert.Zero(t, info.FirstDeployed)
assert.True(t, info.LastDeployed.IsZero()) assert.Zero(t, info.LastDeployed)
assert.True(t, info.Deleted.IsZero()) assert.Zero(t, info.Deleted)
assert.Equal(t, common.StatusDeployed, info.Status) assert.Equal(t, common.StatusDeployed, info.Status)
assert.Equal(t, "test", info.Description) assert.Equal(t, "test", info.Description)
@ -371,8 +366,7 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
var result map[string]any var result map[string]any
err = json.Unmarshal(data, &result) require.NoError(t, json.Unmarshal(data, &result))
require.NoError(t, err)
// Zero time values should be omitted due to omitzero tag // Zero time values should be omitted due to omitzero tag
assert.NotContains(t, result, "first_deployed") assert.NotContains(t, result, "first_deployed")

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
v2 "helm.sh/helm/v4/internal/release/v2" v2 "helm.sh/helm/v4/internal/release/v2"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"sort" "sort"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"bytes" "bytes"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"fmt" "fmt"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"
@ -143,7 +143,6 @@ metadata:
// This test will fail if 'six' or 'seven' was added. // This test will fail if 'six' or 'seven' was added.
assert.Len(t, generic, 2) assert.Len(t, generic, 2)
assert.Len(t, hs, 4) assert.Len(t, hs, 4)
for _, out := range hs { for _, out := range hs {

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"sort" "sort"

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package util // import "helm.sh/helm/v4/internal/release/v2/util" package util
import ( import (
"testing" "testing"

@ -148,28 +148,22 @@ func TestResolve(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
l, err := r.Resolve(tt.req, repoNames) l, err := r.Resolve(tt.req, repoNames)
if err != nil { if tt.err {
if tt.err { require.Error(t, err)
return } else {
}
require.NoError(t, err) require.NoError(t, err)
} h, err := HashReq(tt.req, tt.expect.Dependencies)
require.NoError(t, err)
assert.Equal(t, l.Digest, h, "%q: hashes don't match.", tt.name)
if tt.err { // Check fields.
require.Failf(t, "Expected error in test %q", tt.name) require.Len(t, l.Dependencies, len(tt.req), "%s: wrong number of dependencies in lock", tt.name)
d0 := l.Dependencies[0]
e0 := tt.expect.Dependencies[0]
assert.Equal(t, e0.Name, d0.Name, tt.name)
assert.Equal(t, e0.Repository, d0.Repository, tt.name)
assert.Equal(t, e0.Version, d0.Version, tt.name)
} }
h, err := HashReq(tt.req, tt.expect.Dependencies)
require.NoError(t, err)
assert.Equal(t, l.Digest, h, "%q: hashes don't match.", tt.name)
// Check fields.
assert.Len(t, l.Dependencies, len(tt.req), "%s: wrong number of dependencies in lock", tt.name)
d0 := l.Dependencies[0]
e0 := tt.expect.Dependencies[0]
assert.Equal(t, e0.Name, d0.Name, tt.name)
assert.Equal(t, e0.Repository, d0.Repository, tt.name)
assert.Equal(t, e0.Version, d0.Version, tt.name)
}) })
} }
} }
@ -280,20 +274,16 @@ func TestGetLocalPath(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
p, err := GetLocalPath(tt.repo, tt.chartpath) p, err := GetLocalPath(tt.repo, tt.chartpath)
if err != nil {
if tt.err {
return
}
require.NoError(t, err)
}
if tt.err { if tt.err {
require.Failf(t, "Expected error in test %q", tt.name) require.Error(t, err)
} } else {
expect := tt.expect require.NoError(t, err)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
expect = tt.winExpect assert.Equal(t, tt.winExpect, p, tt.name)
} else {
assert.Equal(t, tt.expect, p, tt.name)
}
} }
assert.Equal(t, expect, p, tt.name)
}) })
} }
} }

@ -21,6 +21,7 @@ limitations under the License.
package sympath package sympath
import ( import (
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
@ -40,7 +41,7 @@ func Walk(root string, walkFn filepath.WalkFunc) error {
} else { } else {
err = symwalk(root, info, walkFn) err = symwalk(root, info, walkFn)
} }
if err == filepath.SkipDir { if errors.Is(err, filepath.SkipDir) {
return nil return nil
} }
return err return err
@ -75,7 +76,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if info, err = os.Lstat(resolved); err != nil { if info, err = os.Lstat(resolved); err != nil {
return err return err
} }
if err := symwalk(path, info, walkFn); err != nil && err != filepath.SkipDir { if err := symwalk(path, info, walkFn); err != nil && !errors.Is(err, filepath.SkipDir) {
return err return err
} }
return nil return nil
@ -98,13 +99,13 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
filename := filepath.Join(path, name) filename := filepath.Join(path, name)
fileInfo, err := os.Lstat(filename) fileInfo, err := os.Lstat(filename)
if err != nil { if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { if err := walkFn(filename, fileInfo, err); err != nil && !errors.Is(err, filepath.SkipDir) {
return err return err
} }
} else { } else {
err = symwalk(filename, fileInfo, walkFn) err = symwalk(filename, fileInfo, walkFn)
if err != nil { if err != nil {
if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir { if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || !errors.Is(err, filepath.SkipDir) {
return err return err
} }
} }

@ -132,8 +132,7 @@ func TestWalk(t *testing.T) {
return mark(info, err, &errors, true) return mark(info, err, &errors, true)
} }
// Expect no errors. // Expect no errors.
err := Walk(tree.name, markFn) require.NoError(t, Walk(tree.name, markFn))
require.NoError(t, err)
require.Empty(t, errors, "unexpected errors") require.Empty(t, errors, "unexpected errors")
checkMarks(t, true) checkMarks(t, true)

@ -32,54 +32,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs package fs
import ( import (
"errors"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestRenameWithFallback(t *testing.T) { func TestRenameWithFallback(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
if err := RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil { require.Error(t, RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")), "expected an error for non existing file, but got nil")
t.Fatal("expected an error for non existing file, but got nil")
}
srcpath := filepath.Join(dir, "src") srcpath := filepath.Join(dir, "src")
if srcf, err := os.Create(srcpath); err != nil { srcf, err := os.Create(srcpath)
t.Fatal(err) require.NoError(t, err)
} else { srcf.Close()
srcf.Close()
}
if err := RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil { require.NoError(t, RenameWithFallback(srcpath, filepath.Join(dir, "dst")))
t.Fatal(err)
}
srcpath = filepath.Join(dir, "a") srcpath = filepath.Join(dir, "a")
if err := os.MkdirAll(srcpath, 0o777); err != nil { require.NoError(t, os.MkdirAll(srcpath, 0o777))
t.Fatal(err)
}
dstpath := filepath.Join(dir, "b") dstpath := filepath.Join(dir, "b")
if err := os.MkdirAll(dstpath, 0o777); err != nil { require.NoError(t, os.MkdirAll(dstpath, 0o777))
t.Fatal(err) require.Error(t, RenameWithFallback(srcpath, dstpath), "expected an error if dst is an existing directory, but got nil")
}
if err := RenameWithFallback(srcpath, dstpath); err == nil {
t.Fatal("expected an error if dst is an existing directory, but got nil")
}
} }
func TestCopyDir(t *testing.T) { func TestCopyDir(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
srcdir := filepath.Join(dir, "src") srcdir := filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0o755); err != nil { require.NoError(t, os.MkdirAll(srcdir, 0o755))
t.Fatal(err)
}
files := []struct { files := []struct {
path string path string
@ -94,61 +81,40 @@ func TestCopyDir(t *testing.T) {
for i, file := range files { for i, file := range files {
fn := filepath.Join(srcdir, file.path) fn := filepath.Join(srcdir, file.path)
dn := filepath.Dir(fn) dn := filepath.Dir(fn)
if err := os.MkdirAll(dn, 0o755); err != nil { require.NoError(t, os.MkdirAll(dn, 0o755))
t.Fatal(err)
}
fh, err := os.Create(fn) fh, err := os.Create(fn)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if _, err = fh.WriteString(file.contents); err != nil { _, err = fh.WriteString(file.contents)
t.Fatal(err) require.NoError(t, err)
}
fh.Close() fh.Close()
files[i].fi, err = os.Stat(fn) files[i].fi, err = os.Stat(fn)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
} }
destdir := filepath.Join(dir, "dest") destdir := filepath.Join(dir, "dest")
if err := CopyDir(srcdir, destdir); err != nil { require.NoError(t, CopyDir(srcdir, destdir))
t.Fatal(err)
}
// Compare copy against structure indicated in 'files' // Compare copy against structure indicated in 'files'
for _, file := range files { for _, file := range files {
fn := filepath.Join(srcdir, file.path) fn := filepath.Join(srcdir, file.path)
dn := filepath.Dir(fn) dn := filepath.Dir(fn)
dirOK, err := IsDir(dn) dirOK, err := IsDir(dn)
if err != nil { require.NoError(t, err)
t.Fatal(err) require.Truef(t, dirOK, "expected %s to be a directory", dn)
}
if !dirOK {
t.Fatalf("expected %s to be a directory", dn)
}
got, err := os.ReadFile(fn) got, err := os.ReadFile(fn)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if file.contents != string(got) { require.Equalf(t, file.contents, string(got), "expected: %s, got: %s", file.contents, string(got))
t.Fatalf("expected: %s, got: %s", file.contents, string(got))
}
gotinfo, err := os.Stat(fn) gotinfo, err := os.Stat(fn)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if file.fi.Mode() != gotinfo.Mode() { require.Equalf(t, file.fi.Mode(), gotinfo.Mode(), "expected %s: %#v\n to be the same mode as %s: %#v",
t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", file.path, file.fi.Mode(), fn, gotinfo.Mode())
file.path, file.fi.Mode(), fn, gotinfo.Mode())
}
} }
} }
@ -178,9 +144,7 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
dstdir = filepath.Join(dir, "dst") dstdir = filepath.Join(dir, "dst")
if err := CopyDir(srcdir, dstdir); err == nil { assert.Errorf(t, CopyDir(srcdir, dstdir), "expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
} }
func TestCopyDirFail_DstInaccessible(t *testing.T) { func TestCopyDirFail_DstInaccessible(t *testing.T) {
@ -203,9 +167,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0o755); err != nil { require.NoError(t, os.MkdirAll(srcdir, 0o755))
t.Fatal(err)
}
cleanup := setupInaccessibleDir(t, func(dir string) error { cleanup := setupInaccessibleDir(t, func(dir string) error {
dstdir = filepath.Join(dir, "dst") dstdir = filepath.Join(dir, "dst")
@ -213,9 +175,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) {
}) })
defer cleanup() defer cleanup()
if err := CopyDir(srcdir, dstdir); err == nil { assert.Errorf(t, CopyDir(srcdir, dstdir), "expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
} }
func TestCopyDirFail_SrcIsNotDir(t *testing.T) { func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
@ -225,44 +185,25 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if _, err = os.Create(srcdir); err != nil { _, err = os.Create(srcdir)
t.Fatal(err) require.NoError(t, err)
}
dstdir = filepath.Join(dir, "dst") dstdir = filepath.Join(dir, "dst")
if err = CopyDir(srcdir, dstdir); err == nil { require.ErrorIsf(t, CopyDir(srcdir, dstdir), errSrcNotDir, "expected %v error for CopyDir(%s, %s)", errSrcNotDir, srcdir, dstdir)
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
if !errors.Is(err, errSrcNotDir) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
}
} }
func TestCopyDirFail_DstExists(t *testing.T) { func TestCopyDirFail_DstExists(t *testing.T) {
var srcdir, dstdir string var srcdir, dstdir string
var err error
dir := t.TempDir() dir := t.TempDir()
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if err = os.MkdirAll(srcdir, 0o755); err != nil { require.NoError(t, os.MkdirAll(srcdir, 0o755))
t.Fatal(err)
}
dstdir = filepath.Join(dir, "dst") dstdir = filepath.Join(dir, "dst")
if err = os.MkdirAll(dstdir, 0o755); err != nil { require.NoError(t, os.MkdirAll(dstdir, 0o755))
t.Fatal(err) require.ErrorIs(t, CopyDir(srcdir, dstdir), errDstExist, "expected %v error for CopyDir(%s, %s)", errDstExist, srcdir, dstdir)
}
if err = CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
if !errors.Is(err, errDstExist) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err)
}
} }
func TestCopyDirFailOpen(t *testing.T) { func TestCopyDirFailOpen(t *testing.T) {
@ -288,70 +229,46 @@ func TestCopyDirFailOpen(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
srcdir = filepath.Join(dir, "src") srcdir = filepath.Join(dir, "src")
if err := os.MkdirAll(srcdir, 0o755); err != nil { require.NoError(t, os.MkdirAll(srcdir, 0o755))
t.Fatal(err)
}
srcfn := filepath.Join(srcdir, "file") srcfn := filepath.Join(srcdir, "file")
srcf, err := os.Create(srcfn) srcf, err := os.Create(srcfn)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
srcf.Close() srcf.Close()
// setup source file so that it cannot be read // setup source file so that it cannot be read
if err = os.Chmod(srcfn, 0o222); err != nil { require.NoError(t, os.Chmod(srcfn, 0o222))
t.Fatal(err)
}
dstdir = filepath.Join(dir, "dst") dstdir = filepath.Join(dir, "dst")
assert.Errorf(t, CopyDir(srcdir, dstdir), "expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
if err = CopyDir(srcdir, dstdir); err == nil {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
} }
func TestCopyFile(t *testing.T) { func TestCopyFile(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
srcf, err := os.Create(filepath.Join(dir, "srcfile")) srcf, err := os.Create(filepath.Join(dir, "srcfile"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
want := "hello world" want := "hello world"
if _, err := srcf.WriteString(want); err != nil { _, err = srcf.WriteString(want)
t.Fatal(err) require.NoError(t, err)
}
srcf.Close() srcf.Close()
destf := filepath.Join(dir, "destf") destf := filepath.Join(dir, "destf")
if err := CopyFile(srcf.Name(), destf); err != nil { require.NoError(t, CopyFile(srcf.Name(), destf))
t.Fatal(err)
}
got, err := os.ReadFile(destf) got, err := os.ReadFile(destf)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if want != string(got) { require.Equalf(t, want, string(got), "expected: %s, got: %s", want, string(got))
t.Fatalf("expected: %s, got: %s", want, string(got))
}
wantinfo, err := os.Stat(srcf.Name()) wantinfo, err := os.Stat(srcf.Name())
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
gotinfo, err := os.Stat(destf) gotinfo, err := os.Stat(destf)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if wantinfo.Mode() != gotinfo.Mode() { assert.Equalf(t, wantinfo.Mode(), gotinfo.Mode(), "expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode())
t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode())
}
} }
func TestCopyFileSymlink(t *testing.T) { func TestCopyFileSymlink(t *testing.T) {
@ -366,9 +283,7 @@ func TestCopyFileSymlink(t *testing.T) {
for symlink, dst := range testcases { for symlink, dst := range testcases {
t.Run(symlink, func(t *testing.T) { t.Run(symlink, func(t *testing.T) {
var err error var err error
if err = CopyFile(symlink, dst); err != nil { require.NoErrorf(t, CopyFile(symlink, dst), "failed to copy symlink")
t.Fatalf("failed to copy symlink: %s", err)
}
var want, got string var want, got string
@ -377,31 +292,21 @@ func TestCopyFileSymlink(t *testing.T) {
// regular users aren't granted usually. So we copy the file // regular users aren't granted usually. So we copy the file
// content as a fall back instead of creating a real symlink. // content as a fall back instead of creating a real symlink.
srcb, err := os.ReadFile(symlink) srcb, err := os.ReadFile(symlink)
if err != nil { require.NoError(t, err)
t.Fatalf("%+v", err)
}
dstb, err := os.ReadFile(dst) dstb, err := os.ReadFile(dst)
if err != nil { require.NoError(t, err)
t.Fatalf("%+v", err)
}
want = string(srcb) want = string(srcb)
got = string(dstb) got = string(dstb)
} else { } else {
want, err = os.Readlink(symlink) want, err = os.Readlink(symlink)
if err != nil { require.NoError(t, err)
t.Fatalf("%+v", err)
}
got, err = os.Readlink(dst) got, err = os.Readlink(dst)
if err != nil { require.NoErrorf(t, err, "could not resolve symlink")
t.Fatalf("could not resolve symlink: %s", err)
}
} }
if want != got { require.Equalf(t, want, got, "resolved path is incorrect. expected %s, got %s", want, got)
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
}
}) })
} }
} }
@ -424,9 +329,7 @@ func TestCopyFileFail(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
srcf, err := os.Create(filepath.Join(dir, "srcfile")) srcf, err := os.Create(filepath.Join(dir, "srcfile"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
srcf.Close() srcf.Close()
var dstdir string var dstdir string
@ -438,9 +341,7 @@ func TestCopyFileFail(t *testing.T) {
defer cleanup() defer cleanup()
fn := filepath.Join(dstdir, "file") fn := filepath.Join(dstdir, "file")
if err := CopyFile(srcf.Name(), fn); err == nil { require.Errorf(t, CopyFile(srcf.Name(), fn), "expected error for %s, got none", fn)
t.Fatalf("expected error for %s, got none", fn)
}
} }
// setupInaccessibleDir creates a temporary location with a single // setupInaccessibleDir creates a temporary location with a single
@ -463,9 +364,7 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
subdir := filepath.Join(dir, "dir") subdir := filepath.Join(dir, "dir")
cleanup := func() { cleanup := func() {
if err := os.Chmod(subdir, 0o777); err != nil { assert.NoError(t, os.Chmod(subdir, 0o777))
t.Error(err)
}
} }
if err := os.Mkdir(subdir, 0o777); err != nil { if err := os.Mkdir(subdir, 0o777); err != nil {
@ -498,9 +397,7 @@ func TestIsDir(t *testing.T) {
} }
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
var dn string var dn string
@ -530,14 +427,16 @@ func TestIsDir(t *testing.T) {
} }
for f, want := range tests { for f, want := range tests {
got, err := IsDir(f) t.Run(f, func(t *testing.T) {
if err != nil && !want.err { got, err := IsDir(f)
t.Fatalf("expected no error, got %v", err)
}
if got != want.exists { if want.err {
t.Fatalf("expected %t for %s, got %t", want.exists, f, got) require.Error(t, err)
} } else {
require.NoError(t, err)
}
require.Equalf(t, want.exists, got, "expected %t for %s, got %t", want.exists, f, got)
})
} }
} }
@ -552,26 +451,18 @@ func TestIsSymlink(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
dirPath := filepath.Join(dir, "directory") dirPath := filepath.Join(dir, "directory")
if err := os.MkdirAll(dirPath, 0o777); err != nil { require.NoError(t, os.MkdirAll(dirPath, 0o777))
t.Fatal(err)
}
filePath := filepath.Join(dir, "file") filePath := filepath.Join(dir, "file")
f, err := os.Create(filePath) f, err := os.Create(filePath)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
f.Close() f.Close()
dirSymlink := filepath.Join(dir, "dirSymlink") dirSymlink := filepath.Join(dir, "dirSymlink")
fileSymlink := filepath.Join(dir, "fileSymlink") fileSymlink := filepath.Join(dir, "fileSymlink")
if err = os.Symlink(dirPath, dirSymlink); err != nil { require.NoError(t, os.Symlink(dirPath, dirSymlink))
t.Fatal(err) require.NoError(t, os.Symlink(filePath, fileSymlink))
}
if err = os.Symlink(filePath, fileSymlink); err != nil {
t.Fatal(err)
}
var ( var (
inaccessibleFile string inaccessibleFile string
@ -609,14 +500,11 @@ func TestIsSymlink(t *testing.T) {
for path, want := range tests { for path, want := range tests {
got, err := IsSymlink(path) got, err := IsSymlink(path)
if err != nil { if want.err {
if !want.err { require.Error(t, err, "expected an error")
t.Errorf("expected no error, got %v", err) } else {
} require.NoError(t, err, "expected no error")
}
if got != want.expected {
t.Errorf("expected %t for %s, got %t", want.expected, path, got)
} }
assert.Equalf(t, want.expected, got, "expected %t for %s, got %t", want.expected, path, got)
} }
} }

@ -58,9 +58,7 @@ func actionConfigFixtureWithDummyResources(t *testing.T, dummyResources kube.Res
slog.SetDefault(logger) slog.SetDefault(logger)
registryClient, err := registry.NewClient() registryClient, err := registry.NewClient()
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
return &Configuration{ return &Configuration{
Releases: storage.Init(driver.NewMemory()), Releases: storage.Init(driver.NewMemory()),
@ -371,16 +369,10 @@ func TestGetVersionSet(t *testing.T) {
client := fakeclientset.NewClientset() client := fakeclientset.NewClientset()
vs, err := GetVersionSet(client.Discovery()) vs, err := GetVersionSet(client.Discovery())
if err != nil { require.NoError(t, err)
t.Error(err)
}
if !vs.Has("v1") { assert.True(t, vs.Has("v1"), "Expected supported versions to at least include v1.")
t.Error("Expected supported versions to at least include v1.") assert.False(t, vs.Has("nosuchversion/v1"), "Non-existent version is reported found.")
}
if vs.Has("nosuchversion/v1") {
t.Error("Non-existent version is reported found.")
}
} }
// Mock PostRenderer for testing // Mock PostRenderer for testing
@ -2031,9 +2023,7 @@ spec:
mockPR := &mockPostRenderer{ mockPR := &mockPostRenderer{
transform: func(content string) string { transform: func(content string) string {
count := strings.Count(content, "kind: ServiceAccount") count := strings.Count(content, "kind: ServiceAccount")
if count > 1 { assert.LessOrEqualf(t, count, 1, "post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count)
t.Errorf("post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count)
}
return content return content
}, },
} }

@ -57,9 +57,7 @@ func TestList(t *testing.T) {
}, },
} { } {
buf := bytes.Buffer{} buf := bytes.Buffer{}
if err := NewDependency().List(tcase.chart, &buf); err != nil { require.NoError(t, NewDependency().List(tcase.chart, &buf))
t.Fatal(err)
}
test.AssertGoldenString(t, buf.String(), tcase.golden) test.AssertGoldenString(t, buf.String(), tcase.golden)
} }
} }
@ -71,22 +69,16 @@ func TestDependencyStatus_Dashes(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
chartpath := filepath.Join(dir, "charts") chartpath := filepath.Join(dir, "charts")
if err := os.MkdirAll(chartpath, 0o700); err != nil { require.NoError(t, os.MkdirAll(chartpath, 0o700))
t.Fatal(err)
}
// Add some fake charts // Add some fake charts
first := buildChart(withName("first-chart")) first := buildChart(withName("first-chart"))
_, err := chartutil.Save(first, chartpath) _, err := chartutil.Save(first, chartpath)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
second := buildChart(withName("first-chart-second-chart")) second := buildChart(withName("first-chart-second-chart"))
_, err = chartutil.Save(second, chartpath) _, err = chartutil.Save(second, chartpath)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
dep := &chart.Dependency{ dep := &chart.Dependency{
Name: "first-chart", Name: "first-chart",
@ -95,9 +87,7 @@ func TestDependencyStatus_Dashes(t *testing.T) {
// Now try to get the deps // Now try to get the deps
stat := NewDependency().dependencyStatus(dir, dep, first) stat := NewDependency().dependencyStatus(dir, dep, first)
if stat != "ok" { assert.Equal(t, "ok", stat, "Unexpected status: %q", stat)
t.Errorf("Unexpected status: %q", stat)
}
} }
func TestStatArchiveForStatus(t *testing.T) { func TestStatArchiveForStatus(t *testing.T) {
@ -105,9 +95,7 @@ func TestStatArchiveForStatus(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
chartpath := filepath.Join(dir, "charts") chartpath := filepath.Join(dir, "charts")
if err := os.MkdirAll(chartpath, 0o700); err != nil { require.NoError(t, os.MkdirAll(chartpath, 0o700))
t.Fatal(err)
}
// unsaved chart // unsaved chart
lilith := buildChart(withName("lilith")) lilith := buildChart(withName("lilith"))

@ -65,8 +65,7 @@ func TestGetMetadata_Run_BasicMetadata(t *testing.T) {
Namespace: "default", Namespace: "default",
} }
err := cfg.Releases.Create(rel) require.NoError(t, cfg.Releases.Create(rel))
require.NoError(t, err)
result, err := client.Run(releaseName) result, err := client.Run(releaseName)
require.NoError(t, err) require.NoError(t, err)
@ -645,8 +644,7 @@ func TestGetMetadata_Labels(t *testing.T) {
rel.Labels = customLabels rel.Labels = customLabels
metaGetter := NewGetMetadata(actionConfigFixture(t)) metaGetter := NewGetMetadata(actionConfigFixture(t))
err := metaGetter.cfg.Releases.Create(rel) require.NoError(t, metaGetter.cfg.Releases.Create(rel))
require.NoError(t, err)
metadata, err := metaGetter.Run(rel.Name) metadata, err := metaGetter.Run(rel.Name)
require.NoError(t, err) require.NoError(t, err)

@ -50,9 +50,7 @@ func TestHistoryRun(t *testing.T) {
client.Max = 3 client.Max = 3
client.cfg.Releases.MaxHistory = 3 client.cfg.Releases.MaxHistory = 3
for _, rel := range []*release.Release{simpleRelease, updatedRelease} { for _, rel := range []*release.Release{simpleRelease, updatedRelease} {
if err := client.cfg.Releases.Create(rel); err != nil { require.NoError(t, client.cfg.Releases.Create(rel), "Could not add releases to Config")
t.Fatal(err, "Could not add releases to Config")
}
} }
releases, err := config.Releases.ListReleases() releases, err := config.Releases.ListReleases()

@ -401,16 +401,12 @@ data:
serverSideApply := true serverSideApply := true
err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, nil, 600, serverSideApply) err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, nil, 600, serverSideApply)
if !reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord) { require.Truef(t, reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord), "Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord)
t.Fatalf("Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord)
}
if err != nil && !tc.expectError {
t.Fatal("Got an unexpected error.")
}
if err == nil && tc.expectError { if !tc.expectError {
t.Fatal("Expected and error but did not get it.") require.NoError(t, err)
} else {
require.Error(t, err)
} }
}) })
} }
@ -490,8 +486,7 @@ data:
ctx := context.Background() ctx := context.Background()
waitOptions := []kube.WaitOption{kube.WithWaitContext(ctx)} waitOptions := []kube.WaitOption{kube.WithWaitContext(ctx)}
err := configuration.execHook(rel, release.HookPreInstall, kube.StatusWatcherStrategy, waitOptions, 600, false) req.NoError(configuration.execHook(rel, release.HookPreInstall, kube.StatusWatcherStrategy, waitOptions, 600, false))
req.NoError(err)
// Verify that WaitOptions were passed to GetWaiter // Verify that WaitOptions were passed to GetWaiter
is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter")

@ -185,9 +185,7 @@ func TestInstallRelease(t *testing.T) {
vals := map[string]any{} vals := map[string]any{}
ctx, done := context.WithCancel(t.Context()) ctx, done := context.WithCancel(t.Context())
resi, err := instAction.RunWithContext(ctx, buildChart(), vals) resi, err := instAction.RunWithContext(ctx, buildChart(), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
is.Equal("test-install-release", res.Name, "Expected release name.") is.Equal("test-install-release", res.Name, "Expected release name.")
@ -238,9 +236,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) {
instAction := installActionWithConfig(config) instAction := installActionWithConfig(config)
instAction.TakeOwnership = true instAction.TakeOwnership = true
resi, err := instAction.Run(buildChart(), nil) resi, err := instAction.Run(buildChart(), nil)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -262,9 +258,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) {
instAction := installActionWithConfig(config) instAction := installActionWithConfig(config)
instAction.TakeOwnership = false instAction.TakeOwnership = false
resi, err := instAction.Run(buildChart(), nil) resi, err := instAction.Run(buildChart(), nil)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
r, err := instAction.cfg.Releases.Get(res.Name, res.Version) r, err := instAction.cfg.Releases.Get(res.Name, res.Version)
@ -285,7 +279,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) {
instAction := installActionWithConfig(config) instAction := installActionWithConfig(config)
_, err := instAction.Run(buildChart(), nil) _, err := instAction.Run(buildChart(), nil)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "unable to continue with install") is.ErrorContains(err, "unable to continue with install")
} }
func TestInstallReleaseWithValues(t *testing.T) { func TestInstallReleaseWithValues(t *testing.T) {
@ -303,9 +297,7 @@ func TestInstallReleaseWithValues(t *testing.T) {
}, },
} }
resi, err := instAction.Run(buildChart(withSampleValues()), userVals) resi, err := instAction.Run(buildChart(withSampleValues()), userVals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
is.Equal("test-install-release", res.Name, "Expected release name.") is.Equal("test-install-release", res.Name, "Expected release name.")
@ -344,9 +336,7 @@ func TestInstallRelease_WithNotes(t *testing.T) {
instAction.ReleaseName = "with-notes" instAction.ReleaseName = "with-notes"
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(withNotes("note here")), vals) resi, err := instAction.Run(buildChart(withNotes("note here")), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -376,9 +366,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
instAction.ReleaseName = "with-notes" instAction.ReleaseName = "with-notes"
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) resi, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -400,9 +388,7 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) {
instAction.ReleaseName = "with-notes" instAction.ReleaseName = "with-notes"
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -424,9 +410,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
instAction.SubNotes = true instAction.SubNotes = true
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -436,9 +420,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
req.NoError(err) req.NoError(err)
is.Equal("with-notes", rel.Name) is.Equal("with-notes", rel.Name)
// test run can return as either 'parent\nchild' or 'child\nparent' // test run can return as either 'parent\nchild' or 'child\nparent'
if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") { req.True(strings.Contains(rel.Info.Notes, "parent") || strings.Contains(rel.Info.Notes, "child"), "Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes)
t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes)
}
is.Equal("Install complete", rel.Info.Description) is.Equal("Install complete", rel.Info.Description)
} }
@ -451,9 +433,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) {
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(withSampleTemplates()), vals) resi, err := instAction.Run(buildChart(withSampleTemplates()), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -466,7 +446,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) {
_, err = instAction.cfg.Releases.Get(res.Name, res.Version) _, err = instAction.cfg.Releases.Get(res.Name, res.Version)
req.Error(err) req.Error(err)
is.Len(res.Hooks, 1) is.Len(res.Hooks, 1)
is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run") is.Zero(res.Hooks[0].LastRun.CompletedAt, "expect hook to not be marked as run")
is.Equal("Dry run complete", res.Info.Description) is.Equal("Dry run complete", res.Info.Description)
} }
} }
@ -480,9 +460,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
instAction.DryRunStrategy = DryRunClient instAction.DryRunStrategy = DryRunClient
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) resi, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret") is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret")
@ -495,9 +473,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
instAction.HideSecret = true instAction.HideSecret = true
vals = map[string]any{} vals = map[string]any{}
res2i, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) res2i, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res2, err := releaserToV1Release(res2i) res2, err := releaserToV1Release(res2i)
req.NoError(err) req.NoError(err)
@ -511,9 +487,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
instAction.DryRunStrategy = DryRunNone instAction.DryRunStrategy = DryRunNone
vals = map[string]any{} vals = map[string]any{}
_, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
if err == nil { req.Error(err, "Did not get the expected error when dry-run is false and hide secret is true")
t.Fatal("Did not get the expected error when dry-run is false and hide secret is true")
}
} }
// Regression test for #7955 // Regression test for #7955
@ -532,9 +506,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) {
}) })
resi, err := instAction.Run(mockChart, vals) resi, err := instAction.Run(mockChart, vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -543,6 +515,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) {
func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.DryRunStrategy = DryRunNone instAction.DryRunStrategy = DryRunNone
vals := map[string]any{} vals := map[string]any{}
@ -550,10 +523,8 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
expectedErr := `hello/templates/incorrect:1:10 expectedErr := `hello/templates/incorrect:1:10
executing "hello/templates/incorrect" at <.Values.bad.doh>: executing "hello/templates/incorrect" at <.Values.bad.doh>:
nil pointer evaluating interface {}.doh` nil pointer evaluating interface {}.doh`
if err == nil { req.Error(err, "Install should fail containing error: %s", expectedErr)
t.Fatalf("Install should fail containing error: %s", expectedErr) is.ErrorContains(err, expectedErr)
}
is.Contains(err.Error(), expectedErr)
} }
func TestInstallRelease_NoHooks(t *testing.T) { func TestInstallRelease_NoHooks(t *testing.T) {
@ -562,17 +533,15 @@ func TestInstallRelease_NoHooks(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
instAction.DisableHooks = true instAction.DisableHooks = true
instAction.ReleaseName = "no-hooks" instAction.ReleaseName = "no-hooks"
require.NoError(t, instAction.cfg.Releases.Create(releaseStub())) req.NoError(instAction.cfg.Releases.Create(releaseStub()))
vals := map[string]any{} vals := map[string]any{}
resi, err := instAction.Run(buildChart(), vals) resi, err := instAction.Run(buildChart(), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks") is.Zero(res.Hooks[0].LastRun.CompletedAt, "hooks should not run with no-hooks")
} }
func TestInstallRelease_FailedHooks(t *testing.T) { func TestInstallRelease_FailedHooks(t *testing.T) {
@ -637,7 +606,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) {
vals = map[string]any{} vals = map[string]any{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") is.ErrorContains(err, "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.")
} }
func TestInstallRelease_Wait(t *testing.T) { func TestInstallRelease_Wait(t *testing.T) {
@ -680,7 +649,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) {
_, err := instAction.RunWithContext(ctx, buildChart(), vals) _, err := instAction.RunWithContext(ctx, buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "context canceled") req.ErrorContains(err, "context canceled")
is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish time.Sleep(10 * time.Second) // wait for goroutine to finish
@ -723,8 +692,8 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) {
resi, err := instAction.Run(buildChart(), vals) resi, err := instAction.Run(buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "I timed out") req.ErrorContains(err, "I timed out")
is.Contains(err.Error(), "rollback-on-failure") req.ErrorContains(err, "rollback-on-failure")
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -748,9 +717,9 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) {
_, err := instAction.Run(buildChart(), vals) _, err := instAction.Run(buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "I timed out") req.ErrorContains(err, "I timed out")
is.Contains(err.Error(), "uninstall fail") req.ErrorContains(err, "uninstall fail")
is.Contains(err.Error(), "an error occurred while uninstalling the release") is.ErrorContains(err, "an error occurred while uninstalling the release")
}) })
} }
func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
@ -771,9 +740,9 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
resi, err := instAction.RunWithContext(ctx, buildChart(), vals) resi, err := instAction.RunWithContext(ctx, buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "context canceled") req.ErrorContains(err, "context canceled")
is.Contains(err.Error(), "rollback-on-failure") req.ErrorContains(err, "rollback-on-failure")
is.Contains(err.Error(), "uninstalled") req.ErrorContains(err, "uninstalled")
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -820,36 +789,23 @@ func TestNameTemplate(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
n, err := TemplateName(tc.tpl) t.Run(tc.tpl, func(t *testing.T) {
if err != nil { n, err := TemplateName(tc.tpl)
if tc.expectedErrorStr == "" { if tc.expectedErrorStr == "" {
t.Errorf("Was not expecting error, but got: %v", err) require.NoError(t, err)
continue } else {
} require.Error(t, err)
re, compErr := regexp.Compile(tc.expectedErrorStr) re, compErr := regexp.Compile(tc.expectedErrorStr)
if compErr != nil { require.NoError(t, compErr, "Expected error string failed to compile")
t.Errorf("Expected error string failed to compile: %v", compErr) assert.True(t, re.MatchString(err.Error()), "Error didn't match for %s expected %s", tc.tpl, tc.expectedErrorStr)
continue
}
if !re.MatchString(err.Error()) {
t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err)
continue
} }
}
if err == nil && tc.expectedErrorStr != "" {
t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr)
}
if tc.expected != "" { if tc.expected != "" {
re, err := regexp.Compile(tc.expected) re, err := regexp.Compile(tc.expected)
if err != nil { require.NoError(t, err)
t.Errorf("Expected string failed to compile: %v", err) assert.True(t, re.MatchString(n), "Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
continue
} }
if !re.MatchString(n) { })
t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
}
}
} }
} }
@ -864,9 +820,7 @@ func TestInstallReleaseOutputDir(t *testing.T) {
instAction.OutputDir = dir instAction.OutputDir = dir
_, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
_, err = os.Stat(filepath.Join(dir, "hello", "templates", "goodbye")) _, err = os.Stat(filepath.Join(dir, "hello", "templates", "goodbye"))
req.NoError(err) req.NoError(err)
@ -901,9 +855,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
newDir := filepath.Join(dir, instAction.ReleaseName) newDir := filepath.Join(dir, instAction.ReleaseName)
_, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
_, err = os.Stat(filepath.Join(newDir, "hello", "templates", "goodbye")) _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "goodbye"))
req.NoError(err) req.NoError(err)
@ -925,46 +877,37 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) {
func TestNameAndChart(t *testing.T) { func TestNameAndChart(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t)
instAction := installAction(t) instAction := installAction(t)
chartName := "./foo" chartName := "./foo"
name, chrt, err := instAction.NameAndChart([]string{chartName}) name, chrt, err := instAction.NameAndChart([]string{chartName})
if err != nil { req.NoError(err)
t.Fatal(err)
}
is.Equal(instAction.ReleaseName, name) is.Equal(instAction.ReleaseName, name)
is.Equal(chartName, chrt) is.Equal(chartName, chrt)
instAction.GenerateName = true instAction.GenerateName = true
_, _, err = instAction.NameAndChart([]string{"foo", chartName}) _, _, err = instAction.NameAndChart([]string{"foo", chartName})
if err == nil { req.Error(err, "expected an error")
t.Fatal("expected an error") req.EqualError(err, "cannot set --generate-name and also specify a name")
}
is.Equal("cannot set --generate-name and also specify a name", err.Error())
instAction.GenerateName = false instAction.GenerateName = false
instAction.NameTemplate = "{{ . }}" instAction.NameTemplate = "{{ . }}"
_, _, err = instAction.NameAndChart([]string{"foo", chartName}) _, _, err = instAction.NameAndChart([]string{"foo", chartName})
if err == nil { req.Error(err, "expected an error")
t.Fatal("expected an error") req.EqualError(err, "cannot set --name-template and also specify a name")
}
is.Equal("cannot set --name-template and also specify a name", err.Error())
instAction.NameTemplate = "" instAction.NameTemplate = ""
instAction.ReleaseName = "" instAction.ReleaseName = ""
_, _, err = instAction.NameAndChart([]string{chartName}) _, _, err = instAction.NameAndChart([]string{chartName})
if err == nil { req.Error(err, "expected an error")
t.Fatal("expected an error") req.EqualError(err, "must either provide a name or specify --generate-name")
}
is.Equal("must either provide a name or specify --generate-name", err.Error())
instAction.NameTemplate = "" instAction.NameTemplate = ""
instAction.ReleaseName = "" instAction.ReleaseName = ""
_, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"}) _, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"})
if err == nil { req.Error(err, "expected an error")
t.Fatal("expected an error") is.EqualError(err, "expected at most two arguments, unexpected arguments: bar")
}
is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error())
} }
func TestNameAndChartGenerateName(t *testing.T) { func TestNameAndChartGenerateName(t *testing.T) {
@ -1016,9 +959,7 @@ func TestNameAndChartGenerateName(t *testing.T) {
t.Parallel() t.Parallel()
name, chrt, err := instAction.NameAndChart([]string{tc.Chart}) name, chrt, err := instAction.NameAndChart([]string{tc.Chart})
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
is.Equal(tc.ExpectedName, name) is.Equal(tc.ExpectedName, name)
is.Equal(tc.Chart, chrt) is.Equal(tc.Chart, chrt)
@ -1035,9 +976,7 @@ func TestInstallWithLabels(t *testing.T) {
"key2": "val2", "key2": "val2",
} }
resi, err := instAction.Run(buildChart(), nil) resi, err := instAction.Run(buildChart(), nil)
if err != nil { req.NoError(err, "Failed install")
t.Fatalf("Failed install: %s", err)
}
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -1046,17 +985,15 @@ func TestInstallWithLabels(t *testing.T) {
func TestInstallWithSystemLabels(t *testing.T) { func TestInstallWithSystemLabels(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.Labels = map[string]string{ instAction.Labels = map[string]string{
"owner": "val1", "owner": "val1",
"key2": "val2", "key2": "val2",
} }
_, err := instAction.Run(buildChart(), nil) _, err := instAction.Run(buildChart(), nil)
if err == nil { req.Error(err, "expected an error")
t.Fatal("expected an error") is.EqualError(err, fmt.Sprintf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()))
}
is.Equal(fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
} }
func TestUrlEqual(t *testing.T) { func TestUrlEqual(t *testing.T) {
@ -1111,13 +1048,9 @@ func TestUrlEqual(t *testing.T) {
t.Parallel() t.Parallel()
u1, err := url.Parse(tc.url1) u1, err := url.Parse(tc.url1)
if err != nil { require.NoError(t, err, "Failed to parse URL1 %s", tc.url1)
t.Fatalf("Failed to parse URL1 %s: %v", tc.url1, err)
}
u2, err := url.Parse(tc.url2) u2, err := url.Parse(tc.url2)
if err != nil { require.NoError(t, err, "Failed to parse URL2 %s", tc.url2)
t.Fatalf("Failed to parse URL2 %s: %v", tc.url2, err)
}
is.Equal(tc.expected, urlEqual(u1, u2)) is.Equal(tc.expected, urlEqual(u1, u2))
}) })
@ -1161,7 +1094,7 @@ func TestInstallCRDs(t *testing.T) {
mockChart := buildChart(withFile(mockFile)) mockChart := buildChart(withFile(mockFile))
crdsToInstall := mockChart.CRDObjects() crdsToInstall := mockChart.CRDObjects()
assert.Len(t, crdsToInstall, 1) require.Len(t, crdsToInstall, 1)
assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data)
require.NoError(t, instAction.installCRDs(crdsToInstall)) require.NoError(t, instAction.installCRDs(crdsToInstall))
} }
@ -1285,11 +1218,7 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
instAction := installAction(t) instAction := installAction(t)
assert.Error(t, instAction.installCRDs(tt.input), "got nil expected err")
err := instAction.installCRDs(tt.input)
if err == nil {
t.Error("got nil expected err")
}
}) })
} }
} }

@ -21,6 +21,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/v2/lint/support" "helm.sh/helm/v4/pkg/chart/v2/lint/support"
) )
@ -89,11 +90,10 @@ func TestLintChart(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := lintChart(tt.chartPath, map[string]any{}, namespace, nil, tt.skipSchemaValidation) _, err := lintChart(tt.chartPath, map[string]any{}, namespace, nil, tt.skipSchemaValidation)
switch { if tt.err {
case err != nil && !tt.err: require.Error(t, err, "Expected a chart parsing error")
t.Errorf("%s", err) } else {
case err == nil && tt.err: require.NoError(t, err)
t.Error("Expected a chart parsing error")
} }
}) })
} }
@ -106,14 +106,8 @@ func TestNonExistentChart(t *testing.T) {
testLint := NewLint() testLint := NewLint()
result := testLint.Run(testCharts, values) result := testLint.Run(testCharts, values)
if len(result.Errors) != 1 { require.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors))
t.Error("expected one error, but got", len(result.Errors)) assert.EqualError(t, result.Errors[0], expectedError)
}
actual := result.Errors[0].Error()
if actual != expectedError {
t.Errorf("expected '%s', but got '%s'", expectedError, actual)
}
}) })
t.Run("should error out for corrupted tgz chart", func(t *testing.T) { t.Run("should error out for corrupted tgz chart", func(t *testing.T) {
@ -122,31 +116,23 @@ func TestNonExistentChart(t *testing.T) {
testLint := NewLint() testLint := NewLint()
result := testLint.Run(testCharts, values) result := testLint.Run(testCharts, values)
if len(result.Errors) != 1 { require.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors))
t.Error("expected one error, but got", len(result.Errors)) assert.EqualError(t, result.Errors[0], expectedEOFError)
}
actual := result.Errors[0].Error()
if actual != expectedEOFError {
t.Errorf("expected '%s', but got '%s'", expectedEOFError, actual)
}
}) })
} }
func TestLint_MultipleCharts(t *testing.T) { func TestLint_MultipleCharts(t *testing.T) {
testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint} testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint}
testLint := NewLint() testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { result := testLint.Run(testCharts, values)
t.Error(result.Errors) assert.Empty(t, result.Errors)
}
} }
func TestLint_EmptyResultErrors(t *testing.T) { func TestLint_EmptyResultErrors(t *testing.T) {
testCharts := []string{chart2MultipleChartLint} testCharts := []string{chart2MultipleChartLint}
testLint := NewLint() testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { result := testLint.Run(testCharts, values)
t.Error("Expected no error, got more") assert.Empty(t, result.Errors, "Expected no error, got more")
}
} }
func TestLint_ChartWithWarnings(t *testing.T) { func TestLint_ChartWithWarnings(t *testing.T) {
@ -154,18 +140,16 @@ func TestLint_ChartWithWarnings(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir} testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint() testLint := NewLint()
testLint.Strict = false testLint.Strict = false
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { result := testLint.Run(testCharts, values)
t.Error("Expected no error, got more") assert.Empty(t, result.Errors, "Expected no error, got more")
}
}) })
t.Run("should fail with one error when strict", func(t *testing.T) { t.Run("should fail with one error when strict", func(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir} testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint() testLint := NewLint()
testLint.Strict = true testLint.Strict = true
if result := testLint.Run(testCharts, values); len(result.Errors) != 1 { result := testLint.Run(testCharts, values)
t.Error("expected one error, but got", len(result.Errors)) assert.Len(t, result.Errors, 1, "expected one error")
}
}) })
} }

@ -44,9 +44,7 @@ func TestListStates(t *testing.T) {
"unknown": ListUnknown, "unknown": ListUnknown,
"totally made up key": ListUnknown, "totally made up key": ListUnknown,
} { } {
if expect != expect.FromName(input) { assert.Equal(t, expect.FromName(input), expect, "Expected %d for %s", expect, input)
t.Errorf("Expected %d for %s", expect, input)
}
// This is a cheap way to verify that ListAll actually allows everything but Unknown // This is a cheap way to verify that ListAll actually allows everything but Unknown
if got := expect.FromName(input); got != ListUnknown && got&ListAll == 0 { if got := expect.FromName(input); got != ListUnknown && got&ListAll == 0 {
t.Errorf("Expected %s to match the ListAll filter", input) t.Errorf("Expected %s to match the ListAll filter", input)
@ -54,12 +52,10 @@ func TestListStates(t *testing.T) {
} }
filter := ListDeployed | ListPendingRollback filter := ListDeployed | ListPendingRollback
if status := filter.FromName("deployed"); filter&status == 0 { status := filter.FromName("deployed")
t.Errorf("Expected %d to match mask %d", status, filter) assert.NotEqualf(t, ListStates(0), filter&status, "Expected %d to match mask %d", status, filter)
} status = filter.FromName("failed")
if status := filter.FromName("failed"); filter&status != 0 { assert.Equalf(t, ListStates(0), filter&status, "Expected %d to fail to match mask %d", status, filter)
t.Errorf("Expected %d to fail to match mask %d", status, filter)
}
} }
func TestList_Empty(t *testing.T) { func TestList_Empty(t *testing.T) {
@ -202,8 +198,7 @@ func TestList_StateMask(t *testing.T) {
} }
one.SetStatus(common.StatusUninstalled, "uninstalled") one.SetStatus(common.StatusUninstalled, "uninstalled")
err = lister.cfg.Releases.Update(one) req.NoError(lister.cfg.Releases.Update(one))
req.NoError(err)
res, err := lister.Run() res, err := lister.Run()
req.NoError(err) req.NoError(err)
@ -277,9 +272,7 @@ func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) {
five.Version = 1 five.Version = 1
for _, rel := range []*release.Release{one, two, three, four, five} { for _, rel := range []*release.Release{one, two, three, four, five} {
if err := store.Create(rel); err != nil { require.NoError(t, store.Create(rel))
t.Fatal(err)
}
} }
all, err := store.ListReleases() all, err := store.ListReleases()
@ -328,9 +321,7 @@ func makeMeSomeReleases(t *testing.T, store *storage.Storage) {
three.Version = 3 three.Version = 3
for _, rel := range []*release.Release{one, two, three} { for _, rel := range []*release.Release{one, two, three} {
if err := store.Create(rel); err != nil { require.NoError(t, store.Create(rel))
t.Fatal(err)
}
} }
all, err := store.ListReleases() all, err := store.ListReleases()
@ -389,9 +380,7 @@ func TestSelectorList(t *testing.T) {
lister := newListFixture(t) lister := newListFixture(t)
for _, rel := range []*release.Release{r1, r2, r3} { for _, rel := range []*release.Release{r1, r2, r3} {
if err := lister.cfg.Releases.Create(rel); err != nil { require.NoError(t, lister.cfg.Releases.Create(rel))
t.Fatal(err)
}
} }
t.Run("should fail selector parsing", func(t *testing.T) { t.Run("should fail selector parsing", func(t *testing.T) {

@ -17,7 +17,6 @@ limitations under the License.
package action package action
import ( import (
"errors"
"os" "os"
"path" "path"
"testing" "testing"
@ -35,18 +34,12 @@ func TestPassphraseFileFetcher(t *testing.T) {
testPkg := NewPackage() testPkg := NewPackage()
fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil)
if err != nil { require.NoError(t, err, "Unable to create passphraseFileFetcher")
t.Fatal("Unable to create passphraseFileFetcher", err)
}
passphrase, err := fetcher("key") passphrase, err := fetcher("key")
if err != nil { require.NoError(t, err, "Unable to fetch passphrase")
t.Fatal("Unable to fetch passphrase")
}
if string(passphrase) != secret { assert.Equal(t, secret, string(passphrase), "Expected %s got %s", secret, string(passphrase))
t.Errorf("Expected %s got %s", secret, string(passphrase))
}
} }
func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) {
@ -55,18 +48,12 @@ func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) {
testPkg := NewPackage() testPkg := NewPackage()
fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil)
if err != nil { require.NoError(t, err, "Unable to create passphraseFileFetcher")
t.Fatal("Unable to create passphraseFileFetcher", err)
}
passphrase, err := fetcher("key") passphrase, err := fetcher("key")
if err != nil { require.NoError(t, err, "Unable to fetch passphrase")
t.Fatal("Unable to fetch passphrase")
}
if string(passphrase) != secret { assert.Equal(t, secret, string(passphrase), "Expected %s got %s", secret, string(passphrase))
t.Errorf("Expected %s got %s", secret, string(passphrase))
}
} }
func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
@ -74,21 +61,16 @@ func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
testPkg := NewPackage() testPkg := NewPackage()
stdin, err := os.CreateTemp(directory, "non-existing") stdin, err := os.CreateTemp(directory, "non-existing")
if err != nil { require.NoError(t, err, "Unable to create test file")
t.Fatal("Unable to create test file", err)
}
if _, err := testPkg.passphraseFileFetcher("-", stdin); err == nil { _, err = testPkg.passphraseFileFetcher("-", stdin)
t.Error("Expected passphraseFileFetcher returning an error") assert.Error(t, err, "Expected passphraseFileFetcher returning an error")
}
} }
func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) {
testPkg := NewPackage() testPkg := NewPackage()
stdin, w, err := os.Pipe() stdin, w, err := os.Pipe()
if err != nil { require.NoError(t, err, "Unable to create pipe")
t.Fatal("Unable to create pipe", err)
}
passphrase := "secret-from-stdin" passphrase := "secret-from-stdin"
@ -99,18 +81,12 @@ func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) {
for range 4 { for range 4 {
fetcher, err := testPkg.passphraseFileFetcher("-", stdin) fetcher, err := testPkg.passphraseFileFetcher("-", stdin)
if err != nil { require.NoError(t, err, "Expected passphraseFileFetcher to not return an error")
t.Errorf("Expected passphraseFileFetcher to not return an error, but got %v", err)
}
pass, err := fetcher("key") pass, err := fetcher("key")
if err != nil { require.NoError(t, err, "Expected passphraseFileFetcher invocation to succeed")
t.Errorf("Expected passphraseFileFetcher invocation to succeed, failed with %v", err)
}
if string(pass) != string(passphrase) { assert.Equal(t, string(passphrase), string(pass), "Expected multiple passphrase fetch to return %q, got %q", passphrase, pass)
t.Errorf("Expected multiple passphrase fetch to return %q, got %q", passphrase, pass)
}
} }
} }
@ -148,9 +124,7 @@ func TestValidateVersion(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := validateVersion(tt.args.ver); err != nil { if err := validateVersion(tt.args.ver); err != nil {
if !errors.Is(err, tt.wantErr) { assert.ErrorIs(t, err, tt.wantErr)
t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err)
}
} }
}) })
} }

@ -48,9 +48,7 @@ func TestPullSetRegistryClient(t *testing.T) {
func TestPullRun_ChartNotFound(t *testing.T) { func TestPullRun_ChartNotFound(t *testing.T) {
srv, err := startLocalServerForTests(t, nil) srv, err := startLocalServerForTests(t, nil)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer srv.Close() defer srv.Close()
config := actionConfigFixture(t) config := actionConfigFixture(t)

@ -190,8 +190,7 @@ func TestGetContainerLogs_MultipleContainers(t *testing.T) {
rt := &ReleaseTesting{Namespace: "default"} rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "test-pod") require.NoError(t, rt.getContainerLogs(&buf, client, "test-pod"))
require.NoError(t, err)
output := buf.String() output := buf.String()
assert.Contains(t, output, "POD LOGS: test-pod (main)") assert.Contains(t, output, "POD LOGS: test-pod (main)")
assert.Contains(t, output, "POD LOGS: test-pod (sidecar)") assert.Contains(t, output, "POD LOGS: test-pod (sidecar)")
@ -217,8 +216,7 @@ func TestGetContainerLogs_WithInitContainers(t *testing.T) {
rt := &ReleaseTesting{Namespace: "default"} rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "test-pod") require.NoError(t, rt.getContainerLogs(&buf, client, "test-pod"))
require.NoError(t, err)
output := buf.String() output := buf.String()
// Init containers should appear before regular containers // Init containers should appear before regular containers
assert.Contains(t, output, "POD LOGS: test-pod (init-setup)") assert.Contains(t, output, "POD LOGS: test-pod (init-setup)")
@ -230,8 +228,7 @@ func TestGetContainerLogs_PodNotFound(t *testing.T) {
rt := &ReleaseTesting{Namespace: "default"} rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "nonexistent-pod") assert.ErrorContains(t, rt.getContainerLogs(&buf, client, "nonexistent-pod"), "unable to get pod nonexistent-pod")
assert.ErrorContains(t, err, "unable to get pod nonexistent-pod")
} }
func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) { func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) {
@ -252,8 +249,7 @@ func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) {
rt := &ReleaseTesting{Namespace: "default"} rt := &ReleaseTesting{Namespace: "default"}
var buf bytes.Buffer var buf bytes.Buffer
err := rt.getContainerLogs(&buf, client, "multi-test") require.NoError(t, rt.getContainerLogs(&buf, client, "multi-test"))
require.NoError(t, err)
output := buf.String() output := buf.String()
assert.Contains(t, output, "POD LOGS: multi-test (container-a)") assert.Contains(t, output, "POD LOGS: multi-test (container-a)")
assert.Contains(t, output, "POD LOGS: multi-test (container-b)") assert.Contains(t, output, "POD LOGS: multi-test (container-b)")

@ -78,8 +78,7 @@ func TestRollback_WaitOptionsPassedDownstream(t *testing.T) {
// Access the underlying FailingKubeClient to check recorded options // Access the underlying FailingKubeClient to check recorded options
failer := config.KubeClient.(*kubefake.FailingKubeClient) failer := config.KubeClient.(*kubefake.FailingKubeClient)
err := client.Run(rel.Name) req.NoError(client.Run(rel.Name))
req.NoError(err)
// Verify that WaitOptions were passed to GetWaiter // Verify that WaitOptions were passed to GetWaiter
is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter")

@ -21,6 +21,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
@ -47,9 +48,7 @@ func TestShow(t *testing.T) {
} }
output, err := client.Run("") output, err := client.Run("")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
expect := `name: alpine expect := `name: alpine
@ -69,9 +68,7 @@ bar
baz baz
` `
if output != expect { assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output)
t.Errorf("Expected\n%q\nGot\n%q\n", expect, output)
}
} }
func TestShowNoValues(t *testing.T) { func TestShowNoValues(t *testing.T) {
@ -82,13 +79,9 @@ func TestShowNoValues(t *testing.T) {
// Regression tests for missing values. See issue #1024. // Regression tests for missing values. See issue #1024.
client.OutputFormat = ShowValues client.OutputFormat = ShowValues
output, err := client.Run("") output, err := client.Run("")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
if output != "" { assert.Empty(t, output, "expected empty values buffer, got %s", output)
t.Errorf("expected empty values buffer, got %s", output)
}
} }
func TestShowValuesByJsonPathFormat(t *testing.T) { func TestShowValuesByJsonPathFormat(t *testing.T) {
@ -97,13 +90,9 @@ func TestShowValuesByJsonPathFormat(t *testing.T) {
client.JSONPathTemplate = "{$.nestedKey.simpleKey}" client.JSONPathTemplate = "{$.nestedKey.simpleKey}"
client.chart = buildChart(withSampleValues()) client.chart = buildChart(withSampleValues())
output, err := client.Run("") output, err := client.Run("")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
expect := "simpleValue" expect := "simpleValue"
if output != expect { assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output)
t.Errorf("Expected\n%q\nGot\n%q\n", expect, output)
}
} }
func TestShowCRDs(t *testing.T) { func TestShowCRDs(t *testing.T) {
@ -121,9 +110,7 @@ func TestShowCRDs(t *testing.T) {
} }
output, err := client.Run("") output, err := client.Run("")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
expect := `--- expect := `---
foo foo
@ -135,9 +122,7 @@ bar
baz baz
` `
if output != expect { assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output)
t.Errorf("Expected\n%q\nGot\n%q\n", expect, output)
}
} }
func TestShowNoReadme(t *testing.T) { func TestShowNoReadme(t *testing.T) {
@ -154,9 +139,7 @@ func TestShowNoReadme(t *testing.T) {
} }
output, err := client.Run("") output, err := client.Run("")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
expect := `name: alpine expect := `name: alpine
@ -167,9 +150,7 @@ foo
bar bar
` `
if output != expect { assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output)
t.Errorf("Expected\n%q\nGot\n%q\n", expect, output)
}
} }
func TestShowSetRegistryClient(t *testing.T) { func TestShowSetRegistryClient(t *testing.T) {

@ -121,7 +121,7 @@ func TestUninstallRelease_Wait(t *testing.T) {
unAction.cfg.KubeClient = failer unAction.cfg.KubeClient = failer
resi, err := unAction.Run(rel.Name) resi, err := unAction.Run(rel.Name)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "U timed out") req.ErrorContains(err, "U timed out")
res, err := releaserToV1Release(resi.Release) res, err := releaserToV1Release(resi.Release)
req.NoError(err) req.NoError(err)
is.Equal(common.StatusUninstalled, res.Info.Status) is.Equal(common.StatusUninstalled, res.Info.Status)
@ -163,7 +163,7 @@ func TestUninstallRelease_Cascade(t *testing.T) {
unAction.cfg.KubeClient = failer unAction.cfg.KubeClient = failer
_, err := unAction.Run(rel.Name) _, err := unAction.Run(rel.Name)
require.Error(t, err) require.Error(t, err)
is.Contains(err.Error(), "failed to delete release: come-fail-away") is.ErrorContains(err, "failed to delete release: come-fail-away")
} }
func TestUninstallRun_UnreachableKubeClient(t *testing.T) { func TestUninstallRun_UnreachableKubeClient(t *testing.T) {

@ -176,8 +176,8 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) {
resi, err := upAction.Run(rel.Name, buildChart(), vals) resi, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "arming key removed") req.ErrorContains(err, "arming key removed")
is.Contains(err.Error(), "rollback-on-failure") req.ErrorContains(err, "rollback-on-failure")
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
@ -207,8 +207,8 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) {
_, err := upAction.Run(rel.Name, buildChart(), vals) _, err := upAction.Run(rel.Name, buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "update fail") req.ErrorContains(err, "update fail")
is.Contains(err.Error(), "an error occurred while rolling back the release") is.ErrorContains(err, "an error occurred while rolling back the release")
}) })
} }
@ -240,8 +240,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
rel.Info.Status = common.StatusDeployed rel.Info.Status = common.StatusDeployed
rel.Config = existingValues rel.Config = existingValues
err := upAction.cfg.Releases.Create(rel) req.NoError(upAction.cfg.Releases.Create(rel))
req.NoError(err)
upAction.ReuseValues = true upAction.ReuseValues = true
// setting newValues and upgrading // setting newValues and upgrading
@ -253,11 +252,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
// Now make sure it is actually upgraded // Now make sure it is actually upgraded
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
req.NoError(err) req.NoError(err)
req.NotNil(updatedResi, "Updated Release is nil")
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
updatedRes, err := releaserToV1Release(updatedResi) updatedRes, err := releaserToV1Release(updatedResi)
req.NoError(err) req.NoError(err)
@ -303,8 +299,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
Config: existingValues, Config: existingValues,
Version: 1, Version: 1,
} }
err := upAction.cfg.Releases.Create(rel) req.NoError(upAction.cfg.Releases.Create(rel))
req.NoError(err)
upAction.ReuseValues = true upAction.ReuseValues = true
sampleChartWithSubChart := buildChart( sampleChartWithSubChart := buildChart(
@ -322,11 +317,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
// Now get the upgraded release // Now get the upgraded release
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
req.NoError(err) req.NoError(err)
req.NotNil(updatedResi, "Updated Release is nil")
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
updatedRes, err := releaserToV1Release(updatedResi) updatedRes, err := releaserToV1Release(updatedResi)
req.NoError(err) req.NoError(err)
@ -373,8 +365,7 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) {
rel.Info.Status = common.StatusDeployed rel.Info.Status = common.StatusDeployed
rel.Config = existingValues rel.Config = existingValues
err := upAction.cfg.Releases.Create(rel) req.NoError(upAction.cfg.Releases.Create(rel))
req.NoError(err)
upAction.ResetThenReuseValues = true upAction.ResetThenReuseValues = true
// setting newValues and upgrading // setting newValues and upgrading
@ -386,11 +377,8 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) {
// Now make sure it is actually upgraded // Now make sure it is actually upgraded
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
req.NoError(err) req.NoError(err)
req.NotNil(updatedResi, "Updated Release is nil")
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
updatedRes, err := releaserToV1Release(updatedResi) updatedRes, err := releaserToV1Release(updatedResi)
req.NoError(err) req.NoError(err)
@ -417,7 +405,7 @@ func TestUpgradeRelease_Pending(t *testing.T) {
vals := map[string]any{} vals := map[string]any{}
_, err := upAction.Run(rel.Name, buildChart(), vals) _, err := upAction.Run(rel.Name, buildChart(), vals)
req.Contains(err.Error(), "progress", err) req.ErrorContains(err, "progress")
} }
func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
@ -470,7 +458,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) {
resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
req.Error(err) req.Error(err)
is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled") req.ErrorContains(err, "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled")
res, err := releaserToV1Release(resi) res, err := releaserToV1Release(resi)
req.NoError(err) req.NoError(err)
// Now make sure it is actually upgraded // Now make sure it is actually upgraded
@ -491,9 +479,8 @@ func TestMergeCustomLabels(t *testing.T) {
{map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "null", "k2": "v3"}, map[string]string{"k2": "v3"}}, {map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "null", "k2": "v3"}, map[string]string{"k2": "v3"}},
} }
for _, test := range tests { for _, test := range tests {
if output := mergeCustomLabels(test[0], test[1]); !reflect.DeepEqual(test[2], output) { output := mergeCustomLabels(test[0], test[1])
t.Errorf("Expected {%v}, got {%v}", test[2], output) assert.Truef(t, reflect.DeepEqual(test[2], output), "Expected {%v}, got {%v}", test[2], output)
}
} }
} }
@ -511,8 +498,7 @@ func TestUpgradeRelease_Labels(t *testing.T) {
} }
rel.Info.Status = common.StatusDeployed rel.Info.Status = common.StatusDeployed
err := upAction.cfg.Releases.Create(rel) req.NoError(upAction.cfg.Releases.Create(rel))
req.NoError(err)
upAction.Labels = map[string]string{ upAction.Labels = map[string]string{
"key1": "null", "key1": "null",
@ -528,11 +514,8 @@ func TestUpgradeRelease_Labels(t *testing.T) {
// Now make sure it is actually upgraded and labels were merged // Now make sure it is actually upgraded and labels were merged
updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2)
req.NoError(err) req.NoError(err)
req.NotNil(updatedResi, "Updated Release is nil")
if updatedResi == nil {
is.Fail("Updated Release is nil")
return
}
updatedRes, err := releaserToV1Release(updatedResi) updatedRes, err := releaserToV1Release(updatedResi)
req.NoError(err) req.NoError(err)
is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Equal(common.StatusDeployed, updatedRes.Info.Status)
@ -541,11 +524,8 @@ func TestUpgradeRelease_Labels(t *testing.T) {
// Now make sure it is suppressed release still contains original labels // Now make sure it is suppressed release still contains original labels
initialResi, err := upAction.cfg.Releases.Get(res.Name, 1) initialResi, err := upAction.cfg.Releases.Get(res.Name, 1)
req.NoError(err) req.NoError(err)
req.NotNil(initialResi, "Initial Release is nil")
if initialResi == nil {
is.Fail("Updated Release is nil")
return
}
initialRes, err := releaserToV1Release(initialResi) initialRes, err := releaserToV1Release(initialResi)
req.NoError(err) req.NoError(err)
is.Equal(common.StatusSuperseded, initialRes.Info.Status) is.Equal(common.StatusSuperseded, initialRes.Info.Status)
@ -566,8 +546,7 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) {
} }
rel.Info.Status = common.StatusDeployed rel.Info.Status = common.StatusDeployed
err := upAction.cfg.Releases.Create(rel) req.NoError(upAction.cfg.Releases.Create(rel))
req.NoError(err)
upAction.Labels = map[string]string{ upAction.Labels = map[string]string{
"key1": "null", "key1": "null",
@ -575,12 +554,10 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) {
"owner": "val3", "owner": "val3",
} }
// setting newValues and upgrading // setting newValues and upgrading
_, err = upAction.Run(rel.Name, buildChart(), nil) _, err := upAction.Run(rel.Name, buildChart(), nil)
if err == nil { require.Error(t, err, "expected an error")
t.Fatal("expected an error")
}
is.Equal(fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err) is.EqualError(err, fmt.Sprintf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()))
} }
func TestUpgradeRelease_DryRun(t *testing.T) { func TestUpgradeRelease_DryRun(t *testing.T) {

@ -132,7 +132,7 @@ func TestRequireAdoption(t *testing.T) {
// Verify that a resource that lacks labels/annotations can be adopted // Verify that a resource that lacks labels/annotations can be adopted
found, err := requireAdoption(resources) found, err := requireAdoption(resources)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, found, 1) require.Len(t, found, 1)
assert.Equal(t, found[0], existing) assert.Equal(t, found[0], existing)
assert.NotSame(t, found[0], existing) assert.NotSame(t, found[0], existing)
} }
@ -157,7 +157,7 @@ func TestExistingResourceConflict(t *testing.T) {
// Verify only existing resources are returned // Verify only existing resources are returned
found, err := existingResourceConflict(resources, releaseName, releaseNamespace) found, err := existingResourceConflict(resources, releaseName, releaseNamespace)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, found, 1) require.Len(t, found, 1)
assert.Equal(t, found[0], existing) assert.Equal(t, found[0], existing)
assert.NotSame(t, found[0], existing) assert.NotSame(t, found[0], existing)
@ -171,45 +171,38 @@ func TestCheckOwnership(t *testing.T) {
deployFoo := newDeploymentResource("foo", "ns-a", "") deployFoo := newDeploymentResource("foo", "ns-a", "")
// Verify that a resource that lacks labels/annotations is not owned // Verify that a resource that lacks labels/annotations is not owned
err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
require.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
// Set managed by label and verify annotation error message // Set managed by label and verify annotation error message
_ = accessor.SetLabels(deployFoo.Object, map[string]string{ _ = accessor.SetLabels(deployFoo.Object, map[string]string{
appManagedByLabel: appManagedByHelm, appManagedByLabel: appManagedByHelm,
}) })
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
require.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
// Set only the release name annotation and verify missing release namespace error message // Set only the release name annotation and verify missing release namespace error message
_ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{
helmReleaseNameAnnotation: "rel-a", helmReleaseNameAnnotation: "rel-a",
}) })
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
require.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
// Set both release name and namespace annotations and verify no ownership errors // Set both release name and namespace annotations and verify no ownership errors
_ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{
helmReleaseNameAnnotation: "rel-a", helmReleaseNameAnnotation: "rel-a",
helmReleaseNamespaceAnnotation: "ns-a", helmReleaseNamespaceAnnotation: "ns-a",
}) })
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") require.NoError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"))
require.NoError(t, err)
// Verify ownership error for wrong release name // Verify ownership error for wrong release name
err = checkOwnership(deployFoo.Object, "rel-b", "ns-a") require.EqualError(t, checkOwnership(deployFoo.Object, "rel-b", "ns-a"), `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`)
require.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`)
// Verify ownership error for wrong release namespace // Verify ownership error for wrong release namespace
err = checkOwnership(deployFoo.Object, "rel-a", "ns-b") require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-b"), `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`)
require.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`)
// Verify ownership error for wrong manager label // Verify ownership error for wrong manager label
_ = accessor.SetLabels(deployFoo.Object, map[string]string{ _ = accessor.SetLabels(deployFoo.Object, map[string]string{
appManagedByLabel: "helm", appManagedByLabel: "helm",
}) })
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") assert.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`)
assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`)
} }
func TestVerifyOwnershipBeforeDelete(t *testing.T) { func TestVerifyOwnershipBeforeDelete(t *testing.T) {
@ -249,8 +242,8 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) {
ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, ownedList, 1) require.Len(t, ownedList, 1)
assert.Len(t, unownedList, 1) require.Len(t, unownedList, 1)
assert.Equal(t, "owned", ownedList[0].Name) assert.Equal(t, "owned", ownedList[0].Name)
assert.Equal(t, "unowned", unownedList[0].Name) assert.Equal(t, "unowned", unownedList[0].Name)
}) })
@ -297,8 +290,8 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) {
ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, ownedList, 1) require.Len(t, ownedList, 1)
assert.Len(t, unownedList, 1) require.Len(t, unownedList, 1)
assert.Equal(t, "owned", ownedList[0].Name) assert.Equal(t, "owned", ownedList[0].Name)
assert.Equal(t, "unowned", unownedList[0].Name) assert.Equal(t, "unowned", unownedList[0].Name)
}) })
@ -306,32 +299,26 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) {
func TestSetMetadataVisitor(t *testing.T) { func TestSetMetadataVisitor(t *testing.T) {
var ( var (
err error
deployFoo = newDeploymentResource("foo", "ns-a", "") deployFoo = newDeploymentResource("foo", "ns-a", "")
deployBar = newDeploymentResource("bar", "ns-a-system", "") deployBar = newDeploymentResource("bar", "ns-a-system", "")
resources = kube.ResourceList{deployFoo, deployBar} resources = kube.ResourceList{deployFoo, deployBar}
) )
// Set release tracking metadata and verify no error // Set release tracking metadata and verify no error
err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)) require.NoError(t, resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)))
require.NoError(t, err)
// Verify that release "b" cannot take ownership of "a" // Verify that release "b" cannot take ownership of "a"
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) require.Error(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)))
require.Error(t, err)
// Force release "b" to take ownership // Force release "b" to take ownership
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)) require.NoError(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)))
require.NoError(t, err)
// Check that there is now no ownership error when setting metadata without force // Check that there is now no ownership error when setting metadata without force
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) require.NoError(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)))
require.NoError(t, err)
// Add a new resource that is missing ownership metadata and verify error // Add a new resource that is missing ownership metadata and verify error
resources.Append(newDeploymentResource("baz", "default", "")) resources.Append(newDeploymentResource("baz", "default", ""))
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) assert.ErrorContains(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)), `Deployment "baz" in namespace "" cannot be owned`)
assert.ErrorContains(t, err, `Deployment "baz" in namespace "" cannot be owned`)
} }
func TestValidateNameAndGenerateName(t *testing.T) { func TestValidateNameAndGenerateName(t *testing.T) {

@ -17,68 +17,44 @@ package common
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestVersionSet(t *testing.T) { func TestVersionSet(t *testing.T) {
vs := VersionSet{"v1", "apps/v1"} vs := VersionSet{"v1", "apps/v1"}
if d := len(vs); d != 2 { d := len(vs)
t.Errorf("Expected 2 versions, got %d", d) assert.Equalf(t, 2, d, "Expected 2 versions, got %d", d)
}
if !vs.Has("apps/v1") { assert.True(t, vs.Has("apps/v1"), "Expected to find apps/v1")
t.Error("Expected to find apps/v1")
}
if vs.Has("Spanish/inquisition") { assert.False(t, vs.Has("Spanish/inquisition"), "No one expects the Spanish/inquisition")
t.Error("No one expects the Spanish/inquisition")
}
} }
func TestDefaultVersionSet(t *testing.T) { func TestDefaultVersionSet(t *testing.T) {
if !DefaultVersionSet.Has("v1") { assert.True(t, DefaultVersionSet.Has("v1"), "Expected core v1 version set")
t.Error("Expected core v1 version set")
}
} }
func TestDefaultCapabilities(t *testing.T) { func TestDefaultCapabilities(t *testing.T) {
caps := DefaultCapabilities caps := DefaultCapabilities
kv := caps.KubeVersion kv := caps.KubeVersion
if kv.String() != "v1.20.0" { assert.Equalf(t, "v1.20.0", kv.String(), "Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String())
t.Errorf("Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String()) assert.Equalf(t, "v1.20.0", kv.Version, "Expected default KubeVersion.Version to be v1.20.0, got %q", kv.Version)
} assert.Equalf(t, "v1.20.0", kv.GitVersion(), "Expected default KubeVersion.GitVersion() to be v1.20.0, got %q", kv.Version)
if kv.Version != "v1.20.0" { assert.Equalf(t, "1", kv.Major, "Expected default KubeVersion.Major to be 1, got %q", kv.Major)
t.Errorf("Expected default KubeVersion.Version to be v1.20.0, got %q", kv.Version) assert.Equalf(t, "20", kv.Minor, "Expected default KubeVersion.Minor to be 20, got %q", kv.Minor)
}
if kv.GitVersion() != "v1.20.0" {
t.Errorf("Expected default KubeVersion.GitVersion() to be v1.20.0, got %q", kv.Version)
}
if kv.Major != "1" {
t.Errorf("Expected default KubeVersion.Major to be 1, got %q", kv.Major)
}
if kv.Minor != "20" {
t.Errorf("Expected default KubeVersion.Minor to be 20, got %q", kv.Minor)
}
hv := caps.HelmVersion hv := caps.HelmVersion
if hv.Version != "v4.2" { assert.Equalf(t, "v4.2", hv.Version, "Expected default HelmVersion to be v4.2, got %q", hv.Version)
t.Errorf("Expected default HelmVersion to be v4.2, got %q", hv.Version)
}
} }
func TestParseKubeVersion(t *testing.T) { func TestParseKubeVersion(t *testing.T) {
kv, err := ParseKubeVersion("v1.16.0") kv, err := ParseKubeVersion("v1.16.0")
if err != nil { require.NoError(t, err, "Expected v1.16.0 to parse successfully")
t.Error("Expected v1.16.0 to parse successfully") assert.Equalf(t, "v1.16.0", kv.Version, "Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String())
} assert.Equalf(t, "1", kv.Major, "Expected parsed KubeVersion.Major to be 1, got %q", kv.Major)
if kv.Version != "v1.16.0" { assert.Equalf(t, "16", kv.Minor, "Expected parsed KubeVersion.Minor to be 16, got %q", kv.Minor)
t.Errorf("Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String())
}
if kv.Major != "1" {
t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major)
}
if kv.Minor != "16" {
t.Errorf("Expected parsed KubeVersion.Minor to be 16, got %q", kv.Minor)
}
} }
func TestParseKubeVersionWithVendorSuffixes(t *testing.T) { func TestParseKubeVersionWithVendorSuffixes(t *testing.T) {
@ -101,21 +77,11 @@ func TestParseKubeVersionWithVendorSuffixes(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
kv, err := ParseKubeVersion(tt.input) kv, err := ParseKubeVersion(tt.input)
if err != nil { require.NoErrorf(t, err, "ParseKubeVersion()")
t.Fatalf("ParseKubeVersion() error = %v", err) assert.Equalf(t, tt.wantVer, kv.Version, "Version = %q, want %q", kv.Version, tt.wantVer)
} assert.Equalf(t, tt.wantString, kv.String(), "String() = %q, want %q", kv.String(), tt.wantString)
if kv.Version != tt.wantVer { assert.Equalf(t, tt.wantMajor, kv.Major, "Major = %q, want %q", kv.Major, tt.wantMajor)
t.Errorf("Version = %q, want %q", kv.Version, tt.wantVer) assert.Equalf(t, tt.wantMinor, kv.Minor, "Minor = %q, want %q", kv.Minor, tt.wantMinor)
}
if kv.String() != tt.wantString {
t.Errorf("String() = %q, want %q", kv.String(), tt.wantString)
}
if kv.Major != tt.wantMajor {
t.Errorf("Major = %q, want %q", kv.Major, tt.wantMajor)
}
if kv.Minor != tt.wantMinor {
t.Errorf("Minor = %q, want %q", kv.Minor, tt.wantMinor)
}
}) })
} }
} }

@ -141,9 +141,7 @@ func TestCoalesceValues(t *testing.T) {
) )
vals, err := common.ReadValues(testCoalesceValuesYaml) vals, err := common.ReadValues(testCoalesceValuesYaml)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// taking a copy of the values before passing it // taking a copy of the values before passing it
// to CoalesceValues as argument, so that we can // to CoalesceValues as argument, so that we can
@ -152,9 +150,7 @@ func TestCoalesceValues(t *testing.T) {
maps.Copy(valsCopy, vals) maps.Copy(valsCopy, vals)
v, err := CoalesceValues(c, vals) v, err := CoalesceValues(c, vals)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
j, _ := json.MarshalIndent(v, "", " ") j, _ := json.MarshalIndent(v, "", " ")
t.Logf("Coalesced Values: %s", string(j)) t.Logf("Coalesced Values: %s", string(j))
@ -211,32 +207,26 @@ func TestCoalesceValues(t *testing.T) {
nullKeys := []string{"bottom", "right", "left", "front"} nullKeys := []string{"bottom", "right", "left", "front"}
for _, nullKey := range nullKeys { for _, nullKey := range nullKeys {
if _, ok := v[nullKey]; ok { _, ok := v[nullKey]
t.Errorf("Expected key %q to be removed, still present", nullKey) assert.Falsef(t, ok, "Expected key %q to be removed, still present", nullKey)
}
} }
if _, ok := v["nested"].(map[string]any)["boat"]; ok { _, ok := v["nested"].(map[string]any)["boat"]
t.Error("Expected nested boat key to be removed, still present") assert.False(t, ok, "Expected nested boat key to be removed, still present")
}
subchart := v["pequod"].(map[string]any) subchart := v["pequod"].(map[string]any)
if _, ok := subchart["boat"]; ok { _, ok = subchart["boat"]
t.Error("Expected subchart boat key to be removed, still present") assert.False(t, ok, "Expected subchart boat key to be removed, still present")
}
subsubchart := subchart["ahab"].(map[string]any) subsubchart := subchart["ahab"].(map[string]any)
if _, ok := subsubchart["boat"]; ok { _, ok = subsubchart["boat"]
t.Error("Expected sub-subchart ahab boat key to be removed, still present") assert.False(t, ok, "Expected sub-subchart ahab boat key to be removed, still present")
}
if _, ok := subsubchart["nested"].(map[string]any)["boat"]; ok { _, ok = subsubchart["nested"].(map[string]any)["boat"]
t.Error("Expected sub-subchart nested boat key to be removed, still present") assert.False(t, ok, "Expected sub-subchart nested boat key to be removed, still present")
}
if _, ok := subsubchart["object"]; ok { _, ok = subsubchart["object"]
t.Error("Expected sub-subchart object map to be removed, still present") assert.False(t, ok, "Expected sub-subchart object map to be removed, still present")
}
// CoalesceValues should not mutate the passed arguments // CoalesceValues should not mutate the passed arguments
is.Equal(valsCopy, vals) is.Equal(valsCopy, vals)
@ -306,9 +296,7 @@ func TestMergeValues(t *testing.T) {
) )
vals, err := common.ReadValues(testCoalesceValuesYaml) vals, err := common.ReadValues(testCoalesceValuesYaml)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// taking a copy of the values before passing it // taking a copy of the values before passing it
// to MergeValues as argument, so that we can // to MergeValues as argument, so that we can
@ -317,9 +305,7 @@ func TestMergeValues(t *testing.T) {
maps.Copy(valsCopy, vals) maps.Copy(valsCopy, vals)
v, err := MergeValues(c, vals) v, err := MergeValues(c, vals)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
j, _ := json.MarshalIndent(v, "", " ") j, _ := json.MarshalIndent(v, "", " ")
t.Logf("Coalesced Values: %s", string(j)) t.Logf("Coalesced Values: %s", string(j))
@ -377,25 +363,19 @@ func TestMergeValues(t *testing.T) {
// removed. // removed.
nullKeys := []string{"bottom", "right", "left", "front"} nullKeys := []string{"bottom", "right", "left", "front"}
for _, nullKey := range nullKeys { for _, nullKey := range nullKeys {
if vv, ok := v[nullKey]; !ok { vv, ok := v[nullKey]
t.Errorf("Expected key %q to be present but it was removed", nullKey) assert.Truef(t, ok, "Expected key %q to be present but it was removed", nullKey)
} else if vv != nil { assert.Nilf(t, vv, "Expected key %q to be null but it has a value of %v", nullKey, vv)
t.Errorf("Expected key %q to be null but it has a value of %v", nullKey, vv)
}
} }
if _, ok := v["nested"].(map[string]any)["boat"]; !ok { _, ok := v["nested"].(map[string]any)["boat"]
t.Error("Expected nested boat key to be present but it was removed") assert.True(t, ok, "Expected nested boat key to be present but it was removed")
}
subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any) subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any)
if _, ok := subchart["boat"]; !ok { assert.Contains(t, subchart, "boat", "Expected subchart boat key to be present but it was removed")
t.Error("Expected subchart boat key to be present but it was removed")
}
if _, ok := subchart["nested"].(map[string]any)["bar"]; !ok { _, ok = subchart["nested"].(map[string]any)["bar"]
t.Error("Expected subchart nested bar key to be present but it was removed") assert.True(t, ok, "Expected subchart nested bar key to be present but it was removed")
}
// CoalesceValues should not mutate the passed arguments // CoalesceValues should not mutate the passed arguments
is.Equal(valsCopy, vals) is.Equal(valsCopy, vals)
@ -433,47 +413,27 @@ func TestCoalesceTables(t *testing.T) {
// otherwise the values are coalesced. // otherwise the values are coalesced.
CoalesceTables(dst, src) CoalesceTables(dst, src)
if dst["name"] != "Ishmael" { assert.Equal(t, "Ishmael", dst["name"], "Unexpected name: %s", dst["name"])
t.Errorf("Unexpected name: %s", dst["name"]) assert.Equal(t, "whaler", dst["occupation"], "Unexpected occupation: %s", dst["occupation"])
}
if dst["occupation"] != "whaler" {
t.Errorf("Unexpected occupation: %s", dst["occupation"])
}
addr, ok := dst["address"].(map[string]any) addr, ok := dst["address"].(map[string]any)
if !ok { require.True(t, ok, "Address went away.")
t.Fatal("Address went away.") assert.Equal(t, "123 Spouter Inn Ct.", addr["street"].(string), "Unexpected address: %v", addr["street"])
} assert.Equal(t, "Nantucket", addr["city"].(string), "Unexpected city: %v", addr["city"])
assert.Equal(t, "MA", addr["state"].(string), "Unexpected state: %v", addr["state"])
if addr["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr["street"])
}
if addr["city"].(string) != "Nantucket" { _, ok = addr["country"]
t.Errorf("Unexpected city: %v", addr["city"]) assert.False(t, ok, "The country is not left out.")
}
if addr["state"].(string) != "MA" { det, ok := dst["details"].(map[string]any)
t.Errorf("Unexpected state: %v", addr["state"]) require.Truef(t, ok, "Details is the wrong type: %v", dst["details"])
}
if _, ok = addr["country"]; ok { _, ok = det["friends"]
t.Error("The country is not left out.") assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(")
} assert.Equal(t, "pequod", dst["boat"].(string), "Expected boat string, got %v", dst["boat"])
if det, ok := dst["details"].(map[string]any); !ok { _, ok = dst["hole"]
t.Fatalf("Details is the wrong type: %v", dst["details"]) assert.False(t, ok, "The hole still exists.")
} else if _, ok := det["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst["boat"].(string) != "pequod" {
t.Errorf("Expected boat string, got %v", dst["boat"])
}
if _, ok = dst["hole"]; ok {
t.Error("The hole still exists.")
}
dst2 := map[string]any{ dst2 := map[string]any{
"name": "Ishmael", "name": "Ishmael",
@ -493,40 +453,21 @@ func TestCoalesceTables(t *testing.T) {
// this happens when the --reuse-values flag is set but the chart has no modifications yet // this happens when the --reuse-values flag is set but the chart has no modifications yet
CoalesceTables(dst2, nil) CoalesceTables(dst2, nil)
if dst2["name"] != "Ishmael" { assert.Equal(t, "Ishmael", dst2["name"], "Unexpected name: %s", dst2["name"])
t.Errorf("Unexpected name: %s", dst2["name"])
}
addr2, ok := dst2["address"].(map[string]any) addr2, ok := dst2["address"].(map[string]any)
if !ok { require.True(t, ok, "Address went away.")
t.Fatal("Address went away.") assert.Equal(t, "123 Spouter Inn Ct.", addr2["street"].(string), "Unexpected address: %v", addr2["street"])
} assert.Equal(t, "Nantucket", addr2["city"].(string), "Unexpected city: %v", addr2["city"])
assert.Equal(t, "US", addr2["country"].(string), "Unexpected Country: %v", addr2["country"])
if addr2["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr2["street"]) det2, ok := dst2["details"].(map[string]any)
} require.Truef(t, ok, "Details is the wrong type: %v", dst2["details"])
if addr2["city"].(string) != "Nantucket" { _, ok = det2["friends"]
t.Errorf("Unexpected city: %v", addr2["city"]) assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(")
} assert.Equal(t, "pequod", dst2["boat"].(string), "Expected boat string, got %v", dst2["boat"])
assert.Equal(t, "black", dst2["hole"].(string), "Expected hole string, got %v", dst2["boat"])
if addr2["country"].(string) != "US" {
t.Errorf("Unexpected Country: %v", addr2["country"])
}
if det2, ok := dst2["details"].(map[string]any); !ok {
t.Fatalf("Details is the wrong type: %v", dst2["details"])
} else if _, ok := det2["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst2["boat"].(string) != "pequod" {
t.Errorf("Expected boat string, got %v", dst2["boat"])
}
if dst2["hole"].(string) != "black" {
t.Errorf("Expected hole string, got %v", dst2["boat"])
}
} }
func TestMergeTables(t *testing.T) { func TestMergeTables(t *testing.T) {
@ -561,51 +502,30 @@ func TestMergeTables(t *testing.T) {
// otherwise the values are coalesced. // otherwise the values are coalesced.
MergeTables(dst, src) MergeTables(dst, src)
if dst["name"] != "Ishmael" { assert.Equal(t, "Ishmael", dst["name"], "Unexpected name: %s", dst["name"])
t.Errorf("Unexpected name: %s", dst["name"]) assert.Equal(t, "whaler", dst["occupation"], "Unexpected occupation: %s", dst["occupation"])
}
if dst["occupation"] != "whaler" {
t.Errorf("Unexpected occupation: %s", dst["occupation"])
}
addr, ok := dst["address"].(map[string]any) addr, ok := dst["address"].(map[string]any)
if !ok { require.True(t, ok, "Address went away.")
t.Fatal("Address went away.") assert.Equal(t, "123 Spouter Inn Ct.", addr["street"].(string), "Unexpected address: %v", addr["street"])
} assert.Equal(t, "Nantucket", addr["city"].(string), "Unexpected city: %v", addr["city"])
assert.Equal(t, "MA", addr["state"].(string), "Unexpected state: %v", addr["state"])
if addr["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr["street"])
}
if addr["city"].(string) != "Nantucket" {
t.Errorf("Unexpected city: %v", addr["city"])
}
if addr["state"].(string) != "MA" {
t.Errorf("Unexpected state: %v", addr["state"])
}
// This is one test that is different from CoalesceTables. Because country // This is one test that is different from CoalesceTables. Because country
// is a nil value and it's not removed it's still present. // is a nil value and it's not removed it's still present.
if _, ok = addr["country"]; !ok { _, ok = addr["country"]
t.Error("The country is left out.") assert.True(t, ok, "The country is left out.")
}
if det, ok := dst["details"].(map[string]any); !ok { det, ok := dst["details"].(map[string]any)
t.Fatalf("Details is the wrong type: %v", dst["details"]) require.Truef(t, ok, "Details is the wrong type: %v", dst["details"])
} else if _, ok := det["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst["boat"].(string) != "pequod" { _, ok = det["friends"]
t.Errorf("Expected boat string, got %v", dst["boat"]) assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(")
} assert.Equal(t, "pequod", dst["boat"].(string), "Expected boat string, got %v", dst["boat"])
// This is one test that is different from CoalesceTables. Because hole // This is one test that is different from CoalesceTables. Because hole
// is a nil value and it's not removed it's still present. // is a nil value and it's not removed it's still present.
if _, ok = dst["hole"]; !ok { assert.Contains(t, dst, "hole", "The hole no longer exists.")
t.Error("The hole no longer exists.")
}
dst2 := map[string]any{ dst2 := map[string]any{
"name": "Ishmael", "name": "Ishmael",
@ -626,44 +546,21 @@ func TestMergeTables(t *testing.T) {
// this happens when the --reuse-values flag is set but the chart has no modifications yet // this happens when the --reuse-values flag is set but the chart has no modifications yet
MergeTables(dst2, nil) MergeTables(dst2, nil)
if dst2["name"] != "Ishmael" { assert.Equal(t, "Ishmael", dst2["name"], "Unexpected name: %s", dst2["name"])
t.Errorf("Unexpected name: %s", dst2["name"])
}
addr2, ok := dst2["address"].(map[string]any) addr2, ok := dst2["address"].(map[string]any)
if !ok { require.True(t, ok, "Address went away.")
t.Fatal("Address went away.") assert.Equal(t, "123 Spouter Inn Ct.", addr2["street"].(string), "Unexpected address: %v", addr2["street"])
} assert.Equal(t, "Nantucket", addr2["city"].(string), "Unexpected city: %v", addr2["city"])
assert.Equal(t, "US", addr2["country"].(string), "Unexpected Country: %v", addr2["country"])
if addr2["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr2["street"]) det2, ok := dst2["details"].(map[string]any)
} require.Truef(t, ok, "Details is the wrong type: %v", dst2["details"])
if addr2["city"].(string) != "Nantucket" { assert.Contains(t, det2, "friends", "Could not find your friends. Maybe you don't have any. :-(")
t.Errorf("Unexpected city: %v", addr2["city"]) assert.Equal(t, "pequod", dst2["boat"].(string), "Expected boat string, got %v", dst2["boat"])
} assert.Equal(t, "black", dst2["hole"].(string), "Expected hole string, got %v", dst2["hole"])
assert.Nil(t, dst2["nilval"], "Expected nilvalue to have nil value but it does not")
if addr2["country"].(string) != "US" {
t.Errorf("Unexpected Country: %v", addr2["country"])
}
if det2, ok := dst2["details"].(map[string]any); !ok {
t.Fatalf("Details is the wrong type: %v", dst2["details"])
} else if _, ok := det2["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst2["boat"].(string) != "pequod" {
t.Errorf("Expected boat string, got %v", dst2["boat"])
}
if dst2["hole"].(string) != "black" {
t.Errorf("Expected hole string, got %v", dst2["boat"])
}
if dst2["nilval"] != nil {
t.Error("Expected nilvalue to have nil value but it does not")
}
} }
func TestCoalesceValuesWarnings(t *testing.T) { func TestCoalesceValuesWarnings(t *testing.T) {
@ -716,9 +613,7 @@ func TestCoalesceValuesWarnings(t *testing.T) {
} }
_, err := coalesce(printf, c, vals, "", false) _, err := coalesce(printf, c, vals, "", false)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
t.Logf("vals: %v", vals) t.Logf("vals: %v", vals)
assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.")
@ -761,8 +656,7 @@ func TestCoalesceValuesEmptyMapWithNils(t *testing.T) {
is.Equal("bar", data["foo"]) is.Equal("bar", data["foo"])
// "baz" should be preserved with nil value since it wasn't in chart defaults // "baz" should be preserved with nil value since it wasn't in chart defaults
_, ok = data["baz"] is.Contains(data, "baz", "Expected data.baz key to be present but it was removed")
is.True(ok, "Expected data.baz key to be present but it was removed")
is.Nil(data["baz"], "Expected data.baz key to be nil but it is not") is.Nil(data["baz"], "Expected data.baz key to be nil but it is not")
} }
@ -926,7 +820,6 @@ func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T
keyMapping, ok := childVals["keyMapping"].(map[string]any) keyMapping, ok := childVals["keyMapping"].(map[string]any)
is.True(ok, "keyMapping should be a map") is.True(ok, "keyMapping should be a map")
is.Equal("sha256", keyMapping["format"], "User override should be preserved") is.Equal("sha256", keyMapping["format"], "User override should be preserved")
_, ok = keyMapping["password"] _, ok = keyMapping["password"]

@ -20,75 +20,47 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
func TestValidateAgainstSingleSchema(t *testing.T) { func TestValidateAgainstSingleSchema(t *testing.T) {
values, err := common.ReadValuesFile("./testdata/test-values.yaml") values, err := common.ReadValuesFile("./testdata/test-values.yaml")
if err != nil { require.NoError(t, err, "Error reading YAML file")
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
if err := ValidateAgainstSingleSchema(values, schema); err != nil { schema, err := os.ReadFile("./testdata/test-values.schema.json")
t.Errorf("Error validating Values against Schema: %s", err) require.NoError(t, err, "Error reading YAML file")
} assert.NoErrorf(t, ValidateAgainstSingleSchema(values, schema), "Error validating Values against Schema")
} }
func TestValidateAgainstInvalidSingleSchema(t *testing.T) { func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
values, err := common.ReadValuesFile("./testdata/test-values.yaml") values, err := common.ReadValuesFile("./testdata/test-values.yaml")
if err != nil { require.NoError(t, err, "Error reading YAML file")
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
var errString string schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json")
if err := ValidateAgainstSingleSchema(values, schema); err == nil { require.NoError(t, err, "Error reading YAML file")
t.Fatal("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := `"file:///values.schema.json#" is not valid against metaschema: jsonschema validation failed with 'https://json-schema.org/draft/2020-12/schema#' expectedErrString := `"file:///values.schema.json#" is not valid against metaschema: jsonschema validation failed with 'https://json-schema.org/draft/2020-12/schema#'
- at '': got number, want boolean or object` - at '': got number, want boolean or object`
if errString != expectedErrString { assert.EqualError(t, ValidateAgainstSingleSchema(values, schema), expectedErrString)
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
} }
func TestValidateAgainstSingleSchemaNegative(t *testing.T) { func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
values, err := common.ReadValuesFile("./testdata/test-values-negative.yaml") values, err := common.ReadValuesFile("./testdata/test-values-negative.yaml")
if err != nil { require.NoError(t, err, "Error reading YAML file")
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err != nil {
t.Fatalf("Error reading JSON file: %s", err)
}
var errString string schema, err := os.ReadFile("./testdata/test-values.schema.json")
if err := ValidateAgainstSingleSchema(values, schema); err == nil { require.NoError(t, err, "Error reading JSON file")
t.Fatal("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := `- at '': missing property 'employmentInfo' expectedErrString := `- at '': missing property 'employmentInfo'
- at '/age': minimum: got -5, want 0 - at '/age': minimum: got -5, want 0
` `
if errString != expectedErrString { assert.EqualError(t, ValidateAgainstSingleSchema(values, schema), expectedErrString)
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
} }
const subchartSchema = `{ const subchartSchema = `{
@ -145,9 +117,7 @@ func TestValidateAgainstSchema(t *testing.T) {
}, },
} }
if err := ValidateAgainstSchema(chrt, vals); err != nil { assert.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "Error validating Values against Schema")
t.Errorf("Error validating Values against Schema: %s", err)
}
} }
func TestValidateAgainstSchemaNegative(t *testing.T) { func TestValidateAgainstSchemaNegative(t *testing.T) {
@ -170,19 +140,10 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
"subchart": map[string]any{}, "subchart": map[string]any{},
} }
var errString string
if err := ValidateAgainstSchema(chrt, vals); err == nil {
t.Fatal("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := `subchart: expectedErrString := `subchart:
- at '': missing property 'age' - at '': missing property 'age'
` `
if errString != expectedErrString { assert.EqualError(t, ValidateAgainstSchema(chrt, vals), expectedErrString)
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
} }
func TestValidateAgainstSchema2020(t *testing.T) { func TestValidateAgainstSchema2020(t *testing.T) {
@ -207,9 +168,7 @@ func TestValidateAgainstSchema2020(t *testing.T) {
}, },
} }
if err := ValidateAgainstSchema(chrt, vals); err != nil { assert.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "Error validating Values against Schema")
t.Errorf("Error validating Values against Schema: %s", err)
}
} }
func TestValidateAgainstSchema2020Negative(t *testing.T) { func TestValidateAgainstSchema2020Negative(t *testing.T) {
@ -234,20 +193,11 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) {
}, },
} }
var errString string
if err := ValidateAgainstSchema(chrt, vals); err == nil {
t.Fatal("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := `subchart: expectedErrString := `subchart:
- at '/data': no items match contains schema - at '/data': no items match contains schema
- at '/data/0': got number, want string - at '/data/0': got number, want string
` `
if errString != expectedErrString { assert.EqualError(t, ValidateAgainstSchema(chrt, vals), expectedErrString)
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
} }
func TestHTTPURLLoader_Load(t *testing.T) { func TestHTTPURLLoader_Load(t *testing.T) {
@ -262,12 +212,8 @@ func TestHTTPURLLoader_Load(t *testing.T) {
loader := newHTTPURLLoader() loader := newHTTPURLLoader()
result, err := loader.Load(server.URL) result, err := loader.Load(server.URL)
if err != nil { require.NoError(t, err, "Expected no error, got")
t.Fatalf("Expected no error, got: %v", err) require.NotNil(t, result, "Expected result to be non-nil")
}
if result == nil {
t.Fatal("Expected result to be non-nil")
}
}) })
t.Run("HTTP error status", func(t *testing.T) { t.Run("HTTP error status", func(t *testing.T) {
@ -278,12 +224,8 @@ func TestHTTPURLLoader_Load(t *testing.T) {
loader := newHTTPURLLoader() loader := newHTTPURLLoader()
_, err := loader.Load(server.URL) _, err := loader.Load(server.URL)
if err == nil { require.Error(t, err, "Expected error for HTTP 404")
t.Fatal("Expected error for HTTP 404") assert.ErrorContains(t, err, "404", "Expected error message to contain '404'")
}
if !strings.Contains(err.Error(), "404") {
t.Errorf("Expected error message to contain '404', got: %v", err)
}
}) })
} }
@ -295,9 +237,7 @@ func TestValidateAgainstSingleSchema_UnresolvedURN_Ignored(t *testing.T) {
"$ref": "urn:example:helm:schemas:v1:helm-schema-validation-conditions:v1/helmSchemaValidation-true" "$ref": "urn:example:helm:schemas:v1:helm-schema-validation-conditions:v1/helmSchemaValidation-true"
}`) }`)
vals := map[string]any{"any": "value"} vals := map[string]any{"any": "value"}
if err := ValidateAgainstSingleSchema(vals, schema); err != nil { require.NoErrorf(t, ValidateAgainstSingleSchema(vals, schema), "expected no error when URN unresolved is ignored, got")
t.Fatalf("expected no error when URN unresolved is ignored, got: %v", err)
}
} }
// Non-regression tests for https://github.com/helm/helm/issues/31202 // Non-regression tests for https://github.com/helm/helm/issues/31202
@ -323,14 +263,10 @@ func TestValidateAgainstSchema_MissingSubchartValues_NoPanic(t *testing.T) {
} }
defer func() { defer func() {
if r := recover(); r != nil { require.Nilf(t, recover(), "ValidateAgainstSchema panicked (missing subchart values)")
t.Fatalf("ValidateAgainstSchema panicked (missing subchart values): %v", r)
}
}() }()
if err := ValidateAgainstSchema(chrt, vals); err != nil { require.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "expected no error when subchart values are missing, got")
t.Fatalf("expected no error when subchart values are missing, got: %v", err)
}
} }
func TestValidateAgainstSchema_SubchartNil_NoPanic(t *testing.T) { func TestValidateAgainstSchema_SubchartNil_NoPanic(t *testing.T) {
@ -351,14 +287,10 @@ func TestValidateAgainstSchema_SubchartNil_NoPanic(t *testing.T) {
} }
defer func() { defer func() {
if r := recover(); r != nil { require.Nilf(t, recover(), "ValidateAgainstSchema panicked (nil subchart values)")
t.Fatalf("ValidateAgainstSchema panicked (nil subchart values): %v", r)
}
}() }()
if err := ValidateAgainstSchema(chrt, vals); err != nil { require.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "expected no error when subchart values are nil, got")
t.Fatalf("expected no error when subchart values are nil, got: %v", err)
}
} }
func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) { func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) {
@ -379,13 +311,9 @@ func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) {
} }
defer func() { defer func() {
if r := recover(); r != nil { require.Nilf(t, recover(), "ValidateAgainstSchema panicked (invalid subchart values type)")
t.Fatalf("ValidateAgainstSchema panicked (invalid subchart values type): %v", r)
}
}() }()
// We expect a non-nil error (invalid type), but crucially no panic. // We expect a non-nil error (invalid type), but crucially no panic.
if err := ValidateAgainstSchema(chrt, vals); err == nil { require.Error(t, ValidateAgainstSchema(chrt, vals), "expected an error when subchart values have invalid type, got nil")
t.Fatal("expected an error when subchart values have invalid type, got nil")
}
} }

@ -20,6 +20,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
) )
@ -61,42 +64,26 @@ func TestToRenderValues(t *testing.T) {
} }
res, err := ToRenderValuesWithSchemaValidation(c, overrideValues, o, nil, false) res, err := ToRenderValuesWithSchemaValidation(c, overrideValues, o, nil, false)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// Ensure that the top-level values are all set. // Ensure that the top-level values are all set.
metamap := res["Chart"].(map[string]any) metamap := res["Chart"].(map[string]any)
if name := metamap["Name"]; name.(string) != "test" { name := metamap["Name"]
t.Errorf("Expected chart name 'test', got %q", name) assert.Equalf(t, "test", name.(string), "Expected chart name 'test', got %q", name)
}
relmap := res["Release"].(map[string]any) relmap := res["Release"].(map[string]any)
if name := relmap["Name"]; name.(string) != "Seven Voyages" { name = relmap["Name"]
t.Errorf("Expected release name 'Seven Voyages', got %q", name) assert.Equalf(t, "Seven Voyages", name.(string), "Expected release name 'Seven Voyages', got %q", name)
} namespace := relmap["Namespace"]
if namespace := relmap["Namespace"]; namespace.(string) != "default" { assert.Equalf(t, "default", namespace.(string), "Expected namespace 'default', got %q", namespace)
t.Errorf("Expected namespace 'default', got %q", namespace) revision := relmap["Revision"]
} assert.Equalf(t, 1, revision.(int), "Expected revision '1', got %d", revision)
if revision := relmap["Revision"]; revision.(int) != 1 { assert.False(t, relmap["IsUpgrade"].(bool), "Expected upgrade to be false.")
t.Errorf("Expected revision '1', got %d", revision) assert.True(t, relmap["IsInstall"].(bool), "Expected install to be true.")
} assert.True(t, res["Capabilities"].(*common.Capabilities).APIVersions.Has("v1"), "Expected Capabilities to have v1 as an API")
if relmap["IsUpgrade"].(bool) { assert.Equal(t, "1", res["Capabilities"].(*common.Capabilities).KubeVersion.Major, "Expected Capabilities to have a Kube version")
t.Error("Expected upgrade to be false.")
}
if !relmap["IsInstall"].(bool) {
t.Error("Expected install to be true.")
}
if !res["Capabilities"].(*common.Capabilities).APIVersions.Has("v1") {
t.Error("Expected Capabilities to have v1 as an API")
}
if res["Capabilities"].(*common.Capabilities).KubeVersion.Major != "1" {
t.Error("Expected Capabilities to have a Kube version")
}
vals := res["Values"].(common.Values) vals := res["Values"].(common.Values)
if vals["name"] != "Haroun" { assert.Equal(t, "Haroun", vals["name"], "Expected 'Haroun', got %q (%v)", vals["name"], vals)
t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals)
}
where := vals["where"].(map[string]any) where := vals["where"].(map[string]any)
expects := map[string]string{ expects := map[string]string{
"city": "Baghdad", "city": "Baghdad",
@ -104,8 +91,7 @@ func TestToRenderValues(t *testing.T) {
"title": "caliph", "title": "caliph",
} }
for field, expect := range expects { for field, expect := range expects {
if got := where[field]; got != expect { got := where[field]
t.Errorf("Expected %q, got %q (%v)", expect, got, where) assert.Equalf(t, got, expect, "Expected %q, got %q (%v)", expect, got, where)
}
} }
} }

@ -21,6 +21,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"text/template" "text/template"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestReadValues(t *testing.T) { func TestReadValues(t *testing.T) {
@ -46,29 +49,21 @@ water:
` `
data, err := ReadValues([]byte(doc)) data, err := ReadValues([]byte(doc))
if err != nil { require.NoErrorf(t, err, "Error parsing bytes")
t.Fatalf("Error parsing bytes: %s", err)
}
matchValues(t, data) matchValues(t, data)
tests := []string{`poet: "Coleridge"`, "# Just a comment", ""} tests := []string{`poet: "Coleridge"`, "# Just a comment", ""}
for _, tt := range tests { for _, tt := range tests {
data, err = ReadValues([]byte(tt)) data, err = ReadValues([]byte(tt))
if err != nil { require.NoErrorf(t, err, "Error parsing bytes (%s)", tt)
t.Fatalf("Error parsing bytes (%s): %s", tt, err) require.NotNilf(t, data, `YAML string "%s" gave a nil map`, tt)
}
if data == nil {
t.Errorf(`YAML string "%s" gave a nil map`, tt)
}
} }
} }
func TestReadValuesFile(t *testing.T) { func TestReadValuesFile(t *testing.T) {
data, err := ReadValuesFile("./testdata/coleridge.yaml") data, err := ReadValuesFile("./testdata/coleridge.yaml")
if err != nil { require.NoErrorf(t, err, "Error reading YAML file")
t.Fatalf("Error reading YAML file: %s", err)
}
matchValues(t, data) matchValues(t, data)
} }
@ -108,56 +103,40 @@ chapter:
title: "The Spouter Inn" title: "The Spouter Inn"
` `
d, err := ReadValues([]byte(doc)) d, err := ReadValues([]byte(doc))
if err != nil { require.NoErrorf(t, err, "Failed to parse the White Whale")
t.Fatalf("Failed to parse the White Whale: %s", err)
}
if _, err := d.Table("title"); err == nil { _, err = d.Table("title")
t.Fatal("Title is not a table.") require.Error(t, err, "Title is not a table.")
}
if _, err := d.Table("chapter"); err != nil { _, err = d.Table("chapter")
t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) require.NoErrorf(t, err, "Failed to get the chapter table: %v", d)
}
if v, err := d.Table("chapter.one"); err != nil { v, err := d.Table("chapter.one")
t.Errorf("Failed to get chapter.one: %s", err) require.NoErrorf(t, err, "Failed to get chapter.one")
} else if v["title"] != "Loomings" { assert.Equalf(t, "Loomings", v["title"], "Unexpected title: %s", v["title"])
t.Errorf("Unexpected title: %s", v["title"])
}
if _, err := d.Table("chapter.three"); err != nil { _, err = d.Table("chapter.three")
t.Errorf("Chapter three is missing: %s\n%v", err, d) require.NoErrorf(t, err, "Chapter three is missing: %v", d)
}
if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { _, err = d.Table("chapter.OneHundredThirtySix")
t.Error("I think you mean 'Epilogue'") assert.Error(t, err, "I think you mean 'Epilogue'")
}
} }
func matchValues(t *testing.T, data map[string]any) { func matchValues(t *testing.T, data map[string]any) {
t.Helper() t.Helper()
if data["poet"] != "Coleridge" { assert.Equalf(t, "Coleridge", data["poet"], "Unexpected poet: %s", data["poet"])
t.Errorf("Unexpected poet: %s", data["poet"])
}
if o, err := ttpl("{{len .stanza}}", data); err != nil { o, err := ttpl("{{len .stanza}}", data)
t.Errorf("len stanza: %s", err) require.NoErrorf(t, err, "len stanza")
} else if o != "6" { assert.Equalf(t, "6", o, "Expected 6, got %s", o)
t.Errorf("Expected 6, got %s", o)
}
if o, err := ttpl("{{.mariner.shot}}", data); err != nil { o, err = ttpl("{{.mariner.shot}}", data)
t.Errorf(".mariner.shot: %s", err) require.NoErrorf(t, err, ".mariner.shot")
} else if o != "ALBATROSS" { assert.Equal(t, "ALBATROSS", o, "Expected that mariner shot ALBATROSS")
t.Error("Expected that mariner shot ALBATROSS")
}
if o, err := ttpl("{{.water.water.where}}", data); err != nil { o, err = ttpl("{{.water.water.where}}", data)
t.Errorf(".water.water.where: %s", err) require.NoErrorf(t, err, ".water.water.where")
} else if o != "everywhere" { assert.Equal(t, "everywhere", o, "Expected water water everywhere")
t.Error("Expected water water everywhere")
}
} }
func ttpl(tpl string, v map[string]any) (string, error) { func ttpl(tpl string, v map[string]any) (string, error) {
@ -179,27 +158,18 @@ chapter:
title: "The Spouter Inn" title: "The Spouter Inn"
` `
d, err := ReadValues([]byte(doc)) d, err := ReadValues([]byte(doc))
if err != nil { require.NoErrorf(t, err, "Failed to parse the White Whale")
t.Fatalf("Failed to parse the White Whale: %s", err)
} v, err := d.PathValue("chapter.one.title")
require.NoErrorf(t, err, "Got error instead of title: %v", d)
if v, err := d.PathValue("chapter.one.title"); err != nil { assert.Equalf(t, "Loomings", v, "No error but got wrong value for title: %v", d)
t.Errorf("Got error instead of title: %s\n%v", err, d) _, err = d.PathValue("chapter.one.doesnotexist")
} else if v != "Loomings" { require.Errorf(t, err, "Non-existent key should return error: %v", d)
t.Errorf("No error but got wrong value for title: %s\n%v", err, d) _, err = d.PathValue("chapter.doesnotexist.one")
} require.Errorf(t, err, "Non-existent key in middle of path should return error: %v", d)
if _, err := d.PathValue("chapter.one.doesnotexist"); err == nil { _, err = d.PathValue("")
t.Errorf("Non-existent key should return error: %s\n%v", err, d) require.Error(t, err, "Asking for the value from an empty path should yield an error")
} v, err = d.PathValue("title")
if _, err := d.PathValue("chapter.doesnotexist.one"); err == nil { require.NoErrorf(t, err, "Failed to get title: %v", d)
t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d) assert.Equalf(t, "Moby Dick", v, "Failed to return values for root key title: got %s\n%v", v, d)
}
if _, err := d.PathValue(""); err == nil {
t.Error("Asking for the value from an empty path should yield an error")
}
if v, err := d.PathValue("title"); err == nil {
if v != "Moby Dick" {
t.Error("Failed to return values for root key title")
}
}
} }

@ -21,6 +21,8 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"testing" "testing"
"github.com/stretchr/testify/require"
) )
func TestLoadArchiveFiles(t *testing.T) { func TestLoadArchiveFiles(t *testing.T) {
@ -34,9 +36,7 @@ func TestLoadArchiveFiles(t *testing.T) {
generate: func(_ *tar.Writer) {}, generate: func(_ *tar.Writer) {},
check: func(t *testing.T, _ []*BufferedFile, err error) { check: func(t *testing.T, _ []*BufferedFile, err error) {
t.Helper() t.Helper()
if err.Error() != "no files in chart archive" { require.EqualError(t, err, "no files in chart archive")
t.Fatalf(`expected "no files in chart archive", got [%#v]`, err)
}
}, },
}, },
{ {
@ -44,32 +44,21 @@ func TestLoadArchiveFiles(t *testing.T) {
generate: func(w *tar.Writer) { generate: func(w *tar.Writer) {
// simulate the presence of a `pax_global_header` file like you would get when // simulate the presence of a `pax_global_header` file like you would get when
// processing a GitHub release archive. // processing a GitHub release archive.
err := w.WriteHeader(&tar.Header{ require.NoError(t, w.WriteHeader(&tar.Header{
Typeflag: tar.TypeXGlobalHeader, Typeflag: tar.TypeXGlobalHeader,
Name: "pax_global_header", Name: "pax_global_header",
}) }))
if err != nil {
t.Fatal(err)
}
// we need to have at least one file, otherwise we'll get the "no files in chart archive" error // we need to have at least one file, otherwise we'll get the "no files in chart archive" error
err = w.WriteHeader(&tar.Header{ require.NoError(t, w.WriteHeader(&tar.Header{
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
Name: "dir/empty", Name: "dir/empty",
}) }))
if err != nil {
t.Fatal(err)
}
}, },
check: func(t *testing.T, files []*BufferedFile, err error) { check: func(t *testing.T, files []*BufferedFile, err error) {
t.Helper() t.Helper()
if err != nil { require.NoErrorf(t, err, `got unwanted error for tar file with pax_global_header content`)
t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) require.Lenf(t, files, 1, `expected to get one file but got [%v]`, files)
}
if len(files) != 1 {
t.Fatalf(`expected to get one file but got [%v]`, files)
}
}, },
}, },
} }

@ -24,10 +24,12 @@ import (
"io" "io"
"maps" "maps"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
c3 "helm.sh/helm/v4/internal/chart/v3" c3 "helm.sh/helm/v4/internal/chart/v3"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
c2 "helm.sh/helm/v4/pkg/chart/v2" c2 "helm.sh/helm/v4/pkg/chart/v2"
@ -59,20 +61,13 @@ description: A test chart
Size: int64(len(data)), Size: int64(len(data)),
ModTime: time.Now(), ModTime: time.Now(),
} }
if err := tw.WriteHeader(header); err != nil { require.NoErrorf(t, tw.WriteHeader(header), "Failed to write tar header for %s", name)
t.Fatalf("Failed to write tar header for %s: %v", name, err) _, err := tw.Write(data)
} require.NoErrorf(t, err, "Failed to write tar data for %s", name)
if _, err := tw.Write(data); err != nil {
t.Fatalf("Failed to write tar data for %s: %v", name, err)
}
} }
if err := tw.Close(); err != nil { require.NoErrorf(t, tw.Close(), "Failed to close tar writer")
t.Fatalf("Failed to close tar writer: %v", err) require.NoErrorf(t, gw.Close(), "Failed to close gzip writer")
}
if err := gw.Close(); err != nil {
t.Fatalf("Failed to close gzip writer: %v", err)
}
return &buf return &buf
} }
@ -149,37 +144,26 @@ func TestLoadArchive(t *testing.T) {
loadedChart, err := LoadArchive(reader) loadedChart, err := LoadArchive(reader)
if tc.expectedError != "" { if tc.expectedError != "" {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) { require.Errorf(t, err, "Expected error containing %q, but got no error", tc.expectedError)
t.Errorf("Expected error containing %q, but got %v", tc.expectedError, err) assert.ErrorContains(t, err, tc.expectedError)
} } else {
return require.NoError(t, err)
}
if err != nil { lac, err := chart.NewAccessor(loadedChart)
t.Fatalf("Unexpected error: %v", err) require.NoError(t, err)
}
lac, err := chart.NewAccessor(loadedChart)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
eac, err := chart.NewAccessor(tc.expectedChart)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if lac.Name() != eac.Name() { eac, err := chart.NewAccessor(tc.expectedChart)
t.Errorf("Expected chart name %q, got %q", eac.Name(), lac.Name()) require.NoError(t, err)
} assert.Equalf(t, lac.Name(), eac.Name(), "Expected chart name %q, got %q", eac.Name(), lac.Name())
var loadedAPIVersion string var loadedAPIVersion string
switch lc := loadedChart.(type) { switch lc := loadedChart.(type) {
case *c2.Chart: case *c2.Chart:
loadedAPIVersion = lc.Metadata.APIVersion loadedAPIVersion = lc.Metadata.APIVersion
case *c3.Chart: case *c3.Chart:
loadedAPIVersion = lc.Metadata.APIVersion loadedAPIVersion = lc.Metadata.APIVersion
} }
if loadedAPIVersion != tc.apiVersion { assert.Equalf(t, loadedAPIVersion, tc.apiVersion, "Expected API version %q, got %q", tc.apiVersion, loadedAPIVersion)
t.Errorf("Expected API version %q, got %q", tc.apiVersion, loadedAPIVersion)
} }
}) })
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save