@@ -16,6 +16,7 @@ package server_test
1616
1717import (
1818 "context"
19+ "fmt"
1920 "io"
2021 "net"
2122 "strings"
@@ -275,3 +276,71 @@ func TestCancellation(t *testing.T) {
275276 t .Fatal ("stream did not terminate within 5s after cancellation" )
276277 }
277278}
279+
280+ // TestInterCommandSignal starts two concurrent RPCs on the same server:
281+ // command A waits for SIGUSR1, command B delivers it. This verifies that
282+ // commands share a PID namespace — the property that makes the exec service
283+ // useful inside a single sandbox.
284+ func TestInterCommandSignal (t * testing.T ) {
285+ c := setup (t )
286+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
287+ defer cancel ()
288+
289+ // Command A: set the trap before printing the PID so the handler is
290+ // active by the time we read the PID and send the signal.
291+ streamA , err := c .RunCommand (ctx , & pb.StartCommandRequest {
292+ CommandLine : "trap 'echo got_signal; exit 0' USR1; echo $$; while true; do sleep 0.1; done" ,
293+ })
294+ if err != nil {
295+ t .Fatal (err )
296+ }
297+
298+ // Read until we have the PID line.
299+ var buf string
300+ var pid string
301+ for pid == "" {
302+ ev , err := streamA .Recv ()
303+ if err != nil {
304+ t .Fatalf ("reading PID from command A: %v" , err )
305+ }
306+ if ev .GetExited () != nil {
307+ t .Fatalf ("command A exited before printing PID: %+v" , ev .GetExited ())
308+ }
309+ buf += string (ev .GetOutput ())
310+ if i := strings .Index (buf , "\n " ); i >= 0 {
311+ pid = strings .TrimSpace (buf [:i ])
312+ buf = buf [i + 1 :]
313+ }
314+ }
315+
316+ // Command B: deliver SIGUSR1 to command A.
317+ r := runCtx (t , ctx , c , fmt .Sprintf ("kill -USR1 %s" , pid ), "" )
318+ if r .exitCode != 0 {
319+ t .Fatalf ("kill exit code = %d, err = %q" , r .exitCode , r .errMsg )
320+ }
321+
322+ // Collect the rest of command A's output.
323+ var exitCode int32
324+ for {
325+ ev , err := streamA .Recv ()
326+ if err == io .EOF {
327+ break
328+ }
329+ if err != nil {
330+ t .Fatalf ("Recv: %v" , err )
331+ }
332+ if out := ev .GetOutput (); len (out ) > 0 {
333+ buf += string (out )
334+ }
335+ if info := ev .GetExited (); info != nil {
336+ exitCode = info .GetExitCode ()
337+ }
338+ }
339+
340+ if exitCode != 0 {
341+ t .Errorf ("command A exit code = %d, want 0" , exitCode )
342+ }
343+ if ! strings .Contains (buf , "got_signal" ) {
344+ t .Errorf ("command A output = %q, want to contain %q" , buf , "got_signal" )
345+ }
346+ }
0 commit comments