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.
148 lines
4.3 KiB
148 lines
4.3 KiB
|
2 months ago
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"net"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"tera-clone/services/tera-api/internal/serial"
|
||
|
|
)
|
||
|
|
|
||
|
|
type stubController struct {
|
||
|
|
states []serial.State
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s stubController) States(context.Context) ([]serial.State, error) {
|
||
|
|
return s.states, s.err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (stubController) Apply(context.Context, []serial.ChannelConfig) ([]serial.State, error) {
|
||
|
|
panic("read-only checker must not apply serial configuration")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInspectPassesCanonicalAvailableInventory(t *testing.T) {
|
||
|
|
checkedAt := time.Date(2026, 7, 21, 8, 0, 0, 0, time.UTC)
|
||
|
|
report := inspect(context.Background(), stubController{states: canonicalStates()}, defaultHardwareProfile, defaultMappingVersion, checkedAt)
|
||
|
|
|
||
|
|
if report.Status != "pass" || report.ObservedChannels != 48 || report.AvailableChannels != 48 {
|
||
|
|
t.Fatalf("report = %#v", report)
|
||
|
|
}
|
||
|
|
if len(report.Findings) != 0 {
|
||
|
|
t.Fatalf("findings = %#v", report.Findings)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRunChecksSerialdSocketEndToEnd(t *testing.T) {
|
||
|
|
socketPath := filepath.Join(t.TempDir(), "serial-check.sock")
|
||
|
|
listener, err := net.Listen("unix", socketPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Skipf("unix sockets unavailable: %v", err)
|
||
|
|
}
|
||
|
|
server := serial.SocketServer{Controller: stubController{states: canonicalStates()}}
|
||
|
|
done := make(chan error, 1)
|
||
|
|
go func() { done <- server.Serve(listener) }()
|
||
|
|
t.Cleanup(func() {
|
||
|
|
_ = listener.Close()
|
||
|
|
select {
|
||
|
|
case err := <-done:
|
||
|
|
if err != nil {
|
||
|
|
t.Errorf("socket server: %v", err)
|
||
|
|
}
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Error("socket server did not stop")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
var stdout bytes.Buffer
|
||
|
|
var stderr bytes.Buffer
|
||
|
|
exitCode := run(
|
||
|
|
[]string{"-socket", socketPath},
|
||
|
|
&stdout,
|
||
|
|
&stderr,
|
||
|
|
func() time.Time { return time.Date(2026, 7, 21, 8, 0, 0, 0, time.UTC) },
|
||
|
|
)
|
||
|
|
if exitCode != 0 {
|
||
|
|
t.Fatalf("exit = %d, stderr = %q, stdout = %q", exitCode, stderr.String(), stdout.String())
|
||
|
|
}
|
||
|
|
var report checkReport
|
||
|
|
if err := json.Unmarshal(stdout.Bytes(), &report); err != nil {
|
||
|
|
t.Fatalf("decode report: %v", err)
|
||
|
|
}
|
||
|
|
if report.Status != "pass" || report.AvailableChannels != serial.ChannelCount {
|
||
|
|
t.Fatalf("report = %#v", report)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInspectFailsOnUnavailableChannelWithoutApplying(t *testing.T) {
|
||
|
|
states := canonicalStates()
|
||
|
|
states[47].Available = false
|
||
|
|
states[47].Message = "serial device PCI ancestry is unavailable"
|
||
|
|
report := inspect(context.Background(), stubController{states: states}, defaultHardwareProfile, defaultMappingVersion, time.Time{})
|
||
|
|
|
||
|
|
if report.Status != "fail" || report.AvailableChannels != 47 {
|
||
|
|
t.Fatalf("report = %#v", report)
|
||
|
|
}
|
||
|
|
if !hasFinding(report.Findings, "CH48", "availability") {
|
||
|
|
t.Fatalf("findings = %#v", report.Findings)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInspectFailsOnNCNodeOrWrongPCIGroup(t *testing.T) {
|
||
|
|
states := canonicalStates()
|
||
|
|
states[0].Device = "/dev/ttyWCH0"
|
||
|
|
states[24].PCIBDF = "0000:01:00.0"
|
||
|
|
report := inspect(context.Background(), stubController{states: states}, defaultHardwareProfile, defaultMappingVersion, time.Time{})
|
||
|
|
|
||
|
|
if report.Status != "fail" {
|
||
|
|
t.Fatalf("report = %#v", report)
|
||
|
|
}
|
||
|
|
if !hasFinding(report.Findings, "CH01", "device_mapping") || !hasFinding(report.Findings, "CH25", "pci_ancestry") {
|
||
|
|
t.Fatalf("findings = %#v", report.Findings)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInspectReportsSerialdFailure(t *testing.T) {
|
||
|
|
report := inspect(context.Background(), stubController{err: errors.New("socket unavailable")}, defaultHardwareProfile, defaultMappingVersion, time.Time{})
|
||
|
|
|
||
|
|
if report.Status != "fail" || !hasFinding(report.Findings, "", "seriald_states") {
|
||
|
|
t.Fatalf("report = %#v", report)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func canonicalStates() []serial.State {
|
||
|
|
states := make([]serial.State, 0, serial.ChannelCount)
|
||
|
|
for channelNumber := 1; channelNumber <= serial.ChannelCount; channelNumber++ {
|
||
|
|
ttyNumber := channelNumber + 3
|
||
|
|
bdf := "0000:01:00.0"
|
||
|
|
if channelNumber > 24 {
|
||
|
|
ttyNumber = channelNumber + 7
|
||
|
|
bdf = "0002:21:00.0"
|
||
|
|
}
|
||
|
|
states = append(states, serial.State{
|
||
|
|
Channel: fmt.Sprintf("CH%02d", channelNumber),
|
||
|
|
Device: fmt.Sprintf("/dev/ttyWCH%d", ttyNumber),
|
||
|
|
PCIBDF: bdf,
|
||
|
|
HardwareProfile: defaultHardwareProfile,
|
||
|
|
MappingVersion: defaultMappingVersion,
|
||
|
|
Available: true,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return states
|
||
|
|
}
|
||
|
|
|
||
|
|
func hasFinding(findings []finding, channel, check string) bool {
|
||
|
|
for _, item := range findings {
|
||
|
|
if item.Channel == channel && item.Check == check {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|