154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// ── frame builders (mirror the StreamHub wire format) ───────────────────────
|
|
|
|
func putf(b []byte, v float64) []byte {
|
|
var t [8]byte
|
|
binary.LittleEndian.PutUint64(t[:], math.Float64bits(v))
|
|
return append(b, t[:]...)
|
|
}
|
|
|
|
func putSignals(b []byte, sigs map[string]points) []byte {
|
|
var n [4]byte
|
|
binary.LittleEndian.PutUint32(n[:], uint32(len(sigs)))
|
|
b = append(b, n[:]...)
|
|
for k, p := range sigs {
|
|
var kl [2]byte
|
|
binary.LittleEndian.PutUint16(kl[:], uint16(len(k)))
|
|
b = append(b, kl[:]...)
|
|
b = append(b, []byte(k)...)
|
|
var cnt [4]byte
|
|
binary.LittleEndian.PutUint32(cnt[:], uint32(len(p.T)))
|
|
b = append(b, cnt[:]...)
|
|
for _, t := range p.T {
|
|
b = putf(b, t)
|
|
}
|
|
for _, v := range p.V {
|
|
b = putf(b, v)
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
func buildPush(srcID string, sigs map[string]points) []byte {
|
|
b := []byte{1, byte(len(srcID))}
|
|
b = append(b, []byte(srcID)...)
|
|
return putSignals(b, sigs)
|
|
}
|
|
|
|
func buildCapture(trigTime, preSec, postSec float64, sigs map[string]points) []byte {
|
|
b := []byte{2}
|
|
b = putf(b, trigTime)
|
|
b = putf(b, preSec)
|
|
b = putf(b, postSec)
|
|
return putSignals(b, sigs)
|
|
}
|
|
|
|
// ── tests ───────────────────────────────────────────────────────────────────
|
|
|
|
func TestParsePushRoundTrip(t *testing.T) {
|
|
in := map[string]points{"Sine": {T: []float64{0, 0.1, 0.2}, V: []float64{1, 2, 3}}}
|
|
f, err := parsePush(buildPush("src", in))
|
|
if err != nil {
|
|
t.Fatalf("parsePush: %v", err)
|
|
}
|
|
if f.sourceID != "src" {
|
|
t.Errorf("sourceID = %q, want src", f.sourceID)
|
|
}
|
|
got := f.signals["Sine"]
|
|
if len(got.T) != 3 || got.T[2] != 0.2 || got.V[1] != 2 {
|
|
t.Errorf("bad payload: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestParsePushRejectsWrongVersion(t *testing.T) {
|
|
if _, err := parsePush([]byte{2, 0, 0}); err == nil {
|
|
t.Error("expected error on non-v1 frame")
|
|
}
|
|
}
|
|
|
|
func TestParsePushTruncated(t *testing.T) {
|
|
full := buildPush("src", map[string]points{"A": {T: []float64{0}, V: []float64{1}}})
|
|
if _, err := parsePush(full[:len(full)-4]); err == nil {
|
|
t.Error("expected truncation error")
|
|
}
|
|
}
|
|
|
|
func TestParseCaptureRoundTrip(t *testing.T) {
|
|
in := map[string]points{"Sig": {T: []float64{1, 2}, V: []float64{9, 8}}}
|
|
f, err := parseCapture(buildCapture(100.5, 0.02, 0.08, in))
|
|
if err != nil {
|
|
t.Fatalf("parseCapture: %v", err)
|
|
}
|
|
if f.trigTime != 100.5 || f.preSec != 0.02 || f.postSec != 0.08 {
|
|
t.Errorf("header = %v/%v/%v", f.trigTime, f.preSec, f.postSec)
|
|
}
|
|
if g := f.signals["Sig"]; len(g.T) != 2 || g.V[0] != 9 {
|
|
t.Errorf("bad capture payload: %+v", g)
|
|
}
|
|
}
|
|
|
|
func TestParseCaptureRejectsWrongVersion(t *testing.T) {
|
|
if _, err := parseCapture([]byte{1, 0, 0, 0}); err == nil {
|
|
t.Error("expected error on non-v2 frame")
|
|
}
|
|
}
|
|
|
|
func TestMergedSortsAndDedups(t *testing.T) {
|
|
c := &client{
|
|
configs: map[string][]signalInfo{},
|
|
zooms: map[uint32]map[string]points{},
|
|
}
|
|
// two pushes, out of order, with one duplicate timestamp
|
|
c.pushes = []*pushFrame{
|
|
{sourceID: "s", signals: map[string]points{"A": {T: []float64{0.2, 0.1}, V: []float64{2, 1}}}},
|
|
{sourceID: "s", signals: map[string]points{"A": {T: []float64{0.1, 0.3}, V: []float64{1, 3}}}},
|
|
}
|
|
m := c.merged()
|
|
got := m["s:A"]
|
|
want := []float64{0.1, 0.2, 0.3}
|
|
if len(got.T) != len(want) {
|
|
t.Fatalf("len = %d, want %d (%+v)", len(got.T), len(want), got)
|
|
}
|
|
for i := range want {
|
|
if got.T[i] != want[i] {
|
|
t.Errorf("t[%d] = %v, want %v", i, got.T[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEdgeCrosses(t *testing.T) {
|
|
rising := points{T: []float64{0, 1, 2}, V: []float64{-1, 1, 2}}
|
|
if !edgeCrosses(rising, 0.5, "rising", 0.0) {
|
|
t.Error("rising cross not detected")
|
|
}
|
|
if edgeCrosses(rising, 0.5, "falling", 0.0) {
|
|
t.Error("false falling detection on rising data")
|
|
}
|
|
falling := points{T: []float64{0, 1}, V: []float64{1, -1}}
|
|
if !edgeCrosses(falling, 0.5, "falling", 0.0) {
|
|
t.Error("falling cross not detected")
|
|
}
|
|
if !edgeCrosses(falling, 0.5, "both", 0.0) {
|
|
t.Error("both should match a falling edge")
|
|
}
|
|
}
|
|
|
|
func TestHas(t *testing.T) {
|
|
if !has("live,zoom,trigger", "zoom") {
|
|
t.Error("has should find zoom")
|
|
}
|
|
if has("live, window", "trigger") {
|
|
t.Error("has should not find trigger")
|
|
}
|
|
if !has("live, window", "window") {
|
|
t.Error("has should trim spaces")
|
|
}
|
|
}
|