Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 0 additions & 33 deletions example.nix

This file was deleted.

85 changes: 38 additions & 47 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
# nix-shell --argstr buildpath .
buildpath ? "",

# The unikernel to build
unikernel ? "./example",

# vmrunner path, for vmrunner development
vmrunner ? "",

Expand All @@ -16,7 +13,6 @@
smp ? false,

includeos ? import ./default.nix { inherit withCcache; inherit smp; }

}:

includeos.pkgs.mkShell.override { inherit (includeos) stdenv; } rec {
Expand Down Expand Up @@ -48,49 +44,44 @@ includeos.pkgs.mkShell.override { inherit (includeos) stdenv; } rec {
];

shellHook = ''
cat <<-EOF
================================== IncludeOS nix-shell ==================================
Packages:
IncludeOS: ${includeos}
vmrunner: ${vmrunnerPkg}
chainloader: ${includeos.chainloader}

unikernel=$(realpath ${unikernel})
echo -e "Attempting to build unikernel: \n$unikernel"
if [ ! -d "$unikernel" ]; then
echo "$unikernel is not a valid directory"
exit 1
fi
export BUILDPATH=${buildpath}
if [ -z "${buildpath}" ]; then
export BUILDPATH="$(mktemp -d)"
pushd "$BUILDPATH"
else
mkdir -p "$BUILDPATH"
pushd "$BUILDPATH"
fi
cmake "$unikernel" -DARCH=x86_64 -DINCLUDEOS_PACKAGE=${includeos} -DCMAKE_MODULE_PATH=${includeos}/cmake \
-DFOR_PRODUCTION=OFF
make -j $NIX_BUILD_CORES
echo -e "\n====================== IncludeOS nix-shell ====================="
if [ -z "${buildpath}" ]; then
echo -e "\nWorking directory, generated by this script:"
echo $BUILDPATH
echo -e "\nTo use another directory pass in 'buildpath' to nix:"
echo "nix-shell --argstr buildpath you/build/path"
fi
echo -e "\nThe C++ compiler set to:"
echo $(which $CXX)
echo -e "\nIncludeOS package:"
echo ${includeos}
echo -e "\n---------------------- Network privileges ---------------------"
echo "The vmrunner for IncludeOS tests requires bridged networking for full functionality."
echo "The following commands requiring sudo privileges can be used to set this up:"
echo "1. the qemu-bridge-helper needs sudo to create a bridge. Can be enabled with:"
echo " sudo chmod u+s ${includeos.pkgs.qemu}/libexec/qemu-bridge-helper"
echo "2. bridge43 must exist. Can be set up with vmrunner's create_bridge.sh script:"
echo " ${vmrunnerPkg.create_bridge}"
echo "3. /etc/qemu/bridge.conf must contain this line:"
echo " allow bridge43"
echo ""
echo "Some tests require ping, which requires premissions to send raw packets. On some hosts"
echo "this is not enabled by default for iputils provided by nix. It can be enabled with:"
echo "4. sudo setcap cap_net_raw+ep ${includeos.pkgs.iputils}/bin/ping"
echo " "
echo
Tooling:
CXX $(command -v $CXX)
cmake: $(command -v cmake)
nasm: $(command -v nasm)
qemu-system-x86: $(command -v qemu-system-x86_64)
grub-mkrescue: $(command -v grub-mkrescue)
xorriso: $(command -v xorriso)
ping: $(command -v ping)

---------------------------------- Network privileges ----------------------------------
The vmrunner for IncludeOS tests requires bridged networking for full functionality.
The following checklist can be used to set this up from the host:

1. The qemu-bridge-helper needs root escalation to manipulate bridges. You can provide this
either through capabilities or through root execution. Pick one:
sudo chmod u+s ${includeos.pkgs.qemu}/libexec/qemu-bridge-helper
sudo setcap cap_net_admin+ep ${includeos.pkgs.qemu}/libexec/qemu-bridge-helper

2. bridge43 must exist. Can be set up with vmrunner's create_bridge.sh script (not as root):
${vmrunnerPkg.create_bridge}

3. /etc/qemu/bridge.conf must contain this line:
allow bridge43
Also note that /etc/qemu needs specific permissions, so it might be easiest to install
qemu on the host to generate these directories for you, despite not using its executable here.

4. Some tests also perform ICMP pings, which requires permissions to send raw packets. On some
hosts this is not enabled by default for iputils provided by nix.
It can be enabled with:
sudo setcap cap_net_raw+ep ${includeos.pkgs.iputils}/bin/ping

EOF
'';
}
47 changes: 37 additions & 10 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,23 @@ build_chainloader(){
}

build_example(){
nix-build $CCACHE_FLAG example.nix
nix-build $CCACHE_FLAG unikernel.nix
}

multicore_subset(){
nix-shell --pure --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --run ./test.py
nix-build ./unikernel.nix --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --arg doCheck true

# The following tests are not using multiple CPU's, but have been equippedd with some anyway
# to make sure core functionality is not broken by missing locks etc. when waking up more cores.
nix-shell --pure --arg smp true $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --run ./test.py
nix-shell --pure --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --run ./test.py
nix-shell ./unikernel.nix --arg smp true $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --arg doCheck true
nix-build ./unikernel.nix --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --arg doCheck true
}

smoke_tests(){
nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --run ./test.py
nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/net/integration/tcp --run ./test.py
nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --run ./test.py
nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --run ./test.py
nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --arg doCheck true
nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/net/integration/tcp --arg doCheck true
nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --arg doCheck true
nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --arg doCheck true
}

run unittests "Build and run unit tests"
Expand Down Expand Up @@ -137,21 +137,31 @@ run_testsuite() {

for subfolder in "$base_folder"/*/; do
local skip=false
local sandboxed=true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a cli option for enabling (or disabling) the tests that require the sandbox to be disabled? It should be possible to run tests on systems without the bridge available (even if the net tests are skipped).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also wondering if "sandbox" is the right name here. While it is true that these tests run without a sandbox, the common feature is that they all require access to a network bridge, so being able to run a command that specifically enables them, e.g. "./test.sh --enable-network-bridge-tests", would make more sense to me. But as this has already passed one review I'm happy to leave this as it is for now - we can iterate on this later if needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the naming: I was also thinking of calling it requires_shell or something of that nature, but it does require accessing non-/nix/store paths such as those in /dev/ and /etc/qemu.

I was considering taking another look at test.sh again in the future anyway, so we can definitely find a better nomenclature for it by then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding enabling/disabling the sandbox, I'm not sure I see the value. Introducing conditionals could lead to giving false positives, especially if it sets the precedent for more edge cases. Forcing them to stay enabled pushes people to actually set this up properly. See also includeos/vmrunner#45.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's useful to be able to skip the network tests that require bridge-access in environments where you'd still like to run the regular tests (like CI). Ideally we should find a way to run them without special permissions, but we're not quite there yet. If they just fail I'm ok with that for now - but we may have to make this more flexible for CI later

Copy link
Contributor Author

@mazunki mazunki Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Regarding building specific tests, there is also the drafted #2314 which I will probably redo from scratch later. Might add a flag like --exclude= when I work on that.


for exclude in "${exclusion_list[@]}"; do
if [[ "$subfolder" == *"$exclude"* ]]; then
skip=true
break
fi
done

if [ "$skip" = true ]; then
continue
fi

for unsandbox in "${unsandbox_list[@]}"; do
if [[ "$subfolder" == *"$unsandbox"* ]]; then
sandboxed=false
break
fi
done

# The command to run, as string to be able to print the fully expanded command
cmd="nix-shell --pure $CCACHE_FLAG --argstr unikernel $subfolder --run ./test.py"
if $sandboxed; then
cmd="nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ${subfolder%/} --arg doCheck true"
else
cmd="nix-shell ./unikernel.nix $CCACHE_FLAG --argstr unikernel ${subfolder%/} --arg doCheck true"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer --pure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might look like that, but I'm reasonably sure this actually still is pure. Let me explain.

nix-build is inherently pure, and nix-shell calls nix-build internally so the purity of the unikernel itself remains. Following, we also set packages to include python with a higher precedence than the host. Unless the test.py introduces something odd, this should be perfectly fine.

I initially tried nix-shell --pure, but this would drop nix-build from the PATH, which I didn't find an elegant solution for. I'd be happy to make it --pure if we have a solution for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, note that --arg doCheck true when added to nix-shell doesn't actually run the checkPhase, but it does expose its required *checkInputs to the shell.

fi

echo ""
echo "🚧 Step $steps.$substeps"
Expand Down Expand Up @@ -194,7 +204,11 @@ exclusions=(
"modules" # Requires 32-bit build, which our shell.nix is not set up for
)

unsandbox_list=(
"term" # fails to create the tun device, like the net integration tests
)
run_testsuite "./test/kernel/integration" "${exclusions[@]}"
unsandbox_list=()

#
# C++ STL runtime tests
Expand Down Expand Up @@ -223,7 +237,20 @@ exclusions=(
"websocket" # Linking fails, undefined ref to http_parser_parse_url, http_parser_execute
)

# all the following fail with the following error:
# <vm> failed to create tun device: Operation not permitted
# <vm> qemu-system-x86_64: -netdev bridge,id=net0,br=bridge43: bridge helper failed
unsandbox_list=(
"./test/net/integration/configure"
"./test/net/integration/icmp"
"./test/net/integration/icmp6"
"./test/net/integration/slaac"
"./test/net/integration/tcp"
"./test/net/integration/udp"
"./test/net/integration/dns" # except this one which times out instead
)
run_testsuite "./test/net/integration" "${exclusions[@]}"
unsandbox_list=()

echo -e "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

Expand Down
125 changes: 125 additions & 0 deletions unikernel.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{
# The unikernel to build
unikernel ? ./example,

# The test file to run
test ? "test.py",

# Boot unikernel after building it
doCheck ? false,

# Which architecture to build against
arch ? "x86_64",

# Enable multicore suport.
smp ? false,

# Enable ccache support. See overlay.nix for details.
withCcache ? false,

# Enable stricter requirements
forProduction ? false,

# The includeos library to build and link against
includeos ? import ./default.nix { inherit withCcache; inherit smp; },

# vmrunner path, for vmrunner development
vmrunner ? "",
}:
let
absolutePathOf = base: p:
if p == null then null else
if builtins.isPath p then p
else builtins.toPath (base + "/${p}");

unikernelPath = absolutePathOf ./. unikernel;
vmrunnerPkg =
if vmrunner == "" then
includeos.vmrunner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just be the default above or isn't ${includeos} available yet in the header? If it's not possible to set the default earlier it would be useful with a comment above the "vmrunner ? .." declaration that it will be set to default if left empty.

Copy link
Contributor Author

@mazunki mazunki Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, and personally I agree and prefer what you're suggesting, but did it this way anyway for convenience.

The downside of that is that people need to write out --arg vmrunner '(includeos.pkgs.callPackage "/path/to/vmrunner" {})' instead of just --argstr vmrunner ./path/to/vmrunner, which might be confusing for people that don't know about our overlay.

Up to you, I prefer the explicit-by-the-user approach.

else
includeos.pkgs.callPackage (builtins.toPath /. + vmrunner) {};
in
includeos.stdenv.mkDerivation rec {
pname = "includeos_example";
version = "dev";
src = includeos.pkgs.lib.cleanSource unikernelPath;
dontStrip = true;
inherit doCheck;

nativeBuildInputs = [
includeos.pkgs.buildPackages.nasm
includeos.pkgs.buildPackages.cmake
];

buildInputs = [
includeos
includeos.chainloader
];

cmakeFlags = [
"-DARCH=${arch}"
"-DINCLUDEOS_PACKAGE=${includeos}"
"-DCMAKE_MODULE_PATH=${includeos}/cmake"
"-DFOR_PRODUCTION=${if forProduction then "ON" else "OFF"}"
];

installPhase = ''
runHook preInstall
# we want to place any files required by the test into the output
find -mindepth 1 -maxdepth 1 -type f -exec install -v -D -t "$out/" {} \;

# especially the unikernel image, in case it wasn't at the rootdir already
find -mindepth 2 -name '*.elf.bin' -exec install -v -t "$out/" {} \;
runHook postInstall
'';


nativeCheckInputs = [
includeos.vmrunner
includeos.pkgs.grub2
includeos.pkgs.python3
includeos.pkgs.qemu
includeos.pkgs.iputils
includeos.pkgs.xorriso
];

checkInputs = [
includeos.lest
];

# use `nix-build --arg doCheck true` to run tests normally
checkPhase = ''
runHook preCheck
set -e
if [ -e "${src}/${test}" ]; then
echo "Running IncludeOS test: ${src}/${test}"
python3 "${src}/${test}"
else
echo "Default test script '${test}', but no test was found 😟"
echo "For a custom path, consider specifying the path to the test script:"
echo " --argstr test 'path/to/test.py'"
exit 1
fi
runHook postCheck
'';

# this is a hack
# some tests need to be run through a shell because of net_cap_raw+ep and net_cap_admin+ep
# replace nix-build with nix-shell to test without dropping capabilities
packages = [
(includeos.pkgs.python3.withPackages (p: [
vmrunnerPkg
]))
];
shellHook = ''
set -eu
pkg="$(nix-build ./unikernel.nix --arg doCheck false --arg unikernel ${unikernel})"

testPath="$(realpath "${unikernel}/${test}")"
cd "$pkg"
"$testPath" || exit 1

exit 0
'';

}