Merged in feature/versionTimestamps (pull request #98)

add GetVersionTimestamp

* add GetVersionTimestamp

* docs
This commit is contained in:
Jay Brown
2025-03-10 20:16:44 +00:00
parent a91372dc06
commit 8e4d66a998
3 changed files with 100 additions and 7 deletions
@@ -3,6 +3,8 @@ package observability
import (
"context"
"log/slog"
"sync"
"time"
"runtime/debug"
@@ -17,6 +19,48 @@ const (
SystemName = "doczy"
)
var startupTime time.Time
var startupTimeOnce sync.Once
func GetVersionTimestamp() time.Time {
startupTimeOnce.Do(func() {
info, ok := debug.ReadBuildInfo()
if !ok {
startupTime = time.Now()
return
}
// Look for vcs.time in build settings
for _, setting := range info.Settings {
if setting.Key == "vcs.time" {
// Parse the build timestamp
buildTime, err := time.Parse(time.RFC3339, setting.Value)
if err == nil {
startupTime = buildTime
return
}
break
}
}
// Return current time if no build time found
startupTime = time.Now()
})
return startupTime
}
// GetVersion returns the version of the application in the Golang build info format.
// This is a raw format and will change depending on the build environment and the git tags.
// When building from a commit with an associated Git tag (e.g., v1.2.3),
//
// the runtime version uses the tag name without a timestamp.
//
// The "+dirty" suffix is appended when there are uncommitted changes
//
// present in the version control system (VCS) at the time of building.
//
// An example of a version v0.0.2-0.20250310175756-a91372dc0602+dirty.
func GetVersion() string {
info, ok := debug.ReadBuildInfo()
if !ok {
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"testing"
"time"
"queryorchestration/internal/serviceconfig/observability"
@@ -32,3 +33,52 @@ func TestSetOtel(t *testing.T) {
}
}()
}
func TestGetVersionTimestamp(t *testing.T) {
// Test 1: First call should set the timestamp
t1 := observability.GetVersionTimestamp()
if t1.IsZero() {
t.Error("GetVersionTimestamp returned zero time")
}
// Test 2: Second call should return exactly the same time
time.Sleep(time.Millisecond * 100) // Small delay to ensure time would be different if not cached
t2 := observability.GetVersionTimestamp()
if !t1.Equal(t2) {
t.Errorf("Timestamps should be identical but got t1=%v, t2=%v", t1, t2)
}
// Test 3: Even after a longer delay, should still return the same time
time.Sleep(time.Second) // Longer delay
t3 := observability.GetVersionTimestamp()
if !t1.Equal(t3) {
t.Errorf("Timestamp changed after delay: original=%v, new=%v", t1, t3)
}
// Test 4: Returned time should not be in the future
if t1.After(time.Now()) {
t.Errorf("Timestamp is in the future: %v", t1)
}
}
func TestGetVersion(t *testing.T) {
// Test 1: Basic call
version := observability.GetVersion()
if version == "" {
t.Error("Version should not be empty")
}
// Test 2: Should either be "unknown" or follow semver-like pattern
if version != "unknown" {
// This is a loose check - you might want to make it more strict
if len(version) < 3 {
t.Errorf("Version '%s' seems too short to be valid", version)
}
}
// Test 3: Multiple calls should be consistent
version2 := observability.GetVersion()
if version != version2 {
t.Errorf("Version not consistent between calls: '%s' != '%s'", version, version2)
}
}