test: e2e test for client.ServiceLogs

This commit is contained in:
Pasha Sviderski
2025-12-04 14:24:10 +10:00
parent 1e6aaf451e
commit 4fa30ab8cb
3 changed files with 120 additions and 7 deletions
+6
View File
@@ -94,6 +94,12 @@ func (cli *Client) ContainerLogs(
Since: opts.Since, Since: opts.Since,
Until: opts.Until, Until: opts.Until,
} }
if !opts.Follow && opts.Tail == 0 {
// If not following and tail is 0, set tail to -1 to return all logs.
// Otherwise, no logs will be returned at all.
req.Tail = -1
}
stream, err := cli.Docker.GRPCClient.ContainerLogs(proxyCtx, req) stream, err := cli.Docker.GRPCClient.ContainerLogs(proxyCtx, req)
if err != nil { if err != nil {
return nil, err return nil, err
+3 -7
View File
@@ -29,10 +29,7 @@ func deployTestService(t *testing.T, ctx context.Context, cli *client.Client, na
} }
deployment := cli.NewDeployment(spec, nil) deployment := cli.NewDeployment(spec, nil)
err := deployment.Validate(ctx) _, err := deployment.Run(ctx)
require.NoError(t, err)
_, err = deployment.Run(ctx)
require.NoError(t, err) require.NoError(t, err)
// Wait for all replicas to be running // Wait for all replicas to be running
@@ -41,9 +38,8 @@ func deployTestService(t *testing.T, ctx context.Context, cli *client.Client, na
if err != nil { if err != nil {
return false return false
} }
if uint(len(service.Containers)) != replicas { assertServiceMatchesSpec(t, service, spec)
return false
}
for _, ctr := range service.Containers { for _, ctr := range service.Containers {
if ctr.Container.State.Status != "running" { if ctr.Container.State.Status != "running" {
return false return false
+111
View File
@@ -3,6 +3,7 @@ package e2e
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/netip" "net/netip"
"slices" "slices"
"strings" "strings"
@@ -1682,4 +1683,114 @@ func TestServiceLifecycle(t *testing.T) {
assert.Equal(t, api.ServiceModeGlobal, svc.Mode) assert.Equal(t, api.ServiceModeGlobal, svc.Mode)
assert.Len(t, svc.Containers, 3, "expected 1 container on each machine") assert.Len(t, svc.Containers, 3, "expected 1 container on each machine")
}) })
t.Run("service logs", func(t *testing.T) {
t.Parallel()
name := "test-service-logs"
t.Cleanup(func() {
err := cli.RemoveService(ctx, name)
if !errors.Is(err, api.ErrNotFound) {
require.NoError(t, err)
}
})
spec := api.ServiceSpec{
Name: name,
Container: api.ContainerSpec{
Image: "busybox:1.37.0-musl",
Command: []string{
"sh",
"-c",
"trap 'exit 0' TERM INT; " +
"echo \"Hello from $HOSTNAME\"; sleep 0.5;" +
"echo \"Hello stderr from $HOSTNAME\" >&2; " +
"while true; do sleep 1; done"},
},
Replicas: 3,
}
deployment := cli.NewDeployment(spec, nil)
_, err := deployment.Run(ctx)
require.NoError(t, err)
svc, err := cli.InspectService(ctx, name)
require.NoError(t, err)
assertServiceMatchesSpec(t, svc, spec)
machines := serviceMachines(svc)
assert.Len(t, machines.ToSlice(), 3, "should have containers on all 3 machines")
time.Sleep(1 * time.Second) // Wait for containers to produce the logs.
logsSvc, stream, err := cli.ServiceLogs(ctx, name, api.ServiceLogsOptions{})
require.NoError(t, err)
assertServiceMatchesSpec(t, logsSvc, spec)
collectLogs := func(stream <-chan api.ServiceLogEntry) []string {
var entries []string
timeout := time.After(5 * time.Second)
for {
select {
case entry, ok := <-stream:
if !ok {
return entries
}
require.NoError(t, entry.Err)
if entry.Stream == api.LogStreamStdout || entry.Stream == api.LogStreamStderr {
entries = append(entries, string(entry.Message))
}
case <-timeout:
require.FailNow(t, "timed out waiting for logs")
}
}
}
logs := collectLogs(stream)
require.Len(t, logs, 6, "should have 6 log entries (1 stdout and 1 stderr from each of 3 replicas)")
for _, ctr := range svc.Containers {
stdoutMsg := fmt.Sprintf("Hello from %s\n", ctr.Container.Name)
stderrMsg := fmt.Sprintf("Hello stderr from %s\n", ctr.Container.Name)
assert.Contains(t, logs, stdoutMsg)
assert.Contains(t, logs, stderrMsg)
outIdx := slices.Index(logs, stdoutMsg)
errIdx := slices.Index(logs, stderrMsg)
assert.Less(t, outIdx, errIdx, "stdout log should appear before stderr log")
}
// Test filter logs by machine ID.
machineID := c.Machines[0].ID
_, machineStream, err := cli.ServiceLogs(ctx, name, api.ServiceLogsOptions{
Machines: []string{machineID},
})
require.NoError(t, err)
machineLogs := collectLogs(machineStream)
require.Len(t, machineLogs, 2, "should have 2 log entries (1 stdout and 1 stderr) from the filtered machine")
ctrName := ""
for _, ctr := range svc.Containers {
if ctr.MachineID == machineID {
ctrName = ctr.Container.Name
break
}
}
require.NotEmpty(t, ctrName, "should have found container on the target machine")
stdoutMsg := fmt.Sprintf("Hello from %s\n", ctrName)
stderrMsg := fmt.Sprintf("Hello stderr from %s\n", ctrName)
assert.Equal(t, []string{stdoutMsg, stderrMsg}, machineLogs)
// Test non-existent machine filter returns error.
_, _, err = cli.ServiceLogs(ctx, name, api.ServiceLogsOptions{
Machines: []string{"non-existent-machine"},
})
require.Error(t, err)
assert.Contains(t, err.Error(), "machines not found")
})
} }