Skip to content
quarry Artifact [ace9b156dc]
Login

Artifact [ace9b156dc]

Artifact ace9b156dc7b7cc92c27c2882d4dc72f3754baa8f48bc62827e282bb43e098e6:


package zitcli

import (
	"errors"
	"os/exec"
	"testing"

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

// A child killed by a signal is not a child that failed. The forge
// restarting mid-operation SIGTERMs the whole cgroup, and the reader
// deserves "ask again" rather than "something went wrong".
func TestASignalledChildIsNotAFailure(t *testing.T) {
	cmd := exec.Command("sh", "-c", "kill -TERM $$; sleep 5")
	err := cmd.Run()
	if err == nil {
		t.Fatal("the child was supposed to be signalled")
	}
	if !signalled(err) {
		t.Errorf("FAIL: a SIGTERMed child did not read as signalled: %v", err)
	}
	// The trap this fixes: ExitCode() is -1 for a signalled process, so
	// every switch on the exit status falls through to the default and
	// reports a generic fault.
	if got := exitCode(err); got != -1 {
		t.Errorf("FAIL: exit code = %d, want -1 for a signalled child", got)
	}
}

// An ordinary non-zero exit is still a failure, not a restart.
func TestAnOrdinaryFailureIsNotReadAsARestart(t *testing.T) {
	err := exec.Command("sh", "-c", "exit 3").Run()
	if err == nil {
		t.Fatal("the child was supposed to fail")
	}
	if signalled(err) {
		t.Errorf("FAIL: exit 3 read as a signal")
	}
	if got := exitCode(err); got != 3 {
		t.Errorf("FAIL: exit code = %d, want 3", got)
	}
}

// ErrStopped carries "ask again in a moment" through the same class
// every other temporary refusal in the forge uses, so no caller needs
// to learn a new one.
func TestStoppedReadsAsTemporarilyUnavailable(t *testing.T) {
	if !errors.Is(ErrStopped, ops.ErrUnavailable) {
		t.Errorf("FAIL: ErrStopped is not an unavailability")
	}
}