chore: release v0.21.3

This commit is contained in:
2026-07-26 16:57:19 +02:00
parent 4fd70f6a9d
commit 91b5af1c7b
46 changed files with 2491 additions and 355 deletions
+100
View File
@@ -0,0 +1,100 @@
package tunergenius
import (
"bufio"
"net"
"os"
"strings"
"testing"
"time"
)
// captureLog redirects the stream applog.Printf always writes to, and returns a
// stop function yielding what was logged. Reading only after stop keeps the
// collector goroutine and the test off the same slice.
func captureLog() func() []string {
orig := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w
var lines []string
done := make(chan struct{})
go func() {
sc := bufio.NewScanner(r)
for sc.Scan() {
lines = append(lines, sc.Text())
}
close(done)
}()
return func() []string {
os.Stderr = orig
w.Close()
<-done
r.Close()
return lines
}
}
// A tuner that answers, then vanishes mid-session. A drop used to leave no trace
// at all, so "did the tuner disconnect, or is the meter just reading a gap
// between syllables?" was unanswerable from the log. It must now log — and log
// ONCE, not on every 400 ms retry.
func TestLinkDownLoggedOnceNotPerRetry(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := ln.Addr().(*net.TCPAddr).Port
die := make(chan struct{})
go func() {
conn, err := ln.Accept()
if err != nil {
return
}
conn.Write([]byte("V1.2.11\n"))
buf := make([]byte, 256)
for {
n, err := conn.Read(buf)
if err != nil {
return
}
id := strings.TrimPrefix(strings.SplitN(string(buf[:n]), "|", 2)[0], "C")
conn.Write([]byte("R" + id + "|0|status state=1 fwd=60.7 swr=-25 active=1\n"))
select {
case <-die:
conn.Close()
ln.Close()
return
default:
}
}
}()
stop := captureLog()
c := New("127.0.0.1", port, "")
if err := c.Start(); err != nil {
stop()
t.Fatal(err)
}
time.Sleep(1200 * time.Millisecond)
connected := c.GetStatus().Connected
close(die)
time.Sleep(2 * time.Second) // ~5 poll cycles with the tuner gone
c.Stop()
lines := stop()
if !connected {
t.Fatal("the client never reached the fake tuner — the rest of the test is meaningless")
}
down := 0
for _, l := range lines {
if strings.Contains(l, "link DOWN") {
down++
}
}
t.Logf("logged:\n%s", strings.Join(lines, "\n"))
if down != 1 {
t.Errorf("got %d 'link DOWN' lines, want exactly 1 — the poll retries many times in a 3 s outage and must not repeat itself", down)
}
}