Implemented full e2e testing
This commit is contained in:
Binary file not shown.
@@ -28,7 +28,7 @@ func main() {
|
||||
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")
|
||||
mode := flag.String("mode", "debug", "debug|tcplogger|debug_pause_resume")
|
||||
dur := flag.Duration("dur", 4*time.Second, "how long to run the script/collection")
|
||||
flag.Parse()
|
||||
|
||||
@@ -72,6 +72,8 @@ func main() {
|
||||
ok, msg = runDebugScript(mc, &events, &mu)
|
||||
case "tcplogger":
|
||||
ok, msg = runTcpLoggerScript(mc, *dur, &events, &mu, baseline)
|
||||
case "debug_pause_resume":
|
||||
ok, msg = runPauseResumeScript(mc, &events, &mu)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown -mode %q\n", *mode)
|
||||
os.Exit(2)
|
||||
@@ -252,3 +254,150 @@ func runTcpLoggerScript(mc *debugger.MarteController, dur time.Duration, events
|
||||
}
|
||||
return false, "no matching log event received within duration"
|
||||
}
|
||||
|
||||
// readValueResponse sends "VALUE <name>" and waits for the matching
|
||||
// {"Name":...,"Value":"...",...} JSON reply (DebugServiceBase::GetSignalValue,
|
||||
// dispatched via HandleCommand's "VALUE" token, terminated by a bare
|
||||
// "OK VALUE" sentinel line). MarteController's readLoop recognises the
|
||||
// leading '{' and accumulates until the sentinel, then delivers the clean
|
||||
// JSON text as a map[string]any{"type":"response","tag":"VALUE","data":...}
|
||||
// event. Unlike TRACE, VALUE reads the signal's live memory address directly
|
||||
// regardless of whether tracing is enabled, so it works as a completely
|
||||
// independent observation channel for the PAUSE/RESUME test below.
|
||||
func readValueResponse(events *[]event, mu *sync.Mutex, start int, timeout time.Duration) (uint32, bool) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
mu.Lock()
|
||||
for _, e := range (*events)[start:] {
|
||||
m, ok := e.data.(map[string]any)
|
||||
if !ok || m["type"] != "response" || m["tag"] != "VALUE" {
|
||||
continue
|
||||
}
|
||||
data, _ := m["data"].(string)
|
||||
var v struct {
|
||||
Value string `json:"Value"`
|
||||
}
|
||||
if json.Unmarshal([]byte(data), &v) == nil {
|
||||
var n uint32
|
||||
if _, err := fmt.Sscanf(v.Value, "%d", &n); err == nil {
|
||||
mu.Unlock()
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// waitForBareOK waits for a text_line event whose data is exactly "OK" — the
|
||||
// literal reply DebugServiceBase::HandleCommand sends for PAUSE/RESUME
|
||||
// ("out += \"OK\\n\";", no per-signal count unlike FORCE/TRACE/BREAK's
|
||||
// "OK <TOKEN> <count>\n"), so it cannot be confused with those other acks.
|
||||
func waitForBareOK(events *[]event, mu *sync.Mutex, start int, timeout time.Duration) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
mu.Lock()
|
||||
for _, e := range (*events)[start:] {
|
||||
m, ok := e.data.(map[string]any)
|
||||
if ok && m["type"] == "text_line" && m["data"] == "OK" {
|
||||
mu.Unlock()
|
||||
return true
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// runPauseResumeScript exercises PAUSE/RESUME over the TCP 8080 command
|
||||
// protocol. This complements runDebugScript (FORCE/TRACE/BREAK/UNFORCE):
|
||||
// those only confirm DebugService *acknowledges* a command; this confirms
|
||||
// PAUSE actually halts the RT GAM loop rather than merely replying "OK".
|
||||
// DebugBrokerWrapper's pause spin (DebugBrokerWrapper.h) only runs in the
|
||||
// OUTPUT broker's Execute(), AFTER it has already committed the GAM's
|
||||
// result for the current cycle — input brokers deliberately never spin
|
||||
// (would risk stalling cross-thread EventSem posts). So the *input*-side
|
||||
// registration for a signal (e.g. "App.Data.Timer.Counter", the raw
|
||||
// LinuxTimer memory feeding TimerGAM) keeps advancing even while paused;
|
||||
// only the *output*-side registration ("App.Data.DDB1.Counter", TimerGAM's
|
||||
// copy into DDB1 after IOGAM's pass-through) actually freezes. Confirmed
|
||||
// empirically: manual PAUSE/RESUME polling showed Timer.Counter advancing
|
||||
// continuously through the pause window while DDB1.Counter held constant.
|
||||
func runPauseResumeScript(mc *debugger.MarteController, events *[]event, mu *sync.Mutex) (bool, string) {
|
||||
const target = "App.Data.DDB1.Counter"
|
||||
const ackTimeout = 2 * time.Second
|
||||
|
||||
sample := func() (uint32, bool) {
|
||||
mu.Lock()
|
||||
start := len(*events)
|
||||
mu.Unlock()
|
||||
mc.SendCommand("VALUE " + target)
|
||||
return readValueResponse(events, mu, start, ackTimeout)
|
||||
}
|
||||
|
||||
v1, ok := sample()
|
||||
if !ok {
|
||||
return false, "no VALUE response received while running"
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
v2, ok := sample()
|
||||
if !ok {
|
||||
return false, "no VALUE response received on second running sample"
|
||||
}
|
||||
if v2 <= v1 {
|
||||
return false, fmt.Sprintf("Counter did not advance while running (%d -> %d)", v1, v2)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
start := len(*events)
|
||||
mu.Unlock()
|
||||
mc.SendCommand("PAUSE")
|
||||
if !waitForBareOK(events, mu, start, ackTimeout) {
|
||||
return false, "no bare \"OK\" ack received for PAUSE"
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // let any already-in-flight cycle settle
|
||||
vPause1, ok := sample()
|
||||
if !ok {
|
||||
return false, "no VALUE response received while paused"
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
vPause2, ok := sample()
|
||||
if !ok {
|
||||
return false, "no VALUE response received on second paused sample"
|
||||
}
|
||||
if vPause2 > vPause1+1 { // tolerate at most one straggling in-flight cycle
|
||||
return false, fmt.Sprintf("Counter kept advancing while paused (%d -> %d)", vPause1, vPause2)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
start = len(*events)
|
||||
mu.Unlock()
|
||||
mc.SendCommand("RESUME")
|
||||
if !waitForBareOK(events, mu, start, ackTimeout) {
|
||||
return false, "no bare \"OK\" ack received for RESUME"
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
v3, ok := sample()
|
||||
if !ok {
|
||||
return false, "no VALUE response received after resume"
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
v4, ok := sample()
|
||||
if !ok {
|
||||
return false, "no VALUE response received on second post-resume sample"
|
||||
}
|
||||
if v4 <= v3 {
|
||||
return false, fmt.Sprintf("Counter did not resume advancing after RESUME (%d -> %d)", v3, v4)
|
||||
}
|
||||
|
||||
if !mc.IsConnected() {
|
||||
return false, "lost connection to DebugService during pause/resume script"
|
||||
}
|
||||
return true, fmt.Sprintf("PAUSE halted Counter (%d -> %d) and RESUME restored advancement (%d -> %d)",
|
||||
vPause1, vPause2, v3, v4)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user