1// SPDX-License-Identifier: MIT
2
3package e2e
4
5import (
6 "context"
7 "encoding/json"
8 "net/http"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/bnema/zerowrap"
14
15 ermite "bnema.dev/ermite-api/gen/ermite"
16 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
17
18 "bnema.dev/ermite-runner/internal/host"
19 "bnema.dev/ermite-runner/internal/host/hosttest"
20)
21
22type timingWriter struct {
23 mu sync.Mutex
24 lines [][]byte
25}
26
27func (w *timingWriter) Write(p []byte) (int, error) {
28 w.mu.Lock()
29 defer w.mu.Unlock()
30 w.lines = append(w.lines, append([]byte(nil), p...))
31 return len(p), nil
32}
33
34func TestJobTiming(t *testing.T) {
35 image := needE2E(t)
36 drv := newDriver(t, image)
37 srv := hosttest.New()
38 defer srv.Close()
39 srv.Queue(&ermite.Job{JobId: "timing", RunId: 1, Attempt: 1, JobToken: "ermitejob_timing", WorkflowPath: ".github/workflows/ci.yml", WorkflowYaml: "name: timing\non: push\njobs:\n main:\n runs-on: ubuntu-latest\n steps:\n - run: echo timing\n", JobKey: "main", Matrix: "{}", Contexts: chainContexts("main"), Repo: "hello", TimeoutMinutes: 2})
40 r := &host.Runner{
41 Cfg: host.Config{URL: srv.URL, Name: "timing", Capacity: 1, Version: "test"}, Driver: drv,
42 Poll: host.NewClient(srv.URL, hosttest.RunnerToken, http.DefaultTransport),
43 Job: func(tok string) ermiteconnect.RunnerServiceClient {
44 return host.NewClient(srv.URL, tok, http.DefaultTransport)
45 },
46 Timing: host.Timing{LogFlush: 20 * time.Millisecond, NoJobPause: 100 * time.Millisecond},
47 }
48 var logs timingWriter
49 ctx, cancel := context.WithCancel(zerowrap.WithCtx(context.Background(), zerowrap.New(zerowrap.Config{Level: "info", Format: "json", Output: &logs})))
50 done := make(chan error, 1)
51 go func() { done <- r.Run(ctx) }()
52 select {
53 case <-srv.Done():
54 case <-time.After(3 * time.Minute):
55 cancel()
56 t.Fatal("job did not complete")
57 }
58 cancel()
59 select {
60 case err := <-done:
61 if err != nil {
62 t.Fatal(err)
63 }
64 case <-time.After(time.Minute):
65 t.Fatal("runner did not stop")
66 }
67 logs.mu.Lock()
68 defer logs.mu.Unlock()
69 for _, line := range logs.lines {
70 var fields map[string]any
71 if json.Unmarshal(line, &fields) != nil || fields["message"] != "job finished" {
72 continue
73 }
74 for _, key := range []string{"sandbox_ms", "guest_dockerd_ms"} {
75 value, ok := fields[key].(float64)
76 if !ok || value < 0 {
77 t.Errorf("%s missing or negative: %v", key, fields)
78 }
79 }
80 return
81 }
82 t.Fatal("no job finished log")
83}