186 lines
5.4 KiB
Go
186 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"os/exec"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.piksar.eu/piotr/groundstation/internal/config"
|
|
"git.piksar.eu/piotr/groundstation/internal/osd"
|
|
"git.piksar.eu/piotr/groundstation/internal/protocol"
|
|
)
|
|
|
|
func TestNewSnapshotDerivesArmPowerAndVideoPresence(t *testing.T) {
|
|
cfg := config.Default()
|
|
frame := protocol.RCFrame{Sequence: 3, Timestamp: 42}
|
|
frame.Channels[4] = 512 // arm channel above the armed threshold
|
|
frame.Channels[5] = -1000 // CH6 knob below the video threshold
|
|
|
|
snapshot := newSnapshot(frame, cfg, nil)
|
|
if snapshot.Sequence != 3 || snapshot.TimestampMS != 42 {
|
|
t.Fatalf("snapshot=%+v must carry the frame provenance", snapshot)
|
|
}
|
|
if snapshot.Channels != frame.Channels {
|
|
t.Fatal("the snapshot must carry the final RC state unchanged")
|
|
}
|
|
if !snapshot.Armed {
|
|
t.Fatal("arm channel above the threshold must derive armed")
|
|
}
|
|
if snapshot.Power {
|
|
t.Fatal("CH6 below the video threshold must derive no requested WFB power")
|
|
}
|
|
if snapshot.Video {
|
|
t.Fatal("without a video launcher there is no decoded-video presence")
|
|
}
|
|
|
|
frame.Channels[4] = -256
|
|
frame.Channels[5] = 100
|
|
snapshot = newSnapshot(frame, cfg, &videoController{cmd: &exec.Cmd{}})
|
|
if snapshot.Armed {
|
|
t.Fatal("arm channel below the threshold must derive disarmed")
|
|
}
|
|
if !snapshot.Power {
|
|
t.Fatal("CH6 above the video threshold must derive requested WFB power")
|
|
}
|
|
if !snapshot.Video {
|
|
t.Fatal("a running video launcher is the supervisor's decoded-video presence")
|
|
}
|
|
}
|
|
|
|
func TestRunningReportsVideoLauncherPresence(t *testing.T) {
|
|
video := newVideoController("fpv-video")
|
|
if video.Running() {
|
|
t.Fatal("a launcher never started must not report running")
|
|
}
|
|
video.cmd = &exec.Cmd{}
|
|
video.done = make(chan error, 1)
|
|
if !video.Running() {
|
|
t.Fatal("a live launcher must report running")
|
|
}
|
|
|
|
// A launcher that exited on its own stays recorded until the next Set;
|
|
// its completed wait must end its decoded-video presence.
|
|
video.done <- errors.New("exited")
|
|
if video.Running() {
|
|
t.Fatal("a launcher with a completed wait must not report running")
|
|
}
|
|
}
|
|
|
|
func TestRunningEndsWhenTheLauncherExitsOnItsOwn(t *testing.T) {
|
|
video := newVideoController("true")
|
|
if err := video.Set(true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The launcher exits on its own; its pending wait result must end its
|
|
// decoded-video presence without anyone consuming the stop path's
|
|
// done signal.
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if !video.Running() {
|
|
return
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
t.Fatal("a launcher that exited on its own must not report running")
|
|
}
|
|
|
|
func TestRunPublishesSnapshotsForValidFramesOnly(t *testing.T) {
|
|
cfg := config.Default()
|
|
|
|
first := protocol.RCFrame{Sequence: 1}
|
|
first.Channels[4] = 512
|
|
second := protocol.RCFrame{Sequence: 2}
|
|
second.Channels[4] = -256
|
|
second.Channels[5] = 100
|
|
|
|
var input bytes.Buffer
|
|
input.Write(protocol.EncodeRC(first))
|
|
input.Write([]byte("junk frame bytes that fail the CRC"))
|
|
input.Write(protocol.EncodeRC(second))
|
|
|
|
publisher := osd.NewPublisher()
|
|
if err := run(context.Background(), &input, log.New(io.Discard, "", 0), nil, cfg, publisher); !errors.Is(err, io.EOF) {
|
|
t.Fatalf("run=%v want clean EOF after the input ends", err)
|
|
}
|
|
|
|
got, ok := publisher.Latest()
|
|
if !ok {
|
|
t.Fatal("valid frames must publish snapshots")
|
|
}
|
|
if got.Sequence != 2 {
|
|
t.Fatalf("sequence=%d want the latest valid frame 2", got.Sequence)
|
|
}
|
|
if got.Armed {
|
|
t.Fatal("the latest frame's arm state must be disarmed")
|
|
}
|
|
if !got.Power {
|
|
t.Fatal("the latest frame's CH6 must request WFB power")
|
|
}
|
|
if got.PublishedMS == 0 {
|
|
t.Fatal("the publisher must stamp the publication time")
|
|
}
|
|
}
|
|
|
|
func TestRunPublishesWithoutAConfiguredVideoLauncher(t *testing.T) {
|
|
cfg := config.Default()
|
|
frame := protocol.RCFrame{Sequence: 5}
|
|
frame.Channels[5] = 0
|
|
|
|
publisher := osd.NewPublisher()
|
|
if err := run(context.Background(),
|
|
bytes.NewReader(protocol.EncodeRC(frame)),
|
|
log.New(io.Discard, "", 0), nil, cfg, publisher); !errors.Is(err, io.EOF) {
|
|
t.Fatalf("run=%v want clean EOF", err)
|
|
}
|
|
|
|
got, ok := publisher.Latest()
|
|
if !ok {
|
|
t.Fatal("frames must publish even without a video launcher")
|
|
}
|
|
if !got.Power || got.Video {
|
|
t.Fatalf("snapshot=%+v must derive the requested power without video presence", got)
|
|
}
|
|
}
|
|
|
|
func TestRunPublishNeverBlocksTheUARTPath(t *testing.T) {
|
|
// A burst of frames must be read and published with no waiting on the
|
|
// state-file writer: the publisher's Publish is non-blocking by design,
|
|
// so the loop over a large input returns promptly.
|
|
cfg := config.Default()
|
|
publisher := osd.NewFilePublisher("/proc/nonexistent/osd-state.bin", log.New(io.Discard, "", 0))
|
|
defer publisher.Close()
|
|
|
|
frame := protocol.RCFrame{Sequence: 1}
|
|
var input bytes.Buffer
|
|
for i := 0; i < 500; i++ {
|
|
frame.Sequence = uint16(i)
|
|
input.Write(protocol.EncodeRC(frame))
|
|
}
|
|
|
|
if err := run(context.Background(), &input, log.New(io.Discard, "", 0), nil, cfg, publisher); !errors.Is(err, io.EOF) {
|
|
t.Fatalf("run=%v want clean EOF", err)
|
|
}
|
|
if got, _ := publisher.Latest(); got.Sequence != 499 {
|
|
t.Fatalf("sequence=%d want the last frame", got.Sequence)
|
|
}
|
|
}
|
|
|
|
func TestNewSnapshotArmChannelConfigurationIsApplied(t *testing.T) {
|
|
cfg := config.Default()
|
|
cfg.OSD.ArmIndex = 6
|
|
cfg.OSD.ArmedThreshold = 256
|
|
|
|
frame := protocol.RCFrame{}
|
|
frame.Channels[6] = 257
|
|
|
|
if snapshot := newSnapshot(frame, cfg, nil); !snapshot.Armed {
|
|
t.Fatal("the configured arm index and threshold must drive the arm state")
|
|
}
|
|
}
|