132 lines
3.6 KiB
Go
132 lines
3.6 KiB
Go
package osd
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestTableSelectionRequiresAnIdentifier(t *testing.T) {
|
|
if (Table{}).Selected() {
|
|
t.Fatal("zero generation and hash mean no table is selected yet")
|
|
}
|
|
if !(Table{Gen: 3}).Selected() || !(Table{Hash: 9}).Selected() {
|
|
t.Fatal("either identifier alone selects a table")
|
|
}
|
|
}
|
|
|
|
func TestCeilingFromChannelScalesTheKnobLinearly(t *testing.T) {
|
|
// The temporary operator maxPower ceiling maps the full CH6 channel
|
|
// range onto the 0..127 level domain. It is operator intent only.
|
|
cases := []struct {
|
|
channel int16
|
|
want uint8
|
|
}{
|
|
{-1024, 0},
|
|
{-1023, 0},
|
|
{0, 63},
|
|
{512, 95},
|
|
{1023, 126},
|
|
{1024, 127},
|
|
}
|
|
for _, c := range cases {
|
|
if got := CeilingFromChannel(c.channel); got != c.want {
|
|
t.Fatalf("CeilingFromChannel(%d)=%d want %d", c.channel, got, c.want)
|
|
}
|
|
}
|
|
if CeilingFromChannel(-2000) != 0 || CeilingFromChannel(2000) != 127 {
|
|
t.Fatal("out-of-range channels must clamp to the level domain")
|
|
}
|
|
}
|
|
|
|
func TestSnapshotCarriesFourDistinctContractValues(t *testing.T) {
|
|
// The four contract values must stay distinct: every field survives a
|
|
// roundtrip at its own offset, so no value can silently alias another
|
|
// (for example a mode/power collapse of CH6 ceiling and applied VTX
|
|
// state).
|
|
snapshot := Snapshot{
|
|
Sequence: 7,
|
|
TimestampMS: 99,
|
|
Armed: true,
|
|
Video: true,
|
|
OpConfig: OperatorConfig{
|
|
Table: Table{Gen: 3, Hash: 0xdeadbeefcafe},
|
|
MaxPower: 95,
|
|
},
|
|
Policy: GSPolicy{
|
|
Pending: false,
|
|
Level: 42,
|
|
Reason: [8]byte{'a', 'd', 'a', 'p', 't'},
|
|
TimestampMS: 1234,
|
|
},
|
|
Ack: VTXAck{
|
|
Pending: false,
|
|
Table: Table{Gen: 2, Hash: 0xfeed},
|
|
Level: 40,
|
|
Lifecycle: VTXLifecycleApplied,
|
|
},
|
|
Link: GSObservedLink{
|
|
Known: true,
|
|
AgeMS: 250,
|
|
},
|
|
PublishedMS: 555,
|
|
}
|
|
snapshot.Channels[5] = 100
|
|
|
|
got, err := Decode(Encode(snapshot))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != snapshot {
|
|
t.Fatalf("roundtrip lost contract state: %+v != %+v", got, snapshot)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotCarriesExplicitPendingStates(t *testing.T) {
|
|
// Until the policy, acknowledgment and link producers exist the
|
|
// snapshot must carry their explicit pending/unknown states, never
|
|
// fabricated levels or metrics.
|
|
snapshot := Snapshot{
|
|
Armed: true,
|
|
OpConfig: OperatorConfig{
|
|
MaxPower: 63,
|
|
},
|
|
Policy: GSPolicy{Pending: true},
|
|
Ack: VTXAck{Pending: true, Lifecycle: VTXLifecyclePending},
|
|
Link: GSObservedLink{Known: false},
|
|
}
|
|
|
|
got, err := Decode(Encode(snapshot))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !got.Policy.Pending || got.Policy.Level != 0 {
|
|
t.Fatalf("policy=%+v must stay explicitly pending", got.Policy)
|
|
}
|
|
if !got.Ack.Pending || got.Ack.Level != 0 || got.Ack.Lifecycle != VTXLifecyclePending {
|
|
t.Fatalf("ack=%+v must stay explicitly pending", got.Ack)
|
|
}
|
|
if got.Link.Known || got.Link.AgeMS != 0 {
|
|
t.Fatalf("link=%+v must stay explicitly unknown", got.Link)
|
|
}
|
|
if got.OpConfig != snapshot.OpConfig {
|
|
t.Fatalf("operator config=%+v must survive", got.OpConfig)
|
|
}
|
|
}
|
|
|
|
func TestDecodeRejectsV1Snapshots(t *testing.T) {
|
|
// A v1-sized record cannot carry the contract; a v2-sized record that
|
|
// claims v1 must be rejected by version, not parsed loosely.
|
|
v1Shaped := make([]byte, Size)
|
|
v1Shaped[0], v1Shaped[1], v1Shaped[2] = Magic0, Magic1, Version-1
|
|
if _, err := Decode(v1Shaped); err == nil {
|
|
t.Fatal("a v1-marked record must be rejected")
|
|
}
|
|
}
|
|
|
|
func TestVTXLifecycleStatesAreDistinct(t *testing.T) {
|
|
if VTXLifecyclePending == VTXLifecycleApplied ||
|
|
VTXLifecycleApplied == VTXLifecycleError ||
|
|
VTXLifecyclePending == VTXLifecycleError {
|
|
t.Fatal("pending, applied and error must be distinct lifecycle states")
|
|
}
|
|
}
|