62 lines
2.0 KiB
Go
62 lines
2.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
package io
|
||
|
|
|
||
|
|
import (
|
||
|
|
"flag"
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"runtime"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestMain installs a heap watchdog for the whole package run. The io
|
||
|
|
// tests are hostile-input tests by design: they feed crafted headers to
|
||
|
|
// the readers and check that nothing panics and nothing grows without
|
||
|
|
// bound. A regression therefore cannot fail politely, it allocates: a
|
||
|
|
// hard-link cycle and a deep group chain both took the host down once.
|
||
|
|
// The watchdog turns any such runaway into an immediate panic that
|
||
|
|
// unwinds the offending code path, so the worst case is a failed test
|
||
|
|
// rather than an OOM kill of the editor that started it.
|
||
|
|
func TestMain(m *testing.M) {
|
||
|
|
// The aggregate read budget bounds what one input may allocate
|
||
|
|
// legally; the watchdog sits an order above it. Fuzzing runs many
|
||
|
|
// workers holding inputs at once and the engine carries its own
|
||
|
|
// corpus and coverage bookkeeping, so the fuzz run watches a wider
|
||
|
|
// ceiling while still catching the unbounded growth the guard
|
||
|
|
// exists for: a runaway reaches any finite cap in seconds.
|
||
|
|
//
|
||
|
|
// The flags are parsed here, before the ceiling is decided: the
|
||
|
|
// test flags are only registered at TestMain and m.Run parses them
|
||
|
|
// later, so an earlier read of test.fuzz sees its empty default and
|
||
|
|
// the fuzz ceiling never fires.
|
||
|
|
flag.Parse()
|
||
|
|
const plainCap = 3 << 30
|
||
|
|
ceiling := int64(plainCap)
|
||
|
|
if f := flag.Lookup("test.fuzz"); f != nil && f.Value.String() != "" {
|
||
|
|
ceiling = 32 << 30
|
||
|
|
}
|
||
|
|
stop := make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
ticker := time.NewTicker(20 * time.Millisecond)
|
||
|
|
defer ticker.Stop()
|
||
|
|
for {
|
||
|
|
select {
|
||
|
|
case <-stop:
|
||
|
|
return
|
||
|
|
case <-ticker.C:
|
||
|
|
var m runtime.MemStats
|
||
|
|
runtime.ReadMemStats(&m)
|
||
|
|
if int64(m.HeapAlloc) > ceiling {
|
||
|
|
panic(fmt.Sprintf("io test binary heap above %d GiB: a reader is allocating without bound", ceiling>>30))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
code := m.Run()
|
||
|
|
close(stop)
|
||
|
|
os.Exit(code)
|
||
|
|
}
|