simon d24b0cb5c3 Add goTool autotest with bench configs and UART scenarios.
JSON configs describe network and node MACs; scenarios run command
sequences with expect checks. Share UART client API across CLI and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 23:36:28 +02:00

134 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package autotest
import (
"encoding/json"
"fmt"
"os"
"strings"
)
// Config describes the bench: ESP-NOW network, master MAC, and known slaves.
type Config struct {
ID string `json:"id"`
Description string `json:"description,omitempty"`
Network uint `json:"network"`
MasterMAC string `json:"master_mac"`
Slaves []SlaveNode `json:"slaves"`
}
type SlaveNode struct {
ID string `json:"id"`
MAC string `json:"mac"`
ClientID *uint `json:"client_id,omitempty"`
}
type Bench struct {
Config
slaveByID map[string]*SlaveNode
}
func LoadConfig(path string) (*Bench, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
return NewBench(cfg)
}
func NewBench(cfg Config) (*Bench, error) {
cfg.MasterMAC = normalizeMAC(cfg.MasterMAC)
if cfg.ID == "" {
return nil, fmt.Errorf("config: id is required")
}
if cfg.MasterMAC == "" {
return nil, fmt.Errorf("config %q: master_mac is required", cfg.ID)
}
if cfg.Network < 1 || cfg.Network > 8 {
return nil, fmt.Errorf("config %q: network must be 18 (DIP / IO expander)", cfg.ID)
}
b := &Bench{Config: cfg, slaveByID: make(map[string]*SlaveNode)}
for i := range cfg.Slaves {
s := &cfg.Slaves[i]
if s.ID == "" {
return nil, fmt.Errorf("config %q: slave missing id", cfg.ID)
}
if _, dup := b.slaveByID[s.ID]; dup {
return nil, fmt.Errorf("config %q: duplicate slave id %q", cfg.ID, s.ID)
}
mac, err := parseMAC(s.MAC)
if err != nil {
return nil, fmt.Errorf("config %q slave %q: %w", cfg.ID, s.ID, err)
}
s.MAC = mac
if s.ClientID == nil {
parts := strings.Split(mac, ":")
var last byte
fmt.Sscanf(parts[5], "%02x", &last)
id := uint(last)
s.ClientID = &id
}
b.slaveByID[s.ID] = s
}
return b, nil
}
func (b *Bench) Slave(id string) (*SlaveNode, error) {
s, ok := b.slaveByID[id]
if !ok {
return nil, fmt.Errorf("unknown slave %q (config %q)", id, b.ID)
}
return s, nil
}
func (b *Bench) ResolveClientID(slaveID string) (uint32, error) {
if slaveID == "" || slaveID == "master" || slaveID == "local" {
return 0, nil
}
s, err := b.Slave(slaveID)
if err != nil {
return 0, err
}
return uint32(*s.ClientID), nil
}
func normalizeMAC(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
s = strings.ReplaceAll(s, "-", ":")
return s
}
func parseMAC(s string) (string, error) {
s = normalizeMAC(s)
parts := strings.Split(s, ":")
if len(parts) != 6 {
return "", fmt.Errorf("invalid mac %q", s)
}
out := make([]byte, 6)
for i, p := range parts {
var b byte
if _, err := fmt.Sscanf(p, "%02x", &b); err != nil {
return "", fmt.Errorf("invalid mac byte %q", p)
}
out[i] = b
}
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
out[0], out[1], out[2], out[3], out[4], out[5]), nil
}
func MACEqual(a, b string) bool {
return normalizeMAC(a) == normalizeMAC(b)
}
func MACFromProto(mac []byte) string {
if len(mac) != 6 {
return ""
}
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}