Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
d9f1dcf
tests: Fix error message
adombeck Sep 1, 2025
54ceaaa
Revert "ssh-tests: Avoid building module twice when updating golden f…
adombeck Sep 9, 2025
e69cc57
tests: Continuously print output of build commands
adombeck Aug 29, 2025
2484c54
tests: Continuously print output of VHS
adombeck Aug 29, 2025
075f80e
tests: Print duration of setup steps
adombeck Aug 25, 2025
3413d49
tests: Print duration of VHS tape execution
adombeck Aug 29, 2025
9b9cc7e
tests: Improve log messages
adombeck Aug 25, 2025
48cbe8b
tests: Make VHS use colors in its output
adombeck Aug 27, 2025
25b8973
tests: Adapt log level of sshd logs
adombeck Aug 27, 2025
3932a03
tests: Support overriding the default VHS wait timeout
adombeck Aug 27, 2025
8805af1
tests: Use consistent log format in sshd_preloader
adombeck Aug 27, 2025
b6287cc
tests: Always daemonize sshd
adombeck Aug 27, 2025
22c8315
ssh tests: Improve formatting of tape output in error message
adombeck Aug 27, 2025
cdf58e4
Remove redundant call to slog.SetLogLoggerLevel
adombeck Aug 27, 2025
438c0ad
tests: Print output of authd continuously
adombeck Aug 27, 2025
5d5a1cb
log: Use the SimpleHandler by default
adombeck Aug 27, 2025
06284b3
tests: Don't print full tape before executing
adombeck Aug 28, 2025
ddcb842
Use correct capitalization for sshd
adombeck Aug 28, 2025
c7937ef
daemon: Improve log messages when receiving SIGINT/SIGHUP
adombeck Aug 29, 2025
169a20b
tests: Add colored log separators
adombeck Aug 28, 2025
c8255a1
tests: Refer to authd as "authd" instead of "daemon"
adombeck Aug 29, 2025
b048896
tests: Run build commands in minimal environment
adombeck Aug 29, 2025
59d52d2
nss: Improve log message
adombeck Aug 29, 2025
d78bbad
tests: Fix AUTHD_TESTS_ARTIFACTS_ALWAYS_SAVE
adombeck Aug 29, 2025
5f79b12
refactor: Rename saveArtifactsForDebugOnCleanup -> maybeSaveFilesAsAr…
adombeck Aug 29, 2025
d71f8c5
refactor: Use variadic argument
adombeck Aug 29, 2025
52f1313
refactor: Rename authdArtifactsDirSync -> authdArtifactsDirOnce
adombeck Aug 29, 2025
8a02bb6
refactor: Rename artifactsPath -> artifactsDir
adombeck Aug 29, 2025
5f739fc
refator: Extract createArtifactsDir
adombeck Aug 29, 2025
55a0039
refactor: Extract maybeSaveBytesAsArtifactOnCleanup
adombeck Aug 29, 2025
4bf0c23
refactor: Extract maybeSaveBufferAsArtifactOnCleanup
adombeck Aug 29, 2025
a3e7f49
refactor: Rename ExpectedOutput -> SanitizedOutput
adombeck Aug 29, 2025
68109a8
refactor: Rename sanitizeGoldenFile -> sanitizedOutput
adombeck Aug 29, 2025
b6390fd
VHS: Save sanitized output even if the tape execution failed
adombeck Aug 29, 2025
668e626
tests: Fix artifacts path
adombeck Aug 29, 2025
cbce469
refactor: Use fileutils.CopyFile
adombeck Aug 29, 2025
5b21f43
tests: Avoid VHS suggesting to upload GIF
adombeck Aug 29, 2025
d8e83ac
refactor: Extract field tapeData.OutputDir
adombeck Aug 29, 2025
92589d8
refactor: Replace tapeData.Outputs with tapeData.OutputFilename
adombeck Aug 29, 2025
72c3d43
refactor: Move the artifact-saving code out of PrepareTape
adombeck Aug 29, 2025
1d4c61a
tests: Run sshd in foreground and without log file
adombeck Aug 29, 2025
bfaeddd
tests: Improve log messages
adombeck Aug 29, 2025
42dcedc
tests: Make the name of the pam service clearer
adombeck Aug 29, 2025
d9708b1
tests: Store sshd output with log level debug3 as test artifact
adombeck Aug 29, 2025
4a917c6
ssh tests: Only build test dependencies once when needed
adombeck Aug 29, 2025
6b8c7f6
tests: Fix log message
adombeck Aug 29, 2025
72ddb84
tests: Log output via t.Log
adombeck Aug 30, 2025
a66ac20
tests: Make use of the new Output method of testing.T
adombeck Aug 31, 2025
e8c2ea3
tests: Run gcov with logging
adombeck Sep 1, 2025
e6fc9de
tests: Add log messages when locking/unlocking rust build dir
adombeck Sep 1, 2025
cc3fdd8
tests: Support printing stdout/stderr on error in RunWithTiming
adombeck Sep 1, 2025
0e9cc85
refactor: Extract testlog package
adombeck Sep 1, 2025
e861d69
refactor: Rename buildCModule -> buildSharedLibrary
adombeck Sep 1, 2025
42bdd9c
tests: Avoid data race when reading/writing sshd output buffer
adombeck Sep 1, 2025
2261218
refactor: Rename some variables for better clarity
adombeck Sep 2, 2025
8bf0e05
tests: Bump time waiting for server to quit
adombeck Sep 2, 2025
dd0d442
refactor: Move artifacts-related functions to testutils
adombeck Sep 2, 2025
21199dd
tests: Avoid data race when writing authd output
adombeck Sep 2, 2025
283b5da
tests: Handle "connection reset by peer" when waiting for sshd to start
adombeck Sep 2, 2025
b579fbc
Use correct capitalization for VHS
adombeck Sep 3, 2025
042e6c2
refactor: Rename RunAuthd -> StartAuthd
adombeck Sep 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,12 @@ The test suite must pass before merging the PR to our main branch. Any new featu

#### Tests with dependencies

Some tests, such as the [PAM CLI tests](https://github.com/ubuntu/authd/blob/5ba54c0a573f34e99782fe624b090ab229798fc3/pam/integration-tests/integration_test.go#L21), use external tools such as [vhs](https://github.com/charmbracelet/vhs)
Some tests, such as the [PAM CLI tests](https://github.com/ubuntu/authd/blob/5ba54c0a573f34e99782fe624b090ab229798fc3/pam/integration-tests/integration_test.go#L21), use external tools such as [VHS](https://github.com/charmbracelet/vhs)
to record and run the tape files needed for the tests. Those tools are not included in the project dependencies and must be installed manually.

Information about these tools and their usage will be linked below:

- [vhs](https://github.com/charmbracelet/vhs?tab=readme-ov-file#tutorial): tutorial on using vhs as a CLI-based video recorder
- [VHS](https://github.com/charmbracelet/vhs?tab=readme-ov-file#tutorial): tutorial on using VHS as a CLI-based video recorder

### Code style

Expand Down
2 changes: 0 additions & 2 deletions cmd/authd/daemon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -94,6 +93,5 @@ func setVerboseMode(level int) {
log.SetLevel(log.InfoLevel)
default:
log.SetLevel(log.DebugLevel)
slog.SetLogLoggerLevel(slog.LevelDebug)
}
}
3 changes: 3 additions & 0 deletions cmd/authd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,16 @@ func installSignalHandler(a app) func() {
for {
switch v, ok := <-c; v {
case syscall.SIGINT, syscall.SIGTERM:
log.Infof(context.Background(), "Received signal %d (%s), exiting...", v, v.String())
a.Quit()
return
case syscall.SIGHUP:
if a.Hup() {
log.Info(context.Background(), "Received SIGHUP, exiting...")
a.Quit()
return
}
log.Info(context.Background(), "Received SIGHUP, but nothing to do")
default:
// channel was closed: we exited
if !ok {
Expand Down
5 changes: 2 additions & 3 deletions internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,12 @@ func (d *Daemon) Serve(ctx context.Context) (err error) {
// Quit gracefully quits listening loop and stops the grpc server.
// It can drops any existing connexion is force is true.
func (d Daemon) Quit(ctx context.Context, force bool) {
log.Infof(ctx, "Stopping daemon requested for socket %s.", d.lis.Addr())
if force {
d.grpcServer.Stop()
return
}

log.Info(ctx, "Wait for active requests to close.")
log.Info(ctx, "Waiting for pending requests to finish")
d.grpcServer.GracefulStop()
log.Debug(ctx, "All connections have now ended.")
log.Info(ctx, "gRPC server gracefully stopped")
}
12 changes: 6 additions & 6 deletions internal/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,10 @@ func TestQuit(t *testing.T) {
require.True(t, connected, "new connection should be made allowed")
}

// Request quitting.
quiteDone := make(chan struct{})
// Request server shutdown
shutdownRequested := make(chan struct{})
go func() {
defer close(quiteDone)
defer close(shutdownRequested)
d.Quit(context.Background(), tc.force)
}()

Expand All @@ -269,8 +269,8 @@ func TestQuit(t *testing.T) {

serverHasQuit := func() bool {
select {
case _, running := <-quiteDone:
return !running
case _, ok := <-shutdownRequested:
return !ok
default:
return false
}
Expand All @@ -287,7 +287,7 @@ func TestQuit(t *testing.T) {
// drop connection
disconnectClient()

require.Eventually(t, serverHasQuit, 100*time.Millisecond, 10*time.Millisecond, "Server should quit with no more active connection")
require.Eventually(t, serverHasQuit, 1*time.Second, 10*time.Millisecond, "Server should quit with no more active connection")
})
}
}
Expand Down
163 changes: 163 additions & 0 deletions internal/testlog/testlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Package testlog provides utilities for logging test output.
package testlog

import (
"fmt"
"os/exec"
"testing"
"time"
)

type runWithTimingOptions struct {
doNotSetStdoutAndStderr bool
onlyPrintStdoutAndStderrOnError bool
}

// RunWithTimingOption is a function that configures the RunWithTiming function.
type RunWithTimingOption func(options *runWithTimingOptions)

// DoNotSetStdoutAndStderr prevents RunWithTiming from setting the stdout and stderr of the given command.
// By default, RunWithTiming sets stdout and stderr to the test's output.
func DoNotSetStdoutAndStderr() RunWithTimingOption {
return func(options *runWithTimingOptions) {
options.doNotSetStdoutAndStderr = true
}
}

// OnlyPrintStdoutAndStderrOnError makes RunWithTiming only print the stdout and stderr of the given command if it fails.
func OnlyPrintStdoutAndStderrOnError() RunWithTimingOption {
return func(options *runWithTimingOptions) {
options.onlyPrintStdoutAndStderrOnError = true
}
}

// RunWithTiming runs the given command while logging its duration with the provided message.
//
//nolint:thelper // we do call t.Helper() if t is not nil
func RunWithTiming(t *testing.T, msg string, cmd *exec.Cmd, options ...RunWithTimingOption) error {
if t != nil {
t.Helper()
}

w := testWriterOrStderr(t)

opts := runWithTimingOptions{}
for _, f := range options {
f(&opts)
}

if opts.doNotSetStdoutAndStderr && opts.onlyPrintStdoutAndStderrOnError {
panic("onlyPrintStdoutAndStderrOnError and doNotSetStdoutAndStderr cannot be used together")
}

if !opts.doNotSetStdoutAndStderr && !opts.onlyPrintStdoutAndStderrOnError {
cmd.Stdout = w
cmd.Stderr = w
}

LogCommand(t, msg, cmd)
start := time.Now()

var err error
var out []byte
if opts.onlyPrintStdoutAndStderrOnError {
out, err = cmd.CombinedOutput()
if err != nil {
_, _ = w.Write(out)
}
} else {
err = cmd.Run()
}
duration := time.Since(start)

if err != nil {
fmt.Fprintf(w, redSeparatorf("%s failed in %.3fs with %v", msg, duration.Seconds(), err)+"\n")
} else {
fmt.Fprintf(w, separatorf("%s finished in %.3fs", msg, duration.Seconds())+"\n")
}

return err
}

// LogCommand logs the given command to stderr.
//
//nolint:thelper // we do call t.Helper() if t is not nil
func LogCommand(t *testing.T, msg string, cmd *exec.Cmd) {
if t != nil {
t.Helper()
}
w := testWriterOrStderr(t)

sep := "----------------------------------------"
fmt.Fprintf(w, "\n"+separator(msg)+"command: %s\n%s\nenvironment: %s\n%s\n", cmd.String(), sep, cmd.Env, sep)
}

// LogStartSeparatorf logs a separator to stderr with the given formatted message.
//
//nolint:thelper // we do call t.Helper() if t is not nil
func LogStartSeparatorf(t *testing.T, s string, args ...any) {
if t != nil {
t.Helper()
}
w := testWriterOrStderr(t)

fmt.Fprintf(w, "\n"+separatorf(s, args...))
}

// LogStartSeparator logs a separator to stderr with the given message.
//
//nolint:thelper // we do call t.Helper() if t is not nil
func LogStartSeparator(t *testing.T, args ...any) {
if t != nil {
t.Helper()
}

LogStartSeparatorf(t, fmt.Sprint(args...))
}

// LogEndSeparatorf logs a separator to stderr with the given formatted message.
//
//nolint:thelper // we do call t.Helper() if t is not nil
func LogEndSeparatorf(t *testing.T, s string, args ...any) {
if t != nil {
t.Helper()
}
w := testWriterOrStderr(t)

fmt.Fprintf(w, separatorf(s, args...)+"\n")
}

// LogEndSeparator logs a separator to stderr with the given message.
//
//nolint:thelper // we do call t.Helper() if t is not nil
func LogEndSeparator(t *testing.T, args ...any) {
if t != nil {
t.Helper()
}

LogEndSeparatorf(t, fmt.Sprint(args...))
}

// separatorf returns a formatted separator string for logging purposes.
func separatorf(s string, args ...any) string {
return highCyan("===== " + fmt.Sprintf(s, args...) + " =====\n")
}

// separator returns a separator string for logging purposes.
func separator(args ...any) string {
return separatorf(fmt.Sprint(args...))
}

func redSeparatorf(s string, args ...any) string {
return highRed("===== " + fmt.Sprintf(s, args...) + " =====\n")
}

// highCyan returns a string with the given text in high-intensity cyan color for terminal output.
func highCyan(s string) string {
return fmt.Sprintf("\033[1;36m%s\033[0m", s)
}

// highRed returns a string with the given text in high-intensity red color for terminal output.
func highRed(s string) string {
return fmt.Sprintf("\033[1;31m%s\033[0m", s)
}
24 changes: 24 additions & 0 deletions internal/testlog/testlog_go125.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build go1.25

package testlog

import (
"io"
"os"
"testing"
)

// NewTestWriter creates a new TestWriter that logs to t.
//
//nolint:thelper // we're not using t in any way that requires the helper annotation
func NewTestWriter(t *testing.T) io.Writer {
return t.Output()
}

//nolint:thelper // we're not using t in any way that requires the helper annotation
func testWriterOrStderr(t *testing.T) io.Writer {
if t != nil {
return t.Output()
}
return os.Stderr
}
64 changes: 64 additions & 0 deletions internal/testlog/testlog_pre125.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//go:build !go1.25

package testlog

import (
"bytes"
"errors"
"io"
"os"
"strings"
"sync"
"testing"
)

// TestWriter is an io.Writer that sends its output to a testing.T via t.Log,
// ensuring logs are attributed to the correct test.
// It buffers partial writes until a newline is seen.
type TestWriter struct {
t *testing.T
mu sync.Mutex
buf bytes.Buffer
}

// NewTestWriter creates a new TestWriter that logs to t.
//
//nolint:thelper // we're not using t in any way that requires the helper annotation'
func NewTestWriter(t *testing.T) *TestWriter {
return &TestWriter{t: t}
}

// Write implements io.Writer. It buffers until a newline is seen,
// then flushes complete lines to t.Log. Remainders are held until
// the next Write call.
func (w *TestWriter) Write(p []byte) (n int, err error) {
w.t.Helper()

w.mu.Lock()
defer w.mu.Unlock()

n, err = w.buf.Write(p)
for {
line, errLine := w.buf.ReadString('\n')
if errors.Is(errLine, io.EOF) {
// leftover data without newline — keep it for next Write
w.buf.Reset()
w.buf.WriteString(line)
break
}
// Trim the newline, let t.Log add its own
w.t.Log(strings.TrimSuffix(line, "\n"))
}

return n, err
}

// testWriterOrStderr returns a TestWriter if t is non-nil, or os.Stderr otherwise.
//
//nolint:thelper // we're not using t in any way that requires the helper annotation'
func testWriterOrStderr(t *testing.T) io.Writer {
if t != nil {
return NewTestWriter(t)
}
return os.Stderr
}
6 changes: 6 additions & 0 deletions internal/testutils/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,9 @@ func SleepMultiplier() float64 {
func MultipliedSleepDuration(in time.Duration) time.Duration {
return time.Duration(math.Round(float64(in) * SleepMultiplier()))
}

// IsCI returns whether the test is running in CI environment.
var IsCI = sync.OnceValue(func() bool {
_, ok := os.LookupEnv("GITHUB_ACTIONS")
return ok
})
Loading
Loading