spacetest

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

spacetest

PkgGoDev GitHub build and test goroutines file descriptors Go Report Card Coverage

A small package to help with creating transient Linux namespaces in unit testing, without having to deal with the tedious details of proper and robust setup and teardown – and without destroying your host filesystem. It even allows Go tests to create child user and PID namespaces (with the help of forking child processes into new user and PID namespaces).

spacetest leverages the Ginkgo testing framework with Gomega matchers.

The origins of this module lie in thediveo/notwork: in order to reuse the namespace-specific elements without the need to pull in dependencies related solely to "notwork" testing, spacetest was born. And in the tradition of short and preably misleading idiomatic Go package names, the name spacetest was chosen. Because "namespacetest" would have been much too precise. We promise that our module won't explode in your face.

Example

Creating and entering a transient network namespace for testing purposes become as easy and concise as:

import (
    "github.com/thediveo/spacetest/netns"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
)

var _ = Describe("...", func() {

    It("tests something inside a transient network namespace", func() {
        defer netns.EnterTransient()() // !!! double ()()
        // ...
    })

})

Trust us, you don't want to repeat over and over again as well as seeing all the time the individual step sequence and many sanity checks inside EnterTransient(). There's a limit to inflicting full code complexity on developers, and good reason for appropriate abstractions.

Using the Spacer

Using what we call the "spacer" gives you access to creating new child (and grandchild) user and PID namespaces, which isn't directly possible from any multi-threaded Go application. The spacer leverages exec.Command.Start where the kernel allows us to start the child process in new namespaces.

But first, if you use spacetest in a devcontainer, make sure that the Go toolchain is accessible to the devcontainer's root user (it isn't necessarily, depending on your base image and further features):

{
    // ...
    "postCreateCommand": "echo 'Defaults:vscode secure_path = \"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin\"' | sudo tee -a /etc/sudoers.d/vscode",
    // ...
}

Then to create child user and PID child namespaces below your test's user and PID namespaces:

import (
    "context"

    "github.com/thediveo/spacetest/spacer"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
)

var _ = Describe("...", func() {

    It("tests something inside transient user and PID namespaces", func(ctx context.Context) {
        spcclnt := spacer.New(ctx)
		DeferCleanup(func() {
			spcclnt.Close()
		})

		subclnt, subspc := spcclnt.Subspace(true, true)
		DeferCleanup(func() {
            // Note, you don't need to close the returned namespace fd(s), as these will be
            // automatically closed using their own DeferCleanup scheduled by Subspace().
			subclnt.Close()
		})
        // ...
    })

})

You can use the returned sub client to create more user and PID namespaces further down the hierarchy.

DevContainer

[!CAUTION]

Do not use VSCode's "Dev Containers: Clone Repository in Container Volume" command, as it is utterly broken by design, ignoring .devcontainer/devcontainer.json.

  1. git clone https://github.com/thediveo/spacetest
  2. in VSCode: Ctrl+Shift+P, "Dev Containers: Open Workspace in Container..."
  3. select spacetest.code-workspace and off you go...

Supported Go Versions

spacetest supports versions of Go that are noted by the Go release policy, that is, major versions N and N-1 (where N is the current major version).

Contributing

Please see CONTRIBUTING.md.

Copyright 2023–25 Harald Albrecht, licensed under the Apache License, Version 2.0.

Documentation

Overview

Package spacetest supports tests working with Linux kernel namespaces.

This package leverages the Ginkgo testing framework with Gomega matchers.

The top-level “spacetest” package is mostly agnostic to the particular type of namespace handled, but different test helper functions come with different restrictions due to restrictions imposed by the Linux kernel. Please carefully check the documentation for the individual helper functions.

Network Namespaces

The spacetest/netns package is basically just a convenience wrapper around the generic test helper functions from the base spacetest package. Yet, this package helps DRY, especially avoiding litanies of unix.CLONE_NEWNET.

Mount Namespaces

In some situations, mount namespaces need special treatment which the dedicated spacetest/mntns package takes care of: in particular, the test helper functions in this package ensure to remount the “/” in the new mount namespace with private mount point propagation to avoid mishaps (trust us, we /know/).

Other than that, this package helps again with DRY, such as unix.CLONE_NEWNS litanies.

PID and User Namespaces

Please note that user and PID namespaces are notoriously difficult to work with, especially in multi-threaded Go tests. Thus, the spacetest package has somewhat limited support for dealing with user namespaces. Please refer to the github.com/thediveo/spacetest/spacer package for details.

Background

The origins of this module lie in thediveo/notwork: in order to reuse the namespace-specific elements without the need to pull in dependencies related solely to “notwork” testing, “spacetest” was born. And in the tradition of short and preably misleading idiomatic Go package names, the name “spacetest” was chosen. Because “namespacetest” would have been much too precise. We promise that our module won't explode in your face.

Index

Constants

This section is empty.

Variables

View Source
var NS_GET_NSTYPE = ioctl.IO(_NSIO, 0x3) //nolint:godoclint // out of touch

NS_GET_NSTYPE defines the unix.IoctlRetInt request code that returns the type of namespace CLONE_NEW* value referred to by a file descriptor.

Functions

func Call added in v0.10.0

func Call[R any](fn func() R, nsfd int, nsfds ...int) R

Call the passed fn synchronously, returning fn's single result value, while attached to the specified namespace(s) and otherwise defaulting to the caller's currently attached namespaces. See Execute for details and semantics about the other parameters as well as the behaviour especially regarding mount namespaces.

func Current

func Current(typ int) int

Current returns a file descriptor referencing the calling OS-level thread's current namespace of type “typ”. Please note that the caller's go routine should be thread-locked. “typ” should be any of unix.CLONE_NEWNS (for mount namespaces), unix.CLONE_NETNS (network), et cetera.

Additionally, Current schedules a DeferCleanup of the returned file descriptor to be closed at the end of the current test in order to avoid leaking it.

If the specified typ of namespace is unknown, Current fails the current test.

func CurrentIno

func CurrentIno(typ int) uint64

CurrentIno returns the identification (inode number) for the namespace (of the specified type) the OS-level thread is currently attached to.

func EnterTransient

func EnterTransient(typ int) func()

EnterTransient creates and enters a new (and isolated) Linux kernel namespace of the specified type, returning a function that needs to be defer'ed in order to correctly switch the calling go routine and its locked OS-level thread back when the caller itself wants to leave (returns). For instance:

defer spacetest.EnterTransient(unix.CLONE_NEWNET)()

EnterTransient locks the caller's go routine to its OS-level thread and unlocks it when the deferred clean up function finally gets called.

In case the caller cannot be switched back correctly, the defer'ed cleanup function will panic with an error description detailing the reason.

EnterTransient can be used for the following types of namespaces:

  • unix.CLONE_NEWCGROUP,
  • unix.CLONE_NEWIPC,
  • unix.CLONE_NEWNET,
  • unix.CLONE_NEWUTS.

For mount namespaces (unix.CLONE_NEWNS) you will need to use the mount namespace-specific github.com/thediveo/spacetest/mntns.EnterTransient instead.

In order to work with transient PID (unix.CLONE_NEWPID) namespaces use NewTransient and then Execute, as it is not possible to re-associate the current OS-level thread with the original (parent) PID namespace after creating and switching into a new child PID namespace; the returned cleanup function would fail and purposely trigger a panic.

Also, user namespaces cannot be entered with EnterTransient as the Linux kernel does not allow a thread to re-enter one of the original (that is, parent) user namespace(s). Use Execute instead to call a specific function synchronously from a transient go routine with its own transient OS-level thread.

To work with transient time (unix.CLONE_NEWTIME) namespaces use NewTransient and then Execute, as it is not possible to re-associate the current OS-level thread with the original (parent) PID or time namespace after creating and switching into a new child PID or time namespace; the returned cleanup function would fail and purposely trigger a panic.

func Execute

func Execute(fn func(), nsfd int, nsfds ...int)

Execute the passed fn synchronously while attached to the specified namespace(s) and otherwise defaulting to the caller's currently attached namespaces.

Execute will fail the current test when trying to switch to a different user namespace: switching the user namespace is not possible for multi-threaded processes, this is a design decision of the Linux kernel user namespace developers.

When a mount namespace is passed in, then fn will be executed on a separate throw-away go routine (and locked to a throw-away OS-level thread). Where the caller does not specify different namespace(s) the underlying thread will be attached to the caller's namespaces. This ensures the expected behavior when using Execute after especially EnterTransient.

When the list of namespaces to switch to does not contain a mount namespace then the passed fn will be called synchronously on the caller's go routine, while locked to the underlying OS-level thread.

func Ino

func Ino[R Reference](ref R, typ int) uint64

Ino returns the identification (inode number) of the passed Linux kernel namespace that is either referenced by a file descriptor or a VFS path name.

If the specified reference is invalid or doesn't match the passed type of namespace, Ino fails the current test.

func Name

func Name(typ int) string

Name returns the name for the type of Linux kernel namespace specified, or "???" if not known.

func NewTransient

func NewTransient(typ int) int

NewTransient creates a new Linux kernel namespace of the specified type, but doesn't enter it. Instead, it returns a file descriptor referencing the newly created namespace. NewTransient can be used for the following types of namespaces:

  • unix.CLONE_NEWCGROUP,
  • unix.CLONE_NEWIPC,
  • unix.CLONE_NEWNET,
  • unix.CLONE_NEWUTS.

For mount namespaces (unix.CLONE_NEWNS) you will need to use the mount namespace-specific github.com/thediveo/spacetest/mntns.NewTransient instead.

Additionally to creating a new namespace, NewTransient also schedules a Ginkgo deferred cleanup in order to close the fd referencing this new namespace. The caller thus must not close the file descriptor returned.

When NewTransient returns, the caller's Go routine is in the same OS-level thread lock/unlock state as before the call.

func Type

func Type[R Reference](ref R) int

Type returns the type constant for the Linux kernel namespace referenced either by a file descriptor or a VFS path name.

If the specified reference is invalid, Type fails the current test.

Types

type Reference

type Reference interface{ ~int | ~string }

Reference is a Linux kernel namespace reference in VFS path textual form or as an open file descriptor

Directories

Path Synopsis
Package mntns supports running unit tests in separated transient mount namespaces.
Package mntns supports running unit tests in separated transient mount namespaces.
Package netns supports running unit tests in separated transient network namespaces, handling cleanup and error checking automatically.
Package netns supports running unit tests in separated transient network namespaces, handling cleanup and error checking automatically.
Package spacer provides a client to create and communicate with so-called “spacer services” that on demand create new Linux kernel namespaces, especially sub user and PID namespaces.
Package spacer provides a client to create and communicate with so-called “spacer services” that on demand create new Linux kernel namespaces, especially sub user and PID namespaces.
api
Package api defines the specific protocol requests and responses used between clients and servers of the “spacer” namespace creation service.
Package api defines the specific protocol requests and responses used between clients and servers of the “spacer” namespace creation service.
gobmsg
Package gobmsg supports working with gob-encoded discrete messages (as opposed to streams).
Package gobmsg supports working with gob-encoded discrete messages (as opposed to streams).
service
Package service serves the spacer API.
Package service serves the spacer API.
service/cmd/spacer-service command
Package main provides the command for running a spacer service as a separate process.
Package main provides the command for running a spacer service as a separate process.
Package uds supports transferring open file descriptors across process boundaries using peer-to-peer pairs of (stream) unix domain sockets.
Package uds supports transferring open file descriptors across process boundaries using peer-to-peer pairs of (stream) unix domain sockets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL