Skip to content
quarry Artifact [ecc2050269]
Login

Artifact [ecc2050269]

Artifact ecc2050269aac1574fc2819e08ef84ae411d0b910a10d94cd183715253782180:


package xfer

import (
	"errors"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

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

// The store child's stderr must be an *os.File, and the forge must own
// the reader.
//
// This is a structural assertion on purpose. The forge log gained the
// child's diagnostics by assigning a plain io.Writer to cmd.Stderr.
// os/exec connects an *os.File to the child directly, but copies
// anything else on a goroutine that Wait() blocks on, in
// awaitGoroutines, until every write end of that pipe is closed.
// Process.Kill() closes the direct child's; a grandchild holding the
// inherited descriptor keeps its own, and Wait() never returns.
//
// That is not a slow test, it is a stopped forge. Close() and
// Unregister() hold f.mu across the wait, ensure() and ServeHTTP take
// the same mutex, so one removed repository wedges sync for every
// repository. It hung two CI runs at the ten-minute timeout, and the
// goroutine dump named awaitGoroutines under Fronter.Close.
//
// Asserted structurally rather than by racing a grandchild: a
// synthetic child that leaves one holding stderr did NOT reproduce the
// stall reliably here, and a guard that passes while the bug is
// present is worse than none. The type of Stderr is the invariant that
// actually prevents it, it is what regressed, and it cannot pass by
// luck.
func TestTheChildStderrIsAFileNotACopiedWriter(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir := t.TempDir()
	zrun(t, dir, "init")

	f := New()
	defer f.Close()
	f.Register("alpha", dir)
	b, err := f.ensure("alpha")
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := b.cmd.Stderr.(*os.File); !ok {
		t.Fatalf("FAIL: cmd.Stderr is %T, not *os.File — os/exec will copy it on a "+
			"goroutine and Wait() will block in awaitGoroutines until every write "+
			"end is closed, which a grandchild never does", b.cmd.Stderr)
	}
	if b.errR == nil {
		t.Error("FAIL: the forge does not hold the read end, so stop() cannot end the reading")
	}
}

// And stopping releases both, promptly.
func TestStoppingTheChildReleasesItAndItsReader(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir := t.TempDir()
	zrun(t, dir, "init")

	f := New()
	defer f.Close()
	f.Register("alpha", dir)
	b, err := f.ensure("alpha")
	if err != nil {
		t.Fatal(err)
	}
	// The store is real, so the child has something to say and the
	// reader has something to read before it is closed under it.
	_ = filepath.Join(dir, ".zit")

	done := make(chan struct{})
	go func() { b.stop(); close(done) }()
	select {
	case <-done:
	case <-time.After(30 * time.Second):
		t.Fatal("FAIL: stop() did not return — the child's stderr is being waited on")
	}
	if b.cmd != nil {
		t.Error("FAIL: stop() left the command in place")
	}
	if b.errR != nil {
		t.Error("FAIL: stop() left the stderr reader open")
	}
}

// stop returns only once the serve child has exited and been reaped,
// and the child is one of the process's tracked children while it runs,
// so a stopping forge waits for it and logs its end instead of leaving
// it for systemd to SIGKILL.
func TestStoppingTheChildWaitsForItToExit(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir := t.TempDir()
	zrun(t, dir, "init")

	f := New()
	defer f.Close()
	f.Register("alpha", dir)
	before := children.Default.Live()
	b, err := f.ensure("alpha")
	if err != nil {
		t.Fatal(err)
	}
	if children.Default.Live() != before+1 {
		t.Fatal("FAIL: the serve child is not tracked, so a stop cannot wait for it")
	}
	t.Log("ok: the serve child is tracked")
	pid := b.cmd.Process.Pid
	f.Unregister("alpha")
	if err := syscall.Kill(pid, 0); !errors.Is(err, syscall.ESRCH) {
		t.Fatalf("FAIL: pid %d still exists after Unregister returned (kill 0: %v)", pid, err)
	}
	t.Log("ok: the child is gone when Unregister returns")
	if children.Default.Live() != before {
		t.Fatal("FAIL: the stopped serve child is still tracked")
	}
	t.Log("ok: and no longer tracked")
}