coverage: make targets + close zero-cov gaps (namegen, sessionstream)
Adds `make coverage` (per-package + total via -coverpkg=./...), `make coverage-html`, and `make coverage-total` (CI-friendly). Wires coverage.out/coverage.html through `make clean` and .gitignore. Closes the two easy zero-coverage packages: namegen (77.8%) and sessionstream (93.5%). Total statement coverage 51.7% -> 52.1%.
This commit is contained in:
parent
88425fb857
commit
18bf89eae9
5 changed files with 204 additions and 11 deletions
54
internal/namegen/namegen_test.go
Normal file
54
internal/namegen/namegen_test.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package namegen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
adjSet := make(map[string]struct{}, len(adjectives))
|
||||
for _, a := range adjectives {
|
||||
adjSet[a] = struct{}{}
|
||||
}
|
||||
subSet := make(map[string]struct{}, len(substantives))
|
||||
for _, s := range substantives {
|
||||
subSet[s] = struct{}{}
|
||||
}
|
||||
|
||||
seen := make(map[string]int)
|
||||
for i := 0; i < 200; i++ {
|
||||
name := Generate()
|
||||
parts := strings.Split(name, "-")
|
||||
if len(parts) != 2 {
|
||||
t.Fatalf("expected adj-noun form, got %q", name)
|
||||
}
|
||||
if _, ok := adjSet[parts[0]]; !ok {
|
||||
t.Fatalf("unknown adjective %q in %q", parts[0], name)
|
||||
}
|
||||
if _, ok := subSet[parts[1]]; !ok {
|
||||
t.Fatalf("unknown substantive %q in %q", parts[1], name)
|
||||
}
|
||||
seen[name]++
|
||||
}
|
||||
|
||||
// Minimal variety check: adj-noun cartesian product is thousands of
|
||||
// combinations; 200 draws should hit more than a couple.
|
||||
if len(seen) < 10 {
|
||||
t.Fatalf("expected varied output, only saw %d distinct names", len(seen))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomIndex(t *testing.T) {
|
||||
if got := randomIndex(0); got != 0 {
|
||||
t.Fatalf("randomIndex(0) = %d, want 0", got)
|
||||
}
|
||||
if got := randomIndex(1); got != 0 {
|
||||
t.Fatalf("randomIndex(1) = %d, want 0", got)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
n := randomIndex(7)
|
||||
if n < 0 || n >= 7 {
|
||||
t.Fatalf("randomIndex(7) = %d, out of range", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
117
internal/sessionstream/sessionstream_test.go
Normal file
117
internal/sessionstream/sessionstream_test.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package sessionstream
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteReadFrameRoundtrip(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
channel byte
|
||||
payload []byte
|
||||
}{
|
||||
{"stdout_bytes", ChannelStdout, []byte("hello world")},
|
||||
{"stderr_bytes", ChannelStderr, []byte{0x00, 0xff, 0x7f}},
|
||||
{"empty_payload", ChannelStdin, nil},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := WriteFrame(&buf, tc.channel, tc.payload); err != nil {
|
||||
t.Fatalf("WriteFrame: %v", err)
|
||||
}
|
||||
ch, got, err := ReadFrame(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrame: %v", err)
|
||||
}
|
||||
if ch != tc.channel {
|
||||
t.Fatalf("channel = %d, want %d", ch, tc.channel)
|
||||
}
|
||||
if !bytes.Equal(got, tc.payload) && !(len(got) == 0 && len(tc.payload) == 0) {
|
||||
t.Fatalf("payload = %q, want %q", got, tc.payload)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type shortWriter struct {
|
||||
failAfter int
|
||||
written int
|
||||
}
|
||||
|
||||
func (s *shortWriter) Write(p []byte) (int, error) {
|
||||
s.written += len(p)
|
||||
if s.written > s.failAfter {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestWriteFrameWriterError(t *testing.T) {
|
||||
w := &shortWriter{failAfter: 2}
|
||||
err := WriteFrame(w, ChannelStdout, []byte("payload"))
|
||||
if err == nil {
|
||||
t.Fatal("expected error from short writer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFrameTruncated(t *testing.T) {
|
||||
_, _, err := ReadFrame(bytes.NewReader([]byte{0x02, 0x00}))
|
||||
if !errors.Is(err, io.ErrUnexpectedEOF) && err == nil {
|
||||
t.Fatalf("expected EOF-ish error, got %v", err)
|
||||
}
|
||||
|
||||
// Header OK, but payload truncated.
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte{ChannelStdout, 0x00, 0x00, 0x00, 0x05})
|
||||
buf.Write([]byte("ab"))
|
||||
if _, _, err := ReadFrame(&buf); err == nil {
|
||||
t.Fatal("expected truncated payload error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlRoundtrip(t *testing.T) {
|
||||
code := 42
|
||||
msg := ControlMessage{Type: "exit", ExitCode: &code}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WriteControl(&buf, msg); err != nil {
|
||||
t.Fatalf("WriteControl: %v", err)
|
||||
}
|
||||
|
||||
got, err := ReadNextControl(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadNextControl: %v", err)
|
||||
}
|
||||
if got.Type != "exit" {
|
||||
t.Fatalf("type = %q, want exit", got.Type)
|
||||
}
|
||||
if got.ExitCode == nil || *got.ExitCode != 42 {
|
||||
t.Fatalf("exit_code = %v, want 42", got.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadControlBadJSON(t *testing.T) {
|
||||
if _, err := ReadControl([]byte("{not json")); err == nil {
|
||||
t.Fatal("expected JSON error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadNextControlWrongChannel(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := WriteFrame(&buf, ChannelStdout, []byte("not a control frame")); err != nil {
|
||||
t.Fatalf("WriteFrame: %v", err)
|
||||
}
|
||||
if _, err := ReadNextControl(&buf); err == nil {
|
||||
t.Fatal("expected error for non-control channel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatConstant(t *testing.T) {
|
||||
if FormatV1 != "stdio_mux_v1" {
|
||||
t.Fatalf("FormatV1 = %q, want stdio_mux_v1", FormatV1)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue