@@ -13,6 +13,7 @@ import (
1313 "net"
1414 "os"
1515 "os/exec"
16+ "path/filepath"
1617 "runtime"
1718 "strconv"
1819 "strings"
@@ -24,6 +25,7 @@ import (
2425
2526 "github.com/envoyproxy/ai-gateway/cmd/extproc/mainlib"
2627 internaltesting "github.com/envoyproxy/ai-gateway/internal/testing"
28+ testsinternal "github.com/envoyproxy/ai-gateway/tests/internal"
2729)
2830
2931// TestEnvironment holds all the services needed for tests.
@@ -257,14 +259,42 @@ func requireEnvoy(t testing.TB,
257259 envoyYamlPath := t .TempDir () + "/envoy.yaml"
258260 require .NoError (t , os .WriteFile (envoyYamlPath , []byte (processedConfig ), 0o600 ))
259261
260- cmd := exec .CommandContext (t .Context (), "envoy" ,
262+ // Note: do not pass t.Context() to CommandContext, as it's canceled
263+ // *before* t.Cleanup functions are called.
264+ //
265+ // > Context returns a context that is canceled just before
266+ // > Cleanup-registered functions are called.
267+ //
268+ // That means the subprocess gets killed before we can send it an interrupt
269+ // signal for graceful shutdown, which results in orphaned subprocesses.
270+ ctx , cancel := context .WithCancel (context .Background ())
271+ cmd := testsinternal .GoToolCmdContext (ctx , "func-e" , "run" ,
261272 "-c" , envoyYamlPath ,
262273 "--concurrency" , strconv .Itoa (max (runtime .NumCPU (), 2 )),
263274 // This allows multiple Envoy instances to run in parallel.
264275 "--base-id" , strconv .Itoa (time .Now ().Nanosecond ()),
265276 // Add debug logging for http.
266277 "--component-log-level" , "http:debug" ,
267278 )
279+ // func-e will use the version specified in the project root's .envoy-version file.
280+ cmd .Dir = internaltesting .FindProjectRoot ()
281+ version , err := os .ReadFile (filepath .Join (cmd .Dir , ".envoy-version" ))
282+ require .NoError (t , err )
283+ t .Logf ("Starting Envoy version %s" , strings .TrimSpace (string (version )))
284+ cmd .WaitDelay = 3 * time .Second // auto-kill after 3 seconds.
285+ t .Cleanup (func () {
286+ defer cancel ()
287+ // Graceful shutdown, should kill the Envoy subprocess, too.
288+ if err := cmd .Process .Signal (os .Interrupt ); err != nil {
289+ t .Logf ("Failed to send interrupt to aigw process: %v" , err )
290+ }
291+ // Wait for the process to exit gracefully, in worst case this is
292+ // killed in 3 seconds by WaitDelay above. In that case, you may
293+ // have a zombie Envoy process left behind!
294+ if _ , err := cmd .Process .Wait (); err != nil {
295+ t .Logf ("Failed to wait for aigw process to exit: %v" , err )
296+ }
297+ })
268298
269299 // wait for the ready message or exit.
270300 StartAndAwaitReady (t , cmd , stdout , stderr , "starting main dispatch loop" )
0 commit comments