Path: blob/main/cmd/grafana-agent-service/service_test.go
4093 views
package main12import (3"bytes"4"context"5"fmt"6"io"7"net/http"8"os/exec"9"path/filepath"10"runtime"11"sync"12"testing"1314"github.com/go-kit/log"15"github.com/grafana/agent/pkg/flow/componenttest"16"github.com/grafana/agent/pkg/util"17"github.com/phayes/freeport"18"github.com/stretchr/testify/require"19)2021const goosWindows = "windows"2223func Test_serviceManager(t *testing.T) {24l := util.TestLogger(t)2526serviceBinary := buildExampleService(t, l)2728t.Run("can run service binary", func(t *testing.T) {29listenHost := getListenHost(t)3031mgr := newServiceManager(l, serviceManagerConfig{32Path: serviceBinary,33Args: []string{"-listen-addr", listenHost},34})35go mgr.Run(componenttest.TestContext(t))3637util.Eventually(t, func(t require.TestingT) {38resp, err := makeServiceRequest(listenHost, "/echo/response", []byte("Hello, world!"))39require.NoError(t, err)40require.Equal(t, []byte("Hello, world!"), resp)41})42})4344t.Run("terminates service binary", func(t *testing.T) {45listenHost := getListenHost(t)4647mgr := newServiceManager(l, serviceManagerConfig{48Path: serviceBinary,49Args: []string{"-listen-addr", listenHost},50})5152ctx, cancel := context.WithCancel(componenttest.TestContext(t))53defer cancel()54go mgr.Run(ctx)5556util.Eventually(t, func(t require.TestingT) {57resp, err := makeServiceRequest(listenHost, "/echo/response", []byte("Hello, world!"))58require.NoError(t, err)59require.Equal(t, []byte("Hello, world!"), resp)60})6162// Cancel the context, which should stop the manager.63cancel()6465util.Eventually(t, func(t require.TestingT) {66_, err := makeServiceRequest(listenHost, "/echo/response", []byte("Hello, world!"))6768if runtime.GOOS == goosWindows {69require.ErrorContains(t, err, "No connection could be made")70} else {71require.ErrorContains(t, err, "connection refused")72}73})74})7576t.Run("can forward to stdout", func(t *testing.T) {77listenHost := getListenHost(t)7879var buf syncBuffer8081mgr := newServiceManager(l, serviceManagerConfig{82Path: serviceBinary,83Args: []string{"-listen-addr", listenHost},84Stdout: &buf,85})8687ctx, cancel := context.WithCancel(componenttest.TestContext(t))88defer cancel()89go mgr.Run(ctx)9091// Test making the request and testing the buffer contents separately,92// otherwise we may log to stdout more than we intend to.9394util.Eventually(t, func(t require.TestingT) {95_, err := makeServiceRequest(listenHost, "/echo/stdout", []byte("Hello, world!"))96require.NoError(t, err)97})9899util.Eventually(t, func(t require.TestingT) {100require.Equal(t, []byte("Hello, world!"), buf.Bytes())101})102})103104t.Run("can forward to stderr", func(t *testing.T) {105listenHost := getListenHost(t)106107var buf syncBuffer108109mgr := newServiceManager(l, serviceManagerConfig{110Path: serviceBinary,111Args: []string{"-listen-addr", listenHost},112Stderr: &buf,113})114115ctx, cancel := context.WithCancel(componenttest.TestContext(t))116defer cancel()117go mgr.Run(ctx)118119// Test making the request and testing the buffer contents separately,120// otherwise we may log to stderr more than we intend to.121122util.Eventually(t, func(t require.TestingT) {123_, err := makeServiceRequest(listenHost, "/echo/stderr", []byte("Hello, world!"))124require.NoError(t, err)125})126127util.Eventually(t, func(t require.TestingT) {128require.Equal(t, []byte("Hello, world!"), buf.Bytes())129})130})131}132133func buildExampleService(t *testing.T, l log.Logger) string {134t.Helper()135136writer := log.NewStdlibAdapter(l)137138servicePath := filepath.Join(t.TempDir(), "example-service")139if runtime.GOOS == goosWindows {140servicePath = servicePath + ".exe"141}142143cmd := exec.Command(144"go", "build",145"-o", servicePath,146"testdata/example_service.go",147)148cmd.Stdout = writer149cmd.Stderr = writer150151require.NoError(t, cmd.Run())152153return servicePath154}155156func getListenHost(t *testing.T) string {157t.Helper()158159port, err := freeport.GetFreePort()160require.NoError(t, err)161162return fmt.Sprintf("127.0.0.1:%d", port)163}164165func makeServiceRequest(host string, path string, body []byte) ([]byte, error) {166resp, err := http.Post(167fmt.Sprintf("http://%s%s", host, path),168"text/plain",169bytes.NewReader(body),170)171if err != nil {172return nil, err173}174defer resp.Body.Close()175176if resp.StatusCode != http.StatusOK {177return nil, fmt.Errorf("unexpected status code %s", resp.Status)178}179return io.ReadAll(resp.Body)180}181182// syncBuffer wraps around a bytes.Buffer and makes it safe to use from183// multiple goroutines.184type syncBuffer struct {185mut sync.RWMutex186buf bytes.Buffer187}188189func (sb *syncBuffer) Bytes() []byte {190sb.mut.RLock()191defer sb.mut.RUnlock()192193return sb.buf.Bytes()194}195196func (sb *syncBuffer) Write(p []byte) (n int, err error) {197sb.mut.Lock()198defer sb.mut.Unlock()199200return sb.buf.Write(p)201}202203204