From c2a0128c011fab9f1c8375062f1ce2f2f43f4511 Mon Sep 17 00:00:00 2001 From: David Collom Date: Tue, 9 Jul 2024 11:19:31 +0100 Subject: [PATCH] Adding tests for version.go (#206) --- go.mod | 8 +- go.sum | 15 +- pkg/version/version.go | 5 +- pkg/version/version_test.go | 275 ++++++++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+), 11 deletions(-) create mode 100644 pkg/version/version_test.go diff --git a/go.mod b/go.mod index cece0765..d02133b9 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/gofri/go-github-ratelimit v1.1.0 github.com/google/go-containerregistry v0.19.0 github.com/google/go-github/v58 v58.0.0 + github.com/stretchr/testify v1.9.0 ) require ( @@ -98,9 +99,10 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.54.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.starlark.net v0.0.0-20240520160348-046347dcd104 // indirect diff --git a/go.sum b/go.sum index 699e53f6..6bd77fa9 100644 --- a/go.sum +++ b/go.sum @@ -188,12 +188,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -208,8 +208,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/pkg/version/version.go b/pkg/version/version.go index a78ef138..05b7cb7c 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -105,7 +105,6 @@ func (v *Version) Fetch(ctx context.Context, imageURL string, _ *api.Options) (i // latestSemver will return the latest ImageTag based on the given options // restriction, using semver. This should not be used is UseSHA has been // enabled. -// TODO: add tests.. func latestSemver(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error) { var ( latestImageTag *api.ImageTag @@ -153,6 +152,10 @@ func latestSemver(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error) } } + if latestImageTag == nil { + return nil, fmt.Errorf("no suitable version found") + } + return latestImageTag, nil } diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go new file mode 100644 index 00000000..6f744ecd --- /dev/null +++ b/pkg/version/version_test.go @@ -0,0 +1,275 @@ +package version + +import ( + "regexp" + "testing" + "time" + + "github.com/jetstack/version-checker/pkg/api" + + "github.com/stretchr/testify/assert" +) + +// Helper function to parse time +func parseTime(t string) time.Time { + parsedTime, _ := time.Parse(time.RFC3339, t) + return parsedTime +} + +func TestLatestSemver(t *testing.T) { + // Ideal Set of Tags + tags := []api.ImageTag{ + {Tag: "v1.0.0", Timestamp: parseTime("2023-06-01T00:00:00Z")}, + {Tag: "v1.1.0", Timestamp: parseTime("2023-06-02T00:00:00Z")}, + {Tag: "v1.1.1-alpha", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "v1.1.1", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "v2.0.0", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + } + tagsNoPrefix := []api.ImageTag{ + {Tag: "1.0.0", Timestamp: parseTime("2023-06-01T00:00:00Z")}, + {Tag: "1.1.0", Timestamp: parseTime("2023-06-02T00:00:00Z")}, + {Tag: "1.1.1-alpha", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "1.1.1", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "2.0.0", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + } + // Include More Alpha/Beta/RC + alphaBetaTags := []api.ImageTag{ + {Tag: "v1.0.0", Timestamp: parseTime("2023-06-01T00:00:00Z")}, + {Tag: "v1.1.0", Timestamp: parseTime("2023-06-02T00:00:00Z")}, + {Tag: "v1.1.1-alpha", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "v1.1.1", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "v2.0.0-alpha", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + {Tag: "v2.0.0-beta", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + {Tag: "v2.0.0-rc1", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + {Tag: "v2.0.0-rc2", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + } + // Images that are all numerical + nonSemVer := []api.ImageTag{ + {Tag: "20230601", Timestamp: parseTime("2023-06-01T00:00:00Z")}, + {Tag: "20230602", Timestamp: parseTime("2023-06-02T00:00:00Z")}, + {Tag: "20230603", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "20230604", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "20230605", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + } + // This is to simulate an image that USED to SemVer but stopped + stoppedSemVer := []api.ImageTag{ + {Tag: "v1.0.0", Timestamp: parseTime("2023-06-01T00:00:00Z")}, + {Tag: "v1.1.0", Timestamp: parseTime("2023-06-02T00:00:00Z")}, + {Tag: "202306030", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "202306031", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "202306040", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "202306050", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + {Tag: "202306060", Timestamp: parseTime("2023-06-06T00:00:00Z")}, + } + // This is to simulate an image that USED to SemVer but stopped + startedSemVer := []api.ImageTag{ + {Tag: "20230603", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "202306031", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "20230604", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "20230605", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + {Tag: "20230606", Timestamp: parseTime("2023-06-06T00:00:00Z")}, + {Tag: "v1.0.0", Timestamp: parseTime("2023-06-09T00:00:00Z")}, + {Tag: "v1.1.0", Timestamp: parseTime("2023-06-10T00:00:00Z")}, + } + // Mixed Numerical and SemVer along with Older images pushed more recently + badTags := []api.ImageTag{ + {Tag: "v1.0.0", Timestamp: parseTime("2023-06-01T00:00:00Z")}, + {Tag: "v1.1.0", Timestamp: parseTime("2023-06-02T00:00:00Z")}, + {Tag: "9999999", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "v1.1.1-alpha", Timestamp: parseTime("2023-06-03T00:00:00Z")}, + {Tag: "v1.1.1", Timestamp: parseTime("2023-06-04T00:00:00Z")}, + {Tag: "v2.0.0", Timestamp: parseTime("2023-06-05T00:00:00Z")}, + {Tag: "v1.1.1", Timestamp: parseTime("2023-06-06T00:00:00Z")}, + } + + tests := []struct { + name string + opts *api.Options + expected string + tags []api.ImageTag + }{ + { + name: "No constraints", + opts: &api.Options{}, + expected: "v2.0.0", + }, + { + name: "Regex match v1.*", + opts: &api.Options{ + RegexMatcher: regexp.MustCompile("v1.*"), + }, + expected: "v1.1.1", + }, + { + name: "Pin major version 1", + opts: &api.Options{ + PinMajor: intPtr(1), + }, + expected: "v1.1.1", + }, + { + name: "Pin minor version 1.1", + opts: &api.Options{ + PinMajor: intPtr(1), + PinMinor: intPtr(1), + }, + expected: "v1.1.1", + }, + { + name: "Pin patch version 1.1.1", + opts: &api.Options{ + PinMajor: intPtr(1), + PinMinor: intPtr(1), + PinPatch: intPtr(1), + }, + expected: "v1.1.1", + }, + { + name: "Exclude metadata", + opts: &api.Options{ + UseMetaData: false, + }, + expected: "v2.0.0", + }, + { + name: "Include metadata", + opts: &api.Options{ + UseMetaData: true, + }, + expected: "v2.0.0", + }, + { + name: "NoPrefixed Tags", + opts: &api.Options{}, + tags: tagsNoPrefix, + expected: "2.0.0", + }, + // Some Bad/Miss-behaving tags + { + name: "Bad Tags", + opts: &api.Options{ + RegexMatcher: regexp.MustCompile(`^v(\d+)(\.\d+)?(\.\d+)?(.*)$`), + }, + tags: badTags, + expected: "v2.0.0", + }, + // None SemVer tags + { + name: "Non SemVer", + opts: &api.Options{ + RegexMatcher: regexp.MustCompile(`^(\d+)`), + }, + tags: nonSemVer, + expected: "20230605", + }, + { + name: "Stopped SemVer", + opts: &api.Options{ + RegexMatcher: regexp.MustCompile(`^(\d+)`), + }, + tags: stoppedSemVer, + expected: "202306060", + }, + { + name: "Started SemVer", + opts: &api.Options{ + RegexMatcher: regexp.MustCompile(`^v(\d+)(\.\d+)?(\.\d+)?(.*)$`), + }, + tags: startedSemVer, + expected: "v1.1.0", + }, + { + name: "Alpha/Beta SemVer", + opts: &api.Options{ + UseMetaData: true, + }, + tags: alphaBetaTags, + expected: "v1.1.1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if len(tt.tags) > 0 { + tags = tt.tags + } + tag, err := latestSemver(tt.opts, tags) + assert.NoError(t, err) + assert.NotNil(t, tag) + assert.Equal(t, tt.expected, tag.Tag) + }) + } +} + +func TestLatestSHA(t *testing.T) { + tests := []struct { + name string + tags []api.ImageTag + expectedSHA *string + }{ + { + name: "Single tag", + tags: []api.ImageTag{ + {SHA: "sha1", Timestamp: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)}, + }, + expectedSHA: strPtr("sha1"), + }, + { + name: "Multiple tags, latest in the middle", + tags: []api.ImageTag{ + {SHA: "sha1", Timestamp: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {SHA: "sha2", Timestamp: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {SHA: "sha3", Timestamp: time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)}, + }, + expectedSHA: strPtr("sha2"), + }, + { + name: "Multiple tags, latest at the end", + tags: []api.ImageTag{ + {SHA: "sha1", Timestamp: time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {SHA: "sha2", Timestamp: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {SHA: "sha3", Timestamp: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)}, + }, + expectedSHA: strPtr("sha3"), + }, + { + name: "No tags", + tags: []api.ImageTag{}, + expectedSHA: nil, + }, + { + name: "All tags with the same timestamp", + tags: []api.ImageTag{ + {SHA: "sha1", Timestamp: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {SHA: "sha2", Timestamp: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {SHA: "sha3", Timestamp: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)}, + }, + expectedSHA: strPtr("sha1"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := latestSHA(tt.tags) + if err != nil { + t.Errorf("latestSHA() error = %v", err) + return + } + if (got == nil && tt.expectedSHA != nil) || (got != nil && tt.expectedSHA == nil) { + t.Errorf("latestSHA() = %v, want %v", got, tt.expectedSHA) + return + } + if got != nil && got.SHA != *tt.expectedSHA { + t.Errorf("latestSHA() = %v, want %v", got.SHA, *tt.expectedSHA) + } + }) + } +} + +func intPtr(i int64) *int64 { + return &i +} + +func strPtr(s string) *string { + return &s +}