Valheim.Testing.NativeSmoke
Reference source:
f6459d0c772b. For the installed package version, use the published package table.
API reference. This package is the valheim-test .NET tool; its supported surface is the commands below, not a .NET library.
valheim-test checks that a mod loads in the real game. Each run stages a disposable loader profile of a Steam install, keeps private evidence and removes what it made; the install is only read. New to the tool? Start with Your first server load check.
Install
dotnet tool install Valheim.Testing.NativeSmoke --version 0.1.0 --global
The tool needs the .NET 10 SDK. If a Homebrew .NET on macOS cannot start the tool, set DOTNET_ROOT to the directory dotnet --info reports.
Commands
| Command | Does |
|---|---|
start |
Loads a client-side mod in an owned client that hosts a disposable world |
server-load |
Loads a server-side mod on an owned dedicated server; one clean client joins |
server-load-ab |
Runs server-load with and without one mod, to isolate a load interaction |
server-load-phases |
Runs ordered server-only fixture bakes |
build |
Builds a mod against the selected install's publicized assemblies, privately |
init |
Creates an editable test project that reruns a start or server-load run |
finish |
Asks a held run to finish and clean up |
cli |
Sends one strictly pinned command to a running owned client or server |
env |
Lists this machine's environments, checks it, and clears what runs left |
session check |
Checks a session file and, with --hosts, its hosts |
detach |
Experimental: a held Windows dedicated server that outlives the terminal |
steam-runtime capture |
Pins SteamCMD's macOS server libraries for a Mac server profile |
valheim-test help lists every command with its usage.
Exit codes
| Code | start, server-load, server-load-ab |
env, session check |
cli |
|---|---|---|---|
| 0 | Passed | Nothing to refuse or recover | ValheimCLI accepted the command |
| 1 | The run failed | Not used | Not accepted, or the screenshot incomplete |
| 2 | Usage error (not server-load-ab) |
Usage error | Usage error |
| 3 | Refused, or the outcome is unknown | Something refused or left to recover | Refused; no command sent |
server-load-ab reports a usage error, --hold and --preflight-only included, as REFUSED: without a usage line, and exits 3. build, init and finish exit 0 when done, 2 on a usage error and 3 when refused.
Run options
start, server-load, server-load-ab and build share these options. A command refuses an option it does not take as Unknown option. "Repeat": give it more than once.
Mod inputs
| Option | Taken by | Does |
|---|---|---|
--mod DLL |
start, server-load, server-load-ab | A mod to load. Repeat for a set. Without it, start and server-load pick the current project's one built plugin |
--project MOD.csproj |
start, server-load, build | Builds the mod privately first; leave out --mod and --build-inputs |
--build-inputs FILE |
start, server-load | The build-inputs.json from build; the run refuses a changed mod or reference |
--dependency NAME=DLL |
start, server-load, build | A third-party compile reference for --project. Repeat |
--search-root DIR\|DLL |
all four | A folder or exact DLL to search for dependencies. Repeat |
--optional-reference ASSEMBLY |
start, server-load, server-load-ab | Confirms a reference that is deliberately absent. Repeat |
--scenario TEST.dll, --scenario-project TEST.csproj |
start, server-load | A mod-owned scenario; one or the other (scenarios) |
--output NEW_DIR |
all four | A new folder outside the mod project. Default: a new folder under valheim-test-runs/ in ValheimTesting's data folder |
The dependency resolver chooses files and writes dependencies.lock.json. It never downloads a mod.
Machine and installs
| Option | Taken by | Does |
|---|---|---|
--inventory FILE |
all four | Uses a file's environments instead of this machine |
--game DIR |
start, build | The client install to use |
--client-env NAME |
all four | The client environment to use from the inventory |
--server DIR |
server-load, server-load-ab, build | The dedicated-server install to use |
--server-env NAME |
server-load, server-load-ab, build | The server environment to use from the inventory |
--client DIR |
server-load, server-load-ab | The clean client's install |
--client-architecture x64\|arm64 |
start, server-load, server-load-ab | The macOS client slice (Apple Silicon) |
--join HOST:PORT |
server-load, server-load-ab | The address a client on another machine joins |
Loader and ValheimCLI
| Option | Taken by | Does |
|---|---|---|
--loader-package FILE |
server-load, server-load-ab, build | A reviewed loader for the server's profile |
--client-loader-package FILE |
all four | A reviewed loader for the client's profile |
--cli-manifest FILE --cli-files DIR |
start, server-load, server-load-ab | A ValheimCLI core and packs from one build |
--steam-runtime-package FILE |
server-load | A Mac server's pinned SteamCMD libraries |
Timing, logs and holds
| Option | Taken by | Does |
|---|---|---|
--join-seconds 10..900 |
start, server-load, server-load-ab | The deadline to enter the world. Default 180 |
--server-startup-seconds 1..3600 |
server-load, server-load-ab | The deadline for the server to become ready. Default 300 |
--expected-log-error EXACT_HEADER --expected-log-reason REASON |
start, server-load, server-load-ab | Admits one known BepInEx error by its whole header line, with a reason |
--hold |
start, server-load | Keeps a passing run's game open (hold) |
start
valheim-test start --mod /path/to/MyMod.dll
start loads a client-side mod in an owned Windows or macOS client that hosts the packaged world with the packaged character. It prints run ID:, a log-scan line and a PASS or FAIL result line.
| Option | Does |
|---|---|
--source COMMIT |
Records the mod build's source commit |
--compare-mod DLL --compare-source COMMIT |
Runs a second build of the first mod against the same world, as arms before and after |
Both compared builds must declare the same plugin identities. The client needs its desktop session and a running Steam client. Through SSH on Windows, start launches the client in the signed-in desktop through a scheduled task; on macOS, run it from the unlocked console.
server-load
valheim-test server-load --mod /path/to/ServerMod.dll
server-load prints the server and client it chose, writes campaign.json and runs the read-only host preflight before anything is copied. Then one clean client without the mod joins the owned server.
| Option | Does |
|---|---|
--server-only |
Runs no client. Prints SERVER_LOAD_PASS instead of SERVER_JOIN_PASS |
--preflight-only |
Stops after PREFLIGHT PASSED |
--config FILE |
A config file for the server. Repeat |
--plugin-file FILE, --plugin-dir DIR |
A mod's assets beside its DLL; DLLs are refused. Repeat |
--adapter DLL |
A built test adapter, instead of building the bundled one |
--adapter-project ADAPTER.csproj |
Builds a mod's own adapter; needs --session-capability and --session-token-variable |
--adapter-property NAME={mod}\|{cli} |
Maps an adapter project property to the pinned mod or ValheimCLI DLL. Repeat |
--session-capability OWNER/session --session-token-variable NAME |
The custom adapter's capability and token variable |
--world-fixture DIR |
Loads a pinned world fixture instead of the packaged world |
--bake-fixture NEW_DIR |
Saves the world as a new fixture; see Bake a fixture |
--assert-command TEXT --assert-line PREFIX |
One server command whose reply proves the mod is ready; needs --server-only |
--before-save-command TEXT --before-save-line PREFIX |
One change before a bake saves |
The result line is SERVER_JOIN_PASS, SERVER_LOAD_PASS or SERVER_LOAD_FAIL, with the evidence's size and path. The server must be on this machine. Building the bundled adapter needs NuGet.org; offline, pass --adapter DLL.
server-load-ab
valheim-test server-load-ab \
--mod /path/to/FirstMod.dll --mod /path/to/SecondMod.dll \
--remove-mod /path/to/SecondMod.dll \
--output /path/to/new-run
--remove-mod DLL names one of at least two --mod DLLs; --output is required. It refuses --hold, --preflight-only, --project, --build-inputs, --dependency, --scenario, --scenario-project, --steam-runtime-package, --world-fixture and the adapter-project, bake and assertion options. The full set runs in before/ and the set without that mod in after/. The result line is SERVER_MODSET_AB_PASS or SERVER_MODSET_AB_FAIL.
server-load-phases
valheim-test server-load-phases --plan FILE --output NEW_DIR
Both paths must be absolute. The plan and its output are on Bake a fixture.
build
valheim-test build --project /path/to/MyMod/MyMod.csproj
build publicizes the selected install's assemblies into a new private folder and builds the mod against them, never into a game.
| Option | Does |
|---|---|
--role client\|server |
Builds against the client (default, as start selects it) or the dedicated server (as server-load selects it) |
With --role server it takes --server, --server-env and --loader-package; otherwise --game, --client-env and --client-loader-package. It prints Built mod: and Build inputs:. Pass both to a run with --mod DLL --build-inputs FILE.
It sets VALHEIM_INSTALL, VALHEIM_MANAGED, ValheimPath, ValheimManaged, BEPINEX_CORE, BepInExCore, PUBLICIZED_PATH, PublicizedAssembliesPath and CopyOutputDLLPath (a private folder), and empties CopyOutputDLLPath2 and CopyOutputDLLPath3. Guard any other copy into a game first. Give a missing third-party reference with --dependency or --search-root.
Your own scenario
A mod's test project can hold one public scenario that runs on the prepared game:
| Interface | Runs on | Example |
|---|---|---|
IOneShotServerScenario |
server-load; RequiresClient false needs --server-only |
MetadataScenario.cs |
IOneShotHostedScenario |
start |
HostedWorldScenario.cs |
IOneShotScenarioPreflight |
Either; ValidateInputs() runs before anything is staged |
context.RequireInputs<T>() reads its result |
One command builds the mod, the scenario and its adapter, then runs them:
valheim-test server-load --server-only \
--project /path/to/MyMod/MyMod.csproj \
--scenario-project /path/to/MyMod.SystemTests/MyMod.SystemTests.csproj \
--adapter-project /path/to/MyMod.TestAdapter/MyMod.TestAdapter.csproj \
--adapter-property 'MyModDll={mod}' \
--session-capability mymod.testing/session \
--session-token-variable MYMOD_TEST_SESSION_TOKEN
A scenario owns its assertions, so it refuses --hold, --bake-fixture and --assert-command.
init
valheim-test init --output NEW_DIR
valheim-test init server --output NEW_DIR
init creates an editable consumer project for a start run's regression.json, or with server for a server-load run's campaign.json. It pins the Valheim.Testing.Game this tool carries, and with server also Valheim.Testing.GameSessions, and restores from NuGet.org. A tool built from source refuses, because NuGet.org does not serve its version; --candidate-feed LOCAL_PACKAGE_DIR is for maintainers (CONTRIBUTING).
hold and finish
valheim-test server-load --mod /path/to/ServerMod.dll --hold
valheim-test finish --run ID
--hold keeps the owned game running after the checks pass; a failing run is never held. It prints HELD run with the run ID and the cli commands that reach the game. finish --run ID, or Ctrl+C on the runner, stops and cleans up. If a held game exits, the run fails.
cli
valheim-test cli --evidence RUN_EVIDENCE --phase world --command TEXT
| Option | Does |
|---|---|
--evidence RUN_EVIDENCE |
The evidence folder holding the role's lease |
--role client\|server |
Needed only when the folder holds both leases |
--phase menu\|world |
Which published pins to verify first |
--expect-strict FILE |
Other strict pins, in place of --phase |
--command TEXT |
One game command; cli_screenshot takes only a size, 1 to 4 |
--timeout-seconds 1..15 |
The command deadline. Default 5 |
| Run | Evidence folder |
|---|---|
start |
RUN/evidence/smoke |
server-load, the server |
RUN/evidence |
server-load, the joined client |
RUN/evidence/client |
It checks the exact process, the port's owner and the strict pins, records the command in owned-cli-command-*.jsonl, sends it once and never retries.
env
| Command | Does |
|---|---|
env list [--inventory FILE] [--json] |
Prints each environment, detected: and NOT FOUND: lines. No verdict |
env preflight [--inventory FILE] [--json] |
Checks this machine before a one-off run. Prints ELIGIBLE or REFUSED |
env status [--inventory FILE] [--json] |
Lists what earlier runs left, from their journals. Changes nothing |
env recover --run ID [--inventory FILE] [--json] |
Removes what it can prove the run left, except what it kept on purpose |
env recover --run ID --launch-gone |
Settles a run whose only unprovable items are launches you confirmed are gone |
env teardown --run ID [--inventory FILE] [--json] |
As recover, and also removes what the run kept on purpose |
env teardown --copy PATH [--inventory FILE] |
Removes one copy made before runs journalled their copies |
env teardown --run ID --machine-gone [--inventory FILE] |
Releases the Steam leases of a run whose machine is gone for good |
env preflight checks this machine only (its refusals). env status prints each run that left something with its state:
| State | Means |
|---|---|
LIVE |
Its runner still runs; nothing is touched |
UNKNOWN |
It never journalled its end and its runner cannot be checked from here |
RECOVERABLE |
It left items that are provably its own; env recover --run ID |
UNRECOVERABLE |
Something cannot be proven gone or its own |
KEPT |
It ended and kept a runtime or lease on purpose; env teardown --run ID |
The last line is N runs ended and left nothing. Nothing was changed. env status exits 0 only when every run ended and left nothing; a KEPT run, an unjournalled copy or an unreadable host exits 3. env recover that keeps a runtime on purpose names env teardown --run ID and exits 3. Debug a native run says when to use each.
session check
valheim-test session check SESSION [--hosts] [--json]
session check reports every problem with a session.json's inputs and actor assignment without contacting a host, then ELIGIBLE or REFUSED lines. --hosts adds the read-only host checks (installs, loaders, ports, free space, conflicting games, journals, signed-in Steam accounts) and prints READY when they pass. Sessions: GameSessions.
detach
valheim-test detach server-load --server 'C:\Games\Valheim dedicated server' --mod 'C:\Mods\MyMod.dll' --server-only --output 'C:\ValheimTesting\runs\inspection-1'
valheim-test detach status
$evidence = 'C:\ValheimTesting\runs\inspection-1\evidence' # use the printed evidence path
valheim-test cli --evidence $evidence --role server --phase world --command cli_manifest
valheim-test detach save --evidence $evidence
valheim-test detach stop --evidence $evidence
valheim-test detach status --evidence $evidence
valheim-test detach restart --evidence $evidence
valheim-test detach finish --run ID
Experimental, Windows only. detach server-load starts a server-only held run under Task Scheduler and returns once it is held; it needs a signed-in desktop session and absolute paths. detach status exits 0 when nothing is left to recover, a live run included. Recover an interrupted start with env recover --run ID on the same machine.
Use the evidence directory printed by the held server for later cli calls. Detached evidence routes them through the original supervisor: the caller proves its journalled identity, and the supervisor checks the exact server boot, listener owner and strict pins before dispatch. The supervisor alone appends command intent and outcome to the run journal. finish closes command admission, waits for the current operation to settle, and refuses work still queued before teardown.
Each supervisor lifetime accepts at most 64 write-once requests, commands and checkpoints together. Restart never recreates the queue or reuses slots. A request expires if it has not started within 60 seconds; each game call keeps the existing --timeout-seconds 1..15 bound. Private evidence records each operation ID and its completed, refused or unknown outcome. A lost reply or interrupted dispatch is unknown: never repeat that mutation; inspect the evidence and use read-only observations to establish the state. The route stays in evidence after finish, so a later call refuses instead of contacting a leftover server directly. An unknown operation blocks all later game commands and checkpoints; use status and evidence, then finish or exact recovery. This remains Windows server-only; detached clients and detached macOS ownership are separate work.
save requires the pinned world's completed-save result with an advancing save number. stop first confirms a fresh save, asks only the exact owned process to quit, then archives and hashes its world and stopped boot logs. A forced kill or prior exit fails the clean checkpoint. In stopped, the supervisor retains the host lock, profile and original fixture; ordinary game commands are refused. restart verifies the archive and source hashes, restores the immutable original fixture (discarding the current round only after its verified archive), and boots through the same owner. It verifies the new session identity, strict pins, test access and world readiness before publishing the next boot. Requests naming the previous boot are refused even if they were queued before restart. A live waiter follows each boot; an unexpected exit closes admission and marks the state unknown. Final finish uses the normal verified-fetch retirement and releases the run's ownership.
Use an absolute --world-fixture DIR on detach server-load to select a fixture. Before Task Scheduler starts the supervisor, the starter stages that directory and the selected mod inputs and records both source and staged SHA-256 manifests. The supervisor rechecks them before startup and checkpoints. Keep those inputs unchanged for the run's lifetime. Output and staging directories must be outside every original input directory, including through links. Without this option, the packaged smoke fixture remains the original. Each new boot uses the same pinned build unless the run accepts its one explicit stopped build swap.
detach status --evidence DIR reports the current boot and running, saving, stopping, stopped, swapping, starting or unknown state. Checkpoints have a cancellation deadline of at least 15 minutes, extended to cover two configured startup waits plus five minutes. The caller's wait also covers a save or stop already in progress and the stop's bounded log fetch, plus a transport margin. These completion waits are separate from the 60-second admission expiry. A missing reply is never permission to retry. Private checkpoints/ archives, manifests and per-operation records remain in the evidence output.
While the server is clean-stopped, one swap can replace the first selected mod DLL:
valheim-test detach swap --evidence $evidence --mod 'C:\Builds\next\MyMod.dll' --sha256 REVIEWED_SHA256 --build-inputs 'C:\Builds\next\build-inputs.json' --dependencies 'C:\Builds\next\dependencies.lock.json'
valheim-test detach restart --evidence $evidence
valheim-test cli --evidence $evidence --role server --phase world --command cli_manifest
Supply the full SHA-256 reviewed for the new DLL, its valheim-test build input manifest, and a ready dependency lock selecting that DLL first. The build must keep the original single plugin GUID, compile game and loader, companion mods, resolved dependencies, optional-reference decisions and ValheimCLI bytes. Config, assets and the original fixture stay fixed. Source files must be regular files under unlinked directories, outside the owned profile and evidence; input directories are limited to 10,000 entries and 512 MiB. Keep the original and replacement source inputs unchanged until finish.
The supervisor journals intent before staging and activation, verifies source and staged bytes, and replaces the selected profile DLL atomically while stopped. Its existing profile checks and owned session's strict MD5 expectation then transition together. swap does not boot the server or publish new command pins; restart restores the original fixture and publishes the new boot only after strict startup verification. Private evidence retains the original dependency provenance, replacement build inputs and source hashes; the run journal records the source-to-staged target mapping. An interrupted activation has an unknown outcome and cannot be replayed. Finish or exact recovery removes partial staging with the existing owned profile.
steam-runtime capture
valheim-test steam-runtime capture --steamcmd DIR --output NEW_FILE
macOS only. It writes a reviewed manifest of SteamCMD's native server libraries. Pass it to server-load as --steam-runtime-package FILE for a Mac dedicated server profile (Steam runtime package).
What every run does
| Behaviour | Rule |
|---|---|
| Profiles | Each actor runs from a disposable loader profile; the install is only read (profiles) |
| Loader | Windows and Linux: the pinned BepInExPack_Valheim from loader-dependency.json, or a named loader package; never the install's. macOS: its loader package, or the install's native loader |
| ValheimCLI | --cli-manifest and --cli-files, else VALHEIMCLI_BUNDLE, else the bundle pinned in the tool. The run prints which |
| Output | Must be new and outside every install and the mod project; a refusal writes REFUSED.txt with the reason |
| Journal | Every copy, character, process, lease and lock is journalled before it is made, for env status |
| User files | The client's recent-servers list is moved aside before launch and put back after |
| Log scan | Logs are scanned at teardown (log scan); one summary line per arm |
| Environment variable | Does |
|---|---|
VALHEIMCLI_BUNDLE |
A folder with one ValheimCLI manifest and its DLLs |
VALHEIM_TESTING_KEEP_RUNTIME=1 |
Keeps the owned runtime; env status then reports the run as KEPT |
VALHEIM_TESTING_SOURCE_HASH_AFTER_RUN=1 |
Re-hashes the whole source install after the run, not only sizes and times |
What a run writes
| File | Holds |
|---|---|
dependencies.lock.json |
Every resolved file and dependency decision |
campaign.json, plan.json, client-plan.json |
server-load's derived inputs; plan.json holds the server password |
regression.json, environments.json |
start's derived inputs |
evidence/ |
result.json, junit.xml and the kept logs (where each log is) |
result.json reports each step with its phase, four states and the Toolkit that ran (evidence). Keep the output private: it holds local paths, a server password, account identifiers and a copy of your recent-servers list.
Limits
- A pass proves the plugins loaded and the game entered a world, not the mod's gameplay.
server-loadkeeps its server on this machine. A server on another machine runs through an inventory file (run on another machine).clireaches only games a run on this machine owns.- The packaged world and character come from one game version; recheck them after a game update. Run one client with the packaged character at a time.