You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
5.1 KiB
172 lines
5.1 KiB
|
2 months ago
|
package serial
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"sync/atomic"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestMappingControllerRequiresValidatedCompleteMapping(t *testing.T) {
|
||
|
|
mapping := testMapping()
|
||
|
|
mapping.Validated = false
|
||
|
|
|
||
|
|
if _, err := NewMappingController(mapping); err == nil {
|
||
|
|
t.Fatal("expected unvalidated mapping to be rejected")
|
||
|
|
}
|
||
|
|
|
||
|
|
mapping = testMapping()
|
||
|
|
mapping.Channels = mapping.Channels[:47]
|
||
|
|
if _, err := NewMappingController(mapping); err == nil {
|
||
|
|
t.Fatal("expected incomplete mapping to be rejected")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMappingControllerRejectsConnectedChannelsMappedToNCUARTs(t *testing.T) {
|
||
|
|
mapping := testMapping()
|
||
|
|
mapping.Channels[0].Device = "/dev/ttyWCH0"
|
||
|
|
if _, err := NewMappingController(mapping); err == nil {
|
||
|
|
t.Fatal("expected CH01 mapped to NC ttyWCH0 to be rejected")
|
||
|
|
}
|
||
|
|
|
||
|
|
mapping = testMapping()
|
||
|
|
mapping.Channels[24].Device = "/dev/ttyWCH28"
|
||
|
|
if _, err := NewMappingController(mapping); err == nil {
|
||
|
|
t.Fatal("expected CH25 mapped to NC ttyWCH28 to be rejected")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMappingControllerFailsClosedBeforeOpeningUnavailableDevice(t *testing.T) {
|
||
|
|
controller, err := NewMappingController(testMapping())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("new mapping controller: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
states, err := controller.States(context.Background())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("states: %v", err)
|
||
|
|
}
|
||
|
|
if len(states) != ChannelCount || states[0].Channel != "CH01" || states[0].Available {
|
||
|
|
t.Fatalf("states = %#v", states)
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = controller.Apply(context.Background(), []ChannelConfig{{Channel: "CH01", BaudRate: 115200}})
|
||
|
|
if !IsUnavailable(err) {
|
||
|
|
t.Fatalf("apply error = %v, want unavailable error", err)
|
||
|
|
}
|
||
|
|
if !errors.Is(err, ErrSerialUnavailable) {
|
||
|
|
t.Fatalf("apply error = %v, want ErrSerialUnavailable", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMappingControllerReportsUnknownUntilModemSignalIsValidated(t *testing.T) {
|
||
|
|
controller, err := NewMappingController(testMapping())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
states, err := controller.LinkStates(context.Background())
|
||
|
|
if err != nil || len(states) != ChannelCount {
|
||
|
|
t.Fatalf("LinkStates = %+v, %v", states, err)
|
||
|
|
}
|
||
|
|
if states[0].Channel != "CH01" || states[0].State != "unknown" || states[0].Signal != "unknown" || states[0].Available {
|
||
|
|
t.Fatalf("CH01 modem state = %+v", states[0])
|
||
|
|
}
|
||
|
|
|
||
|
|
mapping := testMapping()
|
||
|
|
mapping.Channels[0].LinkSignal = "ri"
|
||
|
|
if _, err := NewMappingController(mapping); err == nil {
|
||
|
|
t.Fatal("NewMappingController accepted an unapproved link signal")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMappingControllerCanObserveDCDDSRAndCTSIndependently(t *testing.T) {
|
||
|
|
mapping := testMapping()
|
||
|
|
mapping.Channels[0].LinkSignals = []string{"dcd", "dsr", "cts"}
|
||
|
|
controller, err := NewMappingController(mapping)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
up := true
|
||
|
|
controller.modemProbe = func(entry MappingChannel) LinkState {
|
||
|
|
state := "down"
|
||
|
|
if up {
|
||
|
|
state = "up"
|
||
|
|
}
|
||
|
|
return LinkState{Channel: entry.Channel, State: state, Signal: entry.LinkSignal, Available: true}
|
||
|
|
}
|
||
|
|
states, err := controller.LinkStates(context.Background())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if len(states) != ChannelCount+2 || states[0].Signal != "dcd" || states[1].Signal != "dsr" || states[2].Signal != "cts" ||
|
||
|
|
states[0].Channel != "CH01" || states[1].Channel != "CH01" || states[2].Channel != "CH01" || !states[2].EverUp {
|
||
|
|
t.Fatalf("multi-signal link states = %+v", states[:min(4, len(states))])
|
||
|
|
}
|
||
|
|
up = false
|
||
|
|
states, err = controller.LinkStates(context.Background())
|
||
|
|
if err != nil || states[2].State != "down" || !states[2].EverUp {
|
||
|
|
t.Fatalf("CTS history after link loss = %+v, %v", states[2], err)
|
||
|
|
}
|
||
|
|
|
||
|
|
mapping.Channels[0].LinkSignals = []string{"dcd", "dcd"}
|
||
|
|
if _, err := NewMappingController(mapping); err == nil {
|
||
|
|
t.Fatal("duplicate modem signals were accepted")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMappingControllerBoundsParallelLinkProbes(t *testing.T) {
|
||
|
|
controller, err := NewMappingController(testMapping())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
controller.SetLinkWorkerCount(8)
|
||
|
|
var active atomic.Int32
|
||
|
|
var maximum atomic.Int32
|
||
|
|
controller.modemProbe = func(entry MappingChannel) LinkState {
|
||
|
|
current := active.Add(1)
|
||
|
|
for {
|
||
|
|
seen := maximum.Load()
|
||
|
|
if current <= seen || maximum.CompareAndSwap(seen, current) {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
time.Sleep(5 * time.Millisecond)
|
||
|
|
active.Add(-1)
|
||
|
|
return LinkState{Channel: entry.Channel, State: "down", Signal: entry.LinkSignal, Available: true}
|
||
|
|
}
|
||
|
|
states, err := controller.LinkStates(context.Background())
|
||
|
|
if err != nil || len(states) != ChannelCount {
|
||
|
|
t.Fatalf("LinkStates = %d, %v", len(states), err)
|
||
|
|
}
|
||
|
|
if maximum.Load() < 2 || maximum.Load() > 8 {
|
||
|
|
t.Fatalf("parallel link workers = %d, want 2..8", maximum.Load())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func testMapping() Mapping {
|
||
|
|
channels := make([]MappingChannel, 0, ChannelCount)
|
||
|
|
for index := 1; index <= ChannelCount; index++ {
|
||
|
|
bdf := "0000:01:00.0"
|
||
|
|
ttyNumber := index + 3
|
||
|
|
if index > 24 {
|
||
|
|
bdf = "0002:21:00.0"
|
||
|
|
ttyNumber = index + 7
|
||
|
|
}
|
||
|
|
channels = append(channels, MappingChannel{
|
||
|
|
Channel: fmt.Sprintf("CH%02d", index),
|
||
|
|
Device: fmt.Sprintf("/dev/ttyWCH%d", ttyNumber),
|
||
|
|
PCIBDF: bdf,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return Mapping{
|
||
|
|
FormatVersion: 1,
|
||
|
|
HardwareProfile: "fet3568j-c",
|
||
|
|
MappingVersion: "validated-test",
|
||
|
|
Evidence: "isolated-loopback-test",
|
||
|
|
Validated: true,
|
||
|
|
Channels: channels,
|
||
|
|
}
|
||
|
|
}
|