Files
sif/internal/output/sink_test.go
T
TigahandGitHub 281e59cc9e feat(output): add routable Sink for per-scan output capture (#331)
* feat(output): add routable Sink for per-scan output capture

introduce a Sink (writer plus an interactive flag) that the package-level
chrome funcs delegate to via DefaultSink, and give ModuleLogger a sink it
writes through. a caller can now hand a scan its own Sink so that scan's
chrome lands on a dedicated writer instead of the process-wide default.
default routing is unchanged: Info/Warn/Module and friends still write to
the same stdout/stderr sink SetSilent configures.

* fix(output): trim redundant sink doc comments

drop comments on thin package-level wrappers that just forward to
the Sink methods, and tighten the Sink methods' own comments to
note the api-mode no-op instead of restating the func name
2026-07-22 12:53:23 -07:00

84 lines
3.2 KiB
Go

/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package output
import (
"bytes"
"strings"
"testing"
)
// TestSinkRoutesToOwnWriter is the seam a per-target buffer depends on: chrome
// written through a Sink must land on that sink's writer, not the process-wide
// default. Both the top-level funcs and a sink-bound module logger must honour
// it so a routed scan's output can be captured whole.
func TestSinkRoutesToOwnWriter(t *testing.T) {
var buf bytes.Buffer
s := NewSink(&buf, false)
s.Info("info-marker")
s.Success("success-marker")
s.Warn("warn-marker")
s.Error("error-marker")
s.Module("MOD").Warn("module-marker")
got := buf.String()
for _, want := range []string{"info-marker", "success-marker", "warn-marker", "error-marker", "module-marker"} {
if !strings.Contains(got, want) {
t.Errorf("sink writer missing %q; got:\n%s", want, got)
}
}
if s.Interactive() {
t.Error("NewSink(w, false).Interactive() = true, want false")
}
}
// TestSinkDoesNotLeakToDefault confirms a routed sink is isolated: writing to it
// must leave the process default sink untouched, which is what lets concurrent
// targets each own their buffer without stepping on each other.
func TestSinkDoesNotLeakToDefault(t *testing.T) {
var target, other bytes.Buffer
// point the default sink at `other` for the duration of this test.
prev := sink
sink = &other
defer func() { sink = prev }()
NewSink(&target, false).Info("only-in-target")
if other.Len() != 0 {
t.Errorf("routed write leaked to default sink: %q", other.String())
}
if !strings.Contains(target.String(), "only-in-target") {
t.Errorf("routed write missing from target sink: %q", target.String())
}
}
// TestDefaultSinkTracksGlobal pins that the package-level funcs still follow the
// SetSilent routing, so existing single-target behaviour is unchanged.
func TestDefaultSinkTracksGlobal(t *testing.T) {
var buf bytes.Buffer
prev := sink
sink = &buf
defer func() { sink = prev }()
Info("default-path")
if !strings.Contains(buf.String(), "default-path") {
t.Errorf("package Info did not reach the default sink: %q", buf.String())
}
if DefaultSink().Writer() != Writer() {
t.Error("DefaultSink().Writer() should equal the package Writer()")
}
}