Table of Contents

Class PinnedServerRun

Namespace
Valheim.Testing.GameSessions
Assembly
Valheim.Testing.GameSessions.dll

The lifecycle of a mod's owned dedicated-server test runner, so the mod supplies only its plan fields, modes and scenarios. Usage: <runner> [--inventory <environments.json>] validate|run <plan.json> <new-output-directory>.

  1. Refuses an existing output directory (evidence is never overwritten) and one inside a pinned source.
  2. Reads the plan and places its server: on this machine, from the install the plan's runtime names, or with --inventory on a host (below); checks the host before writing anything there.
  3. Records provenance: plan, runner and toolkit hashes, mode, platform, crossplay, the profile, the world copy and their input hashes.
  4. Takes the host's lock, checks every runtime or plugin pin the plan supplies, stages the server's disposable loader profile from the install (which is never changed), and binds omitted pins from that profile. It copies the world, checking a supplied manifest or recording its hashes as found, reads a world UID from the fixture only when the plan supplied neither worlduid nor world, places the copy beside the profile, and checks the profile's executable and its game build, loader, patchers, required plugins and staged plugin set before launch.
  5. validate stops there. Otherwise: checks the CLI port is free, starts the GameSession's owned server (ServerActor) from the profile with per-boot logs (boot-N.*) and recorded commands (connection-N.jsonl), waits on the dedicated startup events, establishes test access and runs the scenario with the session and the plan.
  6. Always disposes the session (clients the scenario left open, then only the owned server), records the server's PIDs, scans the session's logs (Logs: every boot's and client's; ScanLogs(IEnumerable<RunLog>, IReadOnlyDictionary<string, LogClassification>?), with the plan's LogScan), writes result.json and junit.xml, and prints PASS (only for run), VALIDATED or FAIL. Ctrl+C and SIGTERM cancel the run.

A standalone plan with omitted pins enforces the values it observed from the staged profile and fixture and records their PinOrigins as observed.

Without --inventory the server's host is this machine (a local host whose one environment is the plan's install); with --inventory <environments.json> it is a host of the private environment inventory instead: the first server environment in inventory order whose host (Linux with bash over SSH, in a container or this machine, or Windows with PowerShell) and ports fit the plan. Either way its choice and reason are recorded as serverEnvironment. The runner takes that host's lock for the run, checks the host's install against the plan's runtime manifest and RuntimePins, stages a disposable loader profile of it in a new run directory there (the install's own mods on the pinned loader and ValheimCLI set, the install never changed) and binds the plan to that profile, ships the verified world copy and verifies it there, makes the executable and pin checks on the profile, checks the CLI port on the host, reaches ValheimCLI only through a loopback tunnel (OpenCliTunnelAsync(int, TimeSpan, int, CancellationToken)), starts each boot with HostServer and waits for its listening line in the host's log, and at teardown stops only the process it started, fetches each boot's logs (boot-N/) and the world copy (host-world/), removes their host directories only after a verified fetch is journalled, removes the profile, closes the tunnel and releases the lock. Clients open on this machine; remote clients and several actors are a campaign's (RunCampaignAsync<TPlan>(string, TPlan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>, string, PinnedServerRunOptions<TPlan>)), where OpenClient(ClientRunPlan, string?, string?) starts each in its host's desktop session on its leased, observed Steam identity. A host operation whose outcome is unknown (a lost reply, a transport failure, an unproven lock or lease release), when nothing else failed for certain, prints UNKNOWN and returns 3: neither a pass nor a failure.

Returns the process exit code: 0 when every step passed, 1 on failure, 2 on bad usage, 3 when the outcome is unknown.

public static class PinnedServerRun
Inheritance
object
PinnedServerRun

Examples

A mod supplies its validated plan, adapter session capability and scenario. The toolkit owns fixture copies, startup, teardown and the report:

return await PinnedServerRun.MainAsync(args, new PinnedServerRunOptions<LifecyclePlan>
{
    Name = "mymod-system-test",
    ReadPlan = LifecyclePlan.ReadValidated,
    Mod = new ModDeclaration("mymod.testing/session", LifecyclePlan.SessionTokenVariable),
    Scenario = (session, plan) =>
    {
        DrySiteServerScenario.Run(plan, session.Server!.Game, session.Server.Restart, session.Report);
        return Task.CompletedTask;
    },
});

validate checks the pinned inputs without launching the game; run executes the scenario on a disposable copy. For client rounds, restarts and failure-safe cleanup, see the compiling full lifecycle example.

Fields

HostMode

The mode that runs a hosted plan's scenario; ValidateHostMode only checks it.

public const string HostMode = "host"

Field Value

string

InPlaceOption

The option, before the mode, that runs every owned client of the run from its install as it is instead of a disposable copy: each client plan the run reads takes InPlace (the plan field of the same name). Nothing is written into the install; it must already hold BepInEx and ValheimCLI.

public const string InPlaceOption = "--in-place"

Field Value

string

InventoryOption

The option that names the private environment inventory the dedicated server is placed from; it comes before the mode.

public const string InventoryOption = "--inventory"

Field Value

string

KeepRuntimeVariable

Set to 1 to keep every actor's whole profile after the run, for hands-on debugging in it, with any runner. By default a run that stopped its server keeps only what the run added or changed in the profile (runtime-changes/) and removes the rest, which is the install's loader and mods.

public const string KeepRuntimeVariable = "VALHEIM_TESTING_KEEP_RUNTIME"

Field Value

string

ValidateHostMode

The mode that runs a hosted plan's scenario; ValidateHostMode only checks it.

public const string ValidateHostMode = "validate-host"

Field Value

string

Methods

MainAsync<TPlan>(string[], HostedRunOptions<TPlan>)

The lifecycle of a hosted run, where one client hosts a pinned fixture world from its menu and there is no dedicated server to pin (#258 step 8). Usage: <runner> validate-host|host <plan.json> <new-output-directory>. validate-host checks the plan and runs the run's own preflight (Preflight(IEnumerable<string>) with HostedRounds: the fixture world's hashes and own world UID, and an owned client's install), and copies or launches nothing. host runs the scenario on a GameSession whose Host (HostingClientActor, on this machine) was prepared, opened and hosts its world; then disposes the session (the host leaves, is stopped or detached, and its world moves into the evidence once no client can host it), scans the host's logs with the plan's LogScan, writes result.json and junit.xml and prints PASS, VALIDATED or FAIL. Ctrl+C and SIGTERM cancel the run. Returns 0 when every step passed, 1 on failure, 2 on bad usage.

public static Task<int> MainAsync<TPlan>(string[] args, HostedRunOptions<TPlan> options) where TPlan : class

Parameters

args string[]
options HostedRunOptions<TPlan>

Returns

Task<int>

Type Parameters

TPlan

MainAsync<TPlan>(string[], PinnedServerRunOptions<TPlan>)

Runs the pinned dedicated-server plan named by args and returns a process exit code after owned cleanup.

public static Task<int> MainAsync<TPlan>(string[] args, PinnedServerRunOptions<TPlan> options) where TPlan : ServerRunPlan

Parameters

args string[]
options PinnedServerRunOptions<TPlan>

Returns

Task<int>

Type Parameters

TPlan

RunCampaignAsync<TPlan>(string, TPlan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>, string, HostedRunOptions<TPlan>)

Runs a hosted plan on a campaign without a dedicated server (#258 step 8b): one of the named clients hosts the campaign's fixture world (its plan section's hostWorld) and the others are its peers (joinsHost), each on the host the inventory assigns it. As RunCampaignAsync<TPlan>(string, TPlan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>, string, PinnedServerRunOptions<TPlan>): the campaign's preflight and the plan's agreement with it (CheckHostedPlan(string, IReadOnlyDictionary<string, ClientRunPlan>)) are Preflight steps; preparing the disposable installs and characters and binding them to the plan are Setup steps. The scenario runs on a GameSession whose Host is the hosting client and whose Clients are its peers, each opened on its host on its leased Steam identity. Teardown: the session (peers, then the host, then its world), the leases, the prepared installs and characters (one retire owner, under the locks held), then the client hosts' locks. clients names the plan's client sections by the campaign's client names. Exit codes as MainAsync<TPlan>(string[], PinnedServerRunOptions<TPlan>).

public static Task<int> RunCampaignAsync<TPlan>(string manifestFile, TPlan plan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>> clients, string output, HostedRunOptions<TPlan> options) where TPlan : class

Parameters

manifestFile string
plan TPlan
clients Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>
output string
options HostedRunOptions<TPlan>

Returns

Task<int>

Type Parameters

TPlan

RunCampaignAsync<TPlan>(string, TPlan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>, string, PinnedServerRunOptions<TPlan>)

Runs plan on a campaign's actors (HostedCampaignManifest): the dedicated server and each named client on the environment the inventory assigns it. The campaign's static preflight and the plan's agreement with it (every plugin the plan pins is one a role selects; no angle-bracket placeholder left in the server's arguments) are Preflight steps; preparing the disposable installs and characters (PrepareAsync(string, string, TimeSpan, Func<string, IGameHost>?, CancellationToken), with its read-only host checks first) and binding them to the plan (ApplyTo(ServerRunPlan, HostedCampaignManifest, IReadOnlyDictionary<string, ClientRunPlan>, string)) are Setup steps; retiring them after the processes stopped is a Cleanup step. clients names the plan's client sections by the campaign's client names. The evidence, result.json and junit.xml are written to output (new), the prepared inputs to its prepared/. Exit codes as MainAsync<TPlan>(string[], PinnedServerRunOptions<TPlan>).

public static Task<int> RunCampaignAsync<TPlan>(string manifestFile, TPlan plan, Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>> clients, string output, PinnedServerRunOptions<TPlan> options) where TPlan : ServerRunPlan

Parameters

manifestFile string
plan TPlan
clients Func<TPlan, IReadOnlyDictionary<string, ClientRunPlan>>
output string
options PinnedServerRunOptions<TPlan>

Returns

Task<int>

Type Parameters

TPlan