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 }