flake: NixOS VM Test for running pytest with Redis; nix flake check

+ refactor getting python module from API package
+ readme: nixpkgs flake input update
This commit is contained in:
Alexander Tomokhov 2024-01-23 23:25:51 +04:00
parent 591138c353
commit 1ec6be59fd
4 changed files with 179 additions and 74 deletions

1
.gitignore vendored
View file

@ -150,3 +150,4 @@ cython_debug/
*.rdb *.rdb
/result /result
/.nixos-test-history

109
README.md
View file

@ -6,52 +6,16 @@
$ nix build $ nix build
``` ```
As a result, you should get the `./result` symlink to a folder (in `/nix/store`) with build contents. In case of successful build, you should get the `./result` symlink to a folder (in `/nix/store`) with build contents.
## develop & test ## develop
```console ```console
$ nix develop $ nix develop
$ [SP devshell] pytest . [SP devshell:/dir/selfprivacy-rest-api]$ python
=================================== test session starts ===================================== Python 3.10.13 (main, Aug 24 2023, 12:59:26) [GCC 12.3.0] on linux
platform linux -- Python 3.10.11, pytest-7.1.3, pluggy-1.0.0 Type "help", "copyright", "credits" or "license" for more information.
rootdir: /data/selfprivacy/selfprivacy-rest-api (ins)>>>
plugins: anyio-3.5.0, datadir-1.4.1, mock-3.8.2
collected 692 items
tests/test_block_device_utils.py ................. [ 2%]
tests/test_common.py ..... [ 3%]
tests/test_jobs.py ........ [ 4%]
tests/test_model_storage.py .. [ 4%]
tests/test_models.py .. [ 4%]
tests/test_network_utils.py ...... [ 5%]
tests/test_services.py ...... [ 6%]
tests/test_graphql/test_api.py . [ 6%]
tests/test_graphql/test_api_backup.py ............... [ 8%]
tests/test_graphql/test_api_devices.py ................. [ 11%]
tests/test_graphql/test_api_recovery.py ......... [ 12%]
tests/test_graphql/test_api_version.py .. [ 13%]
tests/test_graphql/test_backup.py ............................... [ 21%]
tests/test_graphql/test_localsecret.py ... [ 22%]
tests/test_graphql/test_ssh.py ............ [ 23%]
tests/test_graphql/test_system.py ............................. [ 28%]
tests/test_graphql/test_system_nixos_tasks.py ........ [ 29%]
tests/test_graphql/test_users.py .................................. [ 42%]
tests/test_graphql/test_repository/test_json_tokens_repository.py [ 44%]
tests/test_graphql/test_repository/test_tokens_repository.py .... [ 53%]
tests/test_rest_endpoints/test_auth.py .......................... [ 58%]
tests/test_rest_endpoints/test_system.py ........................ [ 63%]
tests/test_rest_endpoints/test_users.py ................................ [ 76%]
tests/test_rest_endpoints/services/test_bitwarden.py ............ [ 78%]
tests/test_rest_endpoints/services/test_gitea.py .............. [ 80%]
tests/test_rest_endpoints/services/test_mailserver.py ..... [ 81%]
tests/test_rest_endpoints/services/test_nextcloud.py ............ [ 83%]
tests/test_rest_endpoints/services/test_ocserv.py .............. [ 85%]
tests/test_rest_endpoints/services/test_pleroma.py .............. [ 87%]
tests/test_rest_endpoints/services/test_services.py .... [ 88%]
tests/test_rest_endpoints/services/test_ssh.py ..................... [100%]
============================== 692 passed in 352.76s (0:05:52) ===============================
``` ```
If you don't have experimental flakes enabled, you can use the following command: If you don't have experimental flakes enabled, you can use the following command:
@ -60,14 +24,67 @@ If you don't have experimental flakes enabled, you can use the following command
nix --extra-experimental-features nix-command --extra-experimental-features flakes develop nix --extra-experimental-features nix-command --extra-experimental-features flakes develop
``` ```
## testing
Run the test suite by running coverage with pytest inside an ephemeral NixOS VM with redis service enabled:
```console
$ nix flake check -L
```
Run the same test suite, but additionally create `./result/coverage.xml` in the current directory:
```console
$ nix build .#checks.x86_64-linux.default -L
```
Alternatively, just print the path to `/nix/store/...coverage.xml` without creating any files in the current directory:
```console
$ nix build .#checks.x86_64-linux.default -L --print-out-paths --no-link
```
Run the same test suite with arbitrary pytest options:
```console
$ pytest-vm.sh # specify pytest options here, e.g. `--last-failed`
```
When running using the script, pytest cache is preserved between runs in `.pytest_cache` folder.
NixOS VM state temporary resides in `${TMPDIR:=/tmp}/nixos-vm-tmp-dir/vm-state-machine` during the test.
Git workdir directory is shared read-write with VM via `.nixos-vm-tmp-dir/shared-xchg` symlink. VM accesses workdir contents via `/tmp/shared` mount point and `/root/source` symlink.
Launch VM and execute commands manually either in Linux console (user `root`) or using python NixOS tests driver API (refer to [NixOS documentation](https://nixos.org/manual/nixos/stable/#ssec-machine-objects)):
```console
$ nix run .#checks.x86_64-linux.default.driverInteractive
```
You can add `--keep-vm-state` in order to keep VM state between runs:
```console
$ TMPDIR=".nixos-vm-tmp-dir" nix run .#checks.x86_64-linux.default.driverInteractive --keep-vm-state
```
Option `-L`/`--print-build-logs` is optional for all nix commands. It tells nix to print each log line one after another instead of overwriting a single one.
## dependencies and dependant modules ## dependencies and dependant modules
Current flake inherits nixpkgs from NixOS configuration flake. So there is no need to refer to extra nixpkgs dependency if you want to be aligned with exact NixOS configuration. This flake depends on a single Nix flake input - nixpkgs repository. nixpkgs repository is used for all software packages used to build, run API service, tests, etc.
![diagram](http://www.plantuml.com/plantuml/proxy?src=https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api/raw/branch/master/nix-dependencies-diagram.puml) In order to synchronize nixpkgs input with the same from selfprivacy-nixos-config repository, use this command:
```console
$ nix flake lock --override-input nixpkgs nixpkgs --inputs-from git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=BRANCH
```
Replace BRANCH with the branch name of selfprivacy-nixos-config repository you want to sync with. During development nixpkgs input update might be required in both selfprivacy-rest-api and selfprivacy-nixos-config repositories simultaneously. So, a new feature branch might be temporarily used until selfprivacy-nixos-config gets the feature branch merged.
Show current flake inputs (e.g. nixpkgs):
```console
$ nix flake metadata
```
Show selfprivacy-nixos-config Nix flake inputs (including nixpkgs):
```console
$ nix flake metadata git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=BRANCH
```
Nix code for NixOS service module for API is located in NixOS configuration repository. Nix code for NixOS service module for API is located in NixOS configuration repository.
## current issues ## troubleshooting
- It's not clear how to store in this repository information about several compatible NixOS configuration commits, where API application tests pass. Currently, here is only a single `flake.lock`. Sometimes commands inside `nix develop` refuse to work properly if the calling shell lacks `LANG` environment variable. Try to set it before entering `nix develop`.

View file

@ -4,7 +4,6 @@ pythonPackages.buildPythonPackage rec {
pname = "selfprivacy-graphql-api"; pname = "selfprivacy-graphql-api";
version = rev; version = rev;
src = builtins.filterSource (p: t: p != ".git" && t != "symlink") ./.; src = builtins.filterSource (p: t: p != ".git" && t != "symlink") ./.;
nativeCheckInputs = [ pythonPackages.pytestCheckHook ];
propagatedBuildInputs = with pythonPackages; [ propagatedBuildInputs = with pythonPackages; [
fastapi fastapi
gevent gevent
@ -13,9 +12,6 @@ pythonPackages.buildPythonPackage rec {
portalocker portalocker
psutil psutil
pydantic pydantic
pytest
pytest-datadir
pytest-mock
pytz pytz
redis redis
setuptools setuptools

139
flake.nix
View file

@ -11,40 +11,131 @@
pythonPackages = pkgs.python310Packages; pythonPackages = pkgs.python310Packages;
rev = self.shortRev or self.dirtyShortRev or "dirty"; rev = self.shortRev or self.dirtyShortRev or "dirty";
}; };
python = self.packages.${system}.default.pythonModule;
python-env =
python.withPackages (ps:
self.packages.${system}.default.propagatedBuildInputs ++ (with ps; [
coverage
pytest
pytest-datadir
pytest-mock
]));
vmtest-src-dir = "/root/source";
shellMOTD = ''
Welcome to SP API development shell!
[formatters]
black
nixpkgs-fmt
[testing in NixOS VM]
nixos-test-driver - run an interactive NixOS VM with with all dependencies
pytest-vm - run pytest in an ephemeral NixOS VM with Redis, accepting pytest arguments
'';
in in
{ {
packages.${system}.default = selfprivacy-graphql-api; # see https://github.com/NixOS/nixpkgs/blob/66a9817cec77098cfdcbb9ad82dbb92651987a84/nixos/lib/test-driver/test_driver/machine.py#L359
packages.${system} = {
default = selfprivacy-graphql-api;
pytest-vm = pkgs.writeShellScriptBin "pytest-vm" ''
set -o errexit
set -o nounset
set -o xtrace
# see https://github.com/NixOS/nixpkgs/blob/66a9817cec77098cfdcbb9ad82dbb92651987a84/nixos/lib/test-driver/test_driver/machine.py#L359
export TMPDIR=''${TMPDIR:=/tmp}/nixos-vm-tmp-dir
readonly NIXOS_VM_SHARED_DIR_HOST="$TMPDIR/shared-xchg"
readonly NIXOS_VM_SHARED_DIR_GUEST="/tmp/shared"
mkdir -p "$TMPDIR"
ln -sfv "$PWD" -T "$NIXOS_VM_SHARED_DIR_HOST"
SCRIPT=$(cat <<EOF
start_all()
machine.succeed("ln -sf $NIXOS_VM_SHARED_DIR_GUEST -T ${vmtest-src-dir} >&2")
machine.succeed("cd ${vmtest-src-dir} && coverage run -m pytest -v $@ >&2")
machine.succeed("cd ${vmtest-src-dir} && coverage report >&2")
EOF
)
if [ -f "/etc/arch-release" ]; then
${self.checks.${system}.default.driverInteractive}/bin/nixos-test-driver --no-interactive <(printf "%s" "$SCRIPT")
else
${self.checks.${system}.default.driver}/bin/nixos-test-driver -- <(printf "%s" "$SCRIPT")
fi
'';
};
nixosModules.default = nixosModules.default =
import ./nixos/module.nix self.packages.${system}.default; import ./nixos/module.nix self.packages.${system}.default;
devShells.${system}.default = pkgs.mkShell { devShells.${system}.default = pkgs.mkShell {
packages = name = "SP API dev shell";
let packages = with pkgs; [
# TODO is there a better way to get environment for VS Code? python-env
python3 = rclone
nixpkgs.lib.findFirst (p: p.pname == "python3") (abort "wtf") redis
self.packages.${system}.default.propagatedBuildInputs; restic
python-env = self.packages.${system}.pytest-vm
python3.withPackages # FIXME consider loading this explicitly only after ArchLinux issue is solved
(_: self.packages.${system}.default.propagatedBuildInputs); self.checks.x86_64-linux.default.driverInteractive
in ];
with pkgs; [
python-env
black
rclone
redis
restic
];
shellHook = '' shellHook = ''
# envs set with export and as attributes are treated differently. # envs set with export and as attributes are treated differently.
# for example. printenv <Name> will not fetch the value of an attribute. # for example. printenv <Name> will not fetch the value of an attribute.
export USE_REDIS_PORT=6379 export TEST_MODE="true"
export TEST_MODE=true
pkill redis-server # more tips for bash-completion to work on non-NixOS:
sleep 2 # https://discourse.nixos.org/t/whats-the-nix-way-of-bash-completion-for-packages/20209/16?u=alexoundos
setsid redis-server --bind 127.0.0.1 --port $USE_REDIS_PORT >/dev/null 2>/dev/null & # Load installed profiles
# maybe set more env-vars for file in "/etc/profile.d/"*.sh; do
# If that folder doesn't exist, bash loves to return the whole glob
[[ -f "$file" ]] && source "$file"
done
printf "%s" "${shellMOTD}"
''; '';
}; };
checks.${system} = {
fmt-check = pkgs.runCommandLocal "sp-api-fmt-check"
{ nativeBuildInputs = [ pkgs.black ]; }
"black --check ${self.outPath} > $out";
default =
pkgs.testers.runNixOSTest {
imports = [{
name = "default";
nodes.machine = { lib, pkgs, ... }: {
imports = [{
boot.consoleLogLevel = lib.mkForce 3;
documentation.enable = false;
services.journald.extraConfig = lib.mkForce "";
services.redis.servers.sp-api = {
enable = true;
save = [ ];
port = 6379; # FIXME
settings.notify-keyspace-events = "KEA";
};
environment.systemPackages = with pkgs; [
python-env
# TODO: these can be passed via wrapper script around app
rclone
restic
];
environment.variables.TEST_MODE = "true";
systemd.tmpfiles.settings.src.${vmtest-src-dir}.L.argument =
self.outPath;
}];
};
testScript = ''
start_all()
machine.succeed("cd ${vmtest-src-dir} && coverage run --data-file=/tmp/.coverage -m pytest -p no:cacheprovider -v >&2")
machine.succeed("coverage xml --rcfile=${vmtest-src-dir}/.coveragerc --data-file=/tmp/.coverage >&2")
machine.copy_from_vm("coverage.xml", ".")
machine.succeed("coverage report >&2")
'';
}];
};
};
}; };
nixConfig.bash-prompt = ''\n\[\e[1;32m\][\[\e[0m\]\[\e[1;34m\]SP devshell\[\e[0m\]\[\e[1;32m\]:\w]\$\[\[\e[0m\] ''; nixConfig.bash-prompt = ''\n\[\e[1;32m\][\[\e[0m\]\[\e[1;34m\]SP devshell\[\e[0m\]\[\e[1;32m\]:\w]\$\[\[\e[0m\] '';
} }