1// SPDX-License-Identifier: MIT
2
3package host
4
5import (
6 "encoding/json"
7 "fmt"
8 "strings"
9 "testing"
10
11 ermite "bnema.dev/ermite-api/gen/ermite"
12)
13
14// jobFinished returns the fields of the one "job finished" line of logs.
15func jobFinished(t *testing.T, logs string) map[string]any {
16 t.Helper()
17 var found map[string]any
18 for _, line := range strings.Split(logs, "\n") {
19 var fields map[string]any
20 if json.Unmarshal([]byte(line), &fields) != nil || fields["message"] != "job finished" {
21 continue
22 }
23 if found != nil {
24 t.Fatal("two job finished lines")
25 }
26 found = fields
27 }
28 if found == nil {
29 t.Fatalf("no job finished line: %s", logs)
30 }
31 return found
32}
33
34// The guest is untrusted: of its Hello.boot_ms, the job finished line keeps
35// only the known phases with a value of at least 0, whatever else it sends.
36func TestHostileGuestBootTimings(t *testing.T) {
37 h := newHarness(t, simpleScript("hello"))
38 hostile := map[string]int64{
39 "kernel": 5,
40 "dockerd": -7, // negative: dropped
41 "setup": 0,
42 "total": 1, // would overwrite a host field as guest_total_ms
43 "": 3,
44 "x\"y": 4,
45 "../../etc": 5,
46 "containerd": 9,
47 }
48 for i := range 500 {
49 hostile[fmt.Sprint("junk", i)] = int64(i)
50 }
51 h.driver.bootMs = hostile
52 h.srv.jobs = []*ermite.Job{testJob("j1")}
53 stop := h.run(t)
54 eventually(t, "CompleteJob", func() bool { return h.srv.completeCount() == 1 })
55 if err := stop(); err != nil {
56 t.Fatal(err)
57 }
58 fields := jobFinished(t, h.logs.String())
59 var guest []string
60 for k := range fields {
61 if strings.HasPrefix(k, "guest_") {
62 guest = append(guest, k)
63 }
64 }
65 want := map[string]float64{"guest_kernel_ms": 5, "guest_setup_ms": 0, "guest_containerd_ms": 9}
66 if len(guest) != len(want) {
67 t.Fatalf("guest fields %v, want %v", guest, want)
68 }
69 for k, v := range want {
70 if got, ok := fields[k].(float64); !ok || got != v {
71 t.Errorf("%s = %v, want %v", k, fields[k], v)
72 }
73 }
74}