common/version/version_test.go

312 lines
7.7 KiB
Go

package version
import (
"runtime"
"strings"
"testing"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
)
func TestCheckVersion(t *testing.T) {
tests := []struct {
In string
Min string
Expected bool
}{
// Basic version comparisons
{"v3.8.4", "v3.8.5", false},
{"v3.9.3", "v3.8.5", true},
{"v3.8.5", "v3.8.5", true},
// Dev snapshot should always pass
{"dev-snapshot", "v3.8.5", true},
{"dev-snapshot", "v99.99.99", true},
// Versions with Git hashes should be stripped
{"v3.8.5/abc123", "v3.8.5", true},
{"v3.8.4/abc123", "v3.8.5", false},
{"v3.9.0/def456", "v3.8.5", true},
// Pre-release versions
{"v3.8.5-alpha", "v3.8.5", false},
{"v3.8.5", "v3.8.5-alpha", true},
{"v3.8.5-beta", "v3.8.5-alpha", true},
}
for _, d := range tests {
r := CheckVersion(d.In, d.Min)
if r != d.Expected {
t.Errorf("CheckVersion(%q, %q) = %t, expected %t", d.In, d.Min, r, d.Expected)
}
}
}
func TestVersionInfo(t *testing.T) {
info := VersionInfo()
// Check that we get a valid Info struct
if info.Version == "" {
t.Error("VersionInfo().Version should not be empty")
}
// Version should start with "v" or be "dev-snapshot"
if !strings.HasPrefix(info.Version, "v") && info.Version != "dev-snapshot" {
t.Errorf("Version should start with 'v' or be 'dev-snapshot', got: %s", info.Version)
}
// GitRevShort should be <= 7 characters if set
if info.GitRevShort != "" && len(info.GitRevShort) > 7 {
t.Errorf("GitRevShort should be <= 7 characters, got: %s", info.GitRevShort)
}
// GitRevShort should be prefix of GitRev if both are set
if info.GitRev != "" && info.GitRevShort != "" {
if !strings.HasPrefix(info.GitRev, info.GitRevShort) {
t.Errorf("GitRevShort should be prefix of GitRev: %s not prefix of %s",
info.GitRevShort, info.GitRev)
}
}
}
func TestVersion(t *testing.T) {
version := Version()
if version == "" {
t.Error("Version() should not return empty string")
}
// Should contain Go version
if !strings.Contains(version, runtime.Version()) {
t.Errorf("Version should contain Go version %s, got: %s", runtime.Version(), version)
}
// Should contain the VERSION variable (or dev-snapshot)
info := VersionInfo()
if !strings.Contains(version, info.Version) {
t.Errorf("Version should contain %s, got: %s", info.Version, version)
}
// Should be in expected format: "version (extras)"
if !strings.Contains(version, "(") || !strings.Contains(version, ")") {
t.Errorf("Version should be in format 'version (extras)', got: %s", version)
}
}
func TestVersionCmd(t *testing.T) {
appName := "testapp"
cmd := VersionCmd(appName)
// Test basic command properties
if cmd.Use != "version" {
t.Errorf("Expected command use to be 'version', got: %s", cmd.Use)
}
if cmd.Short == "" {
t.Error("Command should have a short description")
}
if cmd.Long == "" {
t.Error("Command should have a long description")
}
if cmd.Run == nil {
t.Error("Command should have a Run function")
}
// Test that the command can be executed without error
cmd.SetArgs([]string{})
err := cmd.Execute()
if err != nil {
t.Errorf("VersionCmd execution should not return error, got: %s", err)
}
}
func TestKongVersionCmd(t *testing.T) {
cmd := &KongVersionCmd{Name: "testapp"}
// Test that Run() doesn't return an error
err := cmd.Run()
if err != nil {
t.Errorf("KongVersionCmd.Run() should not return error, got: %s", err)
}
}
func TestRegisterMetric(t *testing.T) {
// Create a test registry
registry := prometheus.NewRegistry()
// Test registering metric without name
RegisterMetric("", registry)
// Gather metrics
metricFamilies, err := registry.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %s", err)
}
// Find the build_info metric
var buildInfoFamily *dto.MetricFamily
for _, family := range metricFamilies {
if family.GetName() == "build_info" {
buildInfoFamily = family
break
}
}
if buildInfoFamily == nil {
t.Fatal("build_info metric not found")
}
if buildInfoFamily.GetHelp() == "" {
t.Error("build_info metric should have help text")
}
metrics := buildInfoFamily.GetMetric()
if len(metrics) == 0 {
t.Fatal("build_info metric should have at least one sample")
}
// Check that the metric has the expected labels
metric := metrics[0]
labels := metric.GetLabel()
expectedLabels := []string{"version", "buildtime", "gittime", "git"}
labelMap := make(map[string]string)
for _, label := range labels {
labelMap[label.GetName()] = label.GetValue()
}
for _, expectedLabel := range expectedLabels {
if _, exists := labelMap[expectedLabel]; !exists {
t.Errorf("Expected label %s not found in metric", expectedLabel)
}
}
// Check that the metric value is 1
if metric.GetGauge().GetValue() != 1 {
t.Errorf("Expected build_info metric value to be 1, got %f", metric.GetGauge().GetValue())
}
}
func TestRegisterMetricWithName(t *testing.T) {
// Create a test registry
registry := prometheus.NewRegistry()
// Test registering metric with custom name
appName := "my-test-app"
RegisterMetric(appName, registry)
// Gather metrics
metricFamilies, err := registry.Gather()
if err != nil {
t.Fatalf("Failed to gather metrics: %s", err)
}
// Find the my_test_app_build_info metric
expectedName := "my_test_app_build_info"
var buildInfoFamily *dto.MetricFamily
for _, family := range metricFamilies {
if family.GetName() == expectedName {
buildInfoFamily = family
break
}
}
if buildInfoFamily == nil {
t.Fatalf("%s metric not found", expectedName)
}
}
func TestVersionConsistency(t *testing.T) {
// Call Version() multiple times and ensure it returns the same result
v1 := Version()
v2 := Version()
if v1 != v2 {
t.Errorf("Version() should return consistent results: %s != %s", v1, v2)
}
}
func TestVersionInfoConsistency(t *testing.T) {
// Ensure VersionInfo() is consistent with Version()
info := VersionInfo()
version := Version()
// Version string should contain the semantic version
if !strings.Contains(version, info.Version) {
t.Errorf("Version() should contain VersionInfo().Version: %s not in %s",
info.Version, version)
}
// If GitRevShort is set, version should contain it
if info.GitRevShort != "" {
if !strings.Contains(version, info.GitRevShort) {
t.Errorf("Version() should contain GitRevShort: %s not in %s",
info.GitRevShort, version)
}
}
}
// Test edge cases
func TestCheckVersionEdgeCases(t *testing.T) {
// Test with empty strings
if CheckVersion("", "v1.0.0") {
t.Error("Empty version should not be >= v1.0.0")
}
// Test with malformed versions (should be handled gracefully)
// Note: semver.Compare might panic or return unexpected results for invalid versions
// but our function should handle the common cases
tests := []struct {
version string
minimum string
desc string
}{
{"v1.0.0/", "v1.0.0", "version with trailing slash"},
{"v1.0.0/abc/def", "v1.0.0", "version with multiple slashes"},
}
for _, test := range tests {
// This should not panic
result := CheckVersion(test.version, test.minimum)
t.Logf("%s: CheckVersion(%q, %q) = %t", test.desc, test.version, test.minimum, result)
}
}
// Benchmark version operations
func BenchmarkVersion(b *testing.B) {
// Reset the cached version to test actual computation
v = ""
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Version()
}
}
func BenchmarkVersionInfo(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = VersionInfo()
}
}
func BenchmarkCheckVersion(b *testing.B) {
version := "v1.2.3/abc123"
minimum := "v1.2.0"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = CheckVersion(version, minimum)
}
}
func BenchmarkCheckVersionDevSnapshot(b *testing.B) {
version := "dev-snapshot"
minimum := "v1.2.0"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = CheckVersion(version, minimum)
}
}