Update deps and boost test coverage from 70.8% to 90%
Update go-git v6, gopsutil v4.26.1, x/term v0.39.0, and transitive deps. Refactor main() into testable run() function, add injectable processLister and termWidthFunc for test isolation, and add tests covering detached HEAD, dirty/clean worktree, empty repo, process listing errors, and terminal width fallback.
This commit is contained in:
16
go.mod
16
go.mod
@@ -3,20 +3,20 @@ module gitea.kajkowalski.nl/kjanat/claude-statusline
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c
|
||||
github.com/shirou/gopsutil/v4 v4.25.11
|
||||
golang.org/x/term v0.38.0
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260206150416-f623c7555599
|
||||
github.com/shirou/gopsutil/v4 v4.26.1
|
||||
golang.org/x/term v0.39.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260207062542-7cf3dc9049c3 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
)
|
||||
|
||||
42
go.sum
42
go.sum
@@ -6,8 +6,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -15,20 +15,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025 h1:24Uc4y1yxMe8V30NhshaDdCaTOw97BWVhVGH/m1+udM=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025/go.mod h1:T6lRF5ejdxaYZLVaCTuTG1+ZSvwI/c2oeiTgBWORJ8Q=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c h1:pR4UmnVFMjNw956fgu+JlSAvmx37qW4ttVF0cu7DL/Q=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c/go.mod h1:EPzgAjDnw+TaCt1w/JUmj+SXwWHUae3c078ixiZQ10Y=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260207062542-7cf3dc9049c3 h1:SYirmki6iTQJnXONzJmHYTD0IRZIPkHSOLt7unzllQA=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260207062542-7cf3dc9049c3/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67 h1:3hutPZF+/FBjR/9MdsLJ7e1mlt9pwHgwxMW7CrbmWII=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67/go.mod h1:xKt0pNHST9tYHvbiLxSY27CQWFwgIxBJuDrOE0JvbZw=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260206150416-f623c7555599 h1:M7Z/G+T9nx6cM1DYsPwV1IWk7zqfGFym14DwUwOB/z8=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260206150416-f623c7555599/go.mod h1:EWlxLBkiFCzXNCadvt05fT9PCAE2sUedgDsvUUIo18s=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@@ -53,8 +51,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
@@ -65,19 +63,19 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
36
main.go
36
main.go
@@ -99,13 +99,14 @@ func formatOutput(left, right string, padding int) string {
|
||||
return fmt.Sprintf("%s%s%s", left, strings.Repeat(" ", padding), right)
|
||||
}
|
||||
|
||||
func main() {
|
||||
jsonStr := readInputFromStdin(bufio.NewReader(os.Stdin))
|
||||
// run reads JSON from r, builds the statusline, and writes it to w.
|
||||
// Returns an error if the input cannot be parsed.
|
||||
func run(r *bufio.Reader, w *strings.Builder) error {
|
||||
jsonStr := readInputFromStdin(r)
|
||||
|
||||
data, err := parseStatusInput(jsonStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("error parsing JSON: %w", err)
|
||||
}
|
||||
|
||||
left, right := buildStatusLine(data)
|
||||
@@ -121,7 +122,17 @@ func main() {
|
||||
padding := calculatePadding(leftVisible, rightVisible, termWidth)
|
||||
output := formatOutput(left, right, padding)
|
||||
|
||||
fmt.Print(output)
|
||||
w.WriteString(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var out strings.Builder
|
||||
if err := run(bufio.NewReader(os.Stdin), &out); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Print(out.String())
|
||||
}
|
||||
|
||||
func formatContextInfo(contextSize int, usage *TokenUsage) string {
|
||||
@@ -136,9 +147,13 @@ func formatContextInfo(contextSize int, usage *TokenUsage) string {
|
||||
return fmt.Sprintf("%dk/%dk", currentK, totalK)
|
||||
}
|
||||
|
||||
// processLister returns the list of running processes.
|
||||
// Replaced in tests to avoid depending on real process state.
|
||||
var processLister = process.Processes
|
||||
|
||||
func getGiteaStatus() string {
|
||||
// Check if gitea process is running using gopsutil (cross-platform)
|
||||
procs, err := process.Processes()
|
||||
procs, err := processLister()
|
||||
if err != nil {
|
||||
return red + "●" + reset
|
||||
}
|
||||
@@ -196,8 +211,15 @@ func getGitInfo(cwd string) string {
|
||||
return fmt.Sprintf(" git:(%s)", branch)
|
||||
}
|
||||
|
||||
func getTerminalWidth() int {
|
||||
// termWidthFunc returns the terminal width.
|
||||
// Replaced in tests to avoid depending on a real TTY.
|
||||
var termWidthFunc = func() (int, error) {
|
||||
width, _, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
return width, err
|
||||
}
|
||||
|
||||
func getTerminalWidth() int {
|
||||
width, err := termWidthFunc()
|
||||
if err != nil {
|
||||
return 80
|
||||
}
|
||||
|
||||
276
main_test.go
276
main_test.go
@@ -3,9 +3,14 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v6"
|
||||
"github.com/go-git/go-git/v6/plumbing/object"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
)
|
||||
|
||||
func TestFormatContextInfo_NilUsage(t *testing.T) {
|
||||
@@ -758,6 +763,277 @@ func TestStatuslineWidthOffset_Constant(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Coverage gap tests ---
|
||||
|
||||
func TestRun_ValidInput(t *testing.T) {
|
||||
jsonStr := `{"model":{"display_name":"Test"},"workspace":{"current_dir":"/tmp"},"context_window":{"context_window_size":100000}}`
|
||||
r := bufio.NewReader(strings.NewReader(jsonStr))
|
||||
var out strings.Builder
|
||||
err := run(r, &out)
|
||||
if err != nil {
|
||||
t.Fatalf("run() returned error: %v", err)
|
||||
}
|
||||
result := out.String()
|
||||
if result == "" {
|
||||
t.Error("run() produced empty output")
|
||||
}
|
||||
if !contains(result, "Test") {
|
||||
t.Errorf("run() output missing model name: %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun_InvalidJSON(t *testing.T) {
|
||||
r := bufio.NewReader(strings.NewReader("not json"))
|
||||
var out strings.Builder
|
||||
err := run(r, &out)
|
||||
if err == nil {
|
||||
t.Error("run() should return error on invalid JSON")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun_EmptyInput(t *testing.T) {
|
||||
r := bufio.NewReader(strings.NewReader(""))
|
||||
var out strings.Builder
|
||||
err := run(r, &out)
|
||||
if err == nil {
|
||||
t.Error("run() should return error on empty input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitInfo_DetachedHEAD(t *testing.T) {
|
||||
// Create a temp git repo and detach HEAD
|
||||
dir := t.TempDir()
|
||||
|
||||
// Initialize repo, create a commit, then detach
|
||||
repo, err := git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
t.Fatalf("git init: %v", err)
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
t.Fatalf("worktree: %v", err)
|
||||
}
|
||||
|
||||
// Create a file and commit
|
||||
if err := os.WriteFile(dir+"/file.txt", []byte("hello"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
wt.Add("file.txt")
|
||||
hash, err := wt.Commit("initial", &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "test",
|
||||
Email: "test@test.com",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("commit: %v", err)
|
||||
}
|
||||
|
||||
// Detach HEAD by checking out the commit hash
|
||||
err = wt.Checkout(&git.CheckoutOptions{
|
||||
Hash: hash,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("checkout: %v", err)
|
||||
}
|
||||
|
||||
result := getGitInfo(dir)
|
||||
shortHash := hash.String()[:7]
|
||||
expected := " git:(" + shortHash + ")"
|
||||
if result != expected {
|
||||
t.Errorf("getGitInfo(detached) = %q, want %q", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitInfo_DirtyWorktree(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
repo, err := git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
t.Fatalf("git init: %v", err)
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
t.Fatalf("worktree: %v", err)
|
||||
}
|
||||
|
||||
// Create a file, commit, then modify it
|
||||
if err := os.WriteFile(dir+"/file.txt", []byte("hello"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
wt.Add("file.txt")
|
||||
_, err = wt.Commit("initial", &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "test",
|
||||
Email: "test@test.com",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("commit: %v", err)
|
||||
}
|
||||
|
||||
// Make the working tree dirty
|
||||
if err := os.WriteFile(dir+"/file.txt", []byte("modified"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
result := getGitInfo(dir)
|
||||
if !contains(result, "✗") {
|
||||
t.Errorf("getGitInfo(dirty) = %q, expected dirty marker ✗", result)
|
||||
}
|
||||
if !contains(result, "git:(master)") {
|
||||
t.Errorf("getGitInfo(dirty) = %q, expected branch name", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitInfo_CleanWorktree(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
repo, err := git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
t.Fatalf("git init: %v", err)
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
t.Fatalf("worktree: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(dir+"/file.txt", []byte("hello"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
wt.Add("file.txt")
|
||||
_, err = wt.Commit("initial", &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "test",
|
||||
Email: "test@test.com",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("commit: %v", err)
|
||||
}
|
||||
|
||||
result := getGitInfo(dir)
|
||||
expected := " git:(master)"
|
||||
if result != expected {
|
||||
t.Errorf("getGitInfo(clean) = %q, want %q", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitInfo_EmptyRepo(t *testing.T) {
|
||||
// A repo with no commits — HEAD doesn't exist
|
||||
dir := t.TempDir()
|
||||
_, err := git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
t.Fatalf("git init: %v", err)
|
||||
}
|
||||
|
||||
result := getGitInfo(dir)
|
||||
// HEAD doesn't exist yet, so repo.Head() returns error
|
||||
if result != "" {
|
||||
t.Errorf("getGitInfo(empty repo) = %q, want empty", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitInfo_UntrackedFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
repo, err := git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
t.Fatalf("git init: %v", err)
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
t.Fatalf("worktree: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(dir+"/tracked.txt", []byte("tracked"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
wt.Add("tracked.txt")
|
||||
_, err = wt.Commit("initial", &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "test",
|
||||
Email: "test@test.com",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("commit: %v", err)
|
||||
}
|
||||
|
||||
// Add an untracked file — this makes the tree dirty
|
||||
if err := os.WriteFile(dir+"/untracked.txt", []byte("new"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
result := getGitInfo(dir)
|
||||
if !contains(result, "✗") {
|
||||
t.Errorf("getGitInfo(untracked) = %q, expected dirty marker", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGiteaStatus_ProcessListError(t *testing.T) {
|
||||
// Save and restore original
|
||||
orig := processLister
|
||||
t.Cleanup(func() { processLister = orig })
|
||||
|
||||
processLister = func() ([]*process.Process, error) {
|
||||
return nil, errors.New("mock error")
|
||||
}
|
||||
|
||||
result := getGiteaStatus()
|
||||
expected := red + "●" + reset
|
||||
if result != expected {
|
||||
t.Errorf("getGiteaStatus(error) = %q, want red dot", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGiteaStatus_NoGiteaProcess(t *testing.T) {
|
||||
orig := processLister
|
||||
t.Cleanup(func() { processLister = orig })
|
||||
|
||||
processLister = func() ([]*process.Process, error) {
|
||||
return []*process.Process{}, nil
|
||||
}
|
||||
|
||||
result := getGiteaStatus()
|
||||
expected := red + "●" + reset
|
||||
if result != expected {
|
||||
t.Errorf("getGiteaStatus(no gitea) = %q, want red dot", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTerminalWidth_Success(t *testing.T) {
|
||||
orig := termWidthFunc
|
||||
t.Cleanup(func() { termWidthFunc = orig })
|
||||
|
||||
termWidthFunc = func() (int, error) {
|
||||
return 120, nil
|
||||
}
|
||||
|
||||
width := getTerminalWidth()
|
||||
if width != 120 {
|
||||
t.Errorf("getTerminalWidth() = %d, want 120", width)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTerminalWidth_Error(t *testing.T) {
|
||||
orig := termWidthFunc
|
||||
t.Cleanup(func() { termWidthFunc = orig })
|
||||
|
||||
termWidthFunc = func() (int, error) {
|
||||
return 0, errors.New("not a terminal")
|
||||
}
|
||||
|
||||
width := getTerminalWidth()
|
||||
if width != 80 {
|
||||
t.Errorf("getTerminalWidth(error) = %d, want 80", width)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
|
||||
func BenchmarkFormatContextInfo(b *testing.B) {
|
||||
|
||||
Reference in New Issue
Block a user