Skip to content
quarry Artifact [995c6b07c6]
Login

Artifact [995c6b07c6]

Artifact 995c6b07c618c05ba98bcfcc9e325ee7aced267bc2b65fc5d8b5abbf0fe2bc64:


package api

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/children"
	"craftdesign.group/quarry/internal/obs"
)

// Each live process says what started it and how long it has run
// against the usual for its kind of call; one past three times its
// usual is marked, and one too young to matter is not.
func TestRunningNowMarksACallPastItsUsual(t *testing.T) {
	now := time.Date(2026, 10, 24, 12, 0, 0, 0, time.UTC)
	d := Deps{Running: func() []children.Child {
		return []children.Child{
			{Repo: "fossil", Verb: "timeline", Cause: "GET /r/fossil/timeline", Start: now.Add(-9 * time.Second)},
			{Repo: "tcl", Verb: "timeline", Cause: "ingest", Start: now.Add(-1500 * time.Millisecond)},
			{Repo: "quarry", Verb: "ls", Cause: "warm", Start: now.Add(-400 * time.Millisecond)},
			{Repo: "pikchr", Verb: "zip", Start: now.Add(-30 * time.Second)},
		}
	}}
	var finished []obs.Subprocess
	for _, ms := range []int{900, 1000, 1100, 1200} {
		finished = append(finished, obs.Subprocess{Verb: "timeline", Duration: time.Duration(ms) * time.Millisecond})
	}
	for _, ms := range []int{50, 60, 70} {
		finished = append(finished, obs.Subprocess{Verb: "ls", Duration: time.Duration(ms) * time.Millisecond})
	}
	got := d.opsRunningNow(now, finished)
	if len(got) != 4 {
		t.Fatalf("FAIL: %d rows, want 4: %+v", len(got), got)
	}
	slow := map[string]bool{}
	for _, r := range got {
		slow[r.Repo] = r.Slow
	}
	// 9 s against a 1.1 s usual: slow. 1.5 s against 1.1 s: not.
	// 400 ms against 60 ms: past three times, but under a second.
	// zip has no usual at all, so nothing to be slow against.
	if !slow["fossil"] || slow["tcl"] || slow["quarry"] || slow["pikchr"] {
		t.Errorf("FAIL: slow marks %v", slow)
	}
	if got[0].Cause != "GET /r/fossil/timeline" || got[0].UsualMs != 1100 || got[0].AgeMs != 9000 {
		t.Errorf("FAIL: the first row reads %+v", got[0])
	}
	if got[3].UsualMs != 0 {
		t.Errorf("FAIL: a verb with too few calls claims a usual: %+v", got[3])
	}
	if rows := (Deps{Running: func() []children.Child { return nil }}).opsRunningNow(now, nil); rows == nil || len(rows) != 0 {
		t.Errorf("FAIL: nothing running should be an empty list, got %#v", rows)
	}
}

// A process's memory comes from /proc: this process's own zit children,
// by verb and start time, each answering for one child.
func TestChildProcsReadTheirOwnChildrenFromProc(t *testing.T) {
	root := t.TempDir()
	write := func(rel, body string) {
		p := filepath.Join(root, rel)
		if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
			t.Fatal(err)
		}
	}
	write("stat", "cpu  1 2 3\nbtime 1761300000\nprocesses 9\n")
	// stat fields after the command: state, ppid, then 17 more, then start ticks.
	stat := func(pid, ppid int, comm string, ticks int) string {
		f := "S " + fmt.Sprint(ppid)
		for i := 0; i < 17; i++ {
			f += " 0"
		}
		return fmt.Sprintf("%d (%s) %s %d 0 0\n", pid, comm, f, ticks)
	}
	write("101/stat", stat(101, 7, "zit", 500))
	write("101/cmdline", "zit\x00timeline\x00-R\x00/srv/fossil\x00")
	write("101/statm", "5000 300 10 0 0 0 0\n")
	// A zit that is not ours, and a child of ours that is not zit.
	write("102/stat", stat(102, 8, "zit", 500))
	write("102/cmdline", "zit\x00ls\x00")
	write("102/statm", "1 1\n")
	write("103/stat", stat(103, 7, "sh (x)", 500))
	write("103/cmdline", "/bin/sh\x00-c\x00true\x00")
	write("103/statm", "1 1\n")
	got := childProcsIn(root, 7)
	if len(got) != 1 {
		t.Fatalf("FAIL: %d processes, want only pid 101: %+v", len(got), got)
	}
	p := got[0]
	if p.PID != 101 || p.Verb != "timeline" || p.RSS != 300*uint64(os.Getpagesize()) {
		t.Errorf("FAIL: process reads %+v", p)
	}
	if want := time.Unix(1761300000, 0).Add(5 * time.Second); !p.Start.Equal(want) {
		t.Errorf("FAIL: started %s, want %s", p.Start, want)
	}
}