Class ClientSession
A game client for a system test: one this session launched and owns, or one an operator runs that it only attaches
to. Either way Actor is strictly pinned to the plan's menu expectations when the session is returned,
and every command it sends is recorded to client-commands.jsonl in the output directory (client-commands-2.jsonl
and so on for later sessions: evidence is never overwritten). Disposing closes the
connection and, for an owned client, stops only the process this session started (never one found by name): it asks the
client to quit and kills it only if it does not (Stopped), then keeps its logs beside the evidence. An
attached client's process is never touched. The client is pinned at its menu. A session given a leased Steam account (a multi-machine session's
client) starts or attaches only while that lease is held (and signed-in checked when the profile asks); the hold's owner releases it after disposing the session.
public sealed class ClientSession
- Inheritance
-
objectClientSession
Properties
Actor
public GameActor Actor { get; }
Property Value
Architecture
The slice the owned client was launched as (Architecture; x64 unless the plan asked for arm64), or null for an attached client, whose operator chose it.
public ClientArchitecture? Architecture { get; }
Property Value
Closed
Whether Dispose() has run.
public bool Closed { get; }
Property Value
- bool
Logs
The owned client's logs as kept beside the evidence once it is disposed (its BepInEx log and Unity's Player.log), for a teardown ScanLogs(IEnumerable<RunLog>, IReadOnlyDictionary<string, LogClassification>?); empty for an attached client, whose logs are its operator's.
public IReadOnlyList<RunLog> Logs { get; }
Property Value
- IReadOnlyList<RunLog>
Owned
public bool Owned { get; }
Property Value
- bool
ProcessId
The owned client's process ID, or null for an attached client.
public int? ProcessId { get; }
Property Value
- int?
QuitTimeout
How long disposing waits for the owned client to quit (its profile save) after asking it, before it kills it. Default one minute.
public TimeSpan QuitTimeout { get; set; }
Property Value
- TimeSpan
Stopped
How the owned client ended once disposed; null before that, and for an attached client.
public ProcessStop? Stopped { get; }
Property Value
Methods
Attach(ClientRunPlan, string, IGameTransport?)
Connects to an operator's client and verifies its menu pins. The operator launched it and still owns it.
public static ClientSession Attach(ClientRunPlan plan, string output, IGameTransport? transport = null)
Parameters
planClientRunPlanoutputstringtransportIGameTransport
Returns
Dispose()
Closes the connection and, for an owned client, stops the process this session started: asks it to quit (it saves its profile, as a player's quit does), waits up to QuitTimeout, and kills it only then. Stopped says which.
public void Dispose()
Launch(ClientRunPlan, string, Func<IOwnedProcess>, Func<IGameTransport>, Func<TimeSpan, CancellationToken, Task>, CancellationToken)
The owned launch with its process, connection and readiness supplied, so a test can drive startup failures and
cleanup without a game. ready gets the time left and a token that is cancelled when the process
exits first.
public static ClientSession Launch(ClientRunPlan plan, string output, Func<IOwnedProcess> start, Func<IGameTransport> connect, Func<TimeSpan, CancellationToken, Task> ready, CancellationToken cancellation = default)
Parameters
planClientRunPlanoutputstringstartFunc<IOwnedProcess>connectFunc<IGameTransport>readyFunc<TimeSpan, CancellationToken, Task>cancellationCancellationToken
Returns
Launch(ClientRunPlan, string, CancellationToken)
Launches the plan's client install with ForClient(string, IEnumerable<string>, IReadOnlyDictionary<string, string>?, ClientPlatform?, ClientArchitecture?, bool, IEnumerable<string>?) and waits, on events, until it reaches its main
menu: ValheimCLI's listening line in this launch's BepInEx log, then the main-menu state push. Every wait races the process exit, which ends
startup at once with its exit code. Refuses before launching
when something already listens on the client's CLI port (a command could reach a client this session does not own),
when no Steam client is running here, when the plan's password variable is not set in this process (the client
inherits it), when the install's game build, loader or patchers are not the plan's InstallPins,
when ForClient(string, IEnumerable<string>, IReadOnlyDictionary<string, string>?, ClientPlatform?, ClientArchitecture?, bool, IEnumerable<string>?) refuses the install for the plan's Architecture (an
arm64 request without an arm64 Doorstop library or a native BepInEx core is refused, never run under Rosetta) or its
Doorstop proxy and configuration are from different versions, or when the rest of
Preflight()'s install checks fail (a pinned plugin build that is not installed, a script
ScriptEngine will not load at start, a standing expectations file that would refuse the run). Once started, BepInEx must
write this launch's first log line within BepInExSeconds, or startup fails then, naming the
loader, instead of at the start deadline. A failed startup stops the process it started. The process's output goes to
client-boot.stdout.log/.stderr.log, and its BepInEx log and Unity's log (the launch's -logFile, toolkit-unity.log in output) are copied beside them
(client-boot.game-0.log, client-boot.game-1.log) when it stops; Logs lists them. When BepInEx never
logged, a BepInEx preloader crash log this launch wrote beside the game is kept as client-boot.game-2.preloader-N.log
and its first error quoted in the failure; an older one is only named.
public static ClientSession Launch(ClientRunPlan plan, string output, CancellationToken cancellation = default)
Parameters
planClientRunPlanoutputstringcancellationCancellationToken
Returns
Open(ClientRunPlan, string, ICollection<RunLog>, CancellationToken)
Open(ClientRunPlan, string, CancellationToken), adding the logs the owned client keeps beside the evidence
(Logs) to logs for the teardown scan: when the session opens, and also when its startup
fails after the process started, so a client that never reached its menu is still scanned and listed in the result.
public static ClientSession Open(ClientRunPlan plan, string output, ICollection<RunLog> logs, CancellationToken cancellation = default)
Parameters
planClientRunPlanoutputstringlogsICollection<RunLog>cancellationCancellationToken
Returns
Open(ClientRunPlan, string, CancellationToken)
Launch(ClientRunPlan, string, CancellationToken) or Attach(ClientRunPlan, string, IGameTransport?), as the plan's mode says.
public static ClientSession Open(ClientRunPlan plan, string output, CancellationToken cancellation = default)
Parameters
planClientRunPlanoutputstringcancellationCancellationToken