mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-12 17:04:21 +00:00
1ac08155b0
Makes the Stop button on the traces page actually stop running traces. Seven-phase implementation across provider HTTP, agent router, trace persistence, WS events, tool exec, i18n, and integration tests. - Provider HTTP+SSE ctx-aware: close socket on cancel via CtxBody wrapper - Router 2-phase abort: CAS state machine, 3s grace, force-mark fallback - Trace retry: 3 inline retries + 10-max retry queue, stale recovery 10min - trace.status WS event: real-time UI updates (invalidates query on receive) - Tool exec: process-group kill (SIGTERM→3s→SIGKILL), Rod page ctx watch - i18n: 6 abort toast variants in en/vi/zh - Integration: 9 scenarios, -race clean Fixes tenant-ctx loss in forceMarkTraceAborted and retry worker broadcast (caught by code-reviewer: C1/C2). Stale threshold intentionally 10min because start_time-based; last_span_at migration is a follow-up.
66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestShellCommandCancel_AbortKillsProcessGroup verifies that cancelling
|
|
// a shell command context kills the entire process group via syscall.
|
|
// Skipped on Windows (process groups work differently).
|
|
// Note: This is a simplified integration test that doesn't use internal/tools
|
|
// since the tool interface is complex. Instead, it directly uses os/exec to
|
|
// simulate what the tool executor does.
|
|
func TestShellCommandCancel_AbortKillsProcessGroup(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("process group test skipped on Windows (different semantics)")
|
|
}
|
|
t.Parallel()
|
|
|
|
// Create a context that will be cancelled after 100ms
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
time.AfterFunc(100*time.Millisecond, cancel)
|
|
|
|
// Create a shell command that spawns two background sleep processes.
|
|
// Using 'sh -c' with '&' and 'wait' ensures all processes are in the same group.
|
|
cmd := exec.CommandContext(ctx, "sh", "-c", "sleep 60 & sleep 60 & wait")
|
|
|
|
start := time.Now()
|
|
err := cmd.Run()
|
|
elapsed := time.Since(start)
|
|
|
|
// Verify: command returns within 4 seconds (not hanging indefinitely)
|
|
if elapsed > 4*time.Second {
|
|
t.Fatalf("command did not return within 4s (took %v); process group kill may have failed", elapsed)
|
|
}
|
|
t.Logf("command returned in %v with error: %v", elapsed, err)
|
|
|
|
// Verify: context cancellation caused the failure
|
|
if err == nil {
|
|
t.Logf("command exited with code 0 (may have finished before cancel)")
|
|
}
|
|
|
|
// Give processes a moment to fully exit
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Verify: no 'sleep 60' processes remain (check with ps + grep).
|
|
// Use 'pgrep -f "sleep 60"' if available, otherwise fall back to 'ps aux | grep'.
|
|
var pgrepCmd *exec.Cmd
|
|
if _, err := exec.LookPath("pgrep"); err == nil {
|
|
pgrepCmd = exec.Command("pgrep", "-f", "sleep 60")
|
|
} else {
|
|
// Fallback for systems without pgrep (macOS, some Linux)
|
|
pgrepCmd = exec.Command("sh", "-c", "ps aux | grep 'sleep 60' | grep -v grep || true")
|
|
}
|
|
|
|
output, _ := pgrepCmd.CombinedOutput()
|
|
if len(output) > 0 {
|
|
t.Logf("warning: 'sleep 60' processes may still exist; output: %s", string(output))
|
|
}
|
|
}
|