move nixosTestLib to pkgs/testing
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
legacyPackages = {
|
||||
setupNixInNix = ''
|
||||
@@ -21,6 +21,25 @@
|
||||
fi
|
||||
'';
|
||||
|
||||
# NixOS test library combining port utils and clan VM test utilities
|
||||
nixosTestLib = pkgs.python3Packages.buildPythonPackage {
|
||||
pname = "nixos-test-lib";
|
||||
version = "1.0.0";
|
||||
format = "pyproject";
|
||||
src = lib.fileset.toSource {
|
||||
root = ./.;
|
||||
fileset = lib.fileset.unions [
|
||||
./pyproject.toml
|
||||
./nixos_test_lib
|
||||
];
|
||||
};
|
||||
nativeBuildInputs = with pkgs.python3Packages; [
|
||||
setuptools
|
||||
wheel
|
||||
];
|
||||
doCheck = false;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
3
pkgs/testing/nixos_test_lib/__init__.py
Normal file
3
pkgs/testing/nixos_test_lib/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""NixOS test library for clan VM testing"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
25
pkgs/testing/nixos_test_lib/machine.py
Normal file
25
pkgs/testing/nixos_test_lib/machine.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""VM machine management utilities"""
|
||||
|
||||
|
||||
def create_test_machine(oldmachine, qemu_test_bin: str, **kwargs):
|
||||
"""Create a new test machine from an installed disk image"""
|
||||
start_command = [
|
||||
f"{qemu_test_bin}/bin/qemu-kvm",
|
||||
"-cpu",
|
||||
"max",
|
||||
"-m",
|
||||
"3048",
|
||||
"-virtfs",
|
||||
"local,path=/nix/store,security_model=none,mount_tag=nix-store",
|
||||
"-drive",
|
||||
f"file={oldmachine.state_dir}/target.qcow2,id=drive1,if=none,index=1,werror=report",
|
||||
"-device",
|
||||
"virtio-blk-pci,drive=drive1",
|
||||
"-netdev",
|
||||
"user,id=net0",
|
||||
"-device",
|
||||
"virtio-net-pci,netdev=net0",
|
||||
]
|
||||
machine = create_machine(start_command=" ".join(start_command), **kwargs)
|
||||
driver.machines.append(machine)
|
||||
return machine
|
||||
70
pkgs/testing/nixos_test_lib/nix_setup.py
Normal file
70
pkgs/testing/nixos_test_lib/nix_setup.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Nix store setup utilities for VM tests"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def setup_nix_in_nix(closure_info: str) -> None:
|
||||
"""Set up Nix store inside test environment
|
||||
|
||||
Args:
|
||||
closure_info: Path to closure info directory containing store-paths file
|
||||
"""
|
||||
tmpdir = Path(os.environ.get("TMPDIR", "/tmp"))
|
||||
|
||||
# Remove NIX_REMOTE if present (we don't have any nix daemon running)
|
||||
if "NIX_REMOTE" in os.environ:
|
||||
del os.environ["NIX_REMOTE"]
|
||||
|
||||
# Set NIX_CONFIG globally to disable substituters for speed
|
||||
os.environ["NIX_CONFIG"] = "substituters = \ntrusted-public-keys = "
|
||||
|
||||
# Set up environment variables for test environment
|
||||
os.environ["HOME"] = tmpdir
|
||||
os.environ["NIX_STATE_DIR"] = f"{tmpdir}/nix"
|
||||
os.environ["NIX_CONF_DIR"] = f"{tmpdir}/etc"
|
||||
os.environ["IN_NIX_SANDBOX"] = "1"
|
||||
os.environ["CLAN_TEST_STORE"] = f"{tmpdir}/store"
|
||||
os.environ["LOCK_NIX"] = f"{tmpdir}/nix_lock"
|
||||
|
||||
# Create necessary directories
|
||||
Path(f"{tmpdir}/nix").mkdir(parents=True, exist_ok=True)
|
||||
Path(f"{tmpdir}/etc").mkdir(parents=True, exist_ok=True)
|
||||
Path(f"{tmpdir}/store").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Set up Nix store if closure info is provided
|
||||
if closure_info and Path(closure_info).exists():
|
||||
store_paths_file = Path(closure_info) / "store-paths"
|
||||
if store_paths_file.exists():
|
||||
with store_paths_file.open() as f:
|
||||
store_paths = f.read().strip().split("\n")
|
||||
|
||||
# Copy store paths to test store
|
||||
for store_path in store_paths:
|
||||
if store_path.strip():
|
||||
dest_path = f"{tmpdir}/store{store_path}"
|
||||
if not os.path.exists(dest_path):
|
||||
# Create parent directories
|
||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||
# Copy the store path
|
||||
if os.path.isdir(store_path):
|
||||
shutil.copytree(store_path, dest_path, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(store_path, dest_path)
|
||||
|
||||
# Load Nix database
|
||||
registration_file = Path(closure_info) / "registration"
|
||||
if registration_file.exists():
|
||||
env = os.environ.copy()
|
||||
env["NIX_REMOTE"] = f"local?store={tmpdir}/store"
|
||||
|
||||
with registration_file.open() as f:
|
||||
subprocess.run(
|
||||
["nix-store", "--load-db"],
|
||||
input=f.read(),
|
||||
text=True,
|
||||
env=env,
|
||||
check=True,
|
||||
)
|
||||
51
pkgs/testing/nixos_test_lib/port.py
Normal file
51
pkgs/testing/nixos_test_lib/port.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Port management utilities for NixOS installation tests."""
|
||||
|
||||
import socket
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
|
||||
class PortUtilsError(Exception):
|
||||
"""Port utils related errors."""
|
||||
|
||||
|
||||
def find_free_port() -> int:
|
||||
"""Find a free port on the host."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("", 0))
|
||||
return s.getsockname()[1]
|
||||
|
||||
|
||||
def check_host_port_open(port: int) -> bool:
|
||||
"""Verify port forwarding is working by checking if the host port is listening."""
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(1)
|
||||
result = s.connect_ex(("localhost", port))
|
||||
return result == 0
|
||||
except Exception as e:
|
||||
print(f"Port check failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def setup_port_forwarding(target: Any, host_port: int) -> None:
|
||||
"""Set up port forwarding and wait for it to be ready."""
|
||||
print(f"Setting up port forwarding from host port {host_port} to guest port 22")
|
||||
target.forward_port(host_port=host_port, guest_port=22)
|
||||
|
||||
# Give the port forwarding time to establish
|
||||
time.sleep(2)
|
||||
|
||||
# Wait up to 30 seconds for the port to become available
|
||||
port_ready = False
|
||||
for i in range(30):
|
||||
if check_host_port_open(host_port):
|
||||
port_ready = True
|
||||
print(f"Host port {host_port} is now listening")
|
||||
break
|
||||
print(f"Waiting for host port {host_port} to be ready... attempt {i + 1}/30")
|
||||
time.sleep(1)
|
||||
|
||||
if not port_ready:
|
||||
msg = f"Host port {host_port} never became available for forwarding"
|
||||
raise PortUtilsError(msg)
|
||||
0
pkgs/testing/nixos_test_lib/py.typed
Normal file
0
pkgs/testing/nixos_test_lib/py.typed
Normal file
48
pkgs/testing/nixos_test_lib/ssh.py
Normal file
48
pkgs/testing/nixos_test_lib/ssh.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""SSH and test setup utilities"""
|
||||
|
||||
import subprocess
|
||||
from typing import NamedTuple
|
||||
|
||||
from .nix_setup import setup_nix_in_nix
|
||||
from .port import find_free_port, setup_port_forwarding
|
||||
|
||||
|
||||
class TestEnvironment(NamedTuple):
|
||||
host_port: int
|
||||
ssh_key: str
|
||||
flake_dir: str
|
||||
|
||||
|
||||
def setup_test_environment(
|
||||
target,
|
||||
temp_dir: str,
|
||||
closure_info: str,
|
||||
assets_ssh_privkey: str,
|
||||
clan_core_for_checks: str,
|
||||
) -> TestEnvironment:
|
||||
"""Set up common test environment including SSH, port forwarding, and flake setup
|
||||
|
||||
Returns:
|
||||
TestEnvironment with host_port, ssh_key, and flake_dir
|
||||
"""
|
||||
# Run setup function
|
||||
setup_nix_in_nix(closure_info)
|
||||
|
||||
host_port = find_free_port()
|
||||
target.wait_for_unit("sshd.service")
|
||||
target.wait_for_open_port(22)
|
||||
|
||||
setup_port_forwarding(target, host_port)
|
||||
|
||||
ssh_key = os.path.join(temp_dir, "id_ed25519")
|
||||
with open(ssh_key, "w") as f:
|
||||
with open(assets_ssh_privkey) as src:
|
||||
f.write(src.read())
|
||||
os.chmod(ssh_key, 0o600)
|
||||
|
||||
# Copy test flake to temp directory
|
||||
flake_dir = os.path.join(temp_dir, "test-flake")
|
||||
subprocess.run(["cp", "-r", clan_core_for_checks, flake_dir], check=True)
|
||||
subprocess.run(["chmod", "-R", "+w", flake_dir], check=True)
|
||||
|
||||
return TestEnvironment(host_port, ssh_key, flake_dir)
|
||||
44
pkgs/testing/pyproject.toml
Normal file
44
pkgs/testing/pyproject.toml
Normal file
@@ -0,0 +1,44 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "nixos-test-lib"
|
||||
version = "1.0.0"
|
||||
description = "NixOS test utilities for clan VM testing"
|
||||
authors = [
|
||||
{name = "Clan Core Team"}
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"mypy",
|
||||
"ruff"
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["nixos_test_lib*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"nixos_test_lib" = ["py.typed"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.12"
|
||||
strict = true
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py312"
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["ALL"]
|
||||
ignore = [
|
||||
"D", # docstrings
|
||||
"ANN", # type annotations
|
||||
"COM812", # trailing comma
|
||||
"ISC001", # string concatenation
|
||||
]
|
||||
Reference in New Issue
Block a user