a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
243 lines
6.9 KiB
Go
243 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/zzet/gortex/internal/persistence"
|
|
"github.com/zzet/gortex/internal/savings"
|
|
)
|
|
|
|
func TestHumanDuration(t *testing.T) {
|
|
cases := map[time.Duration]string{
|
|
24 * time.Hour: "24h",
|
|
7 * 24 * time.Hour: "7d",
|
|
30 * 24 * time.Hour: "30d",
|
|
2 * time.Hour: "2h",
|
|
90 * time.Minute: "1h30m0s",
|
|
}
|
|
for in, want := range cases {
|
|
if got := humanDuration(in); got != want {
|
|
t.Errorf("humanDuration(%v) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBenchSourceAge_Bucketing(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "tokens.json")
|
|
if err := os.WriteFile(path, []byte("[]"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Fresh file, user-provided: explicit timestamp marker.
|
|
if got := benchSourceAge(path, false); !strings.Contains(got, "just generated") {
|
|
t.Errorf("fresh user-provided source = %q, want '(just generated)'", got)
|
|
}
|
|
// Fresh file, auto-discovered: "seconds ago".
|
|
if got := benchSourceAge(path, true); !strings.Contains(got, "seconds ago") {
|
|
t.Errorf("fresh auto-discovered source = %q, want '(seconds ago)'", got)
|
|
}
|
|
|
|
// Backdate file → "min ago".
|
|
old := time.Now().Add(-15 * time.Minute)
|
|
if err := os.Chtimes(path, old, old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := benchSourceAge(path, true); !strings.Contains(got, "min ago") {
|
|
t.Errorf("15-min-old source = %q, want '(N min ago)'", got)
|
|
}
|
|
|
|
// Backdate to days.
|
|
old = time.Now().Add(-3 * 24 * time.Hour)
|
|
if err := os.Chtimes(path, old, old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := benchSourceAge(path, true); !strings.Contains(got, "days ago") {
|
|
t.Errorf("3-day-old source = %q, want '(N days ago)'", got)
|
|
}
|
|
|
|
// Missing file → empty string (no spurious age).
|
|
if got := benchSourceAge(filepath.Join(dir, "missing.json"), false); got != "" {
|
|
t.Errorf("missing file age = %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestFindLatestBenchTokens_FindsMostRecent(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Build a minimal bench/results layout mirroring `bench all`.
|
|
run1 := filepath.Join(dir, "run-20260101-000000")
|
|
run2 := filepath.Join(dir, "run-20260518-000000")
|
|
for _, r := range []string{run1, run2} {
|
|
if err := os.MkdirAll(r, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(r, "tokens.json"), []byte("[]"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Make run2 newer by explicit chtimes.
|
|
now := time.Now()
|
|
_ = os.Chtimes(filepath.Join(run1, "tokens.json"), now.Add(-72*time.Hour), now.Add(-72*time.Hour))
|
|
_ = os.Chtimes(filepath.Join(run2, "tokens.json"), now.Add(-1*time.Hour), now.Add(-1*time.Hour))
|
|
|
|
// chdir to the temp dir so the relative `bench/results` root the
|
|
// function walks resolves under our fixture.
|
|
tmpRoot := filepath.Join(dir, "workspace")
|
|
resultsDir := filepath.Join(tmpRoot, "bench", "results")
|
|
if err := os.MkdirAll(resultsDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, r := range []string{run1, run2} {
|
|
dst := filepath.Join(resultsDir, filepath.Base(r))
|
|
if err := os.MkdirAll(dst, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
src := filepath.Join(r, "tokens.json")
|
|
dstFile := filepath.Join(dst, "tokens.json")
|
|
data, _ := os.ReadFile(src)
|
|
_ = os.WriteFile(dstFile, data, 0o644)
|
|
st, _ := os.Stat(src)
|
|
_ = os.Chtimes(dstFile, st.ModTime(), st.ModTime())
|
|
}
|
|
wd, _ := os.Getwd()
|
|
defer func() { _ = os.Chdir(wd) }()
|
|
if err := os.Chdir(tmpRoot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, ok := findLatestBenchTokens()
|
|
if !ok {
|
|
t.Fatal("expected to find a bench tokens artifact")
|
|
}
|
|
if !strings.Contains(got, filepath.Base(run2)) {
|
|
t.Errorf("findLatestBenchTokens = %q, want a path under %s (newer)", got, filepath.Base(run2))
|
|
}
|
|
}
|
|
|
|
func TestFindLatestBenchTokens_MissingDirReturnsFalse(t *testing.T) {
|
|
wd, _ := os.Getwd()
|
|
defer func() { _ = os.Chdir(wd) }()
|
|
if err := os.Chdir(t.TempDir()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, ok := findLatestBenchTokens(); ok {
|
|
t.Errorf("expected no result in empty dir, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestLoadHistory_EmptyStore(t *testing.T) {
|
|
dir := t.TempDir()
|
|
h, err := loadHistory(dir, 24*time.Hour)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.Calls != 0 || h.Saved != 0 {
|
|
t.Errorf("empty store should produce zero totals, got %+v", h)
|
|
}
|
|
}
|
|
|
|
func TestLoadHistory_SinceZeroUsesCumulative(t *testing.T) {
|
|
// Populate a ledger with a known total, then call loadHistory with
|
|
// since=0. The result must come from the cumulative snapshot, not
|
|
// an event scan.
|
|
dir := t.TempDir()
|
|
store, err := savings.Open(persistence.DefaultSidecarPath(dir))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store.AddObservation(savings.Observation{Repo: "/r", Language: "go", Tool: "get_symbol_source", Returned: 50, Saved: 500})
|
|
h, err := loadHistory(dir, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.Calls != 1 || h.Saved != 500 {
|
|
t.Errorf("since=0 should reflect cumulative ledger, got %+v", h)
|
|
}
|
|
}
|
|
|
|
func TestLoadHistory_WindowFiltersEvents(t *testing.T) {
|
|
dir := t.TempDir()
|
|
store, err := savings.Open(persistence.DefaultSidecarPath(dir))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store.AddObservation(savings.Observation{Repo: "/r", Language: "go", Tool: "read_file", Returned: 10, Saved: 90})
|
|
|
|
h, err := loadHistory(dir, 24*time.Hour)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.Calls != 1 || h.Saved != 90 {
|
|
t.Errorf("fresh event should fall inside a 24h window, got %+v", h)
|
|
}
|
|
}
|
|
|
|
func TestGainHistory_JSON(t *testing.T) {
|
|
h := &gainHistory{
|
|
Path: "/tmp/savings.json",
|
|
Since: 24 * time.Hour,
|
|
Calls: 42,
|
|
Saved: 1000,
|
|
Returned: 200,
|
|
Costs: map[string]float64{"claude-opus-4": 0.015},
|
|
}
|
|
out := h.toJSON()
|
|
raw, _ := json.Marshal(out)
|
|
for _, want := range []string{
|
|
`"calls_counted":42`,
|
|
`"tokens_saved":1000`,
|
|
`"cost_avoided_usd":{`,
|
|
`"claude-opus-4":0.015`,
|
|
} {
|
|
if !bytes.Contains(raw, []byte(want)) {
|
|
t.Errorf("history JSON missing %q\n%s", want, raw)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderGainProjection_HeadlineMarker(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
rows := []tokensMetric{
|
|
{Case: "a", JSONTokens: 100, GCXTokens: 80},
|
|
}
|
|
renderGainProjection(&buf, rows, 1000, "claude-opus-4-8")
|
|
out := buf.String()
|
|
if !strings.Contains(out, "*claude-opus-4-8") {
|
|
t.Errorf("headline model should be marked with *: %s", out)
|
|
}
|
|
if !strings.Contains(out, "*headlined: claude-opus-4-8") {
|
|
t.Errorf("footer should name the headlined model: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderGainProjection_NoHeadlineMarksNone(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
rows := []tokensMetric{
|
|
{Case: "a", JSONTokens: 100, GCXTokens: 80},
|
|
}
|
|
renderGainProjection(&buf, rows, 1000, "")
|
|
out := buf.String()
|
|
if strings.Contains(out, "*headlined") {
|
|
t.Errorf("no headline → no footer; got: %s", out)
|
|
}
|
|
if strings.Contains(out, "*claude-opus-4") {
|
|
t.Errorf("no headline → no row marker; got: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestGainCmd_Registered(t *testing.T) {
|
|
subs := map[string]bool{}
|
|
for _, c := range rootCmd.Commands() {
|
|
subs[c.Name()] = true
|
|
}
|
|
if !subs["gain"] {
|
|
t.Errorf("rootCmd missing `gain`; have %v", subs)
|
|
}
|
|
}
|