package web
import (
"strings"
"testing"
"time"
)
// The panel lists each live process with what started it, its age, its
// usual and its memory; a slow one is marked, and an empty forge says
// nothing is running rather than drawing an empty table.
func TestRunningNowPanelNamesEachProcess(t *testing.T) {
var v opsView
remarshal(map[string]any{"budgetMs": 500, "running": []map[string]any{
{"verb": "timeline", "repo": "fossil", "cause": "GET /r/fossil/timeline", "ageMs": 9000, "usualMs": 1100, "rss": 22 << 20, "slow": true},
{"verb": "ls", "repo": "tcl", "cause": "ingest", "ageMs": 300, "usualMs": 60},
}}, &v)
body := renderOps(t, v)
if !strings.Contains(body, `data-hook="ops-running" data-count="2"`) {
t.Fatalf("FAIL: no running panel with two rows:\n%s", body)
}
if !strings.Contains(body, `data-verb="timeline" data-repo="fossil" data-slow="true"`) ||
!strings.Contains(body, `data-verb="ls" data-repo="tcl" data-slow="false"`) {
t.Errorf("FAIL: rows are not marked by slowness:\n%s", body)
}
for _, want := range []string{"<code>GET /<wbr>r/<wbr>fossil/<wbr>timeline</code>", ">ingest<", "9.00 s", "1.10 s", "22 MB"} {
if !strings.Contains(body, want) {
t.Errorf("FAIL: the panel lacks %q", want)
}
}
empty := renderOps(t, opsView{BudgetMs: 500})
if !strings.Contains(empty, `data-hook="ops-running" data-count="0"`) || strings.Contains(empty, `class="ops-t ops-running"`) {
t.Errorf("FAIL: an idle forge draws a table or no panel")
}
}
// With process memory reported the panel leads with it, the heap is a
// part of it, the cap gets a bar, and the chart plots both series. All
// of it is data attributes; without /proc the panel reads heap-first and
// prints none of it.
func TestMemoryPanelLeadsWithProcess(t *testing.T) {
now := time.Now()
var samples []map[string]any
for i := 0; i < 30; i++ {
samples = append(samples, map[string]any{"at": now.Add(time.Duration(i-30) * time.Minute),
"heap": 800 << 20, "anon": (5 << 30) + uint64(i)<<26, "file": 200 << 20})
}
var v opsView
remarshal(map[string]any{"budgetMs": 500, "memory": map[string]any{
"heap": 800 << 20, "cap": 11 << 30, "anon": 6 << 30, "file": 200 << 20, "current": 8 << 30,
"available": 1 << 30, "swapFree": 100 << 20, "swapTotal": 4 << 30,
"unaccounted": 800 << 20, "samples": samples,
"warnings": []map[string]any{{"kind": "anon-high"}},
}}, &v)
body := renderOps(t, v)
for _, want := range []string{
`data-hook="mem-process" data-bytes="6652166144"`,
`data-hook="mem-resident" data-anon="6442450944" data-file="209715200" data-warnings="1"`,
`data-hook="mem-heap" data-bytes="838860800"`,
`data-hook="mem-available" data-bytes="1073741824"`,
`data-hook="mem-swap" data-free="104857600" data-total="4294967296"`,
`data-hook="mem-cap" data-process="6652166144" data-cap="11811160064" data-near="false"`,
`data-part="outside"`,
`data-series="heap"`, `data-series="process"`,
} {
if !strings.Contains(body, want) {
t.Errorf("FAIL: the panel lacks %q", want)
}
}
var hot opsView
remarshal(map[string]any{"budgetMs": 500, "memory": map[string]any{
"heap": 800 << 20, "cap": 10 << 30, "anon": 8 << 30, "file": 200 << 20}}, &hot)
if hb := renderOps(t, hot); !strings.Contains(hb, `data-near="true"`) || !strings.Contains(hb, "ops-bar-bad") {
t.Errorf("FAIL: 82%% of the cap is not marked near")
}
var none opsView
remarshal(map[string]any{"budgetMs": 500, "memory": map[string]any{"heap": 1 << 30}}, &none)
nb := renderOps(t, none)
for _, bad := range []string{`data-hook="mem-process"`, `data-hook="mem-resident"`, `data-hook="mem-cap"`, `data-series=`} {
if strings.Contains(nb, bad) {
t.Errorf("FAIL: a host without /proc still draws %q", bad)
}
}
if !strings.Contains(nb, `data-hook="mem-part"`) {
t.Errorf("FAIL: the heap-first panel lost its parts")
}
if !t.Failed() {
t.Log("ok")
}
}