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
4 changes: 2 additions & 2 deletions .github/workflows/nanvix-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
uses: nanvix/workflows/.github/workflows/nanvix-ci.yml@v2.0.1
with:
zutil-version: "v0.8.5"
zutil-version: "v0.9.0"
platforms: '["microvm"]'
process-modes: '["multi-process","single-process","standalone"]'
memory-sizes: '["256mb"]'
Expand All @@ -36,7 +36,7 @@
skip-full-test-modes: '["multi-process","single-process","standalone"]'
caller-event-name: ${{ github.event_name }}
windows-test: true
docker-image: "ghcr.io/nanvix/toolchain-gcc:sha-34a3641" # yamllint disable-line rule:line-length

Check warning on line 39 in .github/workflows/nanvix-ci.yml

View workflow job for this annotation

GitHub Actions / ci / Format & Lint

39:64 [comments] too few spaces before comment
secrets:
GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
DISPATCH_TOKEN: ${{ secrets.DISPATCH_TOKEN }}
Expand All @@ -45,7 +45,7 @@
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
uses: nanvix/workflows/.github/workflows/nanvix-ci.yml@v2.0.1
with:
zutil-version: "v0.8.5"
zutil-version: "v0.9.0"
platforms: '["microvm"]'
process-modes: '["multi-process","single-process","standalone"]'
memory-sizes: '["256mb"]'
Expand All @@ -53,7 +53,7 @@
skip-full-test-modes: '["multi-process","single-process","standalone"]'
caller-event-name: 'schedule'
windows-test: true
docker-image: "ghcr.io/nanvix/toolchain-gcc:sha-34a3641" # yamllint disable-line rule:line-length

Check warning on line 56 in .github/workflows/nanvix-ci.yml

View workflow job for this annotation

GitHub Actions / ci / Format & Lint

56:64 [comments] too few spaces before comment
secrets:
GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
DISPATCH_TOKEN: ${{ secrets.DISPATCH_TOKEN }}
31 changes: 30 additions & 1 deletion .nanvix/Makefile.nanvix
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,36 @@ test-integration: $(STATICLIB)
@echo " PASS: libxml2 integration tests"

test-functional: test-integration
@echo " PASS: libxml2 functional tests (library only, no runtime tests)"
@echo "=== libxml2 functional tests ==="
ifeq ($(PROCESS_MODE),standalone)
@echo " NOTE: Standalone functional tests are handled via ./z.sh."
@echo " Delegating to: ./z.sh test test-functional"
@./z.sh test test-functional
else
ifdef CONFIG_NANVIX_DOCKER
@echo " Running test_libxml2$(EXE) via nanvixd.elf (Docker, platform=$(PLATFORM), mode=$(PROCESS_MODE))..."
$(RUN_CMD) sh -c '\
TMPDIR=$$(mktemp -d /tmp/nanvix-libxml2-XXXXXX) && \
trap "rm -rf $$TMPDIR" EXIT && \
mkdir -p $$TMPDIR/ramfs/tmp && \
$(EFFECTIVE_SYSROOT)/bin/mkramfs.elf -o $$TMPDIR/rootfs.img $$TMPDIR/ramfs/ && \
timeout --foreground 120 $(EFFECTIVE_SYSROOT)/bin/nanvixd.elf \
-bin-dir $(EFFECTIVE_SYSROOT)/bin -ramfs $$TMPDIR/rootfs.img \
-- $(DOCKER_WORKSPACE_PATH)/test_libxml2$(EXE)'
@echo " PASS: test_libxml2 (exit code 0)"
else
@echo " Running test_libxml2$(EXE) via nanvixd.elf (platform=$(PLATFORM), mode=$(PROCESS_MODE))..."
@TMPDIR=$$(mktemp -d /tmp/nanvix-libxml2-XXXXXX) && \
trap "rm -rf $$TMPDIR" EXIT && \
mkdir -p $$TMPDIR/ramfs/tmp && \
"$(SYSROOT_PATH)/bin/mkramfs.elf" -o $$TMPDIR/rootfs.img $$TMPDIR/ramfs/ && \
cd "$(SYSROOT_PATH)" && timeout --foreground 120 ./bin/nanvixd.elf \
-bin-dir ./bin -ramfs $$TMPDIR/rootfs.img \
-- $(abspath test_libxml2$(EXE))
@echo " PASS: test_libxml2 (exit code 0)"
endif
endif
@echo " PASS: libxml2 functional tests"

test: test-smoke test-integration test-functional
@echo "=== All libxml2 tests PASSED ==="
Expand Down
2 changes: 1 addition & 1 deletion .nanvix/nanvix.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "libxml2"
version = "2.12.9"
nanvix-version = "0.13.17"
nanvix-version = "0.14.3"
Comment thread
ppenna marked this conversation as resolved.

[builds]
[builds.matrix]
Expand Down
216 changes: 161 additions & 55 deletions .nanvix/z.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
./z clean # Remove build artifacts
"""

import subprocess
import shutil
import sys
import tempfile
from pathlib import Path

from nanvix_zutil import (
Expand Down Expand Up @@ -95,35 +96,125 @@ def build(self) -> None:
def test(self) -> None:
"""Run the libxml2 test suite.

On non-Windows, delegates to the Makefile (smoke + integration + functional).
On Windows, runs test binaries from the repo root via nanvixd.exe natively.
Smoke and integration tests are always delegated to the Makefile.
The functional test in standalone mode is handled in Python via
make_initrd so that initrd creation is shared across platforms.
"""
if IS_WINDOWS:
self._run_tests_windows()
return
targets = self.targets if self.targets else ["test"]
self.run(*self._make_args(*targets), cwd=self.repo_root)

if self.config.deployment_mode == "standalone":
targets = self.targets if self.targets else []
# Targets that require the Python functional path.
_functional_targets = {"test", "test-functional"}
needs_functional = not targets or bool(set(targets) & _functional_targets)
# Delegate non-functional targets to the Makefile.
make_targets = [t for t in targets if t not in _functional_targets]
if not targets:
make_targets = ["test-smoke", "test-integration"]
elif needs_functional and not make_targets:
# Ensure Makefile prerequisites run when only functional
# targets are requested (build + smoke/integration).
if "test" in targets:
make_targets = ["test-smoke", "test-integration"]
else:
make_targets = ["test-integration"]
if make_targets:
self.run(*self._make_args(*make_targets), cwd=self.repo_root)
if needs_functional:
self._run_functional_standalone()
else:
targets = self.targets if self.targets else ["test"]
Comment thread
ppenna marked this conversation as resolved.
self.run(*self._make_args(*targets), cwd=self.repo_root)

def _get_sysroot(self) -> str:
"""Return the sysroot path or fatal if unset."""
sysroot = self.config.get(CFG_SYSROOT, "")
if not sysroot:
log.fatal(
f"{CFG_SYSROOT} is not set.",
code=EXIT_MISSING_DEP,
hint="Run `./z setup` first to download the sysroot.",
)
return sysroot

def _run_functional_standalone(self) -> None:
"""Run the standalone functional test using make_initrd.

Creates an initrd bundling test_libxml2.elf with system daemons via
make_initrd, and a ramfs providing /tmp for test file output.
"""
binary = self.repo_root / "test_libxml2.elf"
if not binary.is_file():
log.fatal(
"test_libxml2.elf not found.",
code=EXIT_MISSING_DEP,
hint="Run `./z build` first.",
)

print("=== libxml2 functional tests ===")
print(" Running test_libxml2.elf via nanvixd standalone...")

sysroot_path = Path(self._get_sysroot())
mkramfs = sysroot_path / "bin" / "mkramfs.elf"

# Bundle test_libxml2.elf + daemons into an initrd.
initrd = self.make_initrd("test_libxml2.elf")

try:
with tempfile.TemporaryDirectory(prefix="nanvix_libxml2_") as tmpdir:
tmpdir_path = Path(tmpdir)
ramfs_dir = tmpdir_path / "ramfs"
ramfs_dir.mkdir()
(ramfs_dir / "tmp").mkdir(exist_ok=True)
ramfs_img = tmpdir_path / "rootfs.img"

self.run(
str(mkramfs),
"-o",
str(ramfs_img),
str(ramfs_dir),
docker=False,
)

# self.run() raises SystemExit on non-zero exit code,
# and nanvixd propagates the guest's exit status, so
# reaching the PASS line guarantees exit code 0.
self.run(
str(sysroot_path / "bin" / "nanvixd.elf"),
"-bin-dir",
str(sysroot_path / "bin"),
"-ramfs",
str(ramfs_img),
"--",
str(initrd),
docker=False,
timeout=120,
)
finally:
if initrd.exists():
initrd.unlink()

print(" PASS: test_libxml2 standalone (exit code 0)")
print(" PASS: libxml2 functional tests")
print("=== All libxml2 tests PASSED ===")
Comment thread
ppenna marked this conversation as resolved.

def _run_tests_windows(self) -> None:
"""Run tests natively on Windows.

Only standalone mode is tested on Windows; multi-process and
single-process require linuxd, which is Linux-only.
single-process require linuxd, which is Linux-only. Uses
make_initrd to bundle each test binary with system daemons,
and a ramfs providing /tmp for any test I/O.
"""
if self.config.deployment_mode != "standalone":
print(
f"Skipping tests on Windows for mode '{self.config.deployment_mode}' (requires linuxd)."
)
return

sysroot = self.config.get(CFG_SYSROOT, "")
if not sysroot:
log.fatal(
f"{CFG_SYSROOT} is not set.",
code=EXIT_MISSING_DEP,
hint="Run `./z setup` first.",
)
sysroot_path = Path(sysroot)
sysroot_path = Path(self._get_sysroot())
nanvixd = sysroot_path / "bin" / "nanvixd.exe"
mkramfs = sysroot_path / "bin" / "mkramfs.exe"
if not nanvixd.is_file():
Expand Down Expand Up @@ -157,56 +248,71 @@ def _run_tests_windows(self) -> None:
hint="Build the test binaries first (run `./z build`) and then rerun `./z test`.",
)

import shutil
import tempfile

failed: list[str] = []
for binary in test_binaries:
name = binary.stem
print(f"RUN {name}...")
with tempfile.TemporaryDirectory(prefix=f"nanvix_{name}_") as tmpdir:
tmpdir_path = Path(tmpdir)
ramfs_dir = tmpdir_path / "ramfs"
ramfs_dir.mkdir()
(ramfs_dir / "tmp").mkdir(exist_ok=True)
shutil.copy2(binary, ramfs_dir / binary.name)
ramfs_img = tmpdir_path / f"rootfs_{name}.img"
try:
subprocess.run(
[str(mkramfs.resolve()), "-o", str(ramfs_img), str(ramfs_dir)],
check=True,
timeout=60,
)
except subprocess.CalledProcessError as e:
print(f"FAIL {name} (mkramfs exit code {e.returncode})")
failed.append(name)
continue
except subprocess.TimeoutExpired:
print(f"FAIL {name} (mkramfs timeout)")
failed.append(name)
continue
try:
result = subprocess.run(
[
str(nanvixd.resolve()),
# make_initrd resolves binaries relative to repo_root;
# copy the ELF there temporarily unless it already lives there.
# This is a constraint of the make_initrd API; cleanup is
# handled in the finally block below.
repo_elf = self.repo_root / binary.name
copied_elf = False
initrd: Path | None = None
try:
if binary.resolve() != repo_elf.resolve():
if repo_elf.exists():
raise FileExistsError(
f"refusing to clobber existing {repo_elf}"
)
shutil.copy2(binary, repo_elf)
copied_elf = True
initrd = self.make_initrd(binary.name)
Comment thread
ppenna marked this conversation as resolved.
with tempfile.TemporaryDirectory(prefix=f"nanvix_{name}_") as tmpdir:
tmpdir_path = Path(tmpdir)
ramfs_dir = tmpdir_path / "ramfs"
ramfs_dir.mkdir()
(ramfs_dir / "tmp").mkdir(exist_ok=True)
ramfs_img = tmpdir_path / f"rootfs_{name}.img"

try:
self.run(
str(mkramfs),
"-o",
str(ramfs_img),
str(ramfs_dir),
docker=False,
)
except SystemExit:
print(f"FAIL {name} (mkramfs failed)")
failed.append(name)
continue

try:
self.run(
str(nanvixd),
"-bin-dir",
str((sysroot_path / "bin").resolve()),
str(sysroot_path / "bin"),
"-ramfs",
str(ramfs_img),
"--",
f"./{binary.name}",
],
stdin=subprocess.DEVNULL,
timeout=120,
)
if result.returncode != 0:
print(f"FAIL {name} (exit code {result.returncode})")
str(initrd),
docker=False,
timeout=120,
)
except SystemExit:
print(f"FAIL {name} (nanvixd non-zero exit)")
failed.append(name)
else:
print(f"OK {name}")
except subprocess.TimeoutExpired:
print(f"FAIL {name} (timeout)")
failed.append(name)
continue
print(f"OK {name}")
except FileExistsError as e:
print(f"FAIL {name} ({e})")
failed.append(name)
finally:
if initrd is not None and initrd.exists():
initrd.unlink()
if copied_elf and repo_elf.exists():
repo_elf.unlink()

if failed:
msg = " ".join(failed)
Expand Down
10 changes: 8 additions & 2 deletions z.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $zutilVersion = if ($env:NANVIX_ZUTIL_VERSION) {
$env:NANVIX_ZUTIL_VERSION
}
else {
"0.8.5"
"0.9.0"
}
$zutilVersion = $zutilVersion -replace "^v", ""

Expand All @@ -42,9 +42,10 @@ catch {
}

function Bootstrap {
param([string]$Reason = "not found")
# Pin nanvix-zutil version for reproducible bootstrapping.
# Override with NANVIX_ZUTIL_VERSION env var if needed.
Write-Information "nanvix-zutil not found -- bootstrapping nanvix-zutil==${zutilVersion}..." -InformationAction Continue
Write-Information "nanvix-zutil ${Reason} -- bootstrapping nanvix-zutil==${zutilVersion}..." -InformationAction Continue

$wheelUrl = "https://github.com/nanvix/zutils/releases/download/v${zutilVersion}/nanvix_zutil-${zutilVersion}-py3-none-any.whl"

Expand Down Expand Up @@ -115,6 +116,11 @@ else {
$bin = "nanvix-zutil"
if ($zutilGlobalVersion -ne "nanvix-zutil ${zutilVersion}") {
Write-Warning "nanvix-zutil global install does not match expected version. Expected ${zutilVersion}, found ${zutilGlobalVersion}."
Bootstrap "version mismatch"
if (-not (Test-Path $venvZutil)) {
throw "Bootstrap completed but $venvZutil not found."
}
$bin = $venvZutil
}
}

Expand Down
7 changes: 5 additions & 2 deletions z.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

set -euo pipefail

PINNED_VERSION="0.8.5"
PINNED_VERSION="0.9.0"
RAW_ZUTIL_VERSION="${NANVIX_ZUTIL_VERSION:-$PINNED_VERSION}"
ZUTIL_VERSION="${RAW_ZUTIL_VERSION#v}"
REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
Expand All @@ -31,7 +31,8 @@ ZUTIL_GLOBAL_VERSION="$(nanvix-zutil --version 2>/dev/null || true)"
function bootstrap() {
# Pin nanvix-zutil version for reproducible bootstrapping.
# Override with NANVIX_ZUTIL_VERSION env var if needed.
echo "nanvix-zutil not found -- bootstrapping nanvix-zutil==${ZUTIL_VERSION}..." >&2
local reason="${1:-not found}"
echo "nanvix-zutil ${reason} -- bootstrapping nanvix-zutil==${ZUTIL_VERSION}..." >&2

if ! command -v python3 &>/dev/null; then
echo "Error: python3 not found. Install Python 3 and ensure python3 is on PATH." >&2
Expand Down Expand Up @@ -69,6 +70,8 @@ else
BIN="nanvix-zutil"
if [ "$ZUTIL_GLOBAL_VERSION" != "nanvix-zutil ${ZUTIL_VERSION}" ]; then
echo "Warning: nanvix-zutil global install does not match expected version. Expected ${ZUTIL_VERSION}, found ${ZUTIL_GLOBAL_VERSION}." >&2
bootstrap "version mismatch"
BIN="$VENV_BIN"
fi
fi

Expand Down
Loading