diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c7e54f0..6e30e673 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,10 +33,6 @@ jobs: - run: go version - - name: install lambda runtime interface emulator - run: curl -L -o /usr/local/bin/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie-x86_64 - - run: chmod +x /usr/local/bin/aws-lambda-rie - - name: Check out code into the Go module directory uses: actions/checkout@v6 @@ -64,10 +60,6 @@ jobs: with: go-version: ${{ matrix.go }} - - name: install lambda runtime interface emulator - run: curl -L -o /usr/local/bin/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie-x86_64 - - run: chmod +x /usr/local/bin/aws-lambda-rie - - name: Check out code into the Go module directory uses: actions/checkout@v6 diff --git a/lambda/sigterm_test.go b/lambda/sigterm_test.go index 278d5926..5894ed16 100644 --- a/lambda/sigterm_test.go +++ b/lambda/sigterm_test.go @@ -4,12 +4,14 @@ package lambda import ( + "fmt" + "io" "io/ioutil" //nolint: staticcheck + "net" "net/http" "os" "os/exec" - "path" - "strconv" + "path/filepath" "strings" "testing" "time" @@ -19,19 +21,22 @@ import ( ) func TestEnableSigterm(t *testing.T) { - if _, err := exec.LookPath("aws-lambda-rie"); err != nil { - t.Skipf("%v - install from https://github.com/aws/aws-lambda-runtime-interface-emulator/", err) + containerCmd := "" + if _, err := exec.LookPath("finch"); err == nil { + containerCmd = "finch" + } else if _, err := exec.LookPath("docker"); err == nil { + containerCmd = "docker" + } else { + t.Skip("finch or docker required") } testDir := t.TempDir() - // compile our handler, it'll always run to timeout ensuring the SIGTERM is triggered by aws-lambda-rie - handlerBuild := exec.Command("go", "build", "-o", path.Join(testDir, "sigterm.handler"), "./testdata/sigterm.go") - handlerBuild.Stderr = os.Stderr - handlerBuild.Stdout = os.Stderr + // compile our handler, it'll always run to timeout ensuring the SIGTERM is triggered + handlerBuild := exec.Command("go", "build", "-o", filepath.Join(testDir, "bootstrap"), "./testdata/sigterm.go") + handlerBuild.Env = append(os.Environ(), "GOOS=linux") require.NoError(t, handlerBuild.Run()) - portI := 0 for name, opts := range map[string]struct { envVars []string assertLogs func(t *testing.T, logs string) @@ -51,43 +56,53 @@ func TestEnableSigterm(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - portI += 1 - addr1 := "localhost:" + strconv.Itoa(8000+portI) - addr2 := "localhost:" + strconv.Itoa(9000+portI) - rieInvokeAPI := "http://" + addr1 + "/2015-03-31/functions/function/invocations" - // run the runtime interface emulator, capture the logs for assertion - cmd := exec.Command("aws-lambda-rie", "--runtime-interface-emulator-address", addr1, "--runtime-api-address", addr2, "sigterm.handler") - cmd.Env = append([]string{ - "PATH=" + testDir, - "AWS_LAMBDA_FUNCTION_TIMEOUT=2", - }, opts.envVars...) - cmd.Stderr = os.Stderr + // Find an available port + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := listener.Addr().(*net.TCPAddr).Port + listener.Close() + + cmdArgs := []string{"run", "--rm", + "-v", testDir + ":/var/runtime:ro,delegated", + "-p", fmt.Sprintf("%d:8080", port), + "-e", "AWS_LAMBDA_FUNCTION_TIMEOUT=2"} + for _, env := range opts.envVars { + cmdArgs = append(cmdArgs, "-e", env) + } + cmdArgs = append(cmdArgs, "public.ecr.aws/lambda/provided:al2023", "bootstrap") + + cmd := exec.Command(containerCmd, cmdArgs...) stdout, err := cmd.StdoutPipe() require.NoError(t, err) - var logs string - done := make(chan interface{}) // closed on completion of log flush + stderr, err := cmd.StderrPipe() + require.NoError(t, err) + + var logBuf strings.Builder + logDone := make(chan struct{}) go func() { - logBytes, err := ioutil.ReadAll(stdout) - require.NoError(t, err) - logs = string(logBytes) - close(done) + _, _ = io.Copy(io.MultiWriter(os.Stderr, &logBuf), io.MultiReader(stdout, stderr)) + close(logDone) }() + require.NoError(t, cmd.Start()) t.Cleanup(func() { _ = cmd.Process.Kill() }) - // give a moment for the port to bind - time.Sleep(500 * time.Millisecond) + time.Sleep(5 * time.Second) // Wait for container to start - client := &http.Client{Timeout: 5 * time.Second} // http client timeout to prevent case from hanging on aws-lambda-rie - resp, err := client.Post(rieInvokeAPI, "application/json", strings.NewReader("{}")) + client := &http.Client{Timeout: 5 * time.Second} + invokeURL := fmt.Sprintf("http://127.0.0.1:%d/2015-03-31/functions/function/invocations", port) + resp, err := client.Post(invokeURL, "application/json", strings.NewReader("{}")) require.NoError(t, err) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) assert.NoError(t, err) - assert.Equal(t, string(body), "Task timed out after 2.00 seconds") + assert.Equal(t, "Task timed out after 2.00 seconds", string(body)) + + _ = cmd.Process.Kill() + _ = cmd.Wait() + <-logDone - require.NoError(t, cmd.Process.Kill()) // now ensure the logs are drained - <-done + logs := logBuf.String() t.Logf("stdout:\n%s", logs) opts.assertLogs(t, logs) })