Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
97f1d94
tests: Fix error message
adombeck Sep 1, 2025
580cfd1
Revert "ssh-tests: Avoid building module twice when updating golden f…
adombeck Sep 9, 2025
edbaf3e
tests: Continuously print output of build commands
adombeck Aug 29, 2025
b9664fe
tests: Continuously print output of VHS
adombeck Aug 29, 2025
5cd8975
tests: Print duration of setup steps
adombeck Aug 25, 2025
adcf8e1
tests: Print duration of VHS tape execution
adombeck Aug 29, 2025
4e22136
tests: Improve log messages
adombeck Aug 25, 2025
0755ebc
tests: Make VHS use colors in its output
adombeck Aug 27, 2025
ea229d3
tests: Adapt log level of sshd logs
adombeck Aug 27, 2025
1a2b79f
tests: Support overriding the default VHS wait timeout
adombeck Aug 27, 2025
fee6fb1
tests: Use consistent log format in sshd_preloader
adombeck Aug 27, 2025
463ecd1
tests: Always daemonize sshd
adombeck Aug 27, 2025
bf51fe9
ssh tests: Improve formatting of tape output in error message
adombeck Aug 27, 2025
39248b4
Remove redundant call to slog.SetLogLoggerLevel
adombeck Aug 27, 2025
a8828bc
tests: Print output of authd continuously
adombeck Aug 27, 2025
0271cba
log: Use the SimpleHandler by default
adombeck Aug 27, 2025
f745e47
tests: Don't print full tape before executing
adombeck Aug 28, 2025
d3bdc1e
Use correct capitalization for sshd
adombeck Aug 28, 2025
3548d3f
daemon: Improve log messages when receiving SIGINT/SIGHUP
adombeck Aug 29, 2025
1266200
tests: Add colored log separators
adombeck Aug 28, 2025
23fadf4
tests: Refer to authd as "authd" instead of "daemon"
adombeck Aug 29, 2025
1191559
tests: Run build commands in minimal environment
adombeck Aug 29, 2025
b2bde29
nss: Improve log message
adombeck Aug 29, 2025
4f1fe8b
tests: Fix AUTHD_TESTS_ARTIFACTS_ALWAYS_SAVE
adombeck Aug 29, 2025
10bd986
refactor: Rename saveArtifactsForDebugOnCleanup -> maybeSaveFilesAsAr…
adombeck Aug 29, 2025
5f9f83a
refactor: Use variadic argument
adombeck Aug 29, 2025
fbeb0c4
refactor: Rename authdArtifactsDirSync -> authdArtifactsDirOnce
adombeck Aug 29, 2025
62856de
refactor: Rename artifactsPath -> artifactsDir
adombeck Aug 29, 2025
f17f1f5
refator: Extract createArtifactsDir
adombeck Aug 29, 2025
1c29e3a
refactor: Extract maybeSaveBytesAsArtifactOnCleanup
adombeck Aug 29, 2025
615b4f2
refactor: Extract maybeSaveBufferAsArtifactOnCleanup
adombeck Aug 29, 2025
1f9663c
refactor: Rename ExpectedOutput -> SanitizedOutput
adombeck Aug 29, 2025
8c52299
VHS: Save sanitized output even if the tape execution failed
adombeck Aug 29, 2025
bb532dd
tests: Fix artifacts path
adombeck Aug 29, 2025
1584aa3
refactor: Use fileutils.CopyFile
adombeck Aug 29, 2025
7fcb1c3
tests: Avoid VHS suggesting to upload GIF
adombeck Aug 29, 2025
52da12d
refactor: Extract field tapeData.OutputDir
adombeck Aug 29, 2025
114adcf
refactor: Replace tapeData.Outputs with tapeData.OutputFilename
adombeck Aug 29, 2025
2b53cb1
refactor: Move the artifact-saving code out of PrepareTape
adombeck Aug 29, 2025
2743fa1
tests: Run sshd in foreground and without log file
adombeck Aug 29, 2025
810b755
tests: Improve log messages
adombeck Aug 29, 2025
cb9e9a4
tests: Make the name of the pam service clearer
adombeck Aug 29, 2025
e30eff7
tests: Store sshd output with log level debug3 as test artifact
adombeck Aug 29, 2025
5377b68
ssh tests: Only build test dependencies once when needed
adombeck Aug 29, 2025
78e9b01
tests: Fix log message
adombeck Aug 29, 2025
e5c2cfa
tests: Log output via t.Log
adombeck Aug 30, 2025
2a0b757
tests: Make use of the new Output method of testing.T
adombeck Aug 31, 2025
bcf14a8
tests: Run gcov with logging
adombeck Sep 1, 2025
d0ef0ca
tests: Add log messages when locking/unlocking rust build dir
adombeck Sep 1, 2025
0ad3aa5
tests: Support printing stdout/stderr on error in RunWithTiming
adombeck Sep 1, 2025
34b62b7
refactor: Extract testlog package
adombeck Sep 1, 2025
37eecfa
refactor: Rename buildCModule -> buildSharedLibrary
adombeck Sep 1, 2025
a1f2083
tests: Avoid data race when reading/writing sshd output buffer
adombeck Sep 1, 2025
8af4041
refactor: Rename some variables for better clarity
adombeck Sep 2, 2025
6660c92
tests: Bump time waiting for server to quit
adombeck Sep 2, 2025
5a1a621
refactor: Move artifacts-related functions to testutils
adombeck Sep 2, 2025
735708b
tests: Avoid data race when writing authd output
adombeck Sep 2, 2025
928e8ae
tests: Handle "connection reset by peer" when waiting for sshd to start
adombeck Sep 2, 2025
43f9abd
Use correct capitalization for VHS
adombeck Sep 3, 2025
60cbe34
testlog: Drop support for Go versions < 1.25
adombeck Dec 2, 2025
7a4cc60
Address linter issues
adombeck Dec 2, 2025
40e0e6b
Rename default PAM runner service name
adombeck Dec 3, 2025
1b25901
refactor: Rename buildSharedLibrary -> buildSharedModule
adombeck Dec 3, 2025
b8751f5
Avoid printing multi-line string in error message
adombeck Dec 3, 2025
7e0e830
refactor: Extract alwaysSaveArtifactsEnvVar
adombeck Dec 3, 2025
2de3445
testlog: Avoid writing output to stderr if -v is not set
adombeck Dec 3, 2025
00d74cf
daemon_test: Use testutils.MultipliedSleepDuration
adombeck Dec 3, 2025
5b57393
testutils: Append "/snap/bin" to MinimalPathEnv
adombeck Dec 3, 2025
0dc52e1
testutils: Simplify SyncBuffer
adombeck Dec 3, 2025
96f381b
testlog: Fix data race when writing to t.Output()
adombeck Dec 3, 2025
a3a9174
ssh_test: Fix data races when setting global variables
adombeck Dec 3, 2025
aca43ef
tests: Fix error message
adombeck Dec 3, 2025
f66a4a3
tests: Use "Teardown:" prefix in log messages more consistently
adombeck Dec 3, 2025
b2582a0
tests: Improve error message
adombeck Dec 3, 2025
6eb5068
tests: Avoid data race in RunVHS
adombeck Dec 3, 2025
478905d
tests: Avoid data race with t.Output and shared authd instances
adombeck Dec 4, 2025
40fa113
tests: Print all PAM messages of sshd to stderr in CI
adombeck Dec 4, 2025
44d38b9
refactor: Extract testutils.IsDebianPackageBuild
adombeck Dec 4, 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
1 change: 1 addition & 0 deletions .github/workflows/qa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ concurrency:
env:
DEBIAN_FRONTEND: noninteractive
GO_TESTS_TIMEOUT: 20m
AUTHD_SSHD_STDERR_LOG_ALL_PAM_MESSAGES: true
c_build_dependencies: >-
clang-tools
clang
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,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
4 changes: 2 additions & 2 deletions cmd/authctl/user/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestUserCommand(t *testing.T) {
func TestUserLockCommand(t *testing.T) {
t.Parallel()

daemonSocket := testutils.StartDaemon(t, daemonPath,
daemonSocket := testutils.StartAuthd(t, daemonPath,
testutils.WithGroupFile(filepath.Join("testdata", "empty.group")),
testutils.WithPreviousDBState("one_user_and_group"),
testutils.WithCurrentUserAsRoot,
Expand Down Expand Up @@ -112,7 +112,7 @@ func TestMain(m *testing.M) {
defer authctlCleanup()

var daemonCleanup func()
daemonPath, daemonCleanup, err = testutils.BuildDaemonWithExampleBroker()
daemonPath, daemonCleanup, err = testutils.BuildAuthdWithExampleBroker()
if err != nil {
fmt.Fprintf(os.Stderr, "Setup: %v\n", err)
os.Exit(1)
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")
}
31 changes: 21 additions & 10 deletions internal/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ubuntu/authd/internal/daemon/testdata/grpctestservice"
"github.com/ubuntu/authd/internal/grpcutils"
"github.com/ubuntu/authd/internal/services/errmessages"
"github.com/ubuntu/authd/internal/testutils"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/health"
Expand Down Expand Up @@ -248,7 +249,7 @@ func TestQuit(t *testing.T) {
}()

// make sure Serve() is called. Even std golang grpc has this timeout in tests
time.Sleep(100 * time.Millisecond)
time.Sleep(testutils.MultipliedSleepDuration(100 * time.Millisecond))

var disconnectClient func()
if tc.activeConnection {
Expand All @@ -257,40 +258,50 @@ 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)
}()

time.Sleep(100 * time.Millisecond)
time.Sleep(testutils.MultipliedSleepDuration(100 * time.Millisecond))

// Any new connection is disallowed
connected, _ := createClientConnection(t, socketPath)
require.False(t, connected, "new connection should be disallowed")

serverHasQuit := func() bool {
select {
case _, running := <-quiteDone:
return !running
case _, ok := <-shutdownRequested:
return !ok
default:
return false
}
}

if !tc.activeConnection || tc.force {
require.Eventually(t, serverHasQuit, 100*time.Millisecond, 10*time.Millisecond, "Server should quit with no active connection or force")
require.Eventually(t,
serverHasQuit,
testutils.MultipliedSleepDuration(100*time.Millisecond),
testutils.MultipliedSleepDuration(10*time.Millisecond),
"Server should quit with no active connection or force",
)
return
}

time.Sleep(100 * time.Millisecond)
time.Sleep(testutils.MultipliedSleepDuration(100 * time.Millisecond))
require.False(t, serverHasQuit(), "Server should still be running because of active connection and not forced")

// 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,
testutils.MultipliedSleepDuration(200*time.Millisecond),
testutils.MultipliedSleepDuration(10*time.Millisecond),
"Server should quit with no more active connection",
)
})
}
}
Expand Down
217 changes: 217 additions & 0 deletions internal/testlog/testlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Package testlog provides utilities for logging test output.
package testlog

import (
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"testing"
"time"
)

var (
isVerboseOnce sync.Once
isVerbose bool
)

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 := testOutput(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.Fprintln(w, redSeparatorf("%s failed in %.3fs with %v", msg, duration.Seconds(), err)+"\n")
} else {
fmt.Fprintln(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 := testOutput(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 := testOutput(t)

fmt.Fprintln(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()
}
w := testOutput(t)

fmt.Fprintln(w, "\n"+separator(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 := testOutput(t)

fmt.Fprintln(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()
}
w := testOutput(t)

fmt.Fprintln(w, separator(args...)+"\n")
}

// 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 highCyan("===== " + fmt.Sprint(args...) + " =====\n")
}

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)
}

// testOutput returns the appropriate output writer for the test.
//
//nolint:thelper // we're not using t in any way that requires the helper annotation
func testOutput(t *testing.T) io.Writer {
if t != nil {
return &syncWriter{w: t.Output()}
}
if verbose() {
return os.Stderr
}
return io.Discard
}

// verbose returns whether verbose mode is enabled.
// testing.Verbose() should be used instead when possible, this function is only
// needed because testing.Verbose() panics when called in a TestMain function.
func verbose() bool {
isVerboseOnce.Do(func() {
for _, arg := range os.Args {
value, ok := strings.CutPrefix(arg, "-test.v=")
if !ok {
continue
}
isVerbose = value == "true"
}
})
return isVerbose
}

// syncWriter is a writer that synchronizes writes to its underlying writer.
type syncWriter struct {
w io.Writer
mu sync.Mutex
}

// Write writes to the underlying writer while synchronizing access.
func (s *syncWriter) Write(p []byte) (n int, err error) {
s.mu.Lock()
defer s.mu.Unlock()

return s.w.Write(p)
}
12 changes: 12 additions & 0 deletions internal/testutils/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,15 @@ 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
})

// IsDebianPackageBuild returns true if the tests are running in a Debian package build environment.
var IsDebianPackageBuild = sync.OnceValue(func() bool {
_, ok := os.LookupEnv("DEB_BUILD_ARCH")
return ok
})
Loading
Loading