Introduce a test framework for Asterinas NixOS
This commit is contained in:
parent
bae5de9e8f
commit
c439df3d02
|
|
@ -35,3 +35,6 @@ distro/result
|
|||
|
||||
# cachix package list
|
||||
cachix.list
|
||||
|
||||
# temporary configuration file for NixOS tests
|
||||
distro/etc_nixos/_config_for_test.nix
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ disable-systemd ? "false", stage-2-hook ? "/bin/sh -l", log-level ? "error"
|
||||
, console ? "hvc0", test-command ? "", extra-substituters ? ""
|
||||
, extra-trusted-public-keys ? "", pkgs ? import <nixpkgs> { } }:
|
||||
, console ? "hvc0", extra-substituters ? "", extra-trusted-public-keys ? ""
|
||||
, config-file-name ? "configuration.nix", pkgs ? import <nixpkgs> { } }:
|
||||
let
|
||||
aster-kernel = builtins.path {
|
||||
name = "aster-kernel-osdk-bin";
|
||||
|
|
@ -16,7 +16,6 @@ let
|
|||
aster-stage-2-hook = stage-2-hook;
|
||||
aster-log-level = log-level;
|
||||
aster-console = console;
|
||||
aster-test-command = test-command;
|
||||
aster-substituters = extra-substituters;
|
||||
aster-trusted-public-keys = extra-trusted-public-keys;
|
||||
};
|
||||
|
|
@ -38,7 +37,7 @@ in pkgs.stdenv.mkDerivation {
|
|||
mkdir -p $out/{bin,etc_nixos}
|
||||
cp ${install_aster_nixos} $out/bin/install_aster_nixos.sh
|
||||
cp -L ${aster_configuration} $out/etc_nixos/aster_configuration.nix
|
||||
cp -L ${etc-nixos}/configuration.nix $out/etc_nixos/configuration.nix
|
||||
cp -L ${etc-nixos}/${config-file-name} $out/etc_nixos/configuration.nix
|
||||
cp -r ${etc-nixos}/modules $out/etc_nixos/modules
|
||||
cp -r ${etc-nixos}/overlays $out/etc_nixos/overlays
|
||||
ln -s ${aster-kernel} $out/kernel
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{ pkgs ? import <nixpkgs> { }, autoInstall ? false, test-command ? ""
|
||||
, extra-substituters ? "", extra-trusted-public-keys ? "", version ? "", ... }:
|
||||
{ pkgs ? import <nixpkgs> { }, autoInstall ? false, extra-substituters ? ""
|
||||
, config-file-name ? "configuration.nix", extra-trusted-public-keys ? ""
|
||||
, version ? "", ... }:
|
||||
let
|
||||
installer = pkgs.callPackage ../aster_nixos_installer {
|
||||
inherit test-command extra-substituters extra-trusted-public-keys;
|
||||
inherit extra-substituters extra-trusted-public-keys config-file-name;
|
||||
};
|
||||
configuration = {
|
||||
imports = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Test Suites
|
||||
|
||||
This directory contains the testing infrastructure for Asterinas, organized into two complementary testing approaches.
|
||||
|
||||
## Test Types
|
||||
|
||||
### Initramfs-Based Tests ([`initramfs/`](initramfs/))
|
||||
|
||||
Tests running in a minimal initramfs environment. Best for:
|
||||
- System call validation
|
||||
- Core functionality testing
|
||||
- Performance benchmarks
|
||||
|
||||
See [`initramfs/README.md`](initramfs/README.md) for details.
|
||||
|
||||
### NixOS-Based Tests ([`nixos/`](nixos/))
|
||||
|
||||
Tests running in NixOS environments.
|
||||
|
||||
See [`nixos/README.md`](nixos/README.md) for details.
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "comma"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.179"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nixos-test-framework"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"inventory",
|
||||
"nixos-test-macro",
|
||||
"rexpect",
|
||||
"strip-ansi-escapes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nixos-test-macro"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rexpect"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1bcd4ac488e9d2d726d147031cceff5cff6425011ff1914049739770fa4726"
|
||||
dependencies = [
|
||||
"comma",
|
||||
"nix",
|
||||
"regex",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
|
||||
dependencies = [
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-hello"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nixos-test-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-nix"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nixos-test-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-podman"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nixos-test-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"common/framework", "common/test_macro",
|
||||
"tests/*",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
repository = "https://github.com/asterinas/asterinas"
|
||||
license = "MPL-2.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
inventory = "0.3"
|
||||
rexpect = "0.6"
|
||||
strip-ansi-escapes = "0.2"
|
||||
nixos-test-framework = { path = "common/framework" }
|
||||
nixos-test-macro = { path = "common/test_macro" }
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
NIXOS_TEST_SUITE ?=
|
||||
NIXOS_TEST_CASE ?=
|
||||
NIXOS_TEST_TIMEOUT ?= 5min
|
||||
|
||||
# Generated configuration file name
|
||||
TEST_CONFIG_FILE_NAME := _configuration_for_test.nix
|
||||
|
||||
# Paths
|
||||
MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
ASTERINAS_ROOT := $(MAKEFILE_DIR)/../..
|
||||
NIXOS_CONFIG_DIR := $(ASTERINAS_ROOT)/distro/etc_nixos
|
||||
BASE_CONFIG := $(NIXOS_CONFIG_DIR)/configuration.nix
|
||||
TEST_CONFIG := $(NIXOS_CONFIG_DIR)/$(TEST_CONFIG_FILE_NAME)
|
||||
MERGE_SCRIPT := $(ASTERINAS_ROOT)/test/nixos/common/merge_nixos_config.sh
|
||||
|
||||
# NixOS build and run scripts
|
||||
BUILD_ISO_SCRIPT := $(ASTERINAS_ROOT)/tools/nixos/build_iso.sh
|
||||
BUILD_NIXOS_SCRIPT := $(ASTERINAS_ROOT)/tools/nixos/build_nixos.sh
|
||||
RUN_SCRIPT := $(ASTERINAS_ROOT)/tools/nixos/run.sh
|
||||
|
||||
# Test directory structure
|
||||
TEST_DIR := tests/$(NIXOS_TEST_SUITE)
|
||||
EXTRA_CONFIG := $(TEST_DIR)/extra_config.nix
|
||||
|
||||
.PHONY: check-test-name
|
||||
check-test-name:
|
||||
@if [ -z "$(NIXOS_TEST_SUITE)" ]; then \
|
||||
echo "Error: NIXOS_TEST_SUITE is not set"; \
|
||||
echo "Usage: make [target] NIXOS_TEST_SUITE=<test_name>"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -d "$(TEST_DIR)" ]; then \
|
||||
echo "Error: Test directory '$(TEST_DIR)' does not exist"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: prepare
|
||||
prepare: check-test-name
|
||||
@echo "==> Preparing configuration for test '$(NIXOS_TEST_SUITE)'..."
|
||||
@if [ -f "$(EXTRA_CONFIG)" ]; then \
|
||||
echo " Found extra config: $(EXTRA_CONFIG)"; \
|
||||
echo " Merging configurations..."; \
|
||||
bash "$(MERGE_SCRIPT)" "$(BASE_CONFIG)" "$(EXTRA_CONFIG)" "$(TEST_CONFIG)"; \
|
||||
if [ $$? -ne 0 ]; then \
|
||||
echo "Error: Configuration merge failed"; \
|
||||
rm "$(TEST_CONFIG)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
else \
|
||||
echo " No extra config found, using base configuration"; \
|
||||
cp "$(BASE_CONFIG)" "$(TEST_CONFIG)"; \
|
||||
fi
|
||||
|
||||
.PHONY: iso
|
||||
iso: prepare
|
||||
@echo "==> Building ISO installer for test '$(NIXOS_TEST_NAME)'..."
|
||||
@echo " Building ISO installer...";
|
||||
@bash "$(BUILD_ISO_SCRIPT)" "$(TEST_CONFIG_FILE_NAME)";
|
||||
@if [ $$? -ne 0 ]; then \
|
||||
echo "Error: ISO build failed"; \
|
||||
rm "$(TEST_CONFIG)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@rm "$(TEST_CONFIG)";
|
||||
|
||||
.PHONY: nixos
|
||||
nixos: prepare
|
||||
@echo "==> Building NixOS image for test '$(NIXOS_TEST_NAME)'..."
|
||||
@bash "$(BUILD_NIXOS_SCRIPT)" "$(TEST_CONFIG_FILE_NAME)";
|
||||
@if [ $$? -ne 0 ]; then \
|
||||
echo "Error: NixOS build failed"; \
|
||||
rm "$(TEST_CONFIG)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@rm "$(TEST_CONFIG)";
|
||||
|
||||
.PHONY: run_nixos
|
||||
run_nixos: check-test-name
|
||||
@cd "$(TEST_DIR)" && \
|
||||
TEST_CASE_ARG=""; \
|
||||
if [ -n "$(NIXOS_TEST_CASE)" ]; then \
|
||||
TEST_CASE_ARG="--test $(NIXOS_TEST_CASE)"; \
|
||||
fi; \
|
||||
QEMU_CMD="bash $(RUN_SCRIPT) nixos"; \
|
||||
cargo run -- --qemu-cmd "$$QEMU_CMD" $$TEST_CASE_ARG || \
|
||||
exit 1
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
@cargo clippy -- -D warnings
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
@cargo fmt
|
||||
@nixfmt .
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
# NixOS-Based Test Suites
|
||||
|
||||
This directory contains NixOS-based tests and a framework for writing and running them. The framework executes tests by interacting with a live instance of the operating system in a virtual environment. Thanks to this interactive design, the framework can test virtually any behavior that a real user could trigger through a terminal. It also offers a simple, imperative API, making it easy to write and maintain these interactive test scenarios.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
test/nixos/
|
||||
├── common/
|
||||
│ ├── template/ # Template for creating new tests
|
||||
│ └── ... # Core implementation of the framework
|
||||
├── tests/
|
||||
│ ├── podman/ # A real test crate
|
||||
│ │ ├── Cargo.toml
|
||||
│ │ ├── extra_config.nix # (Optional) Additional NixOS configuration
|
||||
│ │ └── src/
|
||||
│ │ └── main.rs
|
||||
│ └── ... # Other tests
|
||||
└── Makefile
|
||||
```
|
||||
|
||||
## Creating a New Test
|
||||
|
||||
### Step 1: Copy the Template
|
||||
|
||||
```bash
|
||||
cd test/nixos
|
||||
cp -r common/template tests/my-test
|
||||
```
|
||||
|
||||
### Step 2: Update `Cargo.toml`
|
||||
|
||||
Replace `<test_name>` with your test name:
|
||||
|
||||
### Step 3: Implement Your Tests
|
||||
|
||||
Edit `src/main.rs`:
|
||||
|
||||
```rust
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use nixos_test_framework::*;
|
||||
use nixos_test_macro::nixos_test;
|
||||
|
||||
// This macro generates the main function that runs all registered tests
|
||||
nixos_test_main!();
|
||||
|
||||
// Register a test case using the #[nixos_test] attribute
|
||||
#[nixos_test]
|
||||
fn basic_command_test(nixos_shell: &mut Session) -> Result<(), Error> {
|
||||
nixos_shell.run_cmd("echo 'Hello, World!'")?;
|
||||
nixos_shell.run_cmd_and_expect("cat /etc/os-release", "NixOS")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// You can define multiple test cases in the same file
|
||||
#[nixos_test]
|
||||
fn file_operations_test(nixos_shell: &mut Session) -> Result<(), Error> {
|
||||
nixos_shell.run_cmd("touch /tmp/test.txt")?;
|
||||
nixos_shell.run_cmd_and_expect("ls /tmp", "test.txt")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
The `Session` type provides APIs for interacting with the VM. See the [Session API documentation](common/framework/src/session.rs) for details.
|
||||
|
||||
### Step 4: (Optional) Configure NixOS
|
||||
|
||||
If your test requires additional packages or system configuration, edit `extra_config.nix`:
|
||||
|
||||
```nix
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Add required packages here
|
||||
vim
|
||||
git
|
||||
];
|
||||
|
||||
# Configure system services
|
||||
virtualisation.podman.enable = true;
|
||||
}
|
||||
```
|
||||
This content of this file will be merged with the [default configuration file](../../distro/etc_nixos/configuration.nix) to generate the final configuration file for the testing Asterinas NixOS system.
|
||||
|
||||
## Running Tests
|
||||
|
||||
The following commands should be run under the project root.
|
||||
|
||||
### Build Test Image
|
||||
|
||||
```bash
|
||||
# Build NixOS image for a test suite
|
||||
make nixos NIXOS_TEST_SUITE=my-test
|
||||
|
||||
# Or build using ISO installer workflow
|
||||
make iso NIXOS_TEST_SUITE=my-test
|
||||
make run_iso
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
# Run all tests in the suite
|
||||
make run_nixos NIXOS_TEST_SUITE=my-test
|
||||
|
||||
# Run a specific test case
|
||||
make run_nixos NIXOS_TEST_SUITE=my-test NIXOS_TEST_CASE=basic_command_test
|
||||
|
||||
# Customize timeout with units (default: 5min)
|
||||
make run_nixos NIXOS_TEST_SUITE=my-test NIXOS_TEST_TIMEOUT=10min # 10 minutes
|
||||
make run_nixos NIXOS_TEST_SUITE=my-test NIXOS_TEST_TIMEOUT=600s # 600 seconds
|
||||
make run_nixos NIXOS_TEST_SUITE=my-test NIXOS_TEST_TIMEOUT=600000ms # 600000 milliseconds
|
||||
```
|
||||
|
||||
### Complete Workflow Examples
|
||||
|
||||
```bash
|
||||
# Quick test
|
||||
make nixos NIXOS_TEST_SUITE=my-test && make run_nixos NIXOS_TEST_SUITE=my-test
|
||||
|
||||
# Test with ISO installer
|
||||
make iso NIXOS_TEST_SUITE=my-test && make run_iso && make run_nixos NIXOS_TEST_SUITE=my-test
|
||||
|
||||
# Run specific test with custom timeout (10 minutes)
|
||||
make nixos NIXOS_TEST_SUITE=podman
|
||||
make run_nixos NIXOS_TEST_SUITE=podman NIXOS_TEST_CASE=container_basic_test NIXOS_TEST_TIMEOUT=10min
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- **`NIXOS_TEST_SUITE`**: Name of the test suite to run (required for test mode)
|
||||
- **`NIXOS_TEST_CASE`**: Specific test case to run (optional, runs all if not specified)
|
||||
- **`NIXOS_TEST_TIMEOUT`**: Timeout for command execution with unit suffix (optional, default: 5min)
|
||||
- Supported formats: `<number>ms` (milliseconds), `<number>s` (seconds), `<number>min` (minutes)
|
||||
- Examples: `300000ms`, `300s`, `5min`
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "nixos-test-framework"
|
||||
version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
inventory.workspace = true
|
||||
nixos-test-macro.workspace = true
|
||||
rexpect.workspace = true
|
||||
strip-ansi-escapes.workspace = true
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! An imperative testing framework for NixOS-based tests.
|
||||
//!
|
||||
//! # Core Concepts
|
||||
//!
|
||||
//! ## Test Registration
|
||||
//!
|
||||
//! Use the `#[nixos_test]` attribute to register test cases. The framework
|
||||
//! automatically discovers and runs all registered tests.
|
||||
//!
|
||||
//! ## Session Interaction
|
||||
//!
|
||||
//! Tests are implemented by interacting with a [`Session`] object. The [`Session`] type
|
||||
//! provides methods for executing commands and verifying output. It supports nested execution
|
||||
//! contexts (containers, SSH, etc.) with automatic cleanup.
|
||||
//!
|
||||
//! See the [template crate](https://github.com/asterinas/asterinas/tree/main/test/nixos/common/template)
|
||||
//! for a usage example.
|
||||
//!
|
||||
//! See the [project README](https://github.com/asterinas/asterinas/tree/main/test/nixos)
|
||||
//! for complete documentation on creating and running test suites.
|
||||
|
||||
use std::env;
|
||||
|
||||
pub use inventory;
|
||||
pub use nixos_test_macro::nixos_test;
|
||||
pub use rexpect::error::Error;
|
||||
pub use session::{Session, SessionDesc};
|
||||
|
||||
mod session;
|
||||
|
||||
/// A test case definition.
|
||||
pub struct TestCase {
|
||||
pub name: &'static str,
|
||||
pub test_fn: fn(&mut Session) -> Result<(), Error>,
|
||||
}
|
||||
|
||||
inventory::collect!(TestCase);
|
||||
|
||||
/// Generates the main function that runs all test cases.
|
||||
#[macro_export]
|
||||
macro_rules! nixos_test_main {
|
||||
() => {
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
$crate::__nixos_test_main()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __nixos_test_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
// Check for --help flag
|
||||
if args.contains(&"--help".to_string()) || args.contains(&"-h".to_string()) {
|
||||
print_help();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Parse --qemu-cmd argument
|
||||
let qemu_cmd = parse_arg(&args, "--qemu-cmd").ok_or("Missing --qemu-cmd argument")?;
|
||||
|
||||
// Parse optional --test argument
|
||||
let test_filter = parse_arg(&args, "--test");
|
||||
|
||||
// Parse timeout from NIXOS_TEST_TIMEOUT environment variable
|
||||
let timeout_ms = env::var("NIXOS_TEST_TIMEOUT")
|
||||
.ok()
|
||||
.map(|v| parse_timeout(&v))
|
||||
.transpose()?
|
||||
.unwrap_or(300_000); // Default: 5 minutes
|
||||
|
||||
let all_test_cases: Vec<&TestCase> = inventory::iter::<TestCase>().collect();
|
||||
|
||||
if all_test_cases.is_empty() {
|
||||
return Err("No test cases found".into());
|
||||
}
|
||||
|
||||
// Filter test cases if --test is specified
|
||||
let test_cases: Vec<&TestCase> = if let Some(ref filter) = test_filter {
|
||||
let filtered: Vec<&TestCase> = all_test_cases
|
||||
.into_iter()
|
||||
.filter(|tc| tc.name == filter)
|
||||
.collect();
|
||||
|
||||
if filtered.is_empty() {
|
||||
return Err(format!("Test case '{}' not found", filter).into());
|
||||
}
|
||||
|
||||
filtered
|
||||
} else {
|
||||
all_test_cases
|
||||
};
|
||||
|
||||
if let Some(ref filter) = test_filter {
|
||||
println!("=== Running single test case: {} ===", filter);
|
||||
} else {
|
||||
println!("=== Found {} test case(s) ===", test_cases.len());
|
||||
for tc in &test_cases {
|
||||
println!(" - test_{}", tc.name);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
let mut session = rexpect::spawn(&qemu_cmd, Some(timeout_ms))?;
|
||||
|
||||
println!("--> Waiting for login prompt...");
|
||||
let init_prompt = "root@asterinas:";
|
||||
session.exp_string(init_prompt)?;
|
||||
|
||||
let desc = SessionDesc::new()
|
||||
.expect_prompt(init_prompt)
|
||||
.cmd_to_enter("")
|
||||
.cmd_to_exit("poweroff");
|
||||
let mut session = Session::new(desc, session);
|
||||
|
||||
let mut passed = 0;
|
||||
let mut failed = 0;
|
||||
let mut failed_tests = Vec::new();
|
||||
|
||||
for test_case in test_cases {
|
||||
println!("=== Running test case: {} ===", test_case.name);
|
||||
|
||||
match session.run(test_case.test_fn) {
|
||||
Ok(_) => {
|
||||
println!("✓ Test case 'test_{}' passed\n", test_case.name);
|
||||
passed += 1;
|
||||
}
|
||||
Err(_) => {
|
||||
println!("✗ Test case 'test_{}' failed\n", test_case.name);
|
||||
failed += 1;
|
||||
failed_tests.push(test_case.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("=== Test Summary ===");
|
||||
println!("Passed: {}", passed);
|
||||
println!("Failed: {}", failed);
|
||||
|
||||
let res = if !failed_tests.is_empty() {
|
||||
println!("\nFailed tests:");
|
||||
for name in failed_tests {
|
||||
println!(" - test_{}", name);
|
||||
}
|
||||
|
||||
Err("Some tests failed")
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let shutdown_res = session.shutdown();
|
||||
|
||||
res?;
|
||||
shutdown_res?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses timeout string with units into milliseconds.
|
||||
///
|
||||
/// Supports formats:
|
||||
/// - `<number>ms` - milliseconds
|
||||
/// - `<number>s` - seconds
|
||||
/// - `<number>min` - minutes
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// parse_timeout("300000ms") // Ok(300000)
|
||||
/// parse_timeout("300s") // Ok(300000)
|
||||
/// parse_timeout("5min") // Ok(300000)
|
||||
/// ```
|
||||
fn parse_timeout(timeout_str: &str) -> Result<u64, Box<dyn std::error::Error>> {
|
||||
let timeout_str = timeout_str.trim();
|
||||
|
||||
if let Some(ms) = timeout_str.strip_suffix("ms") {
|
||||
return Ok(ms.trim().parse()?);
|
||||
}
|
||||
|
||||
if let Some(s) = timeout_str.strip_suffix('s') {
|
||||
let seconds: u64 = s.trim().parse()?;
|
||||
return Ok(seconds * 1000);
|
||||
}
|
||||
|
||||
if let Some(m) = timeout_str.strip_suffix("min") {
|
||||
let minutes: u64 = m.trim().parse()?;
|
||||
return Ok(minutes * 60000);
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"Invalid timeout format '{}'. Use: <number>ms, <number>s, or <number>m",
|
||||
timeout_str
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Parse command line argument in the form --flag <value>
|
||||
fn parse_arg(args: &[String], flag: &str) -> Option<String> {
|
||||
for i in 0..args.len() {
|
||||
if args[i] == flag {
|
||||
return args.get(i + 1).cloned();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"\
|
||||
NixOS-Based Test Framework
|
||||
|
||||
USAGE:
|
||||
<test-binary> --qemu-cmd <COMMAND> [OPTIONS]
|
||||
|
||||
REQUIRED ARGUMENTS:
|
||||
--qemu-cmd <COMMAND> Command to launch QEMU with the test environment
|
||||
|
||||
OPTIONS:
|
||||
--test <TEST_NAME> Run only the specified test case
|
||||
-h, --help Print this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
NIXOS_TEST_TIMEOUT Timeout for command execution
|
||||
Supports: <number>ms, <number>s, <number>min
|
||||
Examples: 300000ms, 300s, 5min
|
||||
(default: 5min = 300000ms)
|
||||
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use rexpect::session::PtySession;
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// Describes a runtime session configuration.
|
||||
///
|
||||
/// A session descriptor defines how to interact with a particular execution context,
|
||||
/// such as a shell environment, container, or remote session. It specifies the prompt
|
||||
/// pattern (by `expect_prompt`) and the commands needed to enter (by `cmd_to_enter`)
|
||||
/// and exit (by `cmd_to_exit`) the context.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The session descriptor uses a fluent builder API:
|
||||
///
|
||||
/// ```rust
|
||||
/// use nixos_test_framework::SessionDesc;
|
||||
///
|
||||
/// let desc = SessionDesc::new()
|
||||
/// .expect_prompt("/ #")
|
||||
/// .cmd_to_enter("podman run -it alpine")
|
||||
/// .cmd_to_exit("exit");
|
||||
/// ```
|
||||
pub struct SessionDesc {
|
||||
prompt: &'static str,
|
||||
enter_command: &'static str,
|
||||
exit_cmd: &'static str,
|
||||
}
|
||||
|
||||
impl SessionDesc {
|
||||
/// Creates a new session descriptor with empty fields.
|
||||
///
|
||||
/// Use the builder methods to configure the session before use.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
prompt: "",
|
||||
enter_command: "",
|
||||
exit_cmd: "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the expected prompt pattern for this session.
|
||||
pub fn expect_prompt(mut self, prompt: &'static str) -> Self {
|
||||
self.prompt = prompt;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the command used to enter this session.
|
||||
pub fn cmd_to_enter(mut self, enter_command: &'static str) -> Self {
|
||||
self.enter_command = enter_command;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the command used to exit this session.
|
||||
pub fn cmd_to_exit(mut self, exit_cmd: &'static str) -> Self {
|
||||
self.exit_cmd = exit_cmd;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionDesc {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// An interactive session for running commands in a test environment.
|
||||
///
|
||||
/// `Session` provides a high-level interface for interacting with the test environment.
|
||||
/// It manages execution contexts and handles nested environments automatically.
|
||||
///
|
||||
/// It uses a [`SessionDesc`] to track the current prompt and the correct command
|
||||
/// to exit the current context.
|
||||
pub struct Session {
|
||||
desc: SessionDesc,
|
||||
pty_session: PtySession,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Creates a new session with the given PTY session and descriptor.
|
||||
pub(super) fn new(desc: SessionDesc, pty_session: PtySession) -> Self {
|
||||
Self { desc, pty_session }
|
||||
}
|
||||
|
||||
fn output_error(error: &Error) {
|
||||
match error {
|
||||
Error::EOF {
|
||||
expected,
|
||||
got,
|
||||
exit_code,
|
||||
} => {
|
||||
println!("=== EOF Error Details ===");
|
||||
println!("Expected: {}", expected);
|
||||
println!(
|
||||
"Got: {}",
|
||||
String::from_utf8_lossy(&strip_ansi_escapes::strip(got))
|
||||
);
|
||||
println!("Exit code: {:?}", exit_code);
|
||||
println!("========================");
|
||||
}
|
||||
Error::Timeout {
|
||||
expected,
|
||||
got,
|
||||
timeout,
|
||||
} => {
|
||||
println!("=== Timeout Error Details ===");
|
||||
println!("Expected: {}", expected);
|
||||
println!(
|
||||
"Got: {}",
|
||||
String::from_utf8_lossy(&strip_ansi_escapes::strip(got))
|
||||
);
|
||||
println!("Timeout: {:?}", timeout);
|
||||
println!("============================");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a command in the current session.
|
||||
///
|
||||
/// This method runs the specified command and waits for it to complete.
|
||||
/// The command is considered complete when the session prompt reappears.
|
||||
///
|
||||
/// Returns an error if the command times out or the session terminates unexpectedly.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use nixos_test_framework::*;
|
||||
///
|
||||
/// fn example(nixos_shell: &mut Session) -> Result<(), Error> {
|
||||
/// // Execute simple commands
|
||||
/// nixos_shell.run_cmd("ls -la")?;
|
||||
/// nixos_shell.run_cmd("cd /tmp")?;
|
||||
/// nixos_shell.run_cmd("mkdir test_dir")?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run_cmd(&mut self, command: &str) -> Result<(), Error> {
|
||||
println!("--> Running: {}", command);
|
||||
self.pty_session.send_line(command)?;
|
||||
// Read and consume the echoed command line
|
||||
self.pty_session.exp_string(command).unwrap();
|
||||
|
||||
if let Err(e) = self.pty_session.exp_string(self.desc.prompt) {
|
||||
Self::output_error(&e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes a command and verifies its output contains expected text.
|
||||
///
|
||||
/// This method runs the command and checks that the specified string appears
|
||||
/// in the output. This is useful for validating command results.
|
||||
///
|
||||
/// Returns an error if:
|
||||
/// - The expected string is not found in the output
|
||||
/// - The command times out
|
||||
/// - The session terminates unexpectedly
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use nixos_test_framework::*;
|
||||
///
|
||||
/// fn example(nixos_shell: &mut Session) -> Result<(), Error> {
|
||||
/// // Verify output contains expected string
|
||||
/// nixos_shell.run_cmd_and_expect("echo 'Hello, World!'", "Hello")?;
|
||||
///
|
||||
/// // Check if a file exists
|
||||
/// nixos_shell.run_cmd_and_expect("ls /etc/hostname", "/etc/hostname")?;
|
||||
///
|
||||
/// // Verify system information
|
||||
/// nixos_shell.run_cmd_and_expect("cat /etc/os-release", "NixOS")?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run_cmd_and_expect(&mut self, command: &str, expected: &str) -> Result<(), Error> {
|
||||
println!("--> Running: {} (expecting: {})", command, expected);
|
||||
self.pty_session.send_line(command)?;
|
||||
// Read and consume the echoed command line
|
||||
self.pty_session.exp_string(command).unwrap();
|
||||
|
||||
match self.pty_session.exp_string(self.desc.prompt) {
|
||||
Ok(unread) => {
|
||||
let cleaned_unread =
|
||||
String::from_utf8_lossy(&strip_ansi_escapes::strip(&unread)).to_string();
|
||||
if !cleaned_unread.contains(expected) {
|
||||
println!("=== Unexpected Output ===");
|
||||
println!("Expected: {}", expected);
|
||||
println!("Output before prompt:\n{}", cleaned_unread);
|
||||
println!("=========================");
|
||||
return Err(Error::EOF {
|
||||
expected: expected.to_string(),
|
||||
got: cleaned_unread,
|
||||
exit_code: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
Self::output_error(&e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn run<F>(&mut self, test_ops: F) -> Result<(), Error>
|
||||
where
|
||||
F: FnOnce(&mut Session) -> Result<(), Error>,
|
||||
{
|
||||
(test_ops)(self)
|
||||
}
|
||||
|
||||
/// Enters a nested session, runs operations, and automatically exits.
|
||||
///
|
||||
/// This method is used to work with nested environments like containers, SSH sessions,
|
||||
/// or any interactive shell.
|
||||
///
|
||||
/// Returns an error if entering, running operations, or exiting fails.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use nixos_test_framework::*;
|
||||
///
|
||||
/// fn container_test(nixos_shell: &mut Session) -> Result<(), Error> {
|
||||
/// // Define the container session
|
||||
/// let container_session_desc = SessionDesc::new()
|
||||
/// .expect_prompt("/ #")
|
||||
/// .cmd_to_enter("podman run -it docker.io/library/alpine")
|
||||
/// .cmd_to_exit("exit");
|
||||
///
|
||||
/// // Enter container, run tests, and automatically exit
|
||||
/// nixos_shell.enter_session_and_run(container_session_desc, |alpine_shell| {
|
||||
/// alpine_shell.run_cmd_and_expect(
|
||||
/// "cat /etc/os-release",
|
||||
/// "Alpine"
|
||||
/// )?;
|
||||
/// Ok(())
|
||||
/// })?;
|
||||
///
|
||||
/// // Back in the host - container has been exited
|
||||
/// nixos_shell.run_cmd("echo 'Back on host'")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn enter_session_and_run<F>(&mut self, desc: SessionDesc, test_ops: F) -> Result<(), Error>
|
||||
where
|
||||
F: FnOnce(&mut Session) -> Result<(), Error>,
|
||||
{
|
||||
let old_desc = std::mem::replace(&mut self.desc, desc);
|
||||
|
||||
if let Err(e) = self.run_cmd(self.desc.enter_command) {
|
||||
self.desc = old_desc;
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
let res = self.run(test_ops);
|
||||
|
||||
let exit_cmd = self.desc.exit_cmd;
|
||||
self.desc = old_desc;
|
||||
let exit_res = self.run_cmd(exit_cmd);
|
||||
|
||||
res?;
|
||||
exit_res?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn shutdown(&mut self) -> Result<(), Error> {
|
||||
self.pty_session.send_line(self.desc.exit_cmd)?;
|
||||
|
||||
self.pty_session.process.wait()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Usage: merge_nixos_config.sh <base_file> <extra_file> <merged_file>
|
||||
#
|
||||
# This script takes two NixOS configuration files, <base_file> and <extra_file>,
|
||||
# as inputs and produces a new NixOS configuration file whose content is the
|
||||
# combination of the two inputs. If the same key is set by both <base_file>
|
||||
# and <extra_file>, then the value provided by <extra_file> takes precedence.
|
||||
#
|
||||
# A NixOS configuration file, usually named `configuration.nix`, is written in
|
||||
# the following form:
|
||||
#
|
||||
# { config, lib, pkgs, ... }: {
|
||||
# key1 = value1;
|
||||
# key2 = value2;
|
||||
# }
|
||||
|
||||
set -e
|
||||
|
||||
# Check for the correct number of arguments
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo "Usage: $0 <base_file> <extra_file> <merged_file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_FILE="$1"
|
||||
EXTRA_FILE="$2"
|
||||
MERGED_FILE="$3"
|
||||
|
||||
# Check if input files exist
|
||||
if [ ! -f "$BASE_FILE" ]; then
|
||||
echo "Error: Base file not found at '$BASE_FILE'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$EXTRA_FILE" ]; then
|
||||
echo "Error: Extra file not found at '$EXTRA_FILE'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_CONTENT=$(cat "$BASE_FILE")
|
||||
EXTRA_CONTENT=$(cat "$EXTRA_FILE")
|
||||
|
||||
# Create the merged configuration file by embedding the file contents directly.
|
||||
cat > "$MERGED_FILE" <<EOF
|
||||
# This file is generated by merge_nixos_config.sh
|
||||
# It merges two configuration files. Do not edit directly.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
# The content of the original modules is embedded here.
|
||||
baseModule = (
|
||||
$BASE_CONTENT
|
||||
);
|
||||
|
||||
extraModule = (
|
||||
$EXTRA_CONTENT
|
||||
);
|
||||
|
||||
# Evaluate each module with the standard NixOS arguments
|
||||
base = baseModule { inherit config lib pkgs; };
|
||||
extra = extraModule { inherit config lib pkgs; };
|
||||
in
|
||||
# Deeply merge the base and extra configurations.
|
||||
# The value from extra_file takes precedence.
|
||||
lib.recursiveUpdate base extra
|
||||
EOF
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "test-<test_name>"
|
||||
version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
nixos-test-framework.workspace = true
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{ }
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! The test suite for <TargetAppName> on Asterinas NixOS.
|
||||
//!
|
||||
//! # Document maintenance
|
||||
//!
|
||||
//! An application's test suite and its "Verified Usage" section in Asterinas Book
|
||||
//! should always be kept in sync.
|
||||
//! So whenever you modify the test suite,
|
||||
//! review the documentation and see if should be updated accordingly.
|
||||
|
||||
use nixos_test_framework::*;
|
||||
|
||||
nixos_test_main!();
|
||||
|
||||
#[nixos_test]
|
||||
fn hello_world(nixos_shell: &mut Session) -> Result<(), Error> {
|
||||
nixos_shell.run_cmd("echo 'Hello, World!' > out.txt")?;
|
||||
nixos_shell.run_cmd_and_expect("ls out.txt", "out.txt")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "nixos-test-macro"
|
||||
version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! A procedural macro to register NixOS test cases.
|
||||
//!
|
||||
//! This crate should work together with `nixos_test_framework` crate. The
|
||||
//! registered test cases will be collected and run by the test framework.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{ItemFn, parse_macro_input};
|
||||
|
||||
/// Registers a function as a NixOS test case.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// #[nixos_test]
|
||||
/// fn my_test() {
|
||||
/// // test code here
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn nixos_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as ItemFn);
|
||||
let fn_name = &input.sig.ident;
|
||||
let fn_name_str = fn_name.to_string();
|
||||
|
||||
let expanded = quote! {
|
||||
#input
|
||||
|
||||
::nixos_test_framework::inventory::submit! {
|
||||
::nixos_test_framework::TestCase {
|
||||
name: #fn_name_str,
|
||||
test_fn: #fn_name,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
|
@ -9,12 +9,14 @@ ASTERINAS_DIR=$(realpath ${SCRIPT_DIR}/../..)
|
|||
DISTRO_DIR=$(realpath ${ASTERINAS_DIR}/distro)
|
||||
TARGET_DIR=${ASTERINAS_DIR}/target/nixos
|
||||
VERSION=$(cat ${ASTERINAS_DIR}/VERSION)
|
||||
# Accept config file name as parameter, default to "configuration.nix"
|
||||
CONFIG_FILE_NAME=${1:-"configuration.nix"}
|
||||
|
||||
mkdir -p ${TARGET_DIR}
|
||||
|
||||
nix-build ${DISTRO_DIR}/iso_image \
|
||||
--arg autoInstall ${AUTO_INSTALL} \
|
||||
--argstr test-command "${NIXOS_TEST_COMMAND}" \
|
||||
--argstr config-file-name "${CONFIG_FILE_NAME}" \
|
||||
--argstr extra-substituters "${RELEASE_SUBSTITUTER} ${DEV_SUBSTITUTER}" \
|
||||
--argstr extra-trusted-public-keys "${RELEASE_TRUSTED_PUBLIC_KEY} ${DEV_TRUSTED_PUBLIC_KEY}" \
|
||||
--argstr version ${VERSION} \
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
|||
ASTERINAS_DIR=$(realpath ${SCRIPT_DIR}/../..)
|
||||
ASTER_IMAGE_PATH=${ASTERINAS_DIR}/target/nixos/asterinas.img
|
||||
DISTRO_DIR=$(realpath ${ASTERINAS_DIR}/distro)
|
||||
CONFIG_PATH=${DISTRO_DIR}/etc_nixos/configuration.nix
|
||||
# Accept config file name as parameter, default to "configuration.nix"
|
||||
CONFIG_FILE_NAME=${1:-"configuration.nix"}
|
||||
CONFIG_PATH=${DISTRO_DIR}/etc_nixos/${CONFIG_FILE_NAME}
|
||||
|
||||
pushd $DISTRO_DIR
|
||||
nix-build aster_nixos_installer/default.nix \
|
||||
|
|
@ -16,7 +18,6 @@ nix-build aster_nixos_installer/default.nix \
|
|||
--argstr stage-2-hook "${NIXOS_STAGE_2_INIT}" \
|
||||
--argstr log-level "${LOG_LEVEL}" \
|
||||
--argstr console "${CONSOLE}" \
|
||||
--argstr test-command "${NIXOS_TEST_COMMAND}" \
|
||||
--argstr extra-substituters "${RELEASE_SUBSTITUTER} ${DEV_SUBSTITUTER}" \
|
||||
--argstr extra-trusted-public-keys "${RELEASE_TRUSTED_PUBLIC_KEY} ${DEV_TRUSTED_PUBLIC_KEY}"
|
||||
popd
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ MODE=$1
|
|||
SCRIPT_DIR=$(dirname "$0")
|
||||
ASTERINAS_DIR=$(realpath "${SCRIPT_DIR}/../..")
|
||||
|
||||
# Change to Asterinas root directory to ensure all scripts run from the correct location.
|
||||
cd "${ASTERINAS_DIR}"
|
||||
|
||||
# Base QEMU arguments
|
||||
BASE_QEMU_ARGS="qemu-system-x86_64 \
|
||||
-bios /root/ovmf/release/OVMF.fd \
|
||||
|
|
|
|||
Loading…
Reference in New Issue