Test that pixad writes only its own errors to stderr (closes #359)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Both tests run pixad as a child process. They fail against this code: every start writes the dependency-injection library's event log to stderr, and a startup error is printed twice inside its frames. Model: opus-5-5
This commit is contained in:
1 parent
092176d917
commit
d633b4e773
1 file changed
+167
@@ -0,0 +1,167 @@
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/pixa/internal/app"
|
||||
)
|
||||
|
||||
// These tests run pixad in a child process: the test binary itself,
|
||||
// started again with runPixadInChildEnvVar set, which makes TestMain run
|
||||
// pixad instead of the tests.
|
||||
const runPixadInChildEnvVar = "TEST_RUN_PIXAD_IN_CHILD"
|
||||
|
||||
const validTestSigningKey = "test-signing-key-for-app-tests-0123456789"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if os.Getenv(runPixadInChildEnvVar) == "1" {
|
||||
os.Args = []string{"pixad"}
|
||||
|
||||
app.Run("test")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// pixadCommand returns a command that runs pixad with only the given
|
||||
// environment, in an empty directory with an empty HOME, so that no
|
||||
// config file is found.
|
||||
func pixadCommand(t *testing.T, env ...string) (*exec.Cmd, *bytes.Buffer) {
|
||||
t.Helper()
|
||||
|
||||
testBinary := os.Args[0]
|
||||
cmd := exec.CommandContext(t.Context(), testBinary) //nolint:gosec // not tainted
|
||||
cmd.Dir = t.TempDir()
|
||||
// A test binary built with -cover warns on stderr at exit when
|
||||
// GOCOVERDIR is not set.
|
||||
cmd.Env = append([]string{
|
||||
runPixadInChildEnvVar + "=1",
|
||||
"HOME=" + t.TempDir(),
|
||||
"GOCOVERDIR=" + t.TempDir(),
|
||||
}, env...)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
return cmd, &stderr
|
||||
}
|
||||
|
||||
// TestRunPrintsStartupErrorOnce checks that an invalid setting is
|
||||
// printed once, as the line that names it, and nothing else goes to
|
||||
// stderr.
|
||||
func TestRunPrintsStartupErrorOnce(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmd, stderr := pixadCommand(t, "PIXA_SIGNING_KEY=short")
|
||||
|
||||
err := cmd.Run()
|
||||
|
||||
var exitErr *exec.ExitError
|
||||
if !errors.As(err, &exitErr) || exitErr.ExitCode() != 1 {
|
||||
t.Fatalf("pixad: got %v, want exit status 1; stderr:\n%s", err, stderr)
|
||||
}
|
||||
|
||||
want := `config key "signing_key" (environment variable PIXA_SIGNING_KEY): ` +
|
||||
"value too short: must be at least 32 characters, got 5\n"
|
||||
if stderr.String() != want {
|
||||
t.Errorf("stderr:\n%s\nwant:\n%s", stderr, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWritesNothingToStderrOnStartAndStop starts pixad, waits until
|
||||
// it answers its health check, stops it with SIGTERM, and checks that it
|
||||
// wrote nothing to stderr.
|
||||
func TestRunWritesNothingToStderrOnStartAndStop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
port := freePort(t)
|
||||
cmd, stderr := pixadCommand(t,
|
||||
"PORT="+strconv.Itoa(port),
|
||||
"PIXA_STATE_DIR="+t.TempDir(),
|
||||
"PIXA_SIGNING_KEY="+validTestSigningKey,
|
||||
)
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("starting pixad: %v", err)
|
||||
}
|
||||
|
||||
waitForHealthCheck(t, port)
|
||||
|
||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
t.Fatalf("signalling pixad: %v", err)
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("pixad: got %v, want exit status 0; stderr:\n%s", err, stderr)
|
||||
}
|
||||
|
||||
if stderr.Len() != 0 {
|
||||
t.Errorf("stderr is not empty:\n%s", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// freePort returns a TCP port on the loopback address that nothing
|
||||
// listens on.
|
||||
func freePort(t *testing.T) int {
|
||||
t.Helper()
|
||||
|
||||
var listenConfig net.ListenConfig
|
||||
|
||||
listener, err := listenConfig.Listen(t.Context(), "tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("finding a free port: %v", err)
|
||||
}
|
||||
|
||||
port := listener.Addr().(*net.TCPAddr).Port //nolint:forcetypeassert // a TCP listener
|
||||
|
||||
err = listener.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("closing listener: %v", err)
|
||||
}
|
||||
|
||||
return port
|
||||
}
|
||||
|
||||
// waitForHealthCheck waits up to 30 seconds for pixad to answer its
|
||||
// health check on port.
|
||||
func waitForHealthCheck(t *testing.T, port int) {
|
||||
t.Helper()
|
||||
|
||||
url := fmt.Sprintf("http://127.0.0.1:%d/.well-known/healthcheck.json", port)
|
||||
deadline := time.Now().Add(30 * time.Second)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("health check request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err == nil {
|
||||
_ = resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
t.Fatalf("pixad did not answer its health check on port %d", port)
|
||||
}
|
||||
Reference in new issue
Block a user