feat(e2e): add debugclient Go tool for DebugService/TCPLogger E2E scenarios
Standalone headless client that drives a running MARTeApp.ex's DebugService (TCP 8080 commands, UDP 8081 trace) and TCPLogger (TCP 9090) via marte2debugger/controller's NewHeadlessMarteController, for the upcoming Test/E2E/suite "debug"/"tcplogger" scenario kinds. Scripts FORCE/TRACE/BREAK for -mode debug, and triggers+waits for a TCPLogger log event for -mode tcplogger; reports PASS/FAIL as result_<scenario>.json/status_<scenario>.txt in -out.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
// Command debugclient drives a running MARTeApp.ex DebugService+TCPLogger
|
||||
// instance over TCP/UDP for the Test/E2E/suite "debug" and "tcplogger"
|
||||
// scenario kinds. It scripts a fixed command sequence, captures responses
|
||||
// and log/trace output, and reports PASS/FAIL as JSON to -out.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
debugger "marte2debugger/controller" // see go.mod replace directive; marte2debugger is Client/debugger's module name
|
||||
)
|
||||
|
||||
type event struct {
|
||||
kind string
|
||||
data any
|
||||
}
|
||||
|
||||
func main() {
|
||||
host := flag.String("host", "127.0.0.1", "MARTeApp host")
|
||||
cmdPort := flag.Int("cmdport", 8080, "DebugService TCP command port")
|
||||
udpPort := flag.Int("udpport", 8081, "DebugService UDP trace port")
|
||||
logPort := flag.Int("logport", 9090, "TCPLogger TCP port")
|
||||
scenario := flag.String("scenario", "", "scenario id (for output naming)")
|
||||
outDir := flag.String("out", "/tmp/debug_e2e", "output directory")
|
||||
mode := flag.String("mode", "debug", "debug|tcplogger")
|
||||
dur := flag.Duration("dur", 4*time.Second, "how long to run the script/collection")
|
||||
flag.Parse()
|
||||
|
||||
if *scenario == "" {
|
||||
fmt.Fprintln(os.Stderr, "missing -scenario")
|
||||
os.Exit(2)
|
||||
}
|
||||
os.MkdirAll(*outDir, 0o755)
|
||||
|
||||
var mu sync.Mutex
|
||||
var events []event
|
||||
sink := func(v any) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
// runLog (and every other MarteController event site) passes a
|
||||
// map[string]any literal to sink; store it as-is, no
|
||||
// unmarshal/remarshal needed.
|
||||
events = append(events, event{kind: fmt.Sprintf("%T", v), data: v})
|
||||
}
|
||||
|
||||
mc := debugger.NewHeadlessMarteController(sink)
|
||||
mc.Connect(*host, *cmdPort, *udpPort, *logPort)
|
||||
defer mc.Disconnect()
|
||||
|
||||
time.Sleep(500 * time.Millisecond) // allow TCP/UDP/log connects to establish
|
||||
|
||||
var ok bool
|
||||
var msg string
|
||||
switch *mode {
|
||||
case "debug":
|
||||
ok, msg = runDebugScript(mc)
|
||||
case "tcplogger":
|
||||
ok, msg = runTcpLoggerScript(mc, *dur, &events, &mu)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown -mode %q\n", *mode)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
time.Sleep(300 * time.Millisecond) // drain final events
|
||||
|
||||
result := map[string]any{
|
||||
"id": *scenario,
|
||||
"pass": ok,
|
||||
"detail": msg,
|
||||
}
|
||||
f, _ := os.Create(fmt.Sprintf("%s/result_%s.json", *outDir, *scenario))
|
||||
json.NewEncoder(f).Encode(result)
|
||||
f.Close()
|
||||
|
||||
status := "FAIL"
|
||||
if ok {
|
||||
status = "PASS"
|
||||
}
|
||||
fmt.Printf("%s: %s - %s\n", *scenario, status, msg)
|
||||
os.WriteFile(fmt.Sprintf("%s/status_%s.txt", *outDir, *scenario), []byte(status), 0o644)
|
||||
|
||||
if !ok {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// runDebugScript exercises FORCE/TRACE/BREAK over the TCP 8080 command
|
||||
// protocol (grammar confirmed against Source/Components/Interfaces/
|
||||
// DebugService/DebugServiceBase.cpp's HandleCommand: "FORCE <name> <val>",
|
||||
// "TRACE <name> <0|1> [decim]", "BREAK <name> <op|OFF> <threshold>",
|
||||
// each replying "OK <TOKEN> <count>\n").
|
||||
func runDebugScript(mc *debugger.MarteController) (bool, string) {
|
||||
mc.SendCommand("FORCE App.Data.Timer.Counter 42")
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
mc.SendCommand("TRACE App.Data.Timer.Counter 1 1")
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
mc.SendCommand("BREAK App.Data.Timer.Counter > 1000")
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
mc.SendCommand("BREAK App.Data.Timer.Counter OFF")
|
||||
mc.SendCommand("UNFORCE App.Data.Timer.Counter")
|
||||
if !mc.IsConnected() {
|
||||
return false, "lost connection to DebugService during script"
|
||||
}
|
||||
return true, "FORCE/TRACE/BREAK sequence completed without disconnect"
|
||||
}
|
||||
|
||||
// runTcpLoggerScript triggers a log-worthy event (an invalid FORCE) and
|
||||
// waits for a matching "log" event to arrive over the TCPLogger TCP port
|
||||
// within dur. MarteController's runLog forwards each "LOG <level> <msg>"
|
||||
// line it reads from the TCPLogger connection through sink as
|
||||
// map[string]any{"type": "log", "time": ..., "level": ..., "message": ...},
|
||||
// so we poll the captured events slice for that shape.
|
||||
func runTcpLoggerScript(mc *debugger.MarteController, dur time.Duration, events *[]event, mu *sync.Mutex) (bool, string) {
|
||||
mc.SendCommand("FORCE App.Data.DoesNotExist 1")
|
||||
deadline := time.Now().Add(dur)
|
||||
for time.Now().Before(deadline) {
|
||||
mu.Lock()
|
||||
for _, e := range *events {
|
||||
if m, ok := e.data.(map[string]any); ok && m["type"] == "log" {
|
||||
mu.Unlock()
|
||||
return true, fmt.Sprintf("received log event: %v", m)
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if !mc.IsConnected() {
|
||||
return false, "lost connection during tcplogger wait and no log event received"
|
||||
}
|
||||
return false, "no log event received within duration"
|
||||
}
|
||||
Reference in New Issue
Block a user