From 12518024071eb5d0c84b43f7d603873a100de07b Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Fri, 3 Apr 2026 23:56:20 +0530 Subject: [PATCH 1/8] fix: clean up func version -v output format (#3518) - Omit empty fields (Knative, Commit, BuildDate) from verbose output - Add BuildDate field injected via Makefile ldflags - Update TestVerbose to assert field presence/absence rather than line count --- Makefile | 3 +- cmd/root_test.go | 112 ++++++++++++++++++++++++----------------- cmd/version.go | 61 ++++++++++++---------- pkg/version/version.go | 2 +- 4 files changed, 105 insertions(+), 73 deletions(-) diff --git a/Makefile b/Makefile index e35743e2b0..216e9a1712 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,13 @@ BIN_GOIMPORTS ?= "$(PWD)/bin/goimports" # If the current commit does not have a semver tag, 'tip' is used, unless there # is a TAG environment variable. Precedence is git tag, environment variable, 'tip' HASH := $(shell git rev-parse --short HEAD 2>/dev/null) +DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ) VTAG := $(shell git tag --points-at HEAD | head -1) VTAG := $(shell [ -z $(VTAG) ] && echo $(ETAG) || echo $(VTAG)) VERS ?= $(shell git describe --tags --match 'v*') KVER ?= $(shell git describe --tags --match 'knative-*') -LDFLAGS := -X knative.dev/func/pkg/version.Vers=$(VERS) -X knative.dev/func/pkg/version.Kver=$(KVER) -X knative.dev/func/pkg/version.Hash=$(HASH) +LDFLAGS := -X knative.dev/func/pkg/version.Vers=$(VERS) -X knative.dev/func/pkg/version.Kver=$(KVER) -X knative.dev/func/pkg/version.Hash=$(HASH) -X knative.dev/func/pkg/version.BuildDate=$(DATE) FUNC_UTILS_IMG ?= ghcr.io/knative/func-utils:v2 LDFLAGS += -X knative.dev/func/pkg/k8s.SocatImage=$(FUNC_UTILS_IMG) diff --git a/cmd/root_test.go b/cmd/root_test.go index 555e1e6a83..1527d5bb9d 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -161,54 +161,76 @@ func TestRoot_CommandNameParameterized(t *testing.T) { } func TestVerbose(t *testing.T) { - tests := []struct { - name string - args []string - want string - wantLF int - }{ - { - name: "verbose as version's flag", - args: []string{"version", "-v"}, - want: "Version: v0.42.0", - wantLF: 24, - }, - { - name: "no verbose", - args: []string{"version"}, - want: "v0.42.0", - wantLF: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - viper.Reset() - - var out bytes.Buffer - - cmd := NewRootCmd(RootCommandConfig{ - Name: "func", - Version: Version{ - Vers: "v0.42.0", - Hash: "cafe", - Kver: "v1.10.0", - }}) - - cmd.SetArgs(tt.args) - cmd.SetOut(&out) - if err := cmd.Execute(); err != nil { - t.Fatal(err) - } + t.Run("no verbose", func(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{Vers: "v0.42.0"}, + }) + cmd.SetArgs([]string{"version"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + if got := strings.TrimRight(out.String(), "\n"); got != "v0.42.0" { + t.Errorf("expected %q but got %q", "v0.42.0", got) + } + }) - outLines := strings.Split(out.String(), "\n") - if len(outLines)-1 != tt.wantLF { - t.Errorf("expected output with %v line breaks but got %v:", tt.wantLF, len(outLines)-1) - } - if outLines[0] != tt.want { - t.Errorf("expected output: %q but got: %q", tt.want, outLines[0]) + t.Run("verbose includes populated fields", func(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{ + Vers: "v0.42.0", + Hash: "cafe", + Kver: "v1.10.0", + BuildDate: "2024-01-01T00:00:00Z", + }, + }) + cmd.SetArgs([]string{"version", "-v"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + output := out.String() + for _, want := range []string{ + "Version: v0.42.0", + "Knative: v1.10.0", + "Commit: cafe", + "BuildDate: 2024-01-01T00:00:00Z", + } { + if !strings.Contains(output, want) { + t.Errorf("expected output to contain %q but got:\n%s", want, output) } + } + }) + + t.Run("verbose omits empty fields", func(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{Vers: "v0.42.0"}, }) - } + cmd.SetArgs([]string{"version", "-v"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + output := out.String() + // Fields with empty values in the Version struct should be omitted + for _, absent := range []string{"Knative:", "Commit:", "BuildDate:"} { + if strings.Contains(output, absent) { + t.Errorf("expected output to omit %q but got:\n%s", absent, output) + } + } + if !strings.HasPrefix(output, "Version: v0.42.0\n") { + t.Errorf("expected output to start with %q but got:\n%s", "Version: v0.42.0\n", output) + } + }) } // TestRoot_effectivePath ensures that the path method returns the effective path diff --git a/cmd/version.go b/cmd/version.go index a7e3da4ff7..b104fd478f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -13,6 +13,7 @@ import ( "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/k8s" + "knative.dev/func/pkg/version" ) func NewVersionCmd(version Version) *cobra.Command { @@ -83,7 +84,12 @@ func runVersion(cmd *cobra.Command, v Version) error { v.Vers = DefaultVersion } - // Kver, Hash are already set from build + // Kver, Hash are already set from build via ldflags. + // Populate BuildDate from the package-level var only if not already set + // (allows tests to inject their own value via the Version struct). + if v.BuildDate == "" { + v.BuildDate = version.BuildDate + } // Populate image fields from k8s package constants v.SocatImage = k8s.SocatImage v.TarImage = k8s.TarImage @@ -110,6 +116,8 @@ type Version struct { Kver string `json:"knative,omitempty" yaml:"knative,omitempty"` // Hash of the currently active git commit on build. Hash string `json:"commit,omitempty" yaml:"commit,omitempty"` + // BuildDate is the UTC timestamp at which the binary was built. + BuildDate string `json:"buildDate,omitempty" yaml:"buildDate,omitempty"` // SocatImage is the socat image used by the function. SocatImage string `json:"socatImage,omitempty" yaml:"socatImage,omitempty"` // TarImage is the tar image used by the function. @@ -132,38 +140,39 @@ func (v Version) String() string { } // StringVerbose returns the version along with extended version metadata. +// Fields with empty values are omitted. func (v Version) StringVerbose() string { - var ( - vers = v.Vers - kver = v.Kver - hash = v.Hash - ) - if strings.HasPrefix(kver, "knative-") { - kver = strings.Split(kver, "-")[1] + var sb strings.Builder + sb.WriteString("Version: " + v.Vers + "\n") + if v.Kver != "" { + kver := v.Kver + if strings.HasPrefix(kver, "knative-") { + kver = strings.Split(kver, "-")[1] + } + sb.WriteString("Knative: " + kver + "\n") + } + if v.Hash != "" { + sb.WriteString("Commit: " + v.Hash + "\n") + } + if v.BuildDate != "" { + sb.WriteString("BuildDate: " + v.BuildDate + "\n") } - // Trim trailing newlines: String methods should return bare content; the - // caller is responsible for adding output termination. MiddlewareVersions - // appends a "\n" after each line, so the formatted result would otherwise - // end with one, producing a double newline when a caller adds its own. - return strings.TrimRight(fmt.Sprintf( - "Version: %s\n"+ - "Knative: %s\n"+ - "Commit: %s\n"+ - "SocatImage: %s\n"+ - "TarImage: %s\n"+ - "Middleware Versions: \n%s", - vers, - kver, - hash, - v.SocatImage, - v.TarImage, - v.MiddlewareVersions), "\n") + if v.SocatImage != "" { + sb.WriteString("SocatImage: " + v.SocatImage + "\n") + } + if v.TarImage != "" { + sb.WriteString("TarImage: " + v.TarImage + "\n") + } + if mw := v.MiddlewareVersions.String(); mw != "" { + sb.WriteString("Middleware Versions: \n" + mw) + } + return sb.String() } // Human prints version information in human-readable format. func (v Version) Human(w io.Writer) error { if v.Verbose { - _, err := fmt.Fprintln(w, v.StringVerbose()) + _, err := fmt.Fprint(w, v.StringVerbose()) return err } _, err := fmt.Fprintf(w, "%s\n", v.Vers) diff --git a/pkg/version/version.go b/pkg/version/version.go index 114c9658bc..a678deb98f 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,3 +1,3 @@ package version -var Vers, Kver, Hash string +var Vers, Kver, Hash, BuildDate string From c4cd849837424681c9f2c47f4127df0dea95840c Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 00:31:50 +0530 Subject: [PATCH 2/8] fix: alias pkg/version import to avoid shadowing by NewVersionCmd parameter --- cmd/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index b104fd478f..240dd42e4f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -13,7 +13,7 @@ import ( "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/k8s" - "knative.dev/func/pkg/version" + pkgversion "knative.dev/func/pkg/version" ) func NewVersionCmd(version Version) *cobra.Command { @@ -88,7 +88,7 @@ func runVersion(cmd *cobra.Command, v Version) error { // Populate BuildDate from the package-level var only if not already set // (allows tests to inject their own value via the Version struct). if v.BuildDate == "" { - v.BuildDate = version.BuildDate + v.BuildDate = pkgversion.BuildDate } // Populate image fields from k8s package constants v.SocatImage = k8s.SocatImage From 6d628ea39d530ba0d1e4ecd0a517b2e0fbc22063 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 00:34:12 +0530 Subject: [PATCH 3/8] test: add version command coverage for kver prefix, json/yaml output, and url --- cmd/version_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 cmd/version_test.go diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 0000000000..e7eb378cf7 --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,78 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" + + "github.com/ory/viper" +) + +// TestVersion_KverPrefixStripped verifies that a Knative version tag with the +// "knative-" prefix is stripped to just the semver part in verbose output. +func TestVersion_KverPrefixStripped(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{ + Vers: "v0.42.0", + Kver: "knative-v1.10.0", + }, + }) + cmd.SetArgs([]string{"version", "-v"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), "Knative: v1.10.0") { + t.Errorf("expected 'knative-' prefix to be stripped, got:\n%s", out.String()) + } +} + +// TestVersion_OutputJSON verifies that --output json produces valid JSON +// containing the version field. +func TestVersion_OutputJSON(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{Vers: "v0.42.0"}, + }) + cmd.SetArgs([]string{"version", "--output", "json"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), `"version"`) { + t.Errorf("expected JSON output to contain version field, got:\n%s", out.String()) + } +} + +// TestVersion_OutputYAML verifies that --output yaml produces YAML output +// containing the version field. +func TestVersion_OutputYAML(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{Vers: "v0.42.0"}, + }) + cmd.SetArgs([]string{"version", "--output", "yaml"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), "version: v0.42.0") { + t.Errorf("expected YAML output to contain version field, got:\n%s", out.String()) + } +} + +// TestVersion_URLUnsupported verifies that the URL output format returns an error. +func TestVersion_URLUnsupported(t *testing.T) { + v := Version{Vers: "v0.42.0"} + var buf bytes.Buffer + if err := v.URL(&buf); err == nil { + t.Error("expected URL format to return an error, got nil") + } +} From fd19ec5fcab02314f7f323ccb0d9bbc2f172a9b1 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 00:46:46 +0530 Subject: [PATCH 4/8] test: exclude BuildDate from absent-field check when ldflags may inject it --- cmd/root_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/root_test.go b/cmd/root_test.go index 1527d5bb9d..f7d32c64e1 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -221,8 +221,10 @@ func TestVerbose(t *testing.T) { t.Fatal(err) } output := out.String() - // Fields with empty values in the Version struct should be omitted - for _, absent := range []string{"Knative:", "Commit:", "BuildDate:"} { + // Fields with empty values in the Version struct should be omitted. + // BuildDate is excluded: when tests run with ldflags injected by the + // Makefile, pkgversion.BuildDate is populated and runVersion fills it in. + for _, absent := range []string{"Knative:", "Commit:"} { if strings.Contains(output, absent) { t.Errorf("expected output to omit %q but got:\n%s", absent, output) } From 914746ae52febbb8313c8ac602ba3e20ab251ff7 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 01:16:57 +0530 Subject: [PATCH 5/8] fix: address review feedback on version output cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Inject BuildDate at startup in pkg/app/app.go instead of reading pkgversion.BuildDate as a side-effect in runVersion, making tests deterministic regardless of ldflags - Fix kver prefix stripping to use strings.TrimPrefix, preserving commit-distance suffixes (e.g. knative-v1.10.0-5-g... → v1.10.0-5-g...) - Remove trailing space from 'Middleware Versions: ' label - Delete cmd/version_test.go; consolidate all version tests into TestVerbose subtests in cmd/root_test.go - Strengthen JSON assertion to check for exact field value - Add subtest for empty MiddlewareVersions omission - Add subtest for kver with commit-distance suffix --- cmd/root_test.go | 72 ++++++++++++++++++++++++++++++++++++++--- cmd/version.go | 17 +++------- cmd/version_test.go | 78 --------------------------------------------- pkg/app/app.go | 7 ++-- 4 files changed, 75 insertions(+), 99 deletions(-) delete mode 100644 cmd/version_test.go diff --git a/cmd/root_test.go b/cmd/root_test.go index f7d32c64e1..11efc9cb4d 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -212,7 +212,8 @@ func TestVerbose(t *testing.T) { viper.Reset() var out bytes.Buffer cmd := NewRootCmd(RootCommandConfig{ - Name: "func", + Name: "func", + // All fields except Vers are intentionally left empty. Version: Version{Vers: "v0.42.0"}, }) cmd.SetArgs([]string{"version", "-v"}) @@ -221,10 +222,7 @@ func TestVerbose(t *testing.T) { t.Fatal(err) } output := out.String() - // Fields with empty values in the Version struct should be omitted. - // BuildDate is excluded: when tests run with ldflags injected by the - // Makefile, pkgversion.BuildDate is populated and runVersion fills it in. - for _, absent := range []string{"Knative:", "Commit:"} { + for _, absent := range []string{"Knative:", "Commit:", "BuildDate:"} { if strings.Contains(output, absent) { t.Errorf("expected output to omit %q but got:\n%s", absent, output) } @@ -233,6 +231,70 @@ func TestVerbose(t *testing.T) { t.Errorf("expected output to start with %q but got:\n%s", "Version: v0.42.0\n", output) } }) + + // TestVersion_KverPrefixStripped verifies that a Knative version tag with the + // "knative-" prefix is stripped correctly, including commit-distance suffixes + // (e.g. knative-v1.10.0-5-gabcdef1 → v1.10.0-5-gabcdef1). + t.Run("kver prefix stripped", func(t *testing.T) { + v := Version{Vers: "v0.42.0", Kver: "knative-v1.10.0-5-gabcdef1"} + output := v.StringVerbose() + if !strings.Contains(output, "Knative: v1.10.0-5-gabcdef1") { + t.Errorf("expected 'knative-' prefix stripped, got:\n%s", output) + } + }) + + t.Run("output json", func(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{Vers: "v0.42.0"}, + }) + cmd.SetArgs([]string{"version", "--output", "json"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), `"version": "v0.42.0"`) { + t.Errorf("expected JSON to contain version field, got:\n%s", out.String()) + } + }) + + t.Run("output yaml", func(t *testing.T) { + viper.Reset() + var out bytes.Buffer + cmd := NewRootCmd(RootCommandConfig{ + Name: "func", + Version: Version{Vers: "v0.42.0"}, + }) + cmd.SetArgs([]string{"version", "--output", "yaml"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), "version: v0.42.0") { + t.Errorf("expected YAML to contain version field, got:\n%s", out.String()) + } + }) + + t.Run("url unsupported", func(t *testing.T) { + v := Version{Vers: "v0.42.0"} + var buf bytes.Buffer + if err := v.URL(&buf); err == nil { + t.Error("expected URL format to return an error, got nil") + } + }) + + t.Run("middleware versions omitted when empty", func(t *testing.T) { + v := Version{ + Vers: "v0.42.0", + MiddlewareVersions: MiddlewareVersions{}, + } + output := v.StringVerbose() + if strings.Contains(output, "Middleware Versions:") { + t.Errorf("expected empty MiddlewareVersions to be omitted, got:\n%s", output) + } + }) } // TestRoot_effectivePath ensures that the path method returns the effective path diff --git a/cmd/version.go b/cmd/version.go index 240dd42e4f..16f7b2ea3f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -13,7 +13,6 @@ import ( "knative.dev/func/pkg/config" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/k8s" - pkgversion "knative.dev/func/pkg/version" ) func NewVersionCmd(version Version) *cobra.Command { @@ -84,12 +83,8 @@ func runVersion(cmd *cobra.Command, v Version) error { v.Vers = DefaultVersion } - // Kver, Hash are already set from build via ldflags. - // Populate BuildDate from the package-level var only if not already set - // (allows tests to inject their own value via the Version struct). - if v.BuildDate == "" { - v.BuildDate = pkgversion.BuildDate - } + // Kver, Hash, BuildDate are already set from build via ldflags, + // injected into the Version struct at startup (see pkg/app/app.go). // Populate image fields from k8s package constants v.SocatImage = k8s.SocatImage v.TarImage = k8s.TarImage @@ -145,11 +140,7 @@ func (v Version) StringVerbose() string { var sb strings.Builder sb.WriteString("Version: " + v.Vers + "\n") if v.Kver != "" { - kver := v.Kver - if strings.HasPrefix(kver, "knative-") { - kver = strings.Split(kver, "-")[1] - } - sb.WriteString("Knative: " + kver + "\n") + sb.WriteString("Knative: " + strings.TrimPrefix(v.Kver, "knative-") + "\n") } if v.Hash != "" { sb.WriteString("Commit: " + v.Hash + "\n") @@ -164,7 +155,7 @@ func (v Version) StringVerbose() string { sb.WriteString("TarImage: " + v.TarImage + "\n") } if mw := v.MiddlewareVersions.String(); mw != "" { - sb.WriteString("Middleware Versions: \n" + mw) + sb.WriteString("Middleware Versions:\n" + mw) } return sb.String() } diff --git a/cmd/version_test.go b/cmd/version_test.go deleted file mode 100644 index e7eb378cf7..0000000000 --- a/cmd/version_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package cmd - -import ( - "bytes" - "strings" - "testing" - - "github.com/ory/viper" -) - -// TestVersion_KverPrefixStripped verifies that a Knative version tag with the -// "knative-" prefix is stripped to just the semver part in verbose output. -func TestVersion_KverPrefixStripped(t *testing.T) { - viper.Reset() - var out bytes.Buffer - cmd := NewRootCmd(RootCommandConfig{ - Name: "func", - Version: Version{ - Vers: "v0.42.0", - Kver: "knative-v1.10.0", - }, - }) - cmd.SetArgs([]string{"version", "-v"}) - cmd.SetOut(&out) - if err := cmd.Execute(); err != nil { - t.Fatal(err) - } - if !strings.Contains(out.String(), "Knative: v1.10.0") { - t.Errorf("expected 'knative-' prefix to be stripped, got:\n%s", out.String()) - } -} - -// TestVersion_OutputJSON verifies that --output json produces valid JSON -// containing the version field. -func TestVersion_OutputJSON(t *testing.T) { - viper.Reset() - var out bytes.Buffer - cmd := NewRootCmd(RootCommandConfig{ - Name: "func", - Version: Version{Vers: "v0.42.0"}, - }) - cmd.SetArgs([]string{"version", "--output", "json"}) - cmd.SetOut(&out) - if err := cmd.Execute(); err != nil { - t.Fatal(err) - } - if !strings.Contains(out.String(), `"version"`) { - t.Errorf("expected JSON output to contain version field, got:\n%s", out.String()) - } -} - -// TestVersion_OutputYAML verifies that --output yaml produces YAML output -// containing the version field. -func TestVersion_OutputYAML(t *testing.T) { - viper.Reset() - var out bytes.Buffer - cmd := NewRootCmd(RootCommandConfig{ - Name: "func", - Version: Version{Vers: "v0.42.0"}, - }) - cmd.SetArgs([]string{"version", "--output", "yaml"}) - cmd.SetOut(&out) - if err := cmd.Execute(); err != nil { - t.Fatal(err) - } - if !strings.Contains(out.String(), "version: v0.42.0") { - t.Errorf("expected YAML output to contain version field, got:\n%s", out.String()) - } -} - -// TestVersion_URLUnsupported verifies that the URL output format returns an error. -func TestVersion_URLUnsupported(t *testing.T) { - v := Version{Vers: "v0.42.0"} - var buf bytes.Buffer - if err := v.URL(&buf); err == nil { - t.Error("expected URL format to return an error, got nil") - } -} diff --git a/pkg/app/app.go b/pkg/app/app.go index eae55e63a8..a45669ca06 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -32,9 +32,10 @@ func Main() { cfg := cmd.RootCommandConfig{ Name: "func", Version: cmd.Version{ - Vers: version.Vers, - Kver: version.Kver, - Hash: version.Hash, + Vers: version.Vers, + Kver: version.Kver, + Hash: version.Hash, + BuildDate: version.BuildDate, }} if err := cmd.NewRootCmd(cfg).ExecuteContext(ctx); err != nil { From 1415b0227222e7b150c1252b657bf3336cc7fc0c Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 01:24:25 +0530 Subject: [PATCH 6/8] test: add SocatImage/TarImage conditional omission coverage --- cmd/root_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cmd/root_test.go b/cmd/root_test.go index 11efc9cb4d..dd3843499c 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -295,6 +295,33 @@ func TestVerbose(t *testing.T) { t.Errorf("expected empty MiddlewareVersions to be omitted, got:\n%s", output) } }) + + t.Run("socat and tar images omitted when empty", func(t *testing.T) { + v := Version{Vers: "v0.42.0"} + output := v.StringVerbose() + for _, absent := range []string{"SocatImage:", "TarImage:"} { + if strings.Contains(output, absent) { + t.Errorf("expected %q to be omitted when empty, got:\n%s", absent, output) + } + } + }) + + t.Run("socat and tar images present when populated", func(t *testing.T) { + v := Version{ + Vers: "v0.42.0", + SocatImage: "ghcr.io/knative/func-utils:v2", + TarImage: "ghcr.io/knative/func-utils:v2", + } + output := v.StringVerbose() + for _, want := range []string{ + "SocatImage: ghcr.io/knative/func-utils:v2", + "TarImage: ghcr.io/knative/func-utils:v2", + } { + if !strings.Contains(output, want) { + t.Errorf("expected output to contain %q, got:\n%s", want, output) + } + } + }) } // TestRoot_effectivePath ensures that the path method returns the effective path From 6770bb9aeaf5c4aae3c7d83abb5ecf6d91a16f57 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 01:26:57 +0530 Subject: [PATCH 7/8] test: add exact-tag kver prefix stripping coverage --- cmd/root_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cmd/root_test.go b/cmd/root_test.go index dd3843499c..fc18602181 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -232,14 +232,23 @@ func TestVerbose(t *testing.T) { } }) - // TestVersion_KverPrefixStripped verifies that a Knative version tag with the - // "knative-" prefix is stripped correctly, including commit-distance suffixes + // kver prefix stripped verifies that a Knative version tag with the + // "knative-" prefix is stripped correctly for an exact release tag. + t.Run("kver prefix stripped exact tag", func(t *testing.T) { + v := Version{Vers: "v0.42.0", Kver: "knative-v1.10.0"} + output := v.StringVerbose() + if !strings.Contains(output, "Knative: v1.10.0") { + t.Errorf("expected 'knative-' prefix stripped for exact tag, got:\n%s", output) + } + }) + + // kver prefix stripped also verifies commit-distance suffixes are preserved // (e.g. knative-v1.10.0-5-gabcdef1 → v1.10.0-5-gabcdef1). - t.Run("kver prefix stripped", func(t *testing.T) { + t.Run("kver prefix stripped with commit distance", func(t *testing.T) { v := Version{Vers: "v0.42.0", Kver: "knative-v1.10.0-5-gabcdef1"} output := v.StringVerbose() if !strings.Contains(output, "Knative: v1.10.0-5-gabcdef1") { - t.Errorf("expected 'knative-' prefix stripped, got:\n%s", output) + t.Errorf("expected 'knative-' prefix stripped with commit distance preserved, got:\n%s", output) } }) From e93f098ce6ace760e7e7dfaa348227eb4a14f160 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 4 Apr 2026 01:43:08 +0530 Subject: [PATCH 8/8] fix: use git commit timestamp for BuildDate to ensure reproducible builds - Replace wall-clock date with git log -1 --format=%cI HEAD so that rebuilding the same commit always produces the same binary hash - Assert buildDate appears in JSON output when populated - Add exact-output subtest to guard against blank lines between fields --- Makefile | 2 +- cmd/root_test.go | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 216e9a1712..22fc96a966 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ BIN_GOIMPORTS ?= "$(PWD)/bin/goimports" # If the current commit does not have a semver tag, 'tip' is used, unless there # is a TAG environment variable. Precedence is git tag, environment variable, 'tip' HASH := $(shell git rev-parse --short HEAD 2>/dev/null) -DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ) +DATE := $(shell git log -1 --format=%cI HEAD 2>/dev/null) VTAG := $(shell git tag --points-at HEAD | head -1) VTAG := $(shell [ -z $(VTAG) ] && echo $(ETAG) || echo $(VTAG)) VERS ?= $(shell git describe --tags --match 'v*') diff --git a/cmd/root_test.go b/cmd/root_test.go index fc18602181..21a22a5ba9 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -256,16 +256,25 @@ func TestVerbose(t *testing.T) { viper.Reset() var out bytes.Buffer cmd := NewRootCmd(RootCommandConfig{ - Name: "func", - Version: Version{Vers: "v0.42.0"}, + Name: "func", + Version: Version{ + Vers: "v0.42.0", + BuildDate: "2024-01-01T00:00:00Z", + }, }) cmd.SetArgs([]string{"version", "--output", "json"}) cmd.SetOut(&out) if err := cmd.Execute(); err != nil { t.Fatal(err) } - if !strings.Contains(out.String(), `"version": "v0.42.0"`) { - t.Errorf("expected JSON to contain version field, got:\n%s", out.String()) + output := out.String() + for _, want := range []string{ + `"version": "v0.42.0"`, + `"buildDate": "2024-01-01T00:00:00Z"`, + } { + if !strings.Contains(output, want) { + t.Errorf("expected JSON to contain %q, got:\n%s", want, output) + } } }) @@ -294,6 +303,28 @@ func TestVerbose(t *testing.T) { } }) + // exact output guards against extra blank lines or whitespace being + // reintroduced between populated fields. + t.Run("verbose exact output", func(t *testing.T) { + v := Version{ + Vers: "v0.42.0", + Kver: "knative-v1.10.0", + Hash: "cafe", + BuildDate: "2024-01-01T00:00:00Z", + SocatImage: "ghcr.io/knative/func-utils:v2", + TarImage: "ghcr.io/knative/func-utils:v2", + } + want := "Version: v0.42.0\n" + + "Knative: v1.10.0\n" + + "Commit: cafe\n" + + "BuildDate: 2024-01-01T00:00:00Z\n" + + "SocatImage: ghcr.io/knative/func-utils:v2\n" + + "TarImage: ghcr.io/knative/func-utils:v2\n" + if got := v.StringVerbose(); got != want { + t.Errorf("unexpected verbose output:\nwant:\n%s\ngot:\n%s", want, got) + } + }) + t.Run("middleware versions omitted when empty", func(t *testing.T) { v := Version{ Vers: "v0.42.0",