github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/k6runner/k6runner_test.go (about)

     1  package k6runner
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sort"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/grafana/synthetic-monitoring-agent/internal/model"
    15  	"github.com/grafana/synthetic-monitoring-agent/internal/prober/logger"
    16  	"github.com/grafana/synthetic-monitoring-agent/internal/testhelper"
    17  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	dto "github.com/prometheus/client_model/go"
    20  	"github.com/prometheus/common/expfmt"
    21  	"github.com/rs/zerolog"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestNew(t *testing.T) {
    26  	t.Parallel()
    27  
    28  	r1, err := New(RunnerOpts{Uri: "./testdata/k6-fake"})
    29  	require.NoError(t, err)
    30  	require.IsType(t, Local{}, r1)
    31  	require.Equal(t, "", r1.(Local).blacklistedIP)
    32  
    33  	r2, err := New(RunnerOpts{Uri: "./testdata/k6-fake", BlacklistedIP: "192.168.4.0/24"})
    34  	require.NoError(t, err)
    35  	require.IsType(t, Local{}, r2)
    36  	require.Equal(t, "192.168.4.0/24", r2.(Local).blacklistedIP)
    37  	// Ensure WithLogger preserves config.
    38  	zl := zerolog.New(io.Discard)
    39  	r2 = r2.WithLogger(&zl)
    40  	require.Equal(t, "192.168.4.0/24", r2.(Local).blacklistedIP)
    41  
    42  	r3, err := New(RunnerOpts{Uri: "http://localhost:6565"})
    43  	require.NoError(t, err)
    44  	require.IsType(t, &HttpRunner{}, r3)
    45  
    46  	r4, err := New(RunnerOpts{Uri: "https://localhost:6565"})
    47  	require.NoError(t, err)
    48  	require.IsType(t, &HttpRunner{}, r4)
    49  }
    50  
    51  func TestNewScript(t *testing.T) {
    52  	t.Parallel()
    53  
    54  	runner, err := New(RunnerOpts{Uri: "k6"})
    55  	require.NoError(t, err)
    56  
    57  	script := Script{
    58  		Script: []byte("test"),
    59  		Settings: Settings{
    60  			Timeout: 1000,
    61  		},
    62  	}
    63  
    64  	processor, err := NewProcessor(script, runner)
    65  	require.NoError(t, err)
    66  	require.NotNil(t, processor)
    67  	require.Equal(t, script, processor.script)
    68  	require.Equal(t, runner, processor.runner)
    69  }
    70  
    71  func TestScriptRun(t *testing.T) {
    72  	t.Parallel()
    73  
    74  	runner := testRunner{
    75  		metrics: testhelper.MustReadFile(t, "testdata/test.out"),
    76  		logs:    testhelper.MustReadFile(t, "testdata/test.log"),
    77  	}
    78  
    79  	processor, err := NewProcessor(Script{
    80  		Script: testhelper.MustReadFile(t, "testdata/test.js"),
    81  		Settings: Settings{
    82  			Timeout: 1000,
    83  		},
    84  	}, &runner)
    85  	require.NoError(t, err)
    86  	require.NotNil(t, processor)
    87  
    88  	var (
    89  		registry = prometheus.NewRegistry()
    90  		logger   testLogger
    91  		buf      bytes.Buffer
    92  		zlogger  = zerolog.New(&buf)
    93  	)
    94  
    95  	ctx, cancel := testhelper.Context(context.Background(), t)
    96  	t.Cleanup(cancel)
    97  
    98  	// We already know tha parsing the metrics and the logs is working, so
    99  	// we are only interested in verifying that the script runs without
   100  	// errors.
   101  	success, duration, err := processor.Run(ctx, registry, &logger, zlogger, SecretStore{}, "test-execution-id")
   102  	require.NoError(t, err)
   103  	require.True(t, success)
   104  	require.Equal(t, 500*time.Millisecond, duration)
   105  }
   106  
   107  func TestCheckInfoFromSM(t *testing.T) {
   108  	t.Parallel()
   109  
   110  	check := model.Check{
   111  		RegionId: 4,
   112  		Check: sm.Check{
   113  			Id:       69,
   114  			TenantId: 1234,
   115  			Created:  1234.5,
   116  			Modified: 12345.6,
   117  			Settings: sm.CheckSettings{
   118  				Browser: &sm.BrowserSettings{}, // Make it non-nil so type is Browser.
   119  			},
   120  		},
   121  	}
   122  
   123  	ci := CheckInfoFromSM(check)
   124  
   125  	require.Equal(t, sm.CheckTypeBrowser.String(), ci.Type)
   126  	require.Equal(t, map[string]any{
   127  		"id":       check.Id,
   128  		"tenantID": check.TenantId,
   129  		"regionID": check.RegionId,
   130  		"created":  check.Created,
   131  		"modified": check.Modified,
   132  	}, ci.Metadata)
   133  }
   134  
   135  type testRunner struct {
   136  	metrics []byte
   137  	logs    []byte
   138  }
   139  
   140  var _ Runner = &testRunner{}
   141  
   142  func (r *testRunner) Run(ctx context.Context, script Script, secretStore SecretStore, _ string) (*RunResponse, error) {
   143  	return &RunResponse{
   144  		Metrics: r.metrics,
   145  		Logs:    r.logs,
   146  	}, nil
   147  }
   148  
   149  func (r *testRunner) WithLogger(logger *zerolog.Logger) Runner {
   150  	return r
   151  }
   152  
   153  func (*testRunner) Versions(_ context.Context) <-chan []string {
   154  	return nil // Blocks forever if read.
   155  }
   156  
   157  func TestTextToRegistry(t *testing.T) {
   158  	data := testhelper.MustReadFile(t, "testdata/test.out")
   159  
   160  	expectedMetrics := map[string]struct{}{}
   161  
   162  	promDecoder := expfmt.NewDecoder(bytes.NewBuffer(data), expfmt.NewFormat(expfmt.TypeTextPlain))
   163  
   164  DEC_LOOP:
   165  	for {
   166  		var mf dto.MetricFamily
   167  		switch err := promDecoder.Decode(&mf); err {
   168  		case nil:
   169  			for _, m := range mf.GetMetric() {
   170  				expectedMetrics[buildId(mf.GetName(), m)] = struct{}{}
   171  			}
   172  		case io.EOF:
   173  			break DEC_LOOP
   174  		default:
   175  			t.Fatal(err)
   176  		}
   177  	}
   178  
   179  	var sampleCollector sampleCollector
   180  
   181  	buf := bytes.Buffer{}
   182  	logger := zerolog.New(&buf)
   183  
   184  	err := extractMetricSamples(data, logger, sampleCollector.process)
   185  	require.NoError(t, err)
   186  
   187  	registry := prometheus.NewRegistry()
   188  	err = registry.Register(&sampleCollector.collector)
   189  	require.NoError(t, err)
   190  
   191  	mfs, err := registry.Gather()
   192  	require.NoError(t, err)
   193  
   194  	actualMetrics := map[string]struct{}{}
   195  
   196  	for _, mf := range mfs {
   197  		for _, m := range mf.GetMetric() {
   198  			actualMetrics[buildId(mf.GetName(), m)] = struct{}{}
   199  		}
   200  	}
   201  
   202  	// This is some minimal validation that all the metrics are parsed and
   203  	// added to the registry.
   204  	require.Equal(t, expectedMetrics, actualMetrics)
   205  }
   206  
   207  func buildId(name string, m *dto.Metric) string {
   208  	labels := m.GetLabel()
   209  	sort.Slice(labels, func(i, j int) bool {
   210  		switch rel := strings.Compare(labels[i].GetName(), labels[j].GetName()); rel {
   211  		case 0:
   212  			return strings.Compare(labels[i].GetValue(), labels[j].GetValue()) < 0
   213  		default:
   214  			return rel < 0
   215  		}
   216  	})
   217  
   218  	var s strings.Builder
   219  	s.WriteString(name)
   220  
   221  	for _, l := range labels {
   222  		s.WriteString(",")
   223  		s.WriteString(l.GetName())
   224  		s.WriteString("=")
   225  		s.WriteString(l.GetValue())
   226  	}
   227  
   228  	return s.String()
   229  }
   230  
   231  func TestK6LogsToLogger(t *testing.T) {
   232  	data := testhelper.MustReadFile(t, "testdata/test.log")
   233  
   234  	var logger testLogger
   235  
   236  	err := k6LogsToLogger(data, &logger)
   237  	require.NoError(t, err)
   238  }
   239  
   240  type testLogger struct{}
   241  
   242  var _ logger.Logger = &testLogger{}
   243  
   244  func (l *testLogger) Log(keyvals ...any) error {
   245  	if len(keyvals) == 0 {
   246  		return errors.New("empty log message")
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  type recordingLogger struct {
   253  	buf *bytes.Buffer
   254  }
   255  
   256  var _ logger.Logger = &recordingLogger{}
   257  
   258  func (l recordingLogger) Log(keyvals ...any) error {
   259  	if len(keyvals) == 0 {
   260  		return errors.New("empty log message")
   261  	}
   262  
   263  	if len(keyvals)%2 != 0 {
   264  		return errors.New("not the same number of keys and vals")
   265  	}
   266  
   267  	line := make([]string, 0, len(keyvals)/2)
   268  	for i := 0; i < len(keyvals); i += 2 {
   269  		key := keyvals[i]
   270  		val := keyvals[i+1]
   271  
   272  		line = append(line, fmt.Sprintf("%s=%q", key, val))
   273  	}
   274  
   275  	_, err := fmt.Fprintln(l.buf, strings.Join(line, " "))
   276  
   277  	return err
   278  }