Skip to content

Record & replay

Source · io.systemd.Journal · Root to record; none to replay

Golden-file testing against real systemd. health.go checks that journald answers over Varlink. Its test replays testdata/journal.json, recorded from a real systemd-journald v255, so it runs anywhere. Pass -update on a systemd host to record a fresh golden file through a forwarding fake.

Run it

go test -v ./examples/record-replay/                # replay
sudo go test ./examples/record-replay/ -update      # record against journald
Output (real)
=== RUN   TestCheckJournal
--- PASS: TestCheckJournal (0.00s)
PASS
ok      github.com/rossedman/sdvarlink/examples/record-replay   0.006s

The code

examples/record-replay/health.go
// Package health checks that systemd-journald answers over Varlink. Its
// test replays a golden file recorded against a real journald, so it runs
// anywhere; pass -update on a systemd host to re-record:
//
//  go test ./examples/record-replay/                 # replay
//  sudo go test ./examples/record-replay/ -update    # record against journald
package health

import (
    "context"
    "fmt"
    "slices"

    "github.com/rossedman/sdvarlink/systemd/v257/journal"
    "github.com/rossedman/sdvarlink/varlink"
)

// Report describes the journald instance that answered.
type Report struct {
    Product string
    Version string
}

// CheckJournal verifies journald implements io.systemd.Journal and that a
// Synchronize round trip succeeds.
func CheckJournal(ctx context.Context, c *journal.Client) (*Report, error) {
    info, err := varlink.GetInfo(ctx, c.Caller())
    if err != nil {
        return nil, err
    }
    if !slices.Contains(info.Interfaces, journal.InterfaceName) {
        return nil, fmt.Errorf("health: %s does not implement %s", info.Product, journal.InterfaceName)
    }
    if err := c.Synchronize(ctx); err != nil {
        return nil, fmt.Errorf("health: synchronize: %w", err)
    }
    return &Report{Product: info.Product, Version: info.Version}, nil
}
examples/record-replay/health_test.go
package health

import (
    "context"
    "flag"
    "strings"
    "testing"

    "github.com/rossedman/sdvarlink/systemd/v257/journal"
    "github.com/rossedman/sdvarlink/varlinktest"
)

var update = flag.Bool("update", false, "record testdata/journal.json against the live journald")

func TestCheckJournal(t *testing.T) {
    var srv *varlinktest.Server
    if *update {
        srv = varlinktest.Record(t, journal.DefaultSocket, "testdata/journal.json")
    } else {
        srv = varlinktest.Replay(t, "testdata/journal.json")
    }
    c, err := journal.DialPath(context.Background(), srv.Path())
    if err != nil {
        t.Fatal(err)
    }
    defer c.Close()

    rep, err := CheckJournal(context.Background(), c)
    if err != nil {
        t.Fatal(err)
    }
    if !strings.Contains(rep.Product, "journald") {
        t.Fatalf("report = %+v", rep)
    }
}
examples/record-replay/testdata/journal.json
[
  {
    "call": {
      "method": "org.varlink.service.GetInfo",
      "parameters": {}
    },
    "replies": [
      {
        "vendor": "The systemd Project",
        "product": "systemd (systemd-journald)",
        "version": "255 (255.4-1ubuntu8.17)",
        "url": "https://systemd.io/",
        "interfaces": [
          "io.systemd",
          "io.systemd.Journal",
          "org.varlink.service"
        ]
      }
    ]
  },
  {
    "call": {
      "method": "io.systemd.Journal.Synchronize",
      "parameters": {}
    },
    "replies": [
      {}
    ]
  }
]

How it works

  • varlinktest.Record(t, journal.DefaultSocket, golden) starts a fake that forwards every call to the real journald and writes the exchanges to the golden file when the test ends.
  • varlinktest.Replay(t, golden) answers from the file, failing the test if calls arrive out of order or with different parameters.
  • The code under test doesn't know. It gets a *journal.Client dialed at srv.Path() either way.
  • Reviewable fixtures. The golden file is indented JSON: one entry per call, with its parameters and replies.

See also