diff --git a/.github/workflows/nix-flake.yml b/.github/workflows/nix-flake.yml index 4dfb6453..af9582ac 100644 --- a/.github/workflows/nix-flake.yml +++ b/.github/workflows/nix-flake.yml @@ -19,4 +19,3 @@ jobs: authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - run: nix flake show - run: nix flake check --print-build-logs - - run: nix build --print-build-logs diff --git a/README.md b/README.md index f95defa5..7dcb8a03 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,20 @@ Build and install Refer to [INSTALL.md](./INSTALL.md) +Testing +------- + +Besides manual testing, which is also encouraged, there are also a number of +checks defined that are used for CI (e.g. github actions). + +These checks can be listed using `nix flake show`. You can, for example, test +the current state of the repo or test every patch before publishing them. + +```console +$ nix flake check +$ git rev-list origin/master.. | xargs -I{} nix flake check "git+file://$(pwd)?rev={}" +``` + Bug reports and contributions ----------------------------- diff --git a/checks/README.md b/checks/README.md new file mode 100644 index 00000000..1deecea6 --- /dev/null +++ b/checks/README.md @@ -0,0 +1,72 @@ +# NixOS tests + +Any `*.sh` file in this directory will be run in a NixOS VM for basic +functionality testing as part of CI. To list all outputs, including the checks, +you can use this command: + +```console +$ nix flake show +``` + +You can also run these tests locally by running `nix flake check`. To run one +specific test you can use `nix build` like this: + +```console +$ nix build ".#checks.x86_64-linux.subvolume" +``` + +With the flag `-L`/`--print-build-logs` outputs are shown fully as checks are +executing. Additionally, if the specific check has already been run locally, you +can view the log for the check or force another run with the following: + +```console +$ nix log .#checks.x86_64-linux.subvolume +$ nix build --rebuild .#checks.x86_64-linux.subvolume +``` + +If you need any more packages inside of the VM for a test, you can add them to +`environment.systemPackages` in `default.nix`. If you're unsure about the +package you need, [NixOS package search] may be able to help. + +For more information about the NixOS testing library see the +[testing wiki article]. + +## Kernel version inside VM + +By default `linuxPackages_latest` from nixpkgs is used in the testing VM. This +is the latest stable kernel version available in the nixpkgs revision. Updating +the nixpkgs flake input may update the used kernel. A custom-built kernel can be +used as well but with added build times in CI. + +## Adding new tests + +The easiest way to add new tests is of course to copy an existing test and adapt +it accordingly. Importantly, for nix to see a file as part of the sources, the +file needs to be in the git index. It doesn't have to be committed to the repo +just yet but you need to `git add` it. If `git ls-files` lists the file, nix +will also see it. + +## Interactive debugging of tests + +When writing a new test or experiencing a difficult to understand test failure, +an interactive login can be very handy. This can be achieved by building the +`driverInteractive` attribute of the check, for example like this: + +```console +$ nix build .#checks.x86_64-linux.subvolume.driverInteractive +``` + +The `nix build` will create a symlink in your working directory called `result` +which leads to a script that launches the VM interactively: + +```console +$ ./result/bin/nixos-test-driver +``` + +There is more information about this in the NixOS manual under +[running tests interactively]. + +[Linux wiki article]: https://wiki.nixos.org/wiki/Linux_kernel +[NixOS package search]: https://search.nixos.org +[running tests interactively]: https://nixos.org/manual/nixos/stable/#sec-running-nixos-tests-interactively +[testing wiki article]: https://wiki.nixos.org/wiki/NixOS_Testing_library diff --git a/checks/default.nix b/checks/default.nix new file mode 100644 index 00000000..42581433 --- /dev/null +++ b/checks/default.nix @@ -0,0 +1,52 @@ +{ pkgs }: +let + inherit (builtins) baseNameOf readDir; + inherit (pkgs.lib) + filterAttrs + genAttrs + hasSuffix + mapAttrsToList + removeSuffix + ; + + scriptName = shFile: removeSuffix ".sh" (baseNameOf shFile); + + scriptNames = mapAttrsToList (n: v: scriptName n) ( + filterAttrs (n: v: v == "regular" && hasSuffix ".sh" n) (readDir ./.) + ); + + mkTest = + name: + pkgs.testers.runNixOSTest { + inherit name; + + nodes.machine = + { pkgs, ... }: + { + virtualisation.emptyDiskImages = [ + 4096 + 1024 + ]; + boot.supportedFilesystems = [ "bcachefs" ]; + boot.kernelPackages = pkgs.linuxPackages_latest; + + # Add any packages you need inside test scripts here + environment.systemPackages = with pkgs; [ + f3 + genpass + keyutils + ]; + + environment.variables = { + BCACHEFS_LOG = "trace"; + RUST_BACKTRACE = "full"; + }; + }; + + testScript = '' + machine.succeed("modprobe bcachefs") + machine.succeed("${./${name}.sh} 1>&2") + ''; + }; +in +genAttrs scriptNames mkTest diff --git a/checks/encrypted-multidev.sh b/checks/encrypted-multidev.sh new file mode 100755 index 00000000..3048d61e --- /dev/null +++ b/checks/encrypted-multidev.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euxo pipefail + +blkdev="/dev/vdb" +blkdev2="/dev/vdc" +mnt=$(mktemp -d) +pw=$(genpass) +uuid=$(uuidgen) + +# link user and session keyrings so that the key can be found by the kernel +keyctl link @u @s + +sfdisk "$blkdev" <<EOF +label: gpt +type=linux, size=1G +type=linux, size=1G +type=linux +EOF + +udevadm settle + +echo "$pw" | bcachefs format \ + --verbose \ + --encrypted \ + --replicas=2 \ + --uuid "$uuid" \ + --fs_label test-fs \ + "${blkdev}"{1,2} + +udevadm settle + +echo "$pw" | bcachefs mount "UUID=$uuid" "$mnt" + +bcachefs device add "$mnt" "${blkdev}3" +bcachefs device add "$mnt" "$blkdev2" + +udevadm settle + +blkid + +keyctl search @u user "bcachefs:$uuid" + +umount "$mnt" + +bcachefs mount "UUID=$uuid" "$mnt" diff --git a/checks/encrypted-unlock.sh b/checks/encrypted-unlock.sh new file mode 100755 index 00000000..23a345bb --- /dev/null +++ b/checks/encrypted-unlock.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euxo pipefail + +blkdev="/dev/vdb" +mnt=$(mktemp -d) +pw=$(genpass) +uuid=$(uuidgen) + +# link user and session keyrings so that the key can be found by the kernel +keyctl link @u @s + +echo "$pw" | bcachefs format \ + --verbose \ + --encrypted \ + --uuid "$uuid" \ + --fs_label test-fs \ + "$blkdev" + +udevadm settle + +bcachefs unlock -c "$blkdev" + +echo "$pw" | bcachefs unlock "$blkdev" +key_id=$(keyctl search @u user "bcachefs:$uuid") + +bcachefs mount "$blkdev" "$mnt" +umount "$mnt" + +keyctl unlink "$key_id" + +echo "$pw" | bcachefs unlock -k session "$blkdev" +key_id=$(keyctl search @s user "bcachefs:$uuid") + +mount -t bcachefs "$blkdev" "$mnt" +umount "$mnt" + +keyctl unlink "$key_id" + +bcachefs mount -f <(echo "$pw") "$blkdev" "$mnt" +key_id=$(keyctl search @u user "bcachefs:$uuid") +umount "$mnt" +keyctl unlink "$key_id" + +echo "$pw" | bcachefs mount -k stdin "$blkdev" "$mnt" +key_id=$(keyctl search @u user "bcachefs:$uuid") +umount "$mnt" +keyctl unlink "$key_id" + +echo "$pw" | bcachefs mount "$blkdev" "$mnt" +key_id=$(keyctl search @u user "bcachefs:$uuid") +umount "$mnt" +bcachefs mount -k fail "$blkdev" +bcachefs mount -k wait "$blkdev" "$mnt" +umount "$mnt" +keyctl unlink "$key_id" diff --git a/checks/nested.sh b/checks/nested.sh new file mode 100755 index 00000000..edb955c1 --- /dev/null +++ b/checks/nested.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euxo pipefail + +blkdev="/dev/vdb" +mnt1=$(mktemp -d) +mnt2=$(mktemp -d) +pw=$(genpass) +uuid=$(uuidgen) + +# link user and session keyrings so that the key can be found by the kernel +keyctl link @u @s + +echo "$pw" | bcachefs format \ + --verbose \ + --encrypted \ + --uuid "$uuid" \ + --fs_label test-fs \ + "$blkdev" + +udevadm settle + +echo "$pw" | bcachefs mount "UUID=$uuid" "$mnt1" + +fallocate --length 2G "$mnt1/fs.img" + +bcachefs format \ + --verbose \ + "$mnt1/fs.img" + +loopdev=$(losetup --find --show "$mnt1/fs.img") + +udevadm settle + +mount "$loopdev" "$mnt2" + +f3write "$mnt1" +f3write "$mnt2" + +f3read "$mnt1" +f3read "$mnt2" diff --git a/checks/outputs.sh b/checks/outputs.sh new file mode 100755 index 00000000..c4e55127 --- /dev/null +++ b/checks/outputs.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euxo pipefail + +blkdev="/dev/vdb" +mnt=$(mktemp -d) +uuid=$(uuidgen) + +bcachefs format \ + --verbose \ + --uuid "$uuid" \ + --fs_label test-fs \ + "$blkdev" + +udevadm settle + +mount "$blkdev" "$mnt" + +bcachefs show-super "$blkdev" | grep -i "external.*$uuid" +bcachefs fs usage "$mnt" | grep "$uuid" diff --git a/checks/subvolume.sh b/checks/subvolume.sh new file mode 100755 index 00000000..179554ef --- /dev/null +++ b/checks/subvolume.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euxo pipefail + +blkdev="/dev/vdb" +mnt=$(mktemp -d) +uuid=$(uuidgen) + +bcachefs format \ + --verbose \ + --uuid "$uuid" \ + --fs_label test-fs \ + "$blkdev" + +udevadm settle + +mount "$blkdev" "$mnt" + +touch "$mnt/file1" + +bcachefs subvolume create "$mnt/subvol1" +bcachefs subvolume delete "$mnt/subvol1" + +( + cd "$mnt" + + bcachefs subvolume create subvol1 + bcachefs subvolume create subvol1/subvol1 + bcachefs subvolume create subvol1/subvol2 + touch subvol1/file1 + + rm subvol1/file1 + bcachefs subvolume delete subvol1/subvol2 + bcachefs subvolume delete subvol1/subvol1 + bcachefs subvolume delete subvol1 +) diff --git a/flake.nix b/flake.nix index 4aa66a86..a3254ebd 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,7 @@ }: let inherit (builtins) readFile split; + inherit (lib) fileset; inherit (lib.lists) findFirst; inherit (lib.strings) hasPrefix removePrefix substring; @@ -75,7 +76,17 @@ commonArgs = { inherit version; - src = self; + src = fileset.toSource { + root = ./.; + + fileset = fileset.difference (fileset.gitTracked ./.) ( + fileset.unions [ + ./checks + ./doc + ./tests + ] + ); + }; env = { PKG_CONFIG_SYSTEMD_SYSTEMDSYSTEMUNITDIR = "${placeholder "out"}/lib/systemd/system"; @@ -150,31 +161,37 @@ } ); - checks.cargo-clippy = craneLib.cargoClippy ( - commonArgs - // { - inherit cargoArtifacts; - cargoClippyExtraArgs = "--all-targets -- --deny warnings"; - } - ); + checks = + let + overlay = final: prev: { inherit (config.packages) bcachefs-tools; }; - # we have to build our own `craneLib.cargoTest` - checks.cargo-test = craneLib.mkCargoDerivation ( - commonArgs - // { - inherit cargoArtifacts; - doCheck = true; + cargo-clippy = craneLib.cargoClippy ( + commonArgs + // { + inherit cargoArtifacts; + cargoClippyExtraArgs = "--all-targets -- --deny warnings"; + } + ); - enableParallelChecking = true; + # we have to build our own `craneLib.cargoTest` + cargo-test = craneLib.mkCargoDerivation ( + commonArgs + // { + inherit cargoArtifacts; + doCheck = true; - pnameSuffix = "-test"; - buildPhaseCargoCommand = ""; - checkPhaseCargoCommand = '' - make ''${enableParallelChecking:+-j''${NIX_BUILD_CORES}} $makeFlags libbcachefs.a - cargo test --profile release -- --nocapture - ''; - } - ); + enableParallelChecking = true; + + pnameSuffix = "-test"; + buildPhaseCargoCommand = ""; + checkPhaseCargoCommand = '' + make ''${enableParallelChecking:+-j''${NIX_BUILD_CORES}} $makeFlags libbcachefs.a + cargo test --profile release -- --nocapture + ''; + } + ); + in + (import ./checks { pkgs = pkgs.extend overlay; }) // { inherit cargo-clippy cargo-test; }; devShells.default = pkgs.mkShell { inputsFrom = [