121 lines
3.3 KiB
Go
121 lines
3.3 KiB
Go
package wshub
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// A scope's envelope must not lose a spike, however narrow, and must stay in
|
|
// time order so it can be plotted as a single trace.
|
|
func TestMinMaxDecimateKeepsExtremes(t *testing.T) {
|
|
const n = 10000
|
|
ts := make([]float64, n)
|
|
vs := make([]float64, n)
|
|
for i := range ts {
|
|
ts[i] = float64(i) * 1e-6
|
|
vs[i] = math.Sin(float64(i) * 0.01)
|
|
}
|
|
// A one-sample spike in each direction: exactly what plain decimation drops.
|
|
vs[4321] = 12.5
|
|
vs[6789] = -9.75
|
|
|
|
dt, dv := minMaxDecimate(ts, vs, 200)
|
|
if len(dt) > 200 || len(dt) != len(dv) {
|
|
t.Fatalf("got %d t / %d v points, want <= 200 of each", len(dt), len(dv))
|
|
}
|
|
hiSeen, loSeen := false, false
|
|
for i := range dv {
|
|
switch dv[i] {
|
|
case 12.5:
|
|
hiSeen = true
|
|
if dt[i] != ts[4321] {
|
|
t.Errorf("spike kept at t=%v, want %v: timestamps must be the real ones", dt[i], ts[4321])
|
|
}
|
|
case -9.75:
|
|
loSeen = true
|
|
}
|
|
if i > 0 && dt[i] < dt[i-1] {
|
|
t.Fatalf("output is not time-ordered at %d: %v after %v", i, dt[i], dt[i-1])
|
|
}
|
|
}
|
|
if !hiSeen || !loSeen {
|
|
t.Errorf("envelope lost a spike (max kept=%v, min kept=%v)", hiSeen, loSeen)
|
|
}
|
|
}
|
|
|
|
func TestMinMaxDecimatePassesShortInputThrough(t *testing.T) {
|
|
ts := []float64{1, 2, 3}
|
|
vs := []float64{4, 5, 6}
|
|
dt, dv := minMaxDecimate(ts, vs, 200)
|
|
if len(dt) != 3 || dv[2] != 6 {
|
|
t.Errorf("input below the budget was altered: %v / %v", dt, dv)
|
|
}
|
|
// A flat bucket contributes one point, not two: nothing is invented.
|
|
flatT := make([]float64, 100)
|
|
flatV := make([]float64, 100)
|
|
for i := range flatT {
|
|
flatT[i] = float64(i)
|
|
}
|
|
if ft, _ := minMaxDecimate(flatT, flatV, 10); len(ft) != 5 {
|
|
t.Errorf("flat input decimated to %d points, want 5 (one per bucket)", len(ft))
|
|
}
|
|
}
|
|
|
|
func TestZoomPoints(t *testing.T) {
|
|
cases := []struct {
|
|
n int
|
|
present bool
|
|
want int
|
|
}{
|
|
{0, false, 2400}, // absent → default budget
|
|
{2400, true, 2400}, // explicit budget honoured
|
|
{0, true, 1 << 30}, // 0 → every sample in range
|
|
{-1, true, 1 << 30}, // negative → every sample in range
|
|
{5, true, 2400}, // implausibly small → default budget
|
|
}
|
|
for _, c := range cases {
|
|
if got := zoomPoints(c.n, c.present); got != c.want {
|
|
t.Errorf("zoomPoints(%d,%v) = %d, want %d", c.n, c.present, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestZoomSliceReturnsFullResolution(t *testing.T) {
|
|
h := NewHub()
|
|
rb := newSigRing(1000)
|
|
ts := make([]float64, 500)
|
|
vs := make([]float64, 500)
|
|
for i := range ts {
|
|
ts[i] = float64(i) * 0.001 // 1 kHz
|
|
vs[i] = float64(i)
|
|
}
|
|
rb.write(ts, vs)
|
|
h.rings["s1:sig"] = rb
|
|
|
|
// A budget larger than the range must return every sample untouched.
|
|
res := h.zoomSlice(0.100, 0.199, []string{"s1:sig"}, 1<<30)
|
|
sd, ok := res["s1:sig"]
|
|
if !ok {
|
|
t.Fatal("signal missing from zoom result")
|
|
}
|
|
if len(sd.T) != 100 {
|
|
t.Fatalf("got %d points, want 100", len(sd.T))
|
|
}
|
|
if sd.V[0] != 100 || sd.V[99] != 199 {
|
|
t.Errorf("value range = %v..%v, want 100..199", sd.V[0], sd.V[99])
|
|
}
|
|
|
|
// A small budget decimates but keeps the endpoints.
|
|
dec := h.zoomSlice(0.100, 0.199, []string{"s1:sig"}, 20)
|
|
if len(dec["s1:sig"].T) != 20 {
|
|
t.Errorf("decimated to %d points, want 20", len(dec["s1:sig"].T))
|
|
}
|
|
}
|
|
|
|
func TestZoomSliceUnknownSignal(t *testing.T) {
|
|
h := NewHub()
|
|
if res := h.zoomSlice(0, 1, []string{"nope", ""}, 100); len(res) != 0 {
|
|
t.Errorf("got %d entries, want 0", len(res))
|
|
}
|
|
}
|