diff --git a/Test/E2E/suite/debugclient/debugclient b/Test/E2E/suite/debugclient/debugclient new file mode 100755 index 0000000..bb821a1 Binary files /dev/null and b/Test/E2E/suite/debugclient/debugclient differ diff --git a/Test/E2E/suite/debugclient/go.mod b/Test/E2E/suite/debugclient/go.mod new file mode 100644 index 0000000..0258cf2 --- /dev/null +++ b/Test/E2E/suite/debugclient/go.mod @@ -0,0 +1,15 @@ +module debugclient + +go 1.21 + +require marte2debugger v0.0.0 + +require ( + github.com/gorilla/websocket v1.5.1 // indirect + golang.org/x/net v0.17.0 // indirect + marte2/common v0.0.0 // indirect +) + +replace marte2debugger => ../../../../Client/debugger + +replace marte2/common => ../../../../Common/Client/go diff --git a/Test/E2E/suite/debugclient/go.sum b/Test/E2E/suite/debugclient/go.sum new file mode 100644 index 0000000..272772f --- /dev/null +++ b/Test/E2E/suite/debugclient/go.sum @@ -0,0 +1,4 @@ +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= diff --git a/Test/E2E/suite/debugclient/main.go b/Test/E2E/suite/debugclient/main.go new file mode 100644 index 0000000..6c4d5dc --- /dev/null +++ b/Test/E2E/suite/debugclient/main.go @@ -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 ", +// "TRACE <0|1> [decim]", "BREAK ", +// each replying "OK \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 " +// 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" +}