From 4c01929f9b246212039c48ab8cbff5735f260145 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Oct 2026 21:40:03 -0500 Subject: [PATCH 01/11] Server(fix): Enforce the tmux 3.3a floor why: A newer client can reach an older daemon, and the socket can change after a version check. A tmux below 3.3a has to be refused before any requested effect, by the same exception on every path, without running a hook of the user's and without a process per call. what: - Read the daemon's version with run-shell -C '#{version}'. The daemon expands the format and fails to parse the result as a command, so the version is the tail of "unknown command: " on stderr. run-shell has no after-hook and the parse failure never reaches command-error. Another request's marker, a plain success or any other stderr is not an answer. - Add CommandRequest.discardStdout and set it on the probe. 3.0 and 3.1 daemons keep the client's stdout open after the client exits. - Read standard output without blocking on the pipe: take what is there, wait only while the process is alive, and finish at the first moment after the exit that the pipe holds nothing. Everything an exited process wrote is already in the pipe, so no byte is dropped however long the reader waited for a CPU, and a pipe an old daemon keeps open strands no thread. A read that blocks waits out the whole timeout, on the fenced path and on the control carrier's probe, where stdout is not discarded. The pump threads are daemons, so such a pipe cannot keep a library user's JVM alive either. An idle pump sleeps on the child's exit, so it costs little CPU and sees the exit at once. A close() that kills the child closes its pipes while it may still be alive; the pump then ends quietly, so the caller gets ServerClosedException and not a drain failure. - Map the three answers of a tmux before 3.2 to the version refusal: 3.1's run-shell usage line, 3.0's "server version is too old for client", and "unknown option -- N" from a client before 3.2. Each needs exit 1 and empty stdout, so a lookalike stays a rejection. - Remember the version read for each transport and endpoint. The fence every dispatch carries drops the entry when the daemon was replaced, and so does one of those three answers, so an older replacement raises the version error with nothing sent and a supported one is used. A replacement between a fresh read and the dispatch raises TargetGoneException after one more read. - Prepend a native version guard without rewriting raw argv, batches, existing guards or stdin. Use -N after a live check and on absent non-starting paths. Preserve standalone -V and -h. - Share bounded request execution with carrier probes without exposing a remote-carrier ProcessTransport instance. Retain request budgets, borrowed ownership and dispatch certainty. - Accept a configured executable's basename, or tmux, as the banner prefix of a cold -V report: 3.3a prints argv[0]'s basename. A full version token must follow, and daemon reports stay bare tokens. - Add TmuxVersion.MINIMUM and UnsupportedFeatureException.belowFloor(), so a floor refusal is told from a missing optional feature. Every floor refusal says to upgrade tmux and restart any running server, or use the previous release. - Order versions strictly: next-X.Y < X.Y-rc < X.Y-rcN < X.Y < X.Ya, agreeing with equals. The floor, the control gate and atLeast use it, so a candidate or next- build does not meet a gate on its release. parse is anchored to that grammar with numbers of up to four digits, so 3.3a-master, next-3.9a and 3.8-rc0 are refused. sameRelease still counts a candidate, as an assumption and not a measurement. - Gate what a candidate already ships on the earliest build measured to have it: JSON layouts and the empty dead-pane pid on 3.8-rc2. - isAlive(Duration) answers false when tmux refuses the version probe, as it does when tmux refuses its pid read. - Remove the detached-size and prompt-inventory adaptations the floor makes obsolete, among them the workspace CLI's retry without a size, and regenerate the operations reference and Kotlin ABI for the call-time check. Keep JDK process-argument inspection working with an ASCII-space native no-op. - Teach the Kotlin, Cats, workspace, MCP and junit5 test doubles the probe, so their suites measure the operation under test. - Cover version check, replacement, disappearance, malformed-version, carrier, strict-order and grammar vectors, with the exact exception class per input. Record the renamed-binary banner in the tmux behaviour notes. An idle pump costs about 60-70 ms of process CPU over an 8 s sleep (10-20 ms on master). A longer sleep cap lowers that, 30-40 ms at 100 ms and 10-20 ms at 250 ms, but delays the first read of a burst after an idle spell: a 2 MB burst after 1.5 s idle finished 36-132 ms late at 25 ms and 88-155 ms late at 100 ms. The cap stays at 25 ms. Verified with a 3.3a client: real 3.0a and 3.1c daemons are refused in 0.07 s per case, and a reader that first looks at the pipe 800 ms after the exit still gets every line. Restoring a blocking read, dropping a pre-3.2 mapping, making atLeast ignore candidates, or widening the rc digits each fails the cases above. A pump interrupted while its child lives, or started with the flag set, returns at once and uses little CPU; making it loop on the flag instead fails the interrupt test. --- docs/guide/getting-started.md | 16 + docs/internals/tmux-behaviour.md | 8 + docs/reference/operations.md | 1 + .../it/CommandChainIntegrationTest.java | 9 +- .../it/ControlWatchIntegrationTest.java | 2 +- .../libtmux/it/CreationIntegrationTest.java | 49 +- .../libtmux/it/FieldsIntegrationTest.java | 12 +- .../libtmux/it/LayoutIntegrationTest.java | 6 +- .../it/PaneProcessIntegrationTest.java | 2 +- .../libtmux/it/PushdownIntegrationTest.java | 8 +- .../it/ServerControlIntegrationTest.java | 37 +- .../it/ServerMirrorIntegrationTest.java | 30 +- .../libtmux/it/SnapshotIntegrationTest.java | 13 +- .../it/TmuxVersionIntegrationTest.java | 2 +- .../it/VersionCheckHooksIntegrationTest.java | 66 ++ .../io/github/libtmux/it/VersionProbes.java | 39 ++ .../libtmux/scaladsl/CatsLifecycleSuite.scala | 4 +- .../io/github/libtmux/junit5/FakeTmux.java | 73 +- .../junit5/NamedServerFixtureTest.java | 23 +- .../github/libtmux/junit5/VersionProbes.java | 39 ++ libtmux-kotlin/README.md | 3 +- libtmux-kotlin/api/libtmux-kotlin.api | 1 + .../libtmux/kotlin/FakeControlClients.kt | 4 +- .../io/github/libtmux/kotlin/WaitsTest.kt | 4 +- .../libtmux/mcp/CapabilityRegistryTest.java | 17 +- .../java/io/github/libtmux/mcp/MainTest.java | 19 +- .../libtmux/mcp/PaneInputCohortTest.java | 2 +- .../libtmux/mcp/RunningCommandsTest.java | 9 +- .../github/libtmux/mcp/TmuxMcpServerTest.java | 6 + .../io/github/libtmux/mcp/TypingTest.java | 4 +- .../io/github/libtmux/mcp/VersionProbes.java | 39 ++ .../scaladsl/cats/LifecycleSuite.scala | 17 +- .../scaladsl/cats/ObservationSuite.scala | 4 +- .../libtmux/workspace/cli/Execution.java | 23 +- .../libtmux/workspace/cli/ExecutionTest.java | 12 +- .../libtmux/workspace/VersionProbes.java | 39 ++ .../workspace/WorkspaceBuilderTest.java | 40 +- libtmux/build.gradle.kts | 7 +- .../main/java/io/github/libtmux/Layouts.java | 10 +- .../src/main/java/io/github/libtmux/Pane.java | 2 +- .../main/java/io/github/libtmux/Prompt.java | 37 +- .../main/java/io/github/libtmux/Server.java | 61 +- .../io/github/libtmux/SessionCreation.java | 49 -- .../java/io/github/libtmux/SessionSpec.java | 32 +- .../io/github/libtmux/SnapshotCapture.java | 20 - .../java/io/github/libtmux/TmuxFormats.java | 6 +- .../java/io/github/libtmux/TmuxVersion.java | 63 +- .../io/github/libtmux/catalog/Operation.java | 2 +- .../github/libtmux/control/ControlClient.java | 10 +- .../UnsupportedFeatureException.java | 37 +- .../libtmux/internal/support/Versions.java | 407 +++++++++++ .../internal/support/package-info.java | 13 + .../libtmux/transport/CommandRequest.java | 16 +- .../github/libtmux/transport/Idempotence.java | 8 +- .../libtmux/transport/ProcessTransport.java | 142 +++- .../libtmux/ArgumentTerminatorTest.java | 2 +- .../io/github/libtmux/CreationSpecTest.java | 65 +- .../java/io/github/libtmux/GroupedTmux.java | 26 +- .../java/io/github/libtmux/HandleTest.java | 8 +- .../java/io/github/libtmux/LayoutsTest.java | 46 +- .../io/github/libtmux/MinimumTmuxTest.java | 658 ++++++++++++++++++ .../io/github/libtmux/NarrowLookupTest.java | 16 +- .../java/io/github/libtmux/PaneWaitTest.java | 4 +- .../java/io/github/libtmux/ServerTest.java | 124 ++-- .../io/github/libtmux/TmuxVersionTest.java | 13 +- .../io/github/libtmux/VersionGrammarTest.java | 147 ++++ .../java/io/github/libtmux/VersionProbes.java | 39 ++ .../libtmux/control/ControlClientTest.java | 37 + .../transport/ProcessTransportTest.java | 297 +++++++- 69 files changed, 2548 insertions(+), 538 deletions(-) create mode 100644 integration-tests/src/test/java/io/github/libtmux/it/VersionCheckHooksIntegrationTest.java create mode 100644 integration-tests/src/test/java/io/github/libtmux/it/VersionProbes.java create mode 100644 libtmux-junit5/src/test/java/io/github/libtmux/junit5/VersionProbes.java create mode 100644 libtmux-mcp/src/test/java/io/github/libtmux/mcp/VersionProbes.java create mode 100644 libtmux-workspace/src/test/java/io/github/libtmux/workspace/VersionProbes.java create mode 100644 libtmux/src/main/java/io/github/libtmux/internal/support/Versions.java create mode 100644 libtmux/src/main/java/io/github/libtmux/internal/support/package-info.java create mode 100644 libtmux/src/test/java/io/github/libtmux/MinimumTmuxTest.java create mode 100644 libtmux/src/test/java/io/github/libtmux/VersionGrammarTest.java create mode 100644 libtmux/src/test/java/io/github/libtmux/VersionProbes.java diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 79da1dfbd..496e65d74 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -119,6 +119,22 @@ Being told is the point. A split that silently ignored `empty()` would hand back a pane with a shell in it, and nothing downstream could tell that apart from the pane that was asked for. +The same exception type reports a tmux older than 3.3a, which no call can do +without. `belowFloor()` is true for that refusal and false for a missing +optional feature, so test it before skipping a feature: + +```java +// Given: Server server +try { + server.requireCompatibleTmux(); +} catch (UnsupportedFeatureException refused) { + if (!refused.belowFloor()) { + throw refused; + } + // Upgrade tmux, or use an earlier libtmux release: nothing works below 3.3a. +} +``` + ## A capture is a moment Accessors read tmux once and hand back handles over what they saw. Walking the diff --git a/docs/internals/tmux-behaviour.md b/docs/internals/tmux-behaviour.md index c0a362058..1eae6c731 100644 --- a/docs/internals/tmux-behaviour.md +++ b/docs/internals/tmux-behaviour.md @@ -6,6 +6,14 @@ relies on and that are not self-evident from the tmux manual. A decision that weighs alternatives belongs in [`docs/decisions/`](../decisions/) instead; this page is measurements, kept reachable because source comments cite them. +## Executable version banners on tmux 3.3a + +tmux 3.3a prints the configured executable's basename before its version. +Copies and symlinks named `opaque client` and `opaque-link` report +`opaque client 3.3a` and `opaque-link 3.3a`. The cold-start version check +accepts that exact basename or the canonical `tmux` prefix, followed by a +complete version token. Daemon reports remain bare version tokens. + ## break-pane on tmux 3.7 `break-pane` gets automatic window naming wrong in two opposite, silent ways, diff --git a/docs/reference/operations.md b/docs/reference/operations.md index 2524c53a1..1d086deec 100644 --- a/docs/reference/operations.md +++ b/docs/reference/operations.md @@ -158,6 +158,7 @@ Generated from `@Operation`-annotated methods by the operation-catalog Doclet; d | `io.github.libtmux.Server` | `environment()` | CAPTURED | The server's environment, which every session inherits and every new process is given. | | `io.github.libtmux.Server` | `hooks()` | CAPTURED | The global hooks every session inherits. | | `io.github.libtmux.Server` | `version()` | READ | Which tmux this server is running. | +| `io.github.libtmux.Server` | `requireCompatibleTmux()` | READ | Requires a compatible daemon, or a compatible executable when this endpoint is unused. | | `io.github.libtmux.Server` | `identity()` | CAPTURED | Which server this is. | | `io.github.libtmux.Server` | `open(io.github.libtmux.ServerConfig)` | LIFECYCLE | A server over a transport it owns and closes. | | `io.github.libtmux.Server` | `using(io.github.libtmux.ServerConfig, io.github.libtmux.transport.TmuxTransport)` | LIFECYCLE | A server over a transport the caller owns. | diff --git a/integration-tests/src/test/java/io/github/libtmux/it/CommandChainIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/CommandChainIntegrationTest.java index 8b9d2d496..44e1934d0 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/CommandChainIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/CommandChainIntegrationTest.java @@ -14,7 +14,6 @@ import io.github.libtmux.batch.OperationOutcome; import io.github.libtmux.batch.OperationResult; import io.github.libtmux.exception.UnsupportedFeatureException; -import io.github.libtmux.format.RowFormat; import io.github.libtmux.junit5.TmuxExtension; import io.github.libtmux.transport.CommandRequest; import io.github.libtmux.transport.CommandResult; @@ -192,15 +191,13 @@ void arrangeAcceptsAJsonLayoutOnlyFromTheVersionThatWritesIt() { } } - /** A server that answers its own identity without a real tmux process behind it. */ + /** A server that answers its version without a real tmux process behind it. */ private static Server fakeServer(String version) { - String separator = RowFormat.of("field").separator(); TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).get(0).equals("display-message")) { - return new CommandResult( - 0, List.of(String.join(separator, "4242", version, "1790000000")), List.of()); + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, version); } return new CommandResult(0, List.of(), List.of()); } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java index a139908a4..05c8ff917 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java @@ -147,7 +147,7 @@ void aWatchOverEveryWindowNamesTheWindowEachValueIsFor(Server server) throws Exc void aLayoutChangeCarriesJsonOnceThisClientAskedForIt(Server server) throws Exception { Session session = server.sessions().get(0); Window window = session.windows().get(0); - if (!server.version().atLeast(new TmuxVersion(3, 8, ""))) { + if (!server.version().atLeast(new TmuxVersion(3, 8, "", false, "rc2"))) { return; // classic-only releases have no JSON layout to disagree about } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java index 6e0edf29f..e7704515c 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java @@ -11,11 +11,9 @@ import io.github.libtmux.Server; import io.github.libtmux.Session; import io.github.libtmux.SessionSpec; -import io.github.libtmux.TmuxVersion; import io.github.libtmux.Window; import io.github.libtmux.WindowSpec; import io.github.libtmux.exception.TargetGoneException; -import io.github.libtmux.exception.UnsupportedFeatureException; import io.github.libtmux.junit5.TmuxExtension; import java.nio.file.Files; import java.nio.file.Path; @@ -29,15 +27,11 @@ /** * Making windows and sessions against a real tmux, on whichever release the lane is running. * - *

Neither command changed its flags across the supported range, so what is version-dependent here - * is behaviour 3.2a accepts and then ignores: a detached session's size, and a relative start - * directory. Both branches assert. + *

Sizes and working directories must describe the created process on every supported daemon. */ @ExtendWith(TmuxExtension.class) final class CreationIntegrationTest { - private static final TmuxVersion SIZE_SINCE = new TmuxVersion(3, 3, ""); - // ------------------------------------------------------------------------------ new-window @ParameterizedTest @@ -236,23 +230,11 @@ void aSessionCanNameItsFirstWindowAndRunSomethingInIt(Server server) throws Inte assertTrue(Await.until(() -> "sleep".equals(pane.refresh().currentCommand()))); } - /** 3.2a accepts {@code -x}/{@code -y} for a detached session and gives it the default size. */ @Test - void aSizeIsHonouredOrRefusedDependingOnTheRelease(Server server) { + void aDetachedSessionHonoursItsSize(Server server) { Dimensions wanted = new Dimensions(100, 40); - - if (server.version().atLeast(SIZE_SINCE)) { - Session sized = server.newSession(s -> s.named("sized").sized(wanted)); - - assertEquals(wanted, sized.windows().get(0).size()); - } else { - assertThrows( - UnsupportedFeatureException.class, - () -> server.newSession(s -> s.named("sized").sized(wanted))); - assertTrue( - server.sessions().stream().noneMatch(session -> "sized".equals(session.name())), - "a refused spec must not have reached tmux"); - } + Session sized = server.newSession(s -> s.named("sized").sized(wanted)); + assertEquals(wanted, sized.windows().getFirst().size()); } @Test @@ -273,34 +255,21 @@ void aSessionCanBeMadeBeforeAnythingHasAskedTheServerItsVersion(@TempDir Path di } } - /** - * Sizing is exactly the one thing {@link SessionSpec#argv} needs the version for, and a - * fresh socket has no daemon yet to ask {@link Server#version()}. The version has to come from - * somewhere that does not need one already running — here, the binary itself. - */ @Test - void aSizedSessionCanBeMadeBeforeAnythingHasAskedTheServerItsVersion(Server server, @TempDir Path directory) - throws Exception { + void aSizedSessionCanBeMadeOnAFreshEndpoint(@TempDir Path directory) throws Exception { Path config = directory.resolve("empty.conf"); Files.writeString(config, ""); - Path socket = directory.resolve("s"); Dimensions wanted = new Dimensions(120, 40); - try (Server fresh = Server.open(io.github.libtmux.ServerConfig.builder() .binary(System.getProperty("libtmux.tmux", "tmux")) - .endpoint(io.github.libtmux.ServerEndpoint.socketPath(socket)) + .endpoint(io.github.libtmux.ServerEndpoint.socketPath(directory.resolve("s"))) .configFile(config) .build())) { - if (server.version().atLeast(SIZE_SINCE)) { + try { Session sized = fresh.newSession(s -> s.named("sized").sized(wanted)); - - assertEquals(wanted, sized.windows().get(0).size()); + assertEquals(wanted, sized.windows().getFirst().size()); + } finally { fresh.killServer(); - } else { - assertThrows( - UnsupportedFeatureException.class, - () -> fresh.newSession(s -> s.named("sized").sized(wanted))); - assertTrue(Files.notExists(socket), "a refused spec must not have started the daemon"); } } } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/FieldsIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/FieldsIntegrationTest.java index 15f292d82..94cbd5920 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/FieldsIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/FieldsIntegrationTest.java @@ -46,21 +46,23 @@ void aNameThatIsNotATmuxVariableIsRefusedBeforeItIsSent(Server server) { } /** - * The defect this pins: reading eight names cost eight tmux processes, each at its own moment. - * Counted against the fake, which records every command it was sent. + * Names are read together, after one version probe for the daemon. Reading one name per process would observe + * them at different moments. */ @Test - void eachReadIsOneCommandHoweverManyNames() { + void eachReadIsOneDataCommandHoweverManyNames() { FakeTmux tmux = new FakeTmux(); tmux.addSession("work"); try (Server server = tmux.server()) { int before = tmux.sent().size(); server.variables(List.of("pid", "version", "session_name", "window_name")); - assertEquals(1, tmux.sent().size() - before, "four names, one command: " + tmux.sent()); + assertEquals(2, tmux.sent().size() - before, "one version probe and one combined field read"); + assertTrue(VersionProbes.is(tmux.sent().get(before))); before = tmux.sent().size(); server.paneFields(List.of("pane_tty", "pane_pid", "pane_current_command")); - assertEquals(1, tmux.sent().size() - before, "every pane, one command"); + assertEquals(1, tmux.sent().size() - before, "the daemon is known, so one read for every pane"); + assertTrue(!VersionProbes.is(tmux.sent().get(before))); } } } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/LayoutIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/LayoutIntegrationTest.java index 9f70002e6..6495687d7 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/LayoutIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/LayoutIntegrationTest.java @@ -111,7 +111,7 @@ void aLayoutSaysWhichFormThisServerWrote(Server server) { WindowLayout layout = window.refresh().layout(); - if (server.version().atLeast(new TmuxVersion(3, 8, ""))) { + if (server.version().atLeast(new TmuxVersion(3, 8, "", false, "rc2"))) { assertInstanceOf(WindowLayout.Json.class, layout, "tmux 3.8 and later write JSON: " + layout); } else { assertInstanceOf( @@ -136,7 +136,7 @@ void anExactArrangementCanBeReadBackAndRestored(Server server) { @Test void workspacePreflightAcceptsCapturedJsonAndRefusesTooFewCells(Server server) { String leaf = "{\"V\":2,\"L\":{\"t\":\"p\",\"w\":80,\"h\":24,\"x\":0,\"y\":0,\"i\":0}}"; - if (!server.version().atLeast(new TmuxVersion(3, 8, ""))) { + if (!server.version().atLeast(new TmuxVersion(3, 8, "", false, "rc2"))) { assertThrows(UnsupportedFeatureException.class, () -> Layouts.require(leaf, server, 1)); return; } @@ -260,7 +260,7 @@ void aJsonShapedLayoutThatIsNotRealIsRefusedByTmuxItself(Server server) { assertTrue(server.isAlive(), "tmux must reject a fake JSON layout without dying"); } - private static final TmuxVersion JSON_LAYOUT_FLOOR = new TmuxVersion(3, 8, ""); + private static final TmuxVersion JSON_LAYOUT_FLOOR = new TmuxVersion(3, 8, "", false, "rc2"); private static void assumeTmuxOlderThanJsonLayouts(Server server) { assumeFalse(server.version().atLeast(JSON_LAYOUT_FLOOR), "needs a tmux older than JSON layouts"); diff --git a/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java index f91aead07..8501cb53c 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java @@ -32,7 +32,7 @@ final class PaneProcessIntegrationTest { private static final TmuxVersion REMAIN_ON_EXIT_SINCE = new TmuxVersion(3, 7, ""); /** tmux 3.8 stopped reporting a dead pane's stale {@code #{pane_pid}}. */ - private static final TmuxVersion EMPTY_DEAD_PID_SINCE = new TmuxVersion(3, 8, ""); + private static final TmuxVersion EMPTY_DEAD_PID_SINCE = new TmuxVersion(3, 8, "", false, "rc2"); // ------------------------------------------------------------------------------- dead panes diff --git a/integration-tests/src/test/java/io/github/libtmux/it/PushdownIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/PushdownIntegrationTest.java index 97287b22f..0badc7915 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/PushdownIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/PushdownIntegrationTest.java @@ -17,7 +17,6 @@ import io.github.libtmux.query.FilterExpr; import io.github.libtmux.query.TmuxFilters; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -126,16 +125,17 @@ void aPushedFilterSelectsWhatTheLocalFilterSelects(Server server) { */ @Test void aFilterThatMatchesNothingCostsOneFilteredRead(Server server) { - AtomicInteger dispatches = new AtomicInteger(); + List> verbs = new java.util.ArrayList<>(); try (Server counted = Server.open(server.config().toBuilder() - .observer(report -> dispatches.incrementAndGet()) + .observer(report -> verbs.add(report.verbs())) .build())) { List none = counted.sessions(Session_.name().is("no-such-session")); List nothing = counted.panes(Pane_.command().is("no-such-command")); assertEquals(List.of(), none); assertEquals(List.of(), nothing); - assertEquals(4, dispatches.get(), "two reads of two processes each, and no full capture after either"); + assertEquals(5, verbs.size(), "four data requests and the one version probe"); + assertEquals(1, verbs.stream().filter(List.of("run-shell")::equals).count()); } } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java index ec9ffce1b..8f546e02e 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java @@ -12,7 +12,6 @@ import io.github.libtmux.control.ControlClient; import io.github.libtmux.exception.LibTmuxException; import io.github.libtmux.exception.TargetGoneException; -import io.github.libtmux.exception.UnsupportedFeatureException; import io.github.libtmux.junit5.TmuxExtension; import java.time.Duration; import java.util.List; @@ -30,8 +29,6 @@ @ExtendWith(TmuxExtension.class) final class ServerControlIntegrationTest { - private static final TmuxVersion PROMPT_HISTORY_SINCE = new TmuxVersion(3, 3, ""); - /** Before 3.6, show-messages wants a client attached and refuses without one. */ private static final TmuxVersion MESSAGES_WITHOUT_CLIENT_SINCE = new TmuxVersion(3, 6, ""); @@ -243,36 +240,10 @@ void anOlderReleaseAnswersOnceAClientIsAttached(Server server) throws Exception // ------------------------------------------------------------------------------ prompt history - /** 3.2a has no such command; from 3.3 it answers, empty until something has been typed. */ - @Test - void thePromptHistoryIsReadableOrRefusedDependingOnTheRelease(Server server) { - if (server.version().atLeast(PROMPT_HISTORY_SINCE)) { - assertTrue(server.prompt().history() != null, "a readable history is a list, even when empty"); - server.prompt().clear(); - assertTrue(server.isAlive(), "clearing it is not a reason to lose the server"); - } else { - UnsupportedFeatureException refused = assertThrows( - UnsupportedFeatureException.class, () -> server.prompt().history()); - - assertTrue( - String.valueOf(refused.getMessage()).contains("3.3"), - "the refusal names the release that has it: " + refused.getMessage()); - assertThrows( - UnsupportedFeatureException.class, () -> server.prompt().clear()); - } - } - - /** The floor is exactly 3.2a, and every other lane has to take the working path. */ @Test - void exactlyTheOldestReleaseRefuses(Server server) { - String lane = System.getProperty("libtmux.tmux.expected"); - if (lane == null) { - return; // not a matrix lane; the ordinary suite runs whichever tmux is on PATH - } - - assertEquals( - "3.2a".equals(lane), - !server.version().atLeast(PROMPT_HISTORY_SINCE), - "lane " + lane + " disagrees with the version rule"); + void thePromptHistoryIsReadableAndClearable(Server server) { + assertTrue(server.prompt().history() != null, "a readable history is a list, even when empty"); + server.prompt().clear(); + assertTrue(server.isAlive(), "clearing it must keep the server alive"); } } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/ServerMirrorIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/ServerMirrorIntegrationTest.java index cedd0838e..0f460f0db 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/ServerMirrorIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/ServerMirrorIntegrationTest.java @@ -101,19 +101,23 @@ void aRebuildThatFindsNothingNewPublishesNothing(Server fixture) throws Exceptio ServerConfig counted = fixture.config().toBuilder() .observer(report -> dispatches.incrementAndGet()) .build(); - try (Server server = Server.open(counted); - ServerMirror mirror = ServerMirror.open(server.sessions().get(0))) { - Window window = server.sessions().get(0).windows().get(0); - long epoch = mirror.current().epoch(); - int before = dispatches.get(); - - server.cmd("rename-window", "-t", window.id().value(), window.name()); - - // The rename is one command; a rebuild is two more. - assertTrue( - Await.until(() -> dispatches.get() >= before + 3), - "tmux announced nothing, so this proves nothing"); - assertEquals(Optional.empty(), mirror.awaitNewer(epoch, Duration.ofMillis(300))); + // No shell, so no prompt retitles the pane and bumps the epoch under the test. + fixture.newSession(s -> s.named("quiet").firstWindowNamed("quiet").running("sleep", "600")); + try (Server server = Server.open(counted)) { + Session quiet = server.session("quiet").orElseThrow(); + try (ServerMirror mirror = ServerMirror.open(quiet)) { + Window window = quiet.windows().get(0); + long epoch = mirror.current().epoch(); + int before = dispatches.get(); + + server.cmd("rename-window", "-t", window.id().value(), window.name()); + + // The rename and the rebuild requests that follow it, each on a daemon the version check already knows. + assertTrue( + Await.until(() -> dispatches.get() >= before + 3), + "tmux announced nothing, so this proves nothing"); + assertEquals(Optional.empty(), mirror.awaitNewer(epoch, Duration.ofMillis(300))); + } } } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/SnapshotIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/SnapshotIntegrationTest.java index ab9df59a2..b9a9295bb 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/SnapshotIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/SnapshotIntegrationTest.java @@ -29,17 +29,21 @@ final class SnapshotIntegrationTest { /** - * Every command is a tmux process, so what a capture costs is how many it takes. One asks who - * the server is; one runs the four listings as a group, fenced against that answer. + * One request asks who the server is; one runs the four listings as a group, fenced against + * that answer. The daemon's version is checked once for both. */ @Test - void aCaptureCostsTwoCommands(Server server) { + void aCaptureCostsTwoDataRequestsAndOneVersionProbe(Server server) { AtomicInteger commands = new AtomicInteger(); + AtomicInteger probes = new AtomicInteger(); try (ProcessTransport processes = new ProcessTransport()) { TmuxTransport counting = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { commands.incrementAndGet(); + if (VersionProbes.is(request)) { + probes.incrementAndGet(); + } return processes.execute(request); } @@ -50,7 +54,8 @@ public void close() {} ServerSnapshot captured = measured.snapshot(); assertEquals(1, captured.sessions().size()); - assertEquals(2, commands.get(), "one identity read, then the listings as one group"); + assertEquals(3, commands.get(), "one identity read, grouped listings, and one version probe"); + assertEquals(1, probes.get()); } } } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java index e34d1dde0..8f95b0c86 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java @@ -23,7 +23,7 @@ final class TmuxVersionIntegrationTest { void theServerReportsItsOwnVersion(Server server) { TmuxVersion version = server.version(); - assertTrue(version.atLeast(new TmuxVersion(3, 2, "a")), "this library supports 3.2a and later: " + version); + assertTrue(version.atLeast(new TmuxVersion(3, 3, "a")), "this library supports 3.3a and later: " + version); } /** diff --git a/integration-tests/src/test/java/io/github/libtmux/it/VersionCheckHooksIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/VersionCheckHooksIntegrationTest.java new file mode 100644 index 000000000..23ae01de0 --- /dev/null +++ b/integration-tests/src/test/java/io/github/libtmux/it/VersionCheckHooksIntegrationTest.java @@ -0,0 +1,66 @@ +package io.github.libtmux.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.github.libtmux.Server; +import io.github.libtmux.TmuxVersion; +import io.github.libtmux.junit5.TmuxExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * The version probe must have no effects a user's hooks can see. + * + *

{@code display-message} fires {@code after-display-message} on every release, so a probe made + * with it ran a user's hook before a refusal could stop anything. Each test arms a hook that records + * that it fired, runs ordinary checked calls, and requires the record to stay empty; a control then + * fires the same hook the ordinary way, so an empty record means the hook was quiet and not that it + * was never armed. + */ +@ExtendWith(TmuxExtension.class) +final class VersionCheckHooksIntegrationTest { + + private static final TmuxVersion COMMAND_ERROR_HOOK = new TmuxVersion(3, 5, ""); + + @Test + void versionCheckFiresNoAfterDisplayMessageHook(Server server) { + assertTrue(server.cmd("set-hook", "-g", "after-display-message", "set-option -g @probe-hook fired") + .succeeded()); + + for (int call = 0; call < 3; call++) { + assertTrue(server.cmd("list-sessions").succeeded()); + } + server.requireCompatibleTmux(); + + assertEquals("", recorded(server), "the version check ran the user's after-display-message hook"); + + assertTrue(server.cmd("display-message", "-p", "control").succeeded()); + assertEquals("fired", recorded(server), "the hook never fires, so the check above proves nothing"); + } + + @Test + void versionCheckFiresNoCommandErrorHook(Server server) { + org.junit.jupiter.api.Assumptions.assumeTrue( + server.version().atLeast(COMMAND_ERROR_HOOK), "command-error is a hook from tmux 3.5"); + assertTrue(server.cmd("set-hook", "-g", "command-error", "set-option -g @probe-hook fired") + .succeeded()); + + for (int call = 0; call < 3; call++) { + assertTrue(server.cmd("list-sessions").succeeded()); + } + server.requireCompatibleTmux(); + + assertEquals("", recorded(server), "the version check ran the user's command-error hook"); + + assertFalse(server.cmd("kill-session", "-t", "=libtmux-no-such-session").succeeded()); + assertEquals("fired", recorded(server), "the hook never fires, so the check above proves nothing"); + } + + private static String recorded(Server server) { + // -q: an option never set is an empty answer, not an error, and raw cmd adds no hook itself. + return String.join( + "\n", server.cmd("show-options", "-gqv", "@probe-hook").stdout()); + } +} diff --git a/integration-tests/src/test/java/io/github/libtmux/it/VersionProbes.java b/integration-tests/src/test/java/io/github/libtmux/it/VersionProbes.java new file mode 100644 index 000000000..1265a82ef --- /dev/null +++ b/integration-tests/src/test/java/io/github/libtmux/it/VersionProbes.java @@ -0,0 +1,39 @@ +package io.github.libtmux.it; + +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import java.util.List; + +/** + * The version probe as a test double sees it, and tmux's answer to it. + * + *

The probe is {@code run-shell -C '#{version}'}: the daemon expands the format, fails to + * parse the result as a command, and spells the version in {@code unknown command: }. + */ +final class VersionProbes { + + private static final String MARKER = "libtmux-version-"; + private static final String FORMAT = "#{version}"; + + private VersionProbes() {} + + /** Whether this request is the version probe and nothing else. */ + static boolean is(CommandRequest request) { + return request.commands().size() == 1 && is(request.commands().getFirst()); + } + + static boolean is(List command) { + return command.size() == 3 + && command.get(0).equals("run-shell") + && command.get(1).equals("-C") + && command.get(2).startsWith(MARKER) + && command.get(2).endsWith(FORMAT); + } + + /** The reply of a daemon that reports this version. */ + static CommandResult reply(CommandRequest request, String version) { + String argument = request.commands().getFirst().get(2); + String marker = argument.substring(0, argument.length() - FORMAT.length()); + return new CommandResult(1, List.of(), List.of("unknown command: " + marker + version)); + } +} diff --git a/integration-tests/src/test/scala/io/github/libtmux/scaladsl/CatsLifecycleSuite.scala b/integration-tests/src/test/scala/io/github/libtmux/scaladsl/CatsLifecycleSuite.scala index 6fdba9285..56d1cdfd3 100644 --- a/integration-tests/src/test/scala/io/github/libtmux/scaladsl/CatsLifecycleSuite.scala +++ b/integration-tests/src/test/scala/io/github/libtmux/scaladsl/CatsLifecycleSuite.scala @@ -108,7 +108,7 @@ final class CatsLifecycleSuite extends FunSuite { } } yield () } - program.timeout(1.second).unsafeRunSync() + program.timeout(5.seconds).unsafeRunSync() } } @@ -160,7 +160,7 @@ final class CatsLifecycleSuite extends FunSuite { } } } - .timeout(1.second) + .timeout(5.seconds) .unsafeRunSync() } } diff --git a/libtmux-junit5/src/main/java/io/github/libtmux/junit5/FakeTmux.java b/libtmux-junit5/src/main/java/io/github/libtmux/junit5/FakeTmux.java index e782da0ea..d13a0a92d 100644 --- a/libtmux-junit5/src/main/java/io/github/libtmux/junit5/FakeTmux.java +++ b/libtmux-junit5/src/main/java/io/github/libtmux/junit5/FakeTmux.java @@ -9,6 +9,10 @@ import io.github.libtmux.transport.CommandResult; import io.github.libtmux.transport.ControlCarrier; import io.github.libtmux.transport.TmuxTransport; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -247,7 +251,70 @@ public synchronized CommandResult execute(CommandRequest request) { */ @Override public Optional controlCarrier() { - return Optional.of(command -> attach()); + return Optional.of(command -> { + if (command.size() >= 3 && isVersionProbe(command.subList(command.size() - 3, command.size()))) { + return new VersionProbe(refusal(command.getLast())); + } + if (command.size() >= 4 + && command.subList(command.size() - 4, command.size() - 1) + .equals(List.of("-C", "attach-session", "-t"))) { + return attach(); + } + throw new IOException("the fake carrier supports version probes and control attachment"); + }); + } + + /** + * The version probe: {@code run-shell -C '#{version}'}, which a daemon answers by + * failing to parse the expansion and spelling the version in the error. + */ + private static boolean isVersionProbe(List command) { + return command.size() == 3 + && command.get(0).equals("run-shell") + && command.get(1).equals("-C") + && command.get(2).endsWith("#{version}"); + } + + private static String refusal(String format) { + return "unknown command: " + format.substring(0, format.length() - "#{version}".length()) + VERSION; + } + + private static final class VersionProbe extends Process { + private final InputStream error; + + VersionProbe(String stderr) { + this.error = new ByteArrayInputStream((stderr + "\n").getBytes(StandardCharsets.UTF_8)); + } + + private final InputStream output = InputStream.nullInputStream(); + + @Override + public OutputStream getOutputStream() { + return OutputStream.nullOutputStream(); + } + + @Override + public InputStream getInputStream() { + return output; + } + + @Override + public InputStream getErrorStream() { + return error; + } + + @Override + public int waitFor() { + return 1; + } + + @Override + public int exitValue() { + return 1; + } + + @Override + public void destroy() {} } private synchronized Process attach() { @@ -317,6 +384,10 @@ private CommandResult one(List argv) { if (verb.equals("-V")) { return ok("tmux " + VERSION); } + if (isVersionProbe(argv)) { + sent.add(List.copyOf(argv)); + return new CommandResult(1, List.of(), List.of(refusal(argv.getLast()))); + } if (!COMMANDS.contains(verb)) { return new CommandResult(1, List.of(), List.of("unknown command: " + verb)); } diff --git a/libtmux-junit5/src/test/java/io/github/libtmux/junit5/NamedServerFixtureTest.java b/libtmux-junit5/src/test/java/io/github/libtmux/junit5/NamedServerFixtureTest.java index 681193c88..9d36fe40f 100644 --- a/libtmux-junit5/src/test/java/io/github/libtmux/junit5/NamedServerFixtureTest.java +++ b/libtmux-junit5/src/test/java/io/github/libtmux/junit5/NamedServerFixtureTest.java @@ -10,6 +10,7 @@ import io.github.libtmux.ServerEndpoint; import io.github.libtmux.transport.CommandRequest; import io.github.libtmux.transport.CommandResult; +import io.github.libtmux.transport.ProcessTransport; import io.github.libtmux.transport.TmuxTransport; import java.io.IOException; import java.nio.file.Files; @@ -195,11 +196,17 @@ void replacementBetweenIdentityReadAndKillSurvives(@TempDir Path directory) thro @Override public CommandResult execute(CommandRequest request) { requests.add(request); - List command = request.commands().getFirst(); + List command = request.commands().getLast(); Server destination = command.getFirst().equals("display-message") && identityReads[0]++ < 2 ? original : replacement; - return destination.cmd(command); + try (ProcessTransport processes = new ProcessTransport(1)) { + List endpoint = + new ArrayList<>(destination.config().endpointCommand()); + endpoint.add(1, "-N"); + return processes.execute(new CommandRequest( + endpoint, request.commands(), request.timeout(), request.input())); + } } @Override @@ -213,7 +220,7 @@ public void close() {} assertTrue(String.valueOf(refused.getMessage()).contains("replacement")); assertTrue(replacement.hasSession("race-replacement")); assertEquals( - List.of("display-message", "if-shell"), + List.of("run-shell", "if-shell", "if-shell"), requests.stream() .map(request -> request.commands().getFirst().getFirst()) @@ -237,7 +244,10 @@ void failedAuthenticationNeverSendsAnEndpointCommand(@TempDir Path directory) th @Override public CommandResult execute(CommandRequest request) { requests.add(request); - if (request.commands().getFirst().getFirst().equals("display-message")) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.7c"); + } + if (request.commands().getLast().getFirst().equals("display-message")) { return new CommandResult(0, List.of("malformed"), List.of()); } return new CommandResult(0, List.of(), List.of()); @@ -258,10 +268,11 @@ public void close() {} assertTrue(String.valueOf(refused.getMessage()).contains("malformed identity row")); } assertEquals("keep", Files.readString(sentinel)); - assertEquals(1, requests.size()); + assertEquals(2, requests.size()); + assertTrue(VersionProbes.is(requests.getFirst())); assertEquals( List.of("display-message", "-p", "#{pid}\t#{socket_path}"), - requests.get(0).commands().getFirst()); + requests.getLast().commands().getLast()); assertTrue(requests.stream().allMatch(request -> request.endpoint().contains(sentinel.toString()))); } diff --git a/libtmux-junit5/src/test/java/io/github/libtmux/junit5/VersionProbes.java b/libtmux-junit5/src/test/java/io/github/libtmux/junit5/VersionProbes.java new file mode 100644 index 000000000..7d91ae9f3 --- /dev/null +++ b/libtmux-junit5/src/test/java/io/github/libtmux/junit5/VersionProbes.java @@ -0,0 +1,39 @@ +package io.github.libtmux.junit5; + +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import java.util.List; + +/** + * The version probe as a test double sees it, and tmux's answer to it. + * + *

The probe is {@code run-shell -C '#{version}'}: the daemon expands the format, fails to + * parse the result as a command, and spells the version in {@code unknown command: }. + */ +final class VersionProbes { + + private static final String MARKER = "libtmux-version-"; + private static final String FORMAT = "#{version}"; + + private VersionProbes() {} + + /** Whether this request is the version probe and nothing else. */ + static boolean is(CommandRequest request) { + return request.commands().size() == 1 && is(request.commands().getFirst()); + } + + static boolean is(List command) { + return command.size() == 3 + && command.get(0).equals("run-shell") + && command.get(1).equals("-C") + && command.get(2).startsWith(MARKER) + && command.get(2).endsWith(FORMAT); + } + + /** The reply of a daemon that reports this version. */ + static CommandResult reply(CommandRequest request, String version) { + String argument = request.commands().getFirst().get(2); + String marker = argument.substring(0, argument.length() - FORMAT.length()); + return new CommandResult(1, List.of(), List.of("unknown command: " + marker + version)); + } +} diff --git a/libtmux-kotlin/README.md b/libtmux-kotlin/README.md index 581c71d18..f0c6ee78f 100644 --- a/libtmux-kotlin/README.md +++ b/libtmux-kotlin/README.md @@ -188,7 +188,8 @@ fun nextStep(failure: LibTmuxException): String = is CommandRejectedException -> "change the request" is DispatchException -> if (failure.safeToRetry()) "send it again" else "read state first" is ControlEndedException -> "attach again" - is UnsupportedFeatureException -> "do without" + is UnsupportedFeatureException -> + if (failure.belowFloor()) "upgrade tmux" else "do without" is UnencodableTextException -> "use a UTF-8 locale" is MalformedResponseException -> "report it" is CardinalityException.NoMatch -> "nothing matched" diff --git a/libtmux-kotlin/api/libtmux-kotlin.api b/libtmux-kotlin/api/libtmux-kotlin.api index 3816ce482..1096cba1e 100644 --- a/libtmux-kotlin/api/libtmux-kotlin.api +++ b/libtmux-kotlin/api/libtmux-kotlin.api @@ -392,6 +392,7 @@ public final class io/github/libtmux/kotlin/ServerOperationsKt { public static final fun panes (Lio/github/libtmux/kotlin/Server;Lio/github/libtmux/query/FilterExpr;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun panes (Lio/github/libtmux/kotlin/Server;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun requireAlive (Lio/github/libtmux/kotlin/Server;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun requireCompatibleTmux (Lio/github/libtmux/kotlin/Server;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun run (Lio/github/libtmux/kotlin/Server;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun session (Lio/github/libtmux/kotlin/Server;Lio/github/libtmux/SessionId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun session (Lio/github/libtmux/kotlin/Server;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/FakeControlClients.kt b/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/FakeControlClients.kt index a147da950..d7fd6d651 100644 --- a/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/FakeControlClients.kt +++ b/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/FakeControlClients.kt @@ -42,7 +42,9 @@ internal object FakeControlClients { * this client's command, then the marker's block. */ private val PRELUDE = - "printf '%%begin 100 1 0\\n%%end 100 1 0\\n'\n" + + "for argument in \"\$@\"; do case \"\$argument\" in libtmux-version-*'#{version}') " + + "printf 'unknown command: %s3.7c\\n' \"\${argument%'#{version}'}\" >&2; exit 1;; esac; done\n" + + "printf '%%begin 100 1 0\\n%%end 100 1 0\\n'\n" + "read_request() { IFS= read -r request && IFS= read -r marker; }\n" + "answer() {\n" + " printf '%%begin 1 1 1\\n'\n" + diff --git a/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/WaitsTest.kt b/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/WaitsTest.kt index 5f138ced7..2fecd73ea 100644 --- a/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/WaitsTest.kt +++ b/libtmux-kotlin/src/test/kotlin/io/github/libtmux/kotlin/WaitsTest.kt @@ -219,7 +219,7 @@ private class StalledControlFixture private constructor( """ #!/bin/sh for arg; do - if [ "${'$'}arg" = "-C" ]; then + if [ "${'$'}arg" = "attach-session" ]; then read line < "$gate" break fi @@ -260,7 +260,7 @@ private fun controlClientPid(socket: String): Long? = .children() .filter { process -> val argv = process.info().arguments().orElse(emptyArray()) - "-C" in argv && socket in argv + "-C" in argv && "attach-session" in argv && socket in argv } .findFirst() .map(ProcessHandle::pid) diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java index 6a2429e15..68c394ae5 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java @@ -902,7 +902,7 @@ void anAggregateIncludedAloneDispatchesRepeatedNestedCallsWithoutAdvertisingThem TmuxTransport absent = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - return new CommandResult(1, List.of(), List.of("absent")); + return new CommandResult(1, List.of(), List.of("no server running")); } @Override @@ -950,7 +950,7 @@ void readBatchUsesCamelCaseOnErrorForDispatchAndDisclosure() { TmuxTransport absent = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - return new CommandResult(1, List.of(), List.of("absent")); + return new CommandResult(1, List.of(), List.of("no server running")); } @Override @@ -992,6 +992,9 @@ void aggregateWireLimiterReportsTruncation() throws Exception { TmuxTransport oversizedEnvironment = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return new CommandResult(0, List.of("VALUE=" + "x".repeat(1_048_576)), List.of()); } @@ -1034,8 +1037,11 @@ void aggregateOverflowRollsBackOnlyTheRowThatCrossedTheCap() { TmuxTransport secondResultIsOversized = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return calls.getAndIncrement() == 0 - ? new CommandResult(1, List.of(), List.of("absent")) + ? new CommandResult(1, List.of(), List.of("no server running")) : new CommandResult(0, List.of("VALUE=" + "x".repeat(1_048_576)), List.of()); } @@ -1138,7 +1144,7 @@ void theOnlyResourceDisclosesTheEffectiveSurfaceAndCurrentSocketHonestly() { TmuxTransport absent = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - return new CommandResult(1, List.of(), List.of("absent")); + return new CommandResult(1, List.of(), List.of("no server running")); } @Override @@ -1206,6 +1212,9 @@ void capabilityReportFreezesOneSocketObservation() { TmuxTransport changing = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return probes.getAndIncrement() == 0 ? new CommandResult(0, List.of("123"), List.of()) : new CommandResult(1, List.of(), List.of("gone")); diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java index f41aa04a9..8ae088664 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java @@ -278,7 +278,10 @@ void missingStartupMetadataFailsClosed() { TmuxTransport broken = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - List command = request.commands().getFirst(); + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } + List command = request.commands().getLast(); return command.getFirst().equals("start-server") ? new CommandResult(0, List.of(), List.of()) : new CommandResult(1, List.of(), List.of("metadata unavailable")); @@ -301,6 +304,9 @@ void aReportedSocketPathThatNamesNoFileFailsClosed() { TmuxTransport escaping = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return new CommandResult(0, List.of("/tmp/libtmux-java-dev/escaped\\001/s"), List.of()); } @@ -319,6 +325,9 @@ void explicitSocketMetadataErrorsAreNotMisreportedAsAnAbsentServer() { TmuxTransport denied = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return new CommandResult(1, List.of(), List.of("permission denied")); } @@ -393,6 +402,9 @@ void startupRejectsControlCharactersReportedInAResolvedSocket() { TmuxTransport reporting = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return new CommandResult(0, List.of("/tmp/reported\nsocket"), List.of()); } @@ -411,7 +423,10 @@ private static SocketProfile profile(LaunchConfiguration launch, String marker) TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - List command = request.commands().getFirst(); + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } + List command = request.commands().getLast(); if (command.getFirst().equals("start-server")) { return new CommandResult(0, List.of(), List.of()); } diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/PaneInputCohortTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/PaneInputCohortTest.java index 2d5981100..2efb5d1ae 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/PaneInputCohortTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/PaneInputCohortTest.java @@ -383,7 +383,7 @@ public void close() {} } } - assertEquals(1, requests.size()); + assertEquals(1, requests.size(), "one pane/client snapshot on a daemon already checked"); List> commands = requests.stream() .flatMap(request -> request.commands().stream()) .filter(command -> command.getFirst().startsWith("list-")) diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java index 194a9b18a..259654a25 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java @@ -347,7 +347,7 @@ void successfulRunUsesExactlyTwoPreflights(Server server) { assertEquals(1, sends.size()); assertTrue(preflights.get(0) < discoveries.getFirst()); assertTrue(discoveries.getFirst() < preflights.get(1)); - assertEquals(preflights.get(1) + 1, sends.getFirst(), "a tmux query followed the final preflight"); + assertEquals(preflights.get(1) + 1, sends.getFirst(), "nothing may follow the final preflight but the send"); assertEquals(1, directCommandCount(requests, "wait-for")); } @@ -621,7 +621,9 @@ void uncertainCommandDeliveryStillRunsTheAcceptedCommand(Server server, @TempDir try (ProcessTransport processes = new ProcessTransport()) { TmuxTransport uncertain = borrowing(request -> { CommandResult result = processes.execute(request); - if (request.commands().get(0).stream().anyMatch(argument -> argument.contains("ch_lt"))) { + if (request.commands().stream() + .flatMap(List::stream) + .anyMatch(argument -> argument.contains("ch_lt"))) { throw new DispatchException.Failed( "simulated failure after delivery", DispatchOutcome.UNKNOWN, null); } @@ -967,7 +969,8 @@ private static void armReadonly(Pane pane, String nonce) { } private static Optional nonce(CommandRequest request) { - return request.commands().getFirst().stream() + return request.commands().stream() + .flatMap(List::stream) .filter(argument -> argument.contains("ch_lt")) .flatMap(argument -> NONCE.matcher(argument).results()) .map(MatchResult::group) diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java index 81df0b5ef..39ec181e8 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java @@ -49,6 +49,9 @@ void readBatchBoundsTheCompleteJsonRpcLine() throws Exception { TmuxTransport environment = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return new CommandResult(0, List.of("VALUE=" + payload), List.of()); } @@ -254,6 +257,9 @@ void oversizedRequestIdFailsBeforeToolDispatch() throws Exception { TmuxTransport environment = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } calls.incrementAndGet(); return new CommandResult(0, List.of("VALUE=kept"), List.of()); } diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java index 2dabf92da..b09d8330f 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java @@ -290,7 +290,7 @@ void sendReservationCoversItsFinalPreflight(Server server) throws Exception { List sends = indexes(requests, request -> hasCommand(request, "send-keys")); assertEquals(2, preflights.size()); assertEquals(1, sends.size()); - assertEquals(preflights.getLast() + 1, sends.getFirst()); + assertEquals(preflights.getLast() + 1, sends.getFirst(), "the daemon is known, so nothing separates them"); } @Test @@ -954,7 +954,7 @@ void eachConcurrentPasteReachesOnlyItsOwnPane(Server server) throws Exception { TmuxTransport interleaving = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (String.join(" ", request.commands().get(0)).contains("paste-buffer")) { + if (hasCommand(request, "paste-buffer")) { // Hold both pastes open at once, so a shared buffer name would collide. bothPending.countDown(); await(bothPending); diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/VersionProbes.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/VersionProbes.java new file mode 100644 index 000000000..11ac6b302 --- /dev/null +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/VersionProbes.java @@ -0,0 +1,39 @@ +package io.github.libtmux.mcp; + +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import java.util.List; + +/** + * The version probe as a test double sees it, and tmux's answer to it. + * + *

The probe is {@code run-shell -C '#{version}'}: the daemon expands the format, fails to + * parse the result as a command, and spells the version in {@code unknown command: }. + */ +final class VersionProbes { + + private static final String MARKER = "libtmux-version-"; + private static final String FORMAT = "#{version}"; + + private VersionProbes() {} + + /** Whether this request is the version probe and nothing else. */ + static boolean is(CommandRequest request) { + return request.commands().size() == 1 && is(request.commands().getFirst()); + } + + static boolean is(List command) { + return command.size() == 3 + && command.get(0).equals("run-shell") + && command.get(1).equals("-C") + && command.get(2).startsWith(MARKER) + && command.get(2).endsWith(FORMAT); + } + + /** The reply of a daemon that reports this version. */ + static CommandResult reply(CommandRequest request, String version) { + String argument = request.commands().getFirst().get(2); + String marker = argument.substring(0, argument.length() - FORMAT.length()); + return new CommandResult(1, List.of(), List.of("unknown command: " + marker + version)); + } +} diff --git a/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/LifecycleSuite.scala b/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/LifecycleSuite.scala index d460b02c4..813d310e3 100644 --- a/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/LifecycleSuite.scala +++ b/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/LifecycleSuite.scala @@ -43,9 +43,24 @@ final class LifecycleSuite extends FunSuite { @volatile var thread = "" override def execute(request: CommandRequest): CommandResult = { + val first = request.commands().getFirst() + if ( + first.size() == 3 && first + .get(0) == "run-shell" && first.get(1) == "-C" && + first.get(2).startsWith("libtmux-version-") + ) + return new CommandResult( + 1, + List.of(), + List.of( + "unknown command: " + first + .get(2) + .stripSuffix("#{version}") + "3.7c" + ) + ) calls.incrementAndGet() thread = Thread.currentThread().getName - request.commands().get(0).get(0) match { + request.commands().getLast().getFirst() match { case "block" => entered.complete(()) try { diff --git a/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/ObservationSuite.scala b/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/ObservationSuite.scala index a464ecf77..b9e719609 100644 --- a/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/ObservationSuite.scala +++ b/libtmux-scala-cats/src/test/scala/io/github/libtmux/scaladsl/cats/ObservationSuite.scala @@ -224,7 +224,9 @@ object ObservationSuite { * marker's block. */ val prelude: String = - """|read_request() { IFS= read -r request && IFS= read -r marker; } + """|for argument in "$@"; do case "$argument" in libtmux-version-*'#{version}') + | printf 'unknown command: %s3.7c\n' "${argument%'#{version}'}" >&2; exit 1;; esac; done + |read_request() { IFS= read -r request && IFS= read -r marker; } |answer() { | printf '%%begin 1 1 1\n' | for line in "$@"; do printf '%s\n' "$line"; done diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java index f587195a5..df24e05d1 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java @@ -587,29 +587,12 @@ private static int freeIndex(Set occupied, int first) { return index; } - /** - * A new owned session, sized like tmuxp's terminal-size detection where {@code Server} - * can honor it, and plain otherwise. - * - *

{@code TMUXP_DEFAULT_COLUMNS}/{@code TMUXP_DEFAULT_ROWS} (else {@code COLUMNS}/{@code ROWS}, - * else 80x24) seed the size; the real stdout's terminal overrides it where there is one, and - * {@code COLUMNS}/{@code LINES} override that. {@code TMUXP_DETECT_TERMINAL_SIZE} set to anything - * but {@code 1} disables detection outright. Below tmux 3.3a, {@code Server} refuses a size; that - * refusal is caught here rather than predicted, because it also carries the running version. - */ + /** A new owned session, using tmuxp's terminal-size detection. */ private static Session newOwnedSession(Main.Context context, Server server, WorkspacePlan plan) throws IOException, InterruptedException { SessionSpec.Builder spec = SessionSpec.builder().named(plan.name()).in(plan.directory()); - Optional size = sessionDimensions(context); - if (size.isEmpty()) return server.newSession(spec.build()); - try { - return server.newSession(spec.sized(size.orElseThrow()).build()); - } catch (UnsupportedFeatureException tooOld) { - return server.newSession(SessionSpec.builder() - .named(plan.name()) - .in(plan.directory()) - .build()); - } + sessionDimensions(context).ifPresent(spec::sized); + return server.newSession(spec.build()); } private static Optional sessionDimensions(Main.Context context) diff --git a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java index 4ecde6d62..c4c70fa6d 100644 --- a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java +++ b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java @@ -495,9 +495,9 @@ void loadRejectsANonNumericColumnsAsUsage() throws Exception { "usage", new ObjectMapper().readTree(result.err()).path("code").asText()); } - /** Below 3.3a a size is silently skipped, not sent and ignored, matching {@code SessionSpec}. */ + /** An unsupported executable is refused before session creation, not retried without a size. */ @Test - void loadSkipsSizingOnATmuxOlderThan33a() throws Exception { + void loadRejectsAnUnsupportedTmuxInsteadOfRetryingWithoutSize() throws Exception { Path source = directory.resolve("size-old.yaml"); Path socket = directory.resolve("size-old-socket"); Path trace = directory.resolve("size-old-arguments"); @@ -522,8 +522,10 @@ void loadSkipsSizingOnATmuxOlderThan33a() throws Exception { "-f", "/dev/null", "--json"); - assertEquals(0, result.code(), result.err()); - assertFalse(Files.readString(trace).lines().anyMatch("-x"::equals), Files.readString(trace)); + assertEquals(1, result.code(), result.err()); + assertTrue(result.err().contains("3.3a"), result.err()); + assertFalse(Files.readString(trace).lines().anyMatch("new-session"::equals), Files.readString(trace)); + assertFalse(server.isAlive()); } finally { if (server.isAlive()) server.killServer(); } @@ -1128,7 +1130,7 @@ void allLayoutsAreCheckedBeforeScriptsOrBorrowedStateChange(boolean append, bool "--json"); assertEquals(1, result.code(), result.toString()); assertEquals( - json && !server.version().atLeast(io.github.libtmux.TmuxVersion.parse("3.8")) + json && !server.version().atLeast(io.github.libtmux.TmuxVersion.parse("3.8-rc2")) ? "tmux_failed" : "invalid_workspace", Documents.JSON.readTree(result.err()).path("code").asText()); diff --git a/libtmux-workspace/src/test/java/io/github/libtmux/workspace/VersionProbes.java b/libtmux-workspace/src/test/java/io/github/libtmux/workspace/VersionProbes.java new file mode 100644 index 000000000..d664f3235 --- /dev/null +++ b/libtmux-workspace/src/test/java/io/github/libtmux/workspace/VersionProbes.java @@ -0,0 +1,39 @@ +package io.github.libtmux.workspace; + +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import java.util.List; + +/** + * The version probe as a test double sees it, and tmux's answer to it. + * + *

The probe is {@code run-shell -C '#{version}'}: the daemon expands the format, fails to + * parse the result as a command, and spells the version in {@code unknown command: }. + */ +final class VersionProbes { + + private static final String MARKER = "libtmux-version-"; + private static final String FORMAT = "#{version}"; + + private VersionProbes() {} + + /** Whether this request is the version probe and nothing else. */ + static boolean is(CommandRequest request) { + return request.commands().size() == 1 && is(request.commands().getFirst()); + } + + static boolean is(List command) { + return command.size() == 3 + && command.get(0).equals("run-shell") + && command.get(1).equals("-C") + && command.get(2).startsWith(MARKER) + && command.get(2).endsWith(FORMAT); + } + + /** The reply of a daemon that reports this version. */ + static CommandResult reply(CommandRequest request, String version) { + String argument = request.commands().getFirst().get(2); + String marker = argument.substring(0, argument.length() - FORMAT.length()); + return new CommandResult(1, List.of(), List.of("unknown command: " + marker + version)); + } +} diff --git a/libtmux-workspace/src/test/java/io/github/libtmux/workspace/WorkspaceBuilderTest.java b/libtmux-workspace/src/test/java/io/github/libtmux/workspace/WorkspaceBuilderTest.java index f83fb8ab4..439a109f3 100644 --- a/libtmux-workspace/src/test/java/io/github/libtmux/workspace/WorkspaceBuilderTest.java +++ b/libtmux-workspace/src/test/java/io/github/libtmux/workspace/WorkspaceBuilderTest.java @@ -350,16 +350,8 @@ void anUnsupportedBuiltInLayoutIsRejectedBeforeAnyEffect() { TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).get(0).equals("display-message")) { - return new CommandResult( - 0, - List.of(String.join( - io.github.libtmux.format.RowFormat.of("field") - .separator(), - "4242", - "3.4", - "1790000000")), - List.of()); + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.4"); } effected.set(true); return new CommandResult(0, List.of(), List.of()); @@ -386,16 +378,8 @@ void aJsonLayoutFromAnOldServerIsRefusedForItsVersionNotAsAnUnknownName() { TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).get(0).equals("display-message")) { - return new CommandResult( - 0, - List.of(String.join( - io.github.libtmux.format.RowFormat.of("field") - .separator(), - "4242", - "3.4", - "1790000000")), - List.of()); + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.4"); } effected.set(true); return new CommandResult(0, List.of(), List.of()); @@ -445,16 +429,16 @@ void anUncertainCreationStillTargetsItsUniqueStagingSessionForCleanup() { TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).get(0).equals("new-session")) { - staged.set(request.commands() - .get(0) - .get(request.commands().get(0).indexOf("-s") + 1)); + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.7c"); + } + List command = request.commands().getLast(); + if (command.getFirst().equals("new-session")) { + staged.set(command.get(command.indexOf("-s") + 1)); throw new DispatchException.Failed("reply lost", DispatchOutcome.UNKNOWN, null); } - if (request.commands().get(0).get(0).equals("kill-session")) { - cleaned.set(request.commands() - .get(0) - .get(request.commands().get(0).indexOf("-t") + 1)); + if (command.getFirst().equals("kill-session")) { + cleaned.set(command.get(command.indexOf("-t") + 1)); return new CommandResult(0, List.of(), List.of()); } return new CommandResult(0, List.of(), List.of()); diff --git a/libtmux/build.gradle.kts b/libtmux/build.gradle.kts index b58b2c1ff..b04eb8e99 100644 --- a/libtmux/build.gradle.kts +++ b/libtmux/build.gradle.kts @@ -48,10 +48,11 @@ tasks.jar { "the published module is named ${descriptor.name()}" } val exported = descriptor.exports().map { it.source() }.toSet() - require("io.github.libtmux.internal" !in exported) { - "the published module exports io.github.libtmux.internal" + val internal = { name: String -> name == "io.github.libtmux.internal" || name.startsWith("io.github.libtmux.internal.") } + require(exported.none(internal)) { + "the published module exports ${exported.filter(internal)}" } - val packages = descriptor.packages().filterNot { it == "io.github.libtmux.internal" } + val packages = descriptor.packages().filterNot(internal) val unexported = packages - exported require(unexported.isEmpty()) { "the published module hides packages a caller needs: $unexported" diff --git a/libtmux/src/main/java/io/github/libtmux/Layouts.java b/libtmux/src/main/java/io/github/libtmux/Layouts.java index 226639e6c..55c12b36f 100644 --- a/libtmux/src/main/java/io/github/libtmux/Layouts.java +++ b/libtmux/src/main/java/io/github/libtmux/Layouts.java @@ -34,14 +34,14 @@ public final class Layouts { /** * tmux 3.8 added the JSON (v2) layout format: {@code layout_dump} in {@code layout-custom.c} - * writes {@code {"V":2,"L":...}} instead of the checksummed string from that release on. Landed - * at {@code bf43fdc0} in tmux's own history, which {@code git tag --contains} places on {@code - * 3.8-rc} and nothing earlier. Every release before it has no JSON parser at all: {@code + * writes {@code {"V":2,"L":...}} instead of the checksummed string. The release candidates + * already write it: 3.8-rc2 and 3.8-rc3 do, 3.7c does not, and rc2 is the earliest build + * measured, so the gate names it. Every release before has no JSON parser at all: {@code * layout_construct} dispatches purely on whether the input opens with a curly brace, and * anything that branch cannot parse ends the server outright on 3.3a. A JSON-shaped string is * therefore only something *this* tmux could have written once it is at least this version. */ - private static final TmuxVersion JSON_LAYOUT_SINCE = new TmuxVersion(3, 8, ""); + private static final TmuxVersion JSON_LAYOUT_SINCE = new TmuxVersion(3, 8, "", false, "rc2"); private Layouts() {} @@ -190,7 +190,7 @@ private static IllegalArgumentException ambiguous(String layout, TmuxVersion run private static TmuxVersion version(Server server) { try { - return SessionCreation.versionForCreation(server); + return server.versionForCreation(); } catch (IllegalArgumentException invalid) { throw new MalformedResponseException("could not read tmux version", invalid); } diff --git a/libtmux/src/main/java/io/github/libtmux/Pane.java b/libtmux/src/main/java/io/github/libtmux/Pane.java index 7ec230741..d31ffacab 100644 --- a/libtmux/src/main/java/io/github/libtmux/Pane.java +++ b/libtmux/src/main/java/io/github/libtmux/Pane.java @@ -836,7 +836,7 @@ private Window breakNamed(Optional wanted, String supplied) { new SessionId(fields.get(0)), new WindowIndex(Integer.parseInt(fields.get(2))), new WindowId(fields.get(1))); - if (server.version(snapshot).equals(BREAK_PANE_NAMING_BROKEN)) { + if (server.version(snapshot).sameRelease(BREAK_PANE_NAMING_BROKEN)) { // 3.7 took the name and ignored it, so the caller's choice is applied afterwards. wanted.ifPresent(name -> server.run( snapshot, List.of("rename-window", "-t", fields.get(1), "--", TmuxFormats.literal(name)))); diff --git a/libtmux/src/main/java/io/github/libtmux/Prompt.java b/libtmux/src/main/java/io/github/libtmux/Prompt.java index 489de75c4..205e37a5b 100644 --- a/libtmux/src/main/java/io/github/libtmux/Prompt.java +++ b/libtmux/src/main/java/io/github/libtmux/Prompt.java @@ -2,59 +2,28 @@ import io.github.libtmux.catalog.Kind; import io.github.libtmux.catalog.Operation; -import io.github.libtmux.exception.UnsupportedFeatureException; import java.util.List; import kotlin.annotations.jvm.ReadOnly; -/** - * The server's command-prompt history. - * - *

Whether the commands exist is asked of the daemon, not inferred from a version string. - */ +/** The server's command-prompt history. */ public final class Prompt { - /** The release a refusal names. The daemon's command list is what actually decides. */ - private static final TmuxVersion SINCE = new TmuxVersion(3, 3, ""); - private final Server server; Prompt(Server server) { this.server = server; } - /** - * What has been typed at the prompt, oldest first. - * - * @throws UnsupportedFeatureException if this tmux has no such command - */ + /** What has been typed at the prompt, oldest first. */ @ReadOnly @Operation(Kind.READ) public List history() { - require(); return server.run(List.of("show-prompt-history")).stdout(); } - /** - * Forgets what has been typed at the prompt. - * - * @throws UnsupportedFeatureException if this tmux has no such command - */ + /** Forgets what has been typed at the prompt. */ @Operation(Kind.MUTATION) public void clear() { - require(); server.run(List.of("clear-prompt-history")); } - - private void require() { - if (server.commands().list().stream().anyMatch(line -> named(line, "show-prompt-history"))) { - return; - } - throw new UnsupportedFeatureException("the command prompt's history", SINCE, server.version()); - } - - /** Whether a {@code list-commands} line names this command, not one of its aliases. */ - private static boolean named(String line, String name) { - int space = line.indexOf(' '); - return (space < 0 ? line : line.substring(0, space)).equals(name); - } } diff --git a/libtmux/src/main/java/io/github/libtmux/Server.java b/libtmux/src/main/java/io/github/libtmux/Server.java index 75770b000..a585d03e8 100644 --- a/libtmux/src/main/java/io/github/libtmux/Server.java +++ b/libtmux/src/main/java/io/github/libtmux/Server.java @@ -16,6 +16,7 @@ import io.github.libtmux.format.RowFormat; import io.github.libtmux.internal.CommandStrings; import io.github.libtmux.internal.ErrorText; +import io.github.libtmux.internal.support.Versions; import io.github.libtmux.query.FilterExpr; import io.github.libtmux.query.TmuxFilters; import io.github.libtmux.snapshot.ServerSnapshot; @@ -175,7 +176,7 @@ SnapshotCapture capture() { * already carries the same name. * *

Keep {@code :} and {@code .} out of the name. tmux decides what to do with them and changes - * its mind across the supported range: 3.2a through 3.6 rewrite each one to {@code _}, 3.7 + * its mind across the supported range: 3.3a through 3.6 rewrite each one to {@code _}, 3.7 * refuses the name, and 3.7a onwards keeps it — where a bare {@code -t} target still misreads the * delimiter, so {@link #hasSession} and {@link #killSession} resolve the id by comparing names * instead of building one. {@link Session#name()} reports what tmux settled on. @@ -209,7 +210,7 @@ public Session newSession(Consumer configure) { */ @Operation(Kind.MUTATION) public Session newSession(SessionSpec spec) { - CommandResult result = run(spec.argv("#{session_id}", () -> SessionCreation.versionForCreation(this))); + CommandResult result = run(spec.argv("#{session_id}")); List reported = result.stdout(); if (reported.isEmpty()) { throw new MalformedResponseException(SessionCreation.failureMessage(config, result)); @@ -269,7 +270,13 @@ public boolean isAlive() { */ @Operation(Kind.READ) public boolean isAlive(Duration timeout) { - return cmd(List.of("display-message", "-p", "#{pid}"), timeout).succeeded(); + try { + return cmd(List.of("display-message", "-p", "#{pid}"), timeout).succeeded(); + } catch (CommandRejectedException refused) { + // The version check asks tmux for its version first, and tmux refusing that is the same answer. + // A daemon that exits under the probe is the common case. + return false; + } } /** @@ -366,7 +373,7 @@ public void killServer() { */ @Operation(Kind.MUTATION) public void killServer(Duration timeout) { - CommandResult result = transport.execute( + CommandResult result = execute( request(List.of(List.of("display-message", "-p", "#{pid}"), List.of("kill-server")), timeout, "")); if (result.succeeded()) { // Only a process transport runs tmux on this host; another transport's pid belongs to @@ -376,6 +383,9 @@ public void killServer(Duration timeout) { } return; } + if (Versions.absent(result)) { + return; + } if (!isAlive(timeout)) { return; } @@ -441,7 +451,7 @@ private static void awaitExit(List reported, Duration timeout) { */ @Operation(Kind.CAPTURED) public Batch batch() { - return new Batch(commands -> transport.execute(request(commands, config.defaultTimeout(), ""))); + return new Batch(commands -> execute(request(commands, config.defaultTimeout(), ""))); } /** @@ -452,7 +462,7 @@ public Batch batch() { */ @Operation(Kind.CAPTURED) public CommandChain chain() { - return new CommandChain(batch(), () -> SessionCreation.versionForCreation(this)); + return new CommandChain(batch(), this::versionForCreation); } /** @@ -480,7 +490,7 @@ public String expand(String format) { CommandResult printed(@Nullable ServerSnapshot snapshot, List argv) { List> group = PrintedText.framed(argv); CommandResult result = snapshot == null - ? transport.execute(request(group, config.defaultTimeout(), "")) + ? execute(request(group, config.defaultTimeout(), "")) : fence.guarded(snapshot, CommandStrings.group(group)); return PrintedText.unwrap(result); } @@ -527,8 +537,7 @@ public Prompt prompt() { CommandResult withoutStartingServer(List command) { List argv = new ArrayList<>(config.endpointCommand()); argv.add(1, "-N"); - CommandResult result = - transport.execute(new CommandRequest(argv, List.of(command), config.defaultTimeout(), "")); + CommandResult result = execute(new CommandRequest(argv, List.of(command), config.defaultTimeout(), "")); if (result.succeeded()) { return result; } @@ -624,9 +633,9 @@ WakeReason awaitChannel(String channel, Duration timeout, boolean reserveSignalC try { CommandRequest request = request(List.of("wait-for", "--", channel), timeout); if (reserveSignalCapacity) { - transport.executeWaiting(request); + executeWaiting(request); } else { - transport.execute(request); + execute(request); } } catch (DispatchException.TimedOut e) { if (Thread.interrupted()) { @@ -726,6 +735,24 @@ public TmuxVersion version() { .orElseThrow(() -> new ServerUnavailableException("no tmux server is answering on this endpoint")); } + /** + * Requires a compatible daemon, or a compatible executable when this endpoint is unused. + * + *

Starts no daemon. This checks compatibility at call time; every later operation checks + * the daemon's version again. + * + * @throws UnsupportedFeatureException if the selected tmux is older than the core minimum + * @throws MalformedResponseException if tmux reports an unrecognized version + */ + @Operation(Kind.READ) + public void requireCompatibleTmux() { + versionForCreation(); + } + + TmuxVersion versionForCreation() { + return Versions.versionForCreation(transport, request(List.of("-V"), config.defaultTimeout())); + } + TmuxVersion version(ServerSnapshot snapshot) { return snapshot.serverVersion().orElseGet(this::version); } @@ -836,7 +863,17 @@ public CommandResult cmd(List argv, Duration timeout) { /** Package-private so {@link IncarnationFence} can send its guard with the caller's input. */ CommandResult cmd(List argv, Duration timeout, String input) { - return transport.execute(request(List.of(argv), timeout, input)); + return execute(request(List.of(argv), timeout, input)); + } + + private CommandResult execute(CommandRequest request) { + requireOpen(); + return Versions.execute(transport, request, false); + } + + private CommandResult executeWaiting(CommandRequest request) { + requireOpen(); + return Versions.execute(transport, request, true); } private CommandRequest request(List argv, Duration timeout) { diff --git a/libtmux/src/main/java/io/github/libtmux/SessionCreation.java b/libtmux/src/main/java/io/github/libtmux/SessionCreation.java index b28423988..b6074b1a5 100644 --- a/libtmux/src/main/java/io/github/libtmux/SessionCreation.java +++ b/libtmux/src/main/java/io/github/libtmux/SessionCreation.java @@ -1,13 +1,8 @@ package io.github.libtmux; -import io.github.libtmux.exception.LibTmuxException; -import io.github.libtmux.exception.MalformedResponseException; -import io.github.libtmux.exception.ServerUnavailableException; -import io.github.libtmux.internal.ErrorText; import io.github.libtmux.transport.CommandResult; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; /** Assembles the pieces {@link Server#newSession(SessionSpec)} needs before tmux has a session yet. */ final class SessionCreation { @@ -38,48 +33,4 @@ static String failureMessage(ServerConfig config, CommandResult result) { } return config.binary() + " exited 0 and reported no session id; is it tmux?"; } - - /** - * The version to gate a session-creation spec against. - * - *

{@code new-session} may be the first command said to a fresh socket, where {@link - * Server#version()} fails for want of a daemon. So this asks a running daemon when one answers, - * since it may be a different build than the configured binary, and otherwise asks the binary - * through {@link #binaryVersion}. - * - *

Reads {@link SnapshotCapture#processForCreation()} rather than {@link Server#version()}: a - * daemon too old to talk to fails the identity probe as one never started does ("server exited - * unexpectedly"), and {@link ServerUnavailableException} does not keep that distinction. - * Falling back to {@code binaryVersion} for such a daemon would report the binary's version as - * the daemon's. - */ - static TmuxVersion versionForCreation(Server server) { - return server.capture() - .processForCreation() - .map(SnapshotCapture.ServerProcess::version) - .orElseGet(() -> binaryVersion(server)); - } - - /** - * Which tmux the configured binary is, without asking any server. - * - *

{@code -V} is handled before tmux touches a socket at all, so it answers even against an - * endpoint nothing is listening on yet, with or without a {@code -f} pointed at a config file - * that does not exist. - * - * @throws LibTmuxException if the binary could not be run or did not report a version - */ - private static TmuxVersion binaryVersion(Server server) { - CommandResult result = server.cmd(List.of("-V")); - if (!result.succeeded() || result.stdout().isEmpty()) { - throw new MalformedResponseException( - "tmux -V did not report a version" + ErrorText.suffix(result.stderr())); - } - String reported = result.stdout().get(0); - int space = reported.indexOf(' '); - if (space < 0) { - throw new MalformedResponseException("tmux -V reported an unexpected line: " + reported); - } - return TmuxVersion.parse(reported.substring(space + 1)); - } } diff --git a/libtmux/src/main/java/io/github/libtmux/SessionSpec.java b/libtmux/src/main/java/io/github/libtmux/SessionSpec.java index b362d1c6b..383799691 100644 --- a/libtmux/src/main/java/io/github/libtmux/SessionSpec.java +++ b/libtmux/src/main/java/io/github/libtmux/SessionSpec.java @@ -1,6 +1,5 @@ package io.github.libtmux; -import io.github.libtmux.exception.UnsupportedFeatureException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -9,7 +8,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Supplier; import kotlin.annotations.jvm.ReadOnly; import org.jspecify.annotations.Nullable; @@ -28,14 +26,6 @@ */ public final class SessionSpec { - /** - * 3.2a accepts {@code -x}/{@code -y} for a detached session and then ignores them; upstream's - * {@code df3fe2aa} ("Only use client for sizing when not detached") is the fix, and it is - * already in tag 3.3 - absent from 3.2a, and {@code git log 3.3..3.3a} touches nothing under - * {@code spawn.c} or {@code cmd-new-session.c} - so the floor is 3.3, not 3.3a. - */ - private static final TmuxVersion SIZE_SINCE = new TmuxVersion(3, 3, ""); - private final @Nullable String name; private final @Nullable Path directory; private final Map environment; @@ -96,7 +86,7 @@ public Optional windowName() { return Optional.ofNullable(windowName); } - /** How big the session is, or empty for tmux's default. Requires tmux 3.3. */ + /** How big the session is, or empty for tmux's default. */ public Optional size() { return Optional.ofNullable(size); } @@ -123,24 +113,8 @@ public List command() { return command; } - /** - * The command that makes this session, reporting it through {@code format}. - * - *

The version arrives as a supplier because this is the one creating call that may run when - * there is no server yet, and asking a socket with nothing behind it for its version fails. - * Nothing asks unless the spec depends on the answer. - * - * @param format the row format the caller will read the result back with - * @param running what the server about to run this is, asked only if it matters - * @throws UnsupportedFeatureException if the spec asks for something {@code running} does not have - */ - List argv(String format, Supplier running) { - if (size != null) { - TmuxVersion version = running.get(); - if (!version.atLeast(SIZE_SINCE)) { - throw new UnsupportedFeatureException("a size for a detached session", SIZE_SINCE, version); - } - } + /** The command that makes this session, reporting it through {@code format}. */ + List argv(String format) { List argv = new ArrayList<>(24); argv.add("new-session"); // Always: without -d, tmux tries to attach and fails outright with no terminal. diff --git a/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java b/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java index 53dc30f69..1a5d1d76b 100644 --- a/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java +++ b/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java @@ -124,22 +124,6 @@ Optional process() { return identity(Server::serverAbsent); } - /** - * As {@link #process}, except a live daemon this client cannot actually talk to is never folded - * into "no daemon" - used only by {@link SessionCreation#versionForCreation}, the one caller for which - * that distinction matters. - * - *

tmux reports both the same way from a client's side: "no server running"/"(No such file or - * directory)" for a socket nothing is listening on, and "server exited unexpectedly" for a - * daemon that refused this client's handshake - confirmed against the matrix, a 3.2a client - * against a 3.7c daemon on the same socket fails the second way, rc 1. {@link #process} still - * folds both into "no daemon" for every other caller, which asks "is there a server I can read - * from" and for which a daemon this client cannot use is no more usable than none at all. - */ - Optional processForCreation() { - return identity(SnapshotCapture::daemonGenuinelyAbsent); - } - private Optional identity(Predicate absent) { CommandResult result = server.cmd("display-message", "-p", PROCESS.template()); if (!result.succeeded()) { @@ -169,10 +153,6 @@ private Optional identity(Predicate absent) { return Optional.of(new ServerProcess(pid, TmuxVersion.parse(version), started)); } - private static boolean daemonGenuinelyAbsent(String message) { - return message.contains("no server running") || message.contains("(No such file or directory)"); - } - /** The whole hierarchy in one invocation, fenced against the identity just read. */ private ServerSnapshot capture(ServerProcess process) { boolean floatingKnown = process.version().atLeast(FLOATING_SINCE); diff --git a/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java b/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java index 024c2cb7d..a7aa716d8 100644 --- a/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java +++ b/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java @@ -57,12 +57,14 @@ public static String literal(String value) { * tmux printed it. */ static String printed(String line, TmuxVersion version) { - return version.equals(DOLLAR_ESCAPED) ? ESCAPED_DOLLAR.matcher(line).replaceAll("") : line; + return version.sameRelease(DOLLAR_ESCAPED) + ? ESCAPED_DOLLAR.matcher(line).replaceAll("") + : line; } /** As {@link #printed(String, TmuxVersion)}, for every line. */ static java.util.List printed(java.util.List lines, TmuxVersion version) { - return version.equals(DOLLAR_ESCAPED) + return version.sameRelease(DOLLAR_ESCAPED) ? lines.stream().map(line -> printed(line, version)).toList() : lines; } diff --git a/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java b/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java index d46175302..29e7ebfcf 100644 --- a/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java +++ b/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java @@ -16,13 +16,13 @@ * nothing later's patches guarantee, so it sorts strictly between the two — above every patch of the * release before it, below the release it names and every patch of that. * - *

A release candidate, {@code 3.8-rc}, compares equal to the release it names. tmux freezes - * features at the candidate, so it has everything the release will and differs only in fixes — - * measured, not assumed: a 3.8 candidate writes JSON layouts and empties a dead pane's pid exactly - * as 3.8 does, and a feature gated on 3.8 works on it. It is not {@code equals} to the release, and - * it is written back exactly as tmux wrote it, which matters beyond display: every capture is - * fenced on the version tmux reports, and a candidate printed as {@code 3.8} failed that fence on - * every read. + *

A release candidate, {@code 3.8-rc}, orders below the release it names ({@code next-3.8 < + * 3.8-rc < 3.8-rc2 < 3.8 < 3.8a}), so {@link #compareTo} agrees with {@link #equals}, and {@link + * #atLeast} follows the same order. A candidate that already ships a behaviour is gated by the + * earliest build measured to have it, such as {@code 3.8-rc2} for JSON layouts. A gate on one + * release's own bug asks {@link #sameRelease}. The version is written back exactly as tmux wrote + * it, which matters beyond display: every capture is fenced on the version tmux reports, and a + * candidate printed as {@code 3.8} failed that fence on every read. * * @param major the major number * @param minor the minor number @@ -35,7 +35,14 @@ public record TmuxVersion(int major, int minor, String patch, boolean development, String preRelease) implements Comparable { - private static final Pattern RELEASE = Pattern.compile("^(next-)?(\\d+)\\.(\\d+)([a-z]*)(?:-(rc\\d*))?"); + // Each number is bounded to four digits, so no input overflows an int or ranks oddly: rc0 does + // not exist, and an rc number past four digits is not a tmux report. + private static final String NUMBER = "(0|[1-9]\\d{0,3})"; + private static final Pattern RELEASE = + Pattern.compile("(next-)?" + NUMBER + "\\." + NUMBER + "([a-z]?)(?:-(rc(?:[1-9]\\d{0,3})?))?"); + + /** The oldest tmux this library supports; an older one is refused before any request is sent. */ + public static final TmuxVersion MINIMUM = new TmuxVersion(3, 3, "a"); public TmuxVersion { Objects.requireNonNull(patch, "patch"); @@ -61,7 +68,9 @@ public TmuxVersion(int major, int minor, String patch) { */ public static TmuxVersion parse(String reported) { Matcher release = RELEASE.matcher(reported); - if (!release.find()) { + // tmux reports a development build as next-M.m only: no patch letter, no candidate. + if (!release.matches() + || (release.group(1) != null && (!release.group(4).isEmpty() || release.group(5) != null))) { throw new IllegalArgumentException("not a tmux version: " + reported); } return new TmuxVersion( @@ -72,11 +81,28 @@ public static TmuxVersion parse(String reported) { release.group(5) == null ? "" : release.group(5)); } - /** Whether this version has everything the given one has. */ + /** Whether this version ranks at or above the given one in {@link #compareTo}'s order. */ public boolean atLeast(TmuxVersion other) { return compareTo(other) >= 0; } + /** + * Whether this is the named release, a release candidate for it included. {@code next-} builds + * and other releases are not. For a gate on one release's own bug. + * + *

Counting a candidate is an assumption, not a measurement: the gated bugs were measured on + * the release itself, and a candidate's build is no longer available to check whether it had + * them. Leaving candidates out would skip the workaround on a build that very likely needs it. + */ + public boolean sameRelease(TmuxVersion other) { + return major == other.major + && minor == other.minor + && patch.equals(other.patch) + && !development + && !other.development; + } + + /** {@code next-X.Y < X.Y-rc < X.Y-rcN < X.Y < X.Ya}, so it agrees with {@link #equals}. */ @Override public int compareTo(TmuxVersion other) { int byMajor = Integer.compare(major, other.major); @@ -89,12 +115,23 @@ public int compareTo(TmuxVersion other) { } if (development != other.development) { // A next-M.m build has not shipped everything M.m's own patches will carry, so it - // sorts below the release it names, whatever either side's patch letter is. A release - // candidate is not ordered apart from its release at all: features are frozen at it. + // sorts below the release it names, whatever either side's patch letter is. return development ? -1 : 1; } // An unlettered release precedes its own patches, which empty-string ordering already gives. - return patch.compareTo(other.patch); + int byPatch = patch.compareTo(other.patch); + if (byPatch != 0) { + return byPatch; + } + return Integer.compare(candidate(preRelease), candidate(other.preRelease)); + } + + /** A release outranks every candidate; a bare {@code rc} precedes {@code rc1}, {@code rc2}, ... */ + private static int candidate(String preRelease) { + if (preRelease.isEmpty()) { + return Integer.MAX_VALUE; + } + return preRelease.length() <= 2 ? 0 : Integer.parseInt(preRelease.substring(2)); } @Override diff --git a/libtmux/src/main/java/io/github/libtmux/catalog/Operation.java b/libtmux/src/main/java/io/github/libtmux/catalog/Operation.java index 162ac39bd..80343f21b 100644 --- a/libtmux/src/main/java/io/github/libtmux/catalog/Operation.java +++ b/libtmux/src/main/java/io/github/libtmux/catalog/Operation.java @@ -23,7 +23,7 @@ /** * The first tmux release that has this operation, when it is newer than the supported floor, - * 3.2a. Empty when the whole supported range has it. + * 3.3a. Empty when the whole supported range has it. */ String tmuxSince() default ""; } diff --git a/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java b/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java index 2268dee88..1569da9f6 100644 --- a/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java +++ b/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java @@ -15,6 +15,7 @@ import io.github.libtmux.internal.ErrorText; import io.github.libtmux.internal.ProcessTree; import io.github.libtmux.internal.Utf8; +import io.github.libtmux.internal.support.Versions; import io.github.libtmux.transport.ControlCarrier; import io.github.libtmux.transport.DispatchOutcome; import io.github.libtmux.transport.OperationObserver; @@ -219,6 +220,13 @@ private static ControlClient connect( // control client's own commands travel as UTF-8 over its standard input and are unaffected, // but this argv is encoded by the JVM like any other. Utf8.requireEncodableArguments(command); + long started = System.nanoTime(); + Versions.requireCarrier(carrier, config.endpointCommand(), timeout); + Duration remaining = timeout.minusNanos(System.nanoTime() - started); + if (remaining.isZero() || remaining.isNegative()) { + throw new DispatchException.TimedOut( + "the tmux version check exceeded the attach deadline", DispatchOutcome.NOT_DISPATCHED, null); + } Process process; try { process = carrier.start(command); @@ -228,7 +236,7 @@ private static ControlClient connect( ControlClient client = new ControlClient(process, config.observer()); // Attaching produces a reply of its own. It is awaited like any other, which is also what // proves the client is up before the first command is written. - ControlWriter.Request attached = client.writer.expectInitial(timeout); + ControlWriter.Request attached = client.writer.expectInitial(remaining); client.reader.start(); ControlReply reply; try { diff --git a/libtmux/src/main/java/io/github/libtmux/exception/UnsupportedFeatureException.java b/libtmux/src/main/java/io/github/libtmux/exception/UnsupportedFeatureException.java index 9c2b5a969..8899f7742 100644 --- a/libtmux/src/main/java/io/github/libtmux/exception/UnsupportedFeatureException.java +++ b/libtmux/src/main/java/io/github/libtmux/exception/UnsupportedFeatureException.java @@ -10,25 +10,56 @@ * asked for an empty pane and got a shell has no way to notice before something runs in it. Skip the * feature, or require a newer tmux. * + *

The same type reports a tmux older than {@link TmuxVersion#MINIMUM}, which no call can do + * without. Test {@link #belowFloor()} before treating a failure as an optional feature that can be + * skipped. + * *

The feature and the versions are in the message and not in accessors: a throwable is * serializable, so exposing a {@link TmuxVersion} here would mean making that serializable too. */ public final class UnsupportedFeatureException extends LibTmuxException { private static final long serialVersionUID = 1L; + private final boolean belowFloor; + /** A feature that needs {@code required} or newer, on a server running {@code running}. */ public UnsupportedFeatureException(String feature, TmuxVersion required, TmuxVersion running) { super(feature + " requires tmux " + required + ", but this server runs " + running, null); + this.belowFloor = false; } /** * For a capability that is missing from a range rather than from everything before a release. * - *

Not every gap is a floor: {@code run-shell} reports its command's output on 3.2a, loses it - * in 3.3a and 3.4, and reports it again from 3.5. "Requires 3.5" would be a lie to a 3.2a - * caller, for whom it works. + *

Not every gap is a floor: {@code run-shell} loses its command's output in 3.3a and 3.4 and + * reports it again from 3.5. "Requires 3.5" would be a lie to a 3.3a caller, for whom the + * command runs. */ public UnsupportedFeatureException(String message) { super(message, null); + this.belowFloor = false; + } + + private UnsupportedFeatureException(String message, boolean belowFloor) { + super(message, null); + this.belowFloor = belowFloor; + } + + /** + * A tmux older than {@link TmuxVersion#MINIMUM}, which this library does not support at all. + * + * @param message what was found and what to do about it + */ + public static UnsupportedFeatureException tmuxBelowFloor(String message) { + return new UnsupportedFeatureException(message, true); + } + + /** + * Whether the tmux is older than {@link TmuxVersion#MINIMUM}, so that nothing this library does + * can work on it, as opposed to one optional feature being missing from a tmux that is otherwise + * supported. Upgrade tmux, or use an earlier libtmux release; there is nothing to do without. + */ + public boolean belowFloor() { + return belowFloor; } } diff --git a/libtmux/src/main/java/io/github/libtmux/internal/support/Versions.java b/libtmux/src/main/java/io/github/libtmux/internal/support/Versions.java new file mode 100644 index 000000000..47dd3b190 --- /dev/null +++ b/libtmux/src/main/java/io/github/libtmux/internal/support/Versions.java @@ -0,0 +1,407 @@ +package io.github.libtmux.internal.support; + +import io.github.libtmux.TmuxVersion; +import io.github.libtmux.exception.CommandRejectedException; +import io.github.libtmux.exception.DispatchException; +import io.github.libtmux.exception.LibTmuxException; +import io.github.libtmux.exception.MalformedResponseException; +import io.github.libtmux.exception.ServerUnavailableException; +import io.github.libtmux.exception.TargetGoneException; +import io.github.libtmux.exception.UnsupportedFeatureException; +import io.github.libtmux.internal.ErrorText; +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import io.github.libtmux.transport.ControlCarrier; +import io.github.libtmux.transport.DispatchOutcome; +import io.github.libtmux.transport.Idempotence; +import io.github.libtmux.transport.ProcessTransport; +import io.github.libtmux.transport.TmuxTransport; +import java.io.File; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import org.jspecify.annotations.Nullable; + +/** The tmux version check shared by command dispatch and control-client startup. */ +public final class Versions { + + private static final String PROBE_MARKER = "libtmux-version-"; + private static final String UNKNOWN_COMMAND = "unknown command: "; + private static final String RUN_SHELL_USAGE = "usage: run-shell [-b] [-t target-pane] shell-command"; + private static final String SERVER_TOO_OLD = "server version is too old for client"; + private static final String NO_START_REJECTED = "unknown option -- N"; + private static final String CHANGED = "libtmux-stale-version"; + private static final String REMEDY = + "; upgrade tmux and restart any running server, or use the previous libtmux release"; + + private Versions() {} + + /** + * What the version check last learned about each daemon, so a call does not spend a process asking again. + * The fence on every dispatch is what keeps it honest: a daemon that has been replaced fails the + * fence before any command runs, and the entry is dropped and read again. + */ + private static final Map, String>> KNOWN = + Collections.synchronizedMap(new WeakHashMap<>()); + + /** The fence refused: the daemon is not the one checked. Nothing ran. */ + private static final class Replaced extends RuntimeException { + private static final long serialVersionUID = 1L; + + Replaced() { + super("the tmux daemon version changed before dispatch", null, false, false); + } + } + + /** Checks the live daemon before dispatch and fences the requested commands on that version. */ + public static CommandResult execute(TmuxTransport transport, CommandRequest request, boolean waiting) { + long started = System.nanoTime(); + if (request.commands().equals(List.of(List.of("-V"))) + || request.commands().equals(List.of(List.of("-h")))) { + return transport.execute(request); + } + String known = recall(transport, request); + if (known != null) { + try { + CommandResult result = dispatchChecked(transport, request, known, started, waiting); + // A daemon that has gone answers without running anything, so asking again is safe. + if (!gone(result)) return result; + } catch (Replaced replaced) { + // Nothing ran; read the daemon again below. + } + forget(transport, request); + } + // A replacement between the read and the dispatch means the read was stale, and nothing ran: + // read once more. A refused version surfaces from that read as the version error it is. + for (int attempt = 0; ; attempt++) { + try { + return checkVersion(transport, request, waiting, started); + } catch (Replaced replaced) { + forget(transport, request); + if (attempt == 1) { + throw new TargetGoneException("the tmux daemon version changed before dispatch"); + } + } + } + } + + private static CommandResult checkVersion( + TmuxTransport transport, CommandRequest request, boolean waiting, long started) { + Probe probe = probe(transport, request); + String version; + if (probe.version().isPresent()) { + version = probe.version().get(); + require(version); + remember(transport, request, version); + } else if (absent(probe.result())) { + if (noStart(request.endpoint()) || request.idempotence() == Idempotence.IDEMPOTENT || cleanup(request)) { + CommandRequest absent = new CommandRequest( + withoutStarting(request.endpoint()), + request.commands(), + remaining(request, started), + request.input()); + try { + return dispatch(transport, absent, prefix(request.commands(), "0"), absent.timeout(), waiting); + } catch (Replaced appeared) { + throw new TargetGoneException("the tmux daemon version changed before dispatch"); + } + } + version = binaryVersion(transport, request, remaining(request, started)); + } else { + throw rejected(request, probe.result()); + } + if (probe.version().isPresent()) { + return dispatchChecked(transport, request, version, started, waiting); + } + return dispatch(transport, request, guarded(request.commands(), version), remaining(request, started), waiting); + } + + private static CommandResult dispatchChecked( + TmuxTransport transport, CommandRequest request, String version, long started, boolean waiting) { + CommandRequest live = new CommandRequest( + withoutStarting(request.endpoint()), request.commands(), request.timeout(), request.input()); + return dispatch(transport, live, guarded(request.commands(), version), remaining(request, started), waiting); + } + + private static @Nullable String recall(TmuxTransport transport, CommandRequest request) { + Map, String> known = KNOWN.get(transport); + return known == null ? null : known.get(request.endpoint()); + } + + private static void remember(TmuxTransport transport, CommandRequest request, String version) { + synchronized (KNOWN) { + KNOWN.computeIfAbsent(transport, ignored -> new ConcurrentHashMap<>()) + .put(request.endpoint(), version); + } + } + + private static void forget(TmuxTransport transport, CommandRequest request) { + Map, String> known = KNOWN.get(transport); + if (known != null) known.remove(request.endpoint()); + } + + /** Whether tmux found no daemon to run the request on, which leaves no effect behind. */ + private static boolean gone(CommandResult result) { + return !result.succeeded() + && result.stderr().stream() + .anyMatch(line -> line.contains("no server running") || line.contains("error connecting to")); + } + + /** Reports the daemon version, or validates the executable when the endpoint is unused. */ + public static TmuxVersion versionForCreation(TmuxTransport transport, CommandRequest request) { + long started = System.nanoTime(); + Probe daemon = probe(transport, request); + if (daemon.version().isPresent()) return require(daemon.version().get()); + if (!absent(daemon.result())) throw rejected(request, daemon.result()); + return TmuxVersion.parse(binaryVersion(transport, request, remaining(request, started))); + } + + /** What the version probe learned: the daemon's version, or the failure that says why not. */ + private record Probe(Optional version, CommandResult result) {} + + /** + * Asks the daemon for its version without running a command that fires a user's hooks. + * + *

{@code run-shell -C} parses its expanded argument as a command, so the format expands in + * the daemon and the parse failure carries the answer on stderr as + * {@code unknown command: }. Unlike {@code display-message} the command runs + * no {@code after-*} hook, and the parse failure never reaches {@code command-error}. + */ + private static Probe probe(TmuxTransport transport, CommandRequest request) { + String marker = marker(); + CommandResult result; + try { + result = transport.execute(new CommandRequest( + withoutStarting(request.endpoint()), List.of(probeCommand(marker)), request.timeout(), "", true)); + } catch (DispatchException failure) { + throw beforeDispatch(failure, request); + } + return new Probe(answer(result, marker, request.endpoint().getFirst()), result); + } + + private static String marker() { + return PROBE_MARKER + Long.toHexString(ThreadLocalRandom.current().nextLong()) + "-"; + } + + private static List probeCommand(String marker) { + return List.of("run-shell", "-C", marker + "#{version}"); + } + + /** The version a probe's refusal spelled, or empty when tmux answered anything else. */ + private static Optional answer(CommandResult result, String marker, String binary) { + if (result.succeeded()) { + throw new MalformedResponseException(binary + " exited 0 and reported no tmux version; is it tmux?"); + } + String expected = UNKNOWN_COMMAND + marker; + for (String line : result.stderr()) { + if (line.startsWith(expected) && line.length() > expected.length()) { + return Optional.of(line.substring(expected.length())); + } + } + if (beforeRunShellC(result)) { + throw refused("this tmux is older than 3.2"); + } + return Optional.empty(); + } + + /** + * Whether tmux answered the way a release before 3.2 does. {@code run-shell -C} arrived in 3.2, + * so 3.1 rejects the flag with its usage line; a 3.0 daemon refuses a newer client outright; and + * a client from before 3.2 rejects {@code -N} first. None of them names its version. + */ + private static boolean beforeRunShellC(CommandResult result) { + return result.exitCode() == 1 + && result.stdout().isEmpty() + && result.stderr().stream() + .anyMatch(line -> line.equals(RUN_SHELL_USAGE) + || line.equals(SERVER_TOO_OLD) + || line.endsWith(NO_START_REJECTED)); + } + + private static UnsupportedFeatureException refused(String reason) { + return UnsupportedFeatureException.tmuxBelowFloor( + "libtmux core requires tmux " + TmuxVersion.MINIMUM + " or newer, but " + reason + REMEDY); + } + + /** Checks through the carrier without taking ownership of the caller's carrier. */ + public static void requireCarrier(ControlCarrier carrier, List endpoint, Duration timeout) { + String marker = marker(); + CommandRequest request = + new CommandRequest(withoutStarting(endpoint), List.of(probeCommand(marker)), timeout, "", true); + CommandResult result; + try { + result = ProcessTransport.executeThroughCarrier(carrier, request); + } catch (DispatchException failure) { + throw beforeDispatch(failure, request); + } + Optional version = answer(result, marker, endpoint.getFirst()); + if (version.isEmpty()) throw rejected(request, result); + require(version.get()); + } + + private static boolean cleanup(CommandRequest request) { + return request.commands().stream() + .anyMatch(command -> command.getFirst().equals("kill-server")) + && request.commands().stream() + .allMatch(command -> command.getFirst().equals("kill-server") + || command.equals(List.of("display-message", "-p", "#{pid}"))); + } + + /** The minimum applies to the daemon's own report, including patch letters. */ + public static TmuxVersion require(String reported) { + TmuxVersion running; + try { + running = TmuxVersion.parse(reported); + } catch (IllegalArgumentException malformed) { + throw new MalformedResponseException("tmux reported an unrecognized version: " + reported, malformed); + } + if (running.compareTo(TmuxVersion.MINIMUM) < 0) { + throw refused("this server runs " + running); + } + return running; + } + + private static String binaryVersion(TmuxTransport transport, CommandRequest request, Duration timeout) { + CommandResult result; + try { + result = transport.execute(new CommandRequest(request.endpoint(), List.of(List.of("-V")), timeout, "")); + } catch (DispatchException failure) { + throw beforeDispatch(failure, request); + } + if (!result.succeeded()) { + throw rejected(request, result); + } + String line = reported(result, request.endpoint().getFirst()); + // tmux 3.3a reports argv[0]'s basename; later releases print tmux. + String prefix = new File(request.endpoint().getFirst()).getName() + " "; + if (!line.startsWith(prefix)) prefix = "tmux "; + if (!line.startsWith(prefix)) { + throw new MalformedResponseException( + request.endpoint().getFirst() + " did not report a tmux version: " + line); + } + String version = line.substring(prefix.length()); + try { + require(version); + } catch (UnsupportedFeatureException unsupported) { + throw refused("the configured binary reports " + version); + } + return version; + } + + private static CommandResult dispatch( + TmuxTransport transport, + CommandRequest original, + List> commands, + Duration timeout, + boolean waiting) { + CommandRequest guarded = new CommandRequest(original.endpoint(), commands, timeout, original.input()); + CommandResult result = waiting ? transport.executeWaiting(guarded) : transport.execute(guarded); + // A refused fence names its token. A daemon from before 3.2 answers in its own words, which + // is also a replacement: nothing ran, and the version has to be read again. The 3.0a lane + // owns the second branch (3.0's "too old for client" is no token); a 3.1c daemon runs the + // fence and is caught by the first. + if (!result.succeeded() + && (result.stderr().stream() + .anyMatch(line -> + line.contains(commands.getFirst().getLast())) + || beforeRunShellC(result))) { + throw new Replaced(); + } + return result; + } + + private static List> guarded(List> commands, String version) { + return prefix(commands, "#{==:#{version}," + version + "}"); + } + + private static List> prefix(List> commands, String condition) { + List> fenced = new ArrayList<>(commands.size() + 1); + // A space is a tmux no-op and keeps JDK process-argument inspection intact. + fenced.add(List.of("if-shell", "-F", condition, " ", CHANGED + "-" + UUID.randomUUID())); + fenced.addAll(commands); + return List.copyOf(fenced); + } + + private static List withoutStarting(List endpoint) { + // A selector or filename may itself equal -N, so always insert a flag. + List selected = new ArrayList<>(endpoint); + selected.add(1, "-N"); + return List.copyOf(selected); + } + + /** Recognizes global -N without treating option values as flags. */ + private static boolean noStart(List endpoint) { + for (int index = 1; index < endpoint.size(); index++) { + String option = endpoint.get(index); + if (!option.startsWith("-") || option.equals("--")) return false; + for (int letter = 1; letter < option.length(); letter++) { + char flag = option.charAt(letter); + if (flag == 'N') return true; + if ("cfLST".indexOf(flag) >= 0) { + if (letter + 1 == option.length()) index++; + break; + } + } + } + return false; + } + + /** Whether tmux explicitly reported that nothing is serving the selected endpoint. */ + public static boolean absent(CommandResult result) { + return !result.succeeded() + && result.stderr().stream() + .anyMatch(line -> + line.contains("no server running") || line.contains("(No such file or directory)")); + } + + private static String reported(CommandResult result, String binary) { + if (result.stdout().size() != 1 || result.stdout().getFirst().isBlank()) { + throw new MalformedResponseException(binary + " exited 0 and reported no single tmux version; is it tmux?"); + } + return result.stdout().getFirst(); + } + + private static LibTmuxException rejected(CommandRequest request, CommandResult result) { + String error = ErrorText.suffix(result.stderr()); + String message = "tmux version probe failed (exit " + result.exitCode() + ")" + + (error.isEmpty() ? "; check that " + request.endpoint().getFirst() + " is tmux" : error); + return absent(result) + ? new ServerUnavailableException(message, "run-shell", result.exitCode(), result.stderr()) + : new CommandRejectedException(message, "run-shell", result.exitCode(), result.stderr()); + } + + private static Duration remaining(CommandRequest request, long started) { + Duration left = request.timeout().minusNanos(System.nanoTime() - started); + if (left.isZero() || left.isNegative()) { + throw new DispatchException.TimedOut( + "the tmux version check exceeded the request deadline", + DispatchOutcome.NOT_DISPATCHED, + request.idempotence(), + null); + } + return left; + } + + private static DispatchException beforeDispatch(DispatchException failure, CommandRequest request) { + DispatchException rejected = failure instanceof DispatchException.TimedOut + ? new DispatchException.TimedOut( + String.valueOf(failure.getMessage()), + DispatchOutcome.NOT_DISPATCHED, + request.idempotence(), + failure.getCause()) + : new DispatchException.Failed( + String.valueOf(failure.getMessage()), + DispatchOutcome.NOT_DISPATCHED, + request.idempotence(), + failure.getCause()); + rejected.addSuppressed(failure); + return rejected; + } +} diff --git a/libtmux/src/main/java/io/github/libtmux/internal/support/package-info.java b/libtmux/src/main/java/io/github/libtmux/internal/support/package-info.java new file mode 100644 index 000000000..9a1dee6d1 --- /dev/null +++ b/libtmux/src/main/java/io/github/libtmux/internal/support/package-info.java @@ -0,0 +1,13 @@ +/** + * The tmux version check: whether the daemon behind an endpoint runs a supported tmux release. + * + *

"Support" here means the tmux releases this library works with, not helper code. Like its + * parent package, {@code module-info.java} does not export it, and nothing in it carries a + * compatibility guarantee. + * + *

The package is null-marked: every type is non-null unless annotated otherwise. + */ +@NullMarked +package io.github.libtmux.internal.support; + +import org.jspecify.annotations.NullMarked; diff --git a/libtmux/src/main/java/io/github/libtmux/transport/CommandRequest.java b/libtmux/src/main/java/io/github/libtmux/transport/CommandRequest.java index 6450e55c3..4710ce8d6 100644 --- a/libtmux/src/main/java/io/github/libtmux/transport/CommandRequest.java +++ b/libtmux/src/main/java/io/github/libtmux/transport/CommandRequest.java @@ -27,9 +27,23 @@ * @param input what the commands read from tmux's standard input, empty for the commands that * read none. A tmux argument is bounded by MAX_IMSGSIZE, so text too large to be one * travels here instead. + * @param discardStdout whether the caller reads only the exit status and standard error. tmux 3.0 + * and 3.1 keep the client's standard output open after the client exits; a request that needs + * none of it says so and the transport never opens the pipe. The process transport honours + * it, so the output is empty; a transport that does not still returns once the process has + * exited */ public record CommandRequest( - @ReadOnly List endpoint, @ReadOnly List> commands, Duration timeout, String input) { + @ReadOnly List endpoint, + @ReadOnly List> commands, + Duration timeout, + String input, + boolean discardStdout) { + + /** A request whose standard output is read like any other. */ + public CommandRequest(List endpoint, List> commands, Duration timeout, String input) { + this(endpoint, commands, timeout, input, false); + } public CommandRequest { endpoint = List.copyOf(endpoint); diff --git a/libtmux/src/main/java/io/github/libtmux/transport/Idempotence.java b/libtmux/src/main/java/io/github/libtmux/transport/Idempotence.java index fd478246e..c26a1abf6 100644 --- a/libtmux/src/main/java/io/github/libtmux/transport/Idempotence.java +++ b/libtmux/src/main/java/io/github/libtmux/transport/Idempotence.java @@ -19,6 +19,8 @@ public enum Idempotence { // Full names and their aliases. display-message and capture-pane read only with -p, and are // decided below; every other verb, run-shell and if-shell included, is treated as a change. private static final Set READS = Set.of( + "-V", + "-h", "has-session", "has", "list-buffers", @@ -99,7 +101,11 @@ private static boolean guardedRead(List argv) { List rest = argv.subList(Math.min(index + 1, argv.size()), argv.size()); return !rest.isEmpty() && rest.size() <= 2 - && rest.stream().allMatch(branch -> branch.startsWith("libtmux-stale-") || readsText(branch)); + && rest.stream() + .allMatch(branch -> branch.isEmpty() + || branch.equals(" ") + || branch.startsWith("libtmux-stale-") + || readsText(branch)); } private static boolean readsText(String command) { diff --git a/libtmux/src/main/java/io/github/libtmux/transport/ProcessTransport.java b/libtmux/src/main/java/io/github/libtmux/transport/ProcessTransport.java index 277958521..86d2dae42 100644 --- a/libtmux/src/main/java/io/github/libtmux/transport/ProcessTransport.java +++ b/libtmux/src/main/java/io/github/libtmux/transport/ProcessTransport.java @@ -78,7 +78,19 @@ public final class ProcessTransport implements TmuxTransport { */ private static final long IDLE_PUMP_SECONDS = 10; - private static final ProcessStarter SYSTEM_STARTER = command -> new ProcessBuilder(command).start(); + private static final ProcessStarter SYSTEM_STARTER = new ProcessStarter() { + @Override + public Process start(List command) throws IOException { + return new ProcessBuilder(command).start(); + } + + @Override + public Process start(List command, boolean discardStdout) throws IOException { + ProcessBuilder builder = new ProcessBuilder(command); + if (discardStdout) builder.redirectOutput(ProcessBuilder.Redirect.DISCARD); + return builder.start(); + } + }; private static final System.Logger LOG = System.getLogger(ProcessTransport.class.getName()); @@ -115,6 +127,19 @@ public ProcessTransport(int maxConcurrentProcesses) { this(maxConcurrentProcesses, DEFAULT_MAX_OUTPUT_BYTES); } + /** + * Runs one bounded request through a caller-owned process carrier. + * + *

Reclaims only the processes this request started. The carrier remains owned by the caller + * and may continue starting other processes. + */ + public static CommandResult executeThroughCarrier(ControlCarrier carrier, CommandRequest request) { + try (ProcessTransport transport = + new ProcessTransport(1, DEFAULT_MAX_OUTPUT_BYTES, carrier::start, System::nanoTime)) { + return transport.execute(request); + } + } + /** * @param maxConcurrentProcesses how many tmux processes may run at once * @param maxOutputBytes maximum bytes accepted from each output channel of one process @@ -144,13 +169,11 @@ public ProcessTransport(int maxConcurrentProcesses, int maxOutputBytes) { this.admission = new Semaphore(maxConcurrentProcesses); this.waitingAdmission = maxConcurrentProcesses == 1 ? null : new Semaphore(maxConcurrentProcesses - 1); this.pumps = (ThreadPoolExecutor) Executors.newFixedThreadPool(3 * maxConcurrentProcesses, factory()); - // An idle pump lets go of the JVM. The threads are not daemons on purpose — work in flight - // has to finish, and a drain abandoned halfway is a truncated reply reported as a whole one - // — but "in flight" is the point, not "ever used". Without this a caller who forgot to - // close a transport kept a JVM alive forever after its last command, which for a - // command-line program is indistinguishable from a deadlock. A pump that is running holds - // the JVM exactly as before; one that has had nothing to do for this long does not, and the - // pool makes another the moment there is work. + // An idle pump lets go of its thread. A caller who forgot to close a transport used to keep + // a JVM alive forever after its last command, which for a command-line program is + // indistinguishable from a deadlock; the threads are daemons now, so a pipe some other + // process holds can never do that, and the keep-alive only bounds how many sit idle. Work in + // flight is still finished: the caller's own thread waits for it. this.pumps.setKeepAliveTime(idlePumpNanos, TimeUnit.NANOSECONDS); this.pumps.allowCoreThreadTimeOut(true); this.maxOutputBytes = maxOutputBytes; @@ -504,7 +527,7 @@ private RunningProcess launch(CommandRequest request, long deadline) { gate.unlock(); } try { - Process process = starter.start(request.commandLine()); + Process process = starter.start(request.commandLine(), request.discardStdout()); RunningProcess running = new RunningProcess(process, new ProcessTree(process), request.idempotence()); live.add(running); return running; @@ -532,7 +555,12 @@ private Pumps submit(RunningProcess process, String input) { CompletableFuture failure = new CompletableFuture<>(); try { return new Pumps( - pumps.submit(new Pump(process.process().getInputStream(), maxOutputBytes, finished, failure)), + pumps.submit(new StdoutPump( + process.process(), + maxOutputBytes, + finished, + failure, + () -> killedByClose.contains(process))), pumps.submit(new Pump(process.process().getErrorStream(), maxOutputBytes, finished, failure)), pumps.submit(new InputPump(process.process().getOutputStream(), input, finished)), finished, @@ -684,14 +712,15 @@ private static void closeQuietly(Closeable stream, @Nullable DispatchException f } /** - * Non-daemon, so a drain in flight finishes rather than being abandoned halfway and reported as - * a whole reply. An idle one times out instead, so forgetting to close still lets a JVM exit. + * Daemon threads. A drain in flight still finishes, because the thread that asked for the result + * waits for it; what a daemon pump cannot do is keep a JVM alive after that thread has returned, + * whatever some other process does with the pipe it was given. */ private static ThreadFactory factory() { AtomicInteger index = new AtomicInteger(); return runnable -> { Thread worker = new Thread(runnable, "libtmux-pump-" + index.incrementAndGet()); - worker.setDaemon(false); + worker.setDaemon(true); return worker; }; } @@ -733,6 +762,11 @@ private record RunningProcess(Process process, ProcessTree tree, Idempotence ide @FunctionalInterface interface ProcessStarter { Process start(List command) throws IOException; + + /** As above, with standard output sent to the null device when the caller reads none of it. */ + default Process start(List command, boolean discardStdout) throws IOException { + return start(command); + } } private record Pump(InputStream source, int limit, CountDownLatch finished, CompletableFuture failure) @@ -763,6 +797,88 @@ public byte[] call() throws IOException { } } + /** + * Reads standard output without ever blocking on the pipe. + * + *

A read that blocks is a thread nothing can wake: tmux 3.0 and 3.1 daemons keep the client's + * standard output open after the client has exited, and closing the stream does not end a read + * already waiting in it. So this reads what is there and waits only while the process is alive. + * Once it has exited, everything it wrote is already in the pipe, so the pump reads while bytes + * are waiting and finishes at the first moment none are — a decision on the pipe's contents, not + * on a clock, which cannot drop bytes however long the pump was starved of a CPU. + */ + record StdoutPump( + Process process, + int limit, + CountDownLatch finished, + CompletableFuture failure, + BooleanSupplier killedByClose) + implements Callable { + /** The longest an idle pump sleeps; the child's exit wakes it at once, and data resets it. */ + private static final long MAX_WAIT_NANOS = TimeUnit.MILLISECONDS.toNanos(25); + + @Override + public byte[] call() throws IOException { + InputStream source = process.getInputStream(); + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(Math.min(limit, 8_192)); + byte[] buffer = new byte[8_192]; + int total = 0; + long wait = TimeUnit.MICROSECONDS.toNanos(20); + boolean reading = true; + while (reading) { + try { + int ready = source.available(); + if (ready > 0) { + int read = source.read(buffer, 0, Math.min(ready, buffer.length)); + if (read < 0) { + break; + } + if (read > limit - total) { + throw new OutputLimitExceeded(limit); + } + output.write(buffer, 0, read); + total += read; + wait = TimeUnit.MICROSECONDS.toNanos(20); + } else if (!process.isAlive()) { + // The exit came before this look at the pipe, so anything written is in it. + reading = source.available() > 0; + } else { + // Sleep on the child's exit, so an idle pump costs nothing and the exit is + // seen at once. An interrupt means the transport is closing; stop rather + // than spin on a flag that makes every later wait return immediately. + if (process.waitFor(wait, TimeUnit.NANOSECONDS)) { + continue; + } + wait = Math.min(wait * 2, MAX_WAIT_NANOS); + } + } catch (OutputLimitExceeded tooLong) { + throw tooLong; + } catch (InterruptedException closing) { + Thread.currentThread().interrupt(); + break; + } catch (IOException closed) { + // Terminating the process closes its streams, often while it is still alive. + // What was read is then not wanted, and the failure is the transport's own + // close, which the caller reports, not a defect of the drain. + if (process.isAlive() && !killedByClose.getAsBoolean()) { + throw closed; + } + break; + } + } + byte[] result = output.toByteArray(); + source.close(); + return result; + } catch (IOException | RuntimeException e) { + failure.complete(e); + throw e; + } finally { + finished.countDown(); + } + } + } + private record InputPump(OutputStream target, String input, CountDownLatch finished) implements Runnable { @Override public void run() { diff --git a/libtmux/src/test/java/io/github/libtmux/ArgumentTerminatorTest.java b/libtmux/src/test/java/io/github/libtmux/ArgumentTerminatorTest.java index 06a537acd..13f0fb1d1 100644 --- a/libtmux/src/test/java/io/github/libtmux/ArgumentTerminatorTest.java +++ b/libtmux/src/test/java/io/github/libtmux/ArgumentTerminatorTest.java @@ -103,7 +103,7 @@ void sessionNamesAreResolvedLocallyAndNeverBecomeArguments() { void creationCommandsEndOptionsBeforeTheProgram() { TmuxVersion version = new TmuxVersion(3, 7, "c"); List> commands = List.of( - SessionSpec.builder().running(DASHED).build().argv("#{pane_id}", () -> version), + SessionSpec.builder().running(DASHED).build().argv("#{pane_id}"), WindowSpec.builder().running(DASHED).build().argv("$0", "#{pane_id}"), SplitSpec.builder().running(DASHED).build().argv("%0", "#{pane_id}", version)); diff --git a/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java b/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java index d00d0295e..add164723 100644 --- a/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java +++ b/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java @@ -6,10 +6,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.github.libtmux.exception.UnsupportedFeatureException; import java.nio.file.Path; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; /** @@ -21,8 +19,6 @@ final class CreationSpecTest { private static final TmuxVersion V32A = new TmuxVersion(3, 2, "a"); - private static final TmuxVersion V33 = new TmuxVersion(3, 3, ""); - private static final TmuxVersion V33A = new TmuxVersion(3, 3, "a"); private static final TmuxVersion V37B = new TmuxVersion(3, 7, "b"); private static final String FORMAT = "#{window_id}"; @@ -43,7 +39,7 @@ void aPlainWindowAsksForNothingBeyondTheWindow() { void onlyASessionIsDetachedByDefault() { assertFalse(WindowSpec.builder().build().argv("$1", FORMAT).contains("-d")); assertTrue(WindowSpec.builder().detached().build().argv("$1", FORMAT).contains("-d")); - assertTrue(SessionSpec.builder().build().argv(FORMAT, () -> V37B).contains("-d")); + assertTrue(SessionSpec.builder().build().argv(FORMAT).contains("-d")); } @Test @@ -106,7 +102,7 @@ void aRelativeDirectoryReachesTmuxResolvedAgainstThisProcess() { Path relative = Path.of("sub"); String resolved = relative.toAbsolutePath().toString(); - List session = SessionSpec.builder().in(relative).build().argv(FORMAT, () -> V32A); + List session = SessionSpec.builder().in(relative).build().argv(FORMAT); List window = WindowSpec.builder().in(relative).build().argv("$1", FORMAT); List split = SplitSpec.builder().in(relative).build().argv("%1", FORMAT, V32A); List respawn = Pane.respawnArgv(new PaneId("%1"), relative); @@ -126,7 +122,7 @@ void aWindowWithoutADirectoryUsesTmuxDefaults() { @Test void aPlainSessionIsAlwaysDetached() { - List argv = SessionSpec.builder().build().argv(FORMAT, () -> V37B); + List argv = SessionSpec.builder().build().argv(FORMAT); assertEquals(List.of("new-session", "-d", "-P", "-F", FORMAT), argv); } @@ -134,63 +130,18 @@ void aPlainSessionIsAlwaysDetached() { @Test void aSizeBecomesTheTwoFlagsTmuxWants() { List argv = - SessionSpec.builder().sized(new Dimensions(120, 40)).build().argv(FORMAT, () -> V37B); + SessionSpec.builder().sized(new Dimensions(120, 40)).build().argv(FORMAT); assertEquals("120", argv.get(argv.indexOf("-x") + 1)); assertEquals("40", argv.get(argv.indexOf("-y") + 1)); } - /** - * 3.2a accepts {@code -x}/{@code -y} for a detached session and gives it the default size. - * - *

3.2a and 3.3a alone cannot tell the real floor from one lettered patch too high - both - * take the same branch either way - so the discriminating case is the plain 3.3 answer neither - * lane in the matrix ever sends. - */ - @Test - void aSizeIsRefusedOnTheReleaseThatIgnoresIt() { - SessionSpec spec = SessionSpec.builder().sized(new Dimensions(120, 40)).build(); - - UnsupportedFeatureException refused = - assertThrows(UnsupportedFeatureException.class, () -> spec.argv(FORMAT, () -> V32A)); - - assertEquals( - "a size for a detached session requires tmux 3.3, but this server runs 3.2a", refused.getMessage()); - assertDoesNotThrow(() -> spec.argv(FORMAT, () -> V33)); - assertDoesNotThrow(() -> spec.argv(FORMAT, () -> V33A)); - } - - /** - * Creating the first session on a socket is the one call that runs before there is a server, and - * a socket with nothing behind it cannot answer what version it is. So nothing asks unless the - * spec depends on the answer. - */ - @Test - void theVersionIsNotAskedForUnlessTheSpecNeedsIt() { - AtomicInteger asked = new AtomicInteger(); - - SessionSpec.builder().named("bootstrap").build().argv(FORMAT, () -> { - asked.incrementAndGet(); - return V37B; - }); - - assertEquals(0, asked.get(), "a plain session must be creatable before a server exists"); - - SessionSpec.builder().sized(new Dimensions(80, 24)).build().argv(FORMAT, () -> { - asked.incrementAndGet(); - return V37B; - }); - - assertEquals(1, asked.get(), "a sized session does depend on the version"); - } - - /** tmux reads {@code -f} as one comma-separated list, not as a flag that may repeat. */ @Test void clientFlagsGoOutAsOneCommaSeparatedValue() { List argv = SessionSpec.builder() .clientFlags("no-detach-on-destroy", "active-pane") .build() - .argv(FORMAT, () -> V37B); + .argv(FORMAT); assertEquals(1, argv.stream().filter("-f"::equals).count()); assertEquals("no-detach-on-destroy,active-pane", argv.get(argv.indexOf("-f") + 1)); @@ -206,7 +157,7 @@ void aSessionCarriesItsDirectoryEnvironmentAndFirstWindowName() { .detachOthers() .withoutSize() .build() - .argv(FORMAT, () -> V37B); + .argv(FORMAT); assertEquals("build", argv.get(argv.indexOf("-s") + 1)); assertEquals("editor", argv.get(argv.indexOf("-n") + 1)); @@ -220,7 +171,7 @@ void callerDirectoriesAreLiteralizedExactlyOnceAtEverySpawnBoundary() { Path supplied = Path.of("/srv/#one/##two"); String literal = "/srv/##one/####two"; - List session = SessionSpec.builder().in(supplied).build().argv(FORMAT, () -> V37B); + List session = SessionSpec.builder().in(supplied).build().argv(FORMAT); List window = WindowSpec.builder().in(supplied).build().argv("$1", FORMAT); List split = SplitSpec.builder().in(supplied).build().argv("%1", FORMAT, V37B); List respawn = Pane.respawnArgv(new PaneId("%1"), supplied); @@ -243,7 +194,7 @@ void bothSpecsAreDescriptionsThatCanBeLoweredTwice() { SessionSpec session = SessionSpec.builder().named("shared").build(); assertEquals(window.argv("$1", FORMAT), window.argv("$1", FORMAT)); - assertEquals(session.argv(FORMAT, () -> V37B), session.argv(FORMAT, () -> V37B)); + assertEquals(session.argv(FORMAT), session.argv(FORMAT)); assertEquals( "$2", window.argv("$2", FORMAT).get(window.argv("$2", FORMAT).indexOf("-t") + 1)); } diff --git a/libtmux/src/test/java/io/github/libtmux/GroupedTmux.java b/libtmux/src/test/java/io/github/libtmux/GroupedTmux.java index 70c4fa3ba..17ebdc713 100644 --- a/libtmux/src/test/java/io/github/libtmux/GroupedTmux.java +++ b/libtmux/src/test/java/io/github/libtmux/GroupedTmux.java @@ -59,10 +59,21 @@ static CommandResult execute( String liveVersion, long liveStart, Function, CommandResult> command) { + if (request.endpoint().contains("-N") && VersionProbes.is(request)) { + return VersionProbes.reply(request, liveVersion); + } if (request.commands().size() > 1) { List stdout = new ArrayList<>(); for (List one : request.commands()) { - CommandResult answered = command.apply(one); + CommandResult answered = one.getFirst().equals("if-shell") + ? execute( + new CommandRequest( + request.endpoint(), List.of(one), request.timeout(), request.input()), + livePid, + liveVersion, + liveStart, + command) + : command.apply(one); stdout.addAll(answered.stdout()); if (!answered.succeeded()) { return new CommandResult(answered.exitCode(), stdout, answered.stderr()); @@ -75,7 +86,11 @@ static CommandResult execute( return command.apply(argv); } String stale = argv.get(argv.size() - 1); - Matcher fence = FENCED.matcher(argv.get(2)); + int condition = argv.get(2).equals("-t") ? 4 : 2; + if (argv.get(condition).equals("0")) { + return new CommandResult(1, List.of(), List.of("unknown command: " + stale)); + } + Matcher fence = FENCED.matcher(argv.get(condition)); while (fence.find()) { String live = switch (fence.group(1)) { @@ -87,7 +102,8 @@ static CommandResult execute( return new CommandResult(1, List.of(), List.of("unknown command: " + stale)); } } - return group(argv.get(argv.size() - 2), command); + String branch = argv.get(argv.size() - 2); + return branch.isEmpty() ? new CommandResult(0, List.of(), List.of()) : group(branch, command); } /** Runs each command in turn, and discards the rest once one fails, which is what tmux does. */ @@ -102,7 +118,9 @@ private static CommandResult group(String commands, Function, Comma if (words.isEmpty()) { continue; } - CommandResult answered = words.get(0).equals("display-message") && words.size() == 3 + CommandResult answered = words.get(0).equals("display-message") + && words.size() == 3 + && !words.get(2).contains("#{") ? new CommandResult(0, List.of(words.get(2)), List.of()) : command.apply(words); stdout.addAll(answered.stdout()); diff --git a/libtmux/src/test/java/io/github/libtmux/HandleTest.java b/libtmux/src/test/java/io/github/libtmux/HandleTest.java index 65960363c..8a6533fb5 100644 --- a/libtmux/src/test/java/io/github/libtmux/HandleTest.java +++ b/libtmux/src/test/java/io/github/libtmux/HandleTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -228,7 +229,10 @@ void aHandleUsesTheVersionCapturedWithItsIdentity() { window.selectLayout(Layout.MAIN_HORIZONTAL_MIRRORED); - assertEquals(captured + 1, transport.calls.get(), "the operation must not probe another server version"); + assertEquals(captured + 1, transport.calls.get(), "only the operation reached tmux: the version is known"); + assertFalse( + VersionProbes.is(transport.requests.get(captured)), + "the version check must not capture a new handle identity"); } } @@ -328,7 +332,7 @@ private static Server canned(ServerEndpoint endpoint) { private static String last(CountingTransport transport) { List argv = - transport.requests.get(transport.requests.size() - 1).commands().get(0); + transport.requests.get(transport.requests.size() - 1).commands().getLast(); return argv.get(argv.size() - 2); } diff --git a/libtmux/src/test/java/io/github/libtmux/LayoutsTest.java b/libtmux/src/test/java/io/github/libtmux/LayoutsTest.java index b377a8458..ce90c458c 100644 --- a/libtmux/src/test/java/io/github/libtmux/LayoutsTest.java +++ b/libtmux/src/test/java/io/github/libtmux/LayoutsTest.java @@ -33,6 +33,18 @@ void aJsonLayoutIsAcceptedFromTheVersionThatWritesIt() { assertEquals(JSON_LAYOUT, Layouts.require(JSON_LAYOUT, NEXT_3_9)); } + @ParameterizedTest + @ValueSource(strings = {"3.8-rc2", "3.8-rc3", "3.8", "next-3.9"}) + void aJsonLayoutIsAcceptedFromTheEarliestBuildMeasuredToWriteIt(String version) { + assertEquals(JSON_LAYOUT, Layouts.require(JSON_LAYOUT, TmuxVersion.parse(version))); + } + + @ParameterizedTest + @ValueSource(strings = {"3.7c", "next-3.8", "3.8-rc", "3.8-rc1"}) + void aJsonLayoutIsRefusedBeforeTheEarliestBuildMeasuredToWriteIt(String version) { + assertThrows(UnsupportedFeatureException.class, () -> Layouts.require(JSON_LAYOUT, TmuxVersion.parse(version))); + } + /** * Before this round, {@code require(String, TmuxVersion)} had no JSON branch at all, so a JSON * layout was refused as an unrecognised name on every version, including ones that write it. @@ -150,7 +162,7 @@ void namesUseTheRunningDaemonBeforeConsideringTheClient() { assertEquals("main-horizontal", Layouts.require("main-h", server, 1)); assertThrows( UnsupportedFeatureException.class, () -> Layouts.require("main-horizontal-mirrored", server, 1)); - assertEquals(List.of("display-message", "display-message"), transport.commands); + assertEquals(List.of("run-shell", "run-shell"), transport.commands); } try (VersionTransport transport = new VersionTransport(new CommandResult(0, List.of("3.7c"), List.of())); Server server = server(transport)) { @@ -175,7 +187,7 @@ void onlyColdEndpointsUseTheSelectedClientVersion(String reason) { try (VersionTransport transport = new VersionTransport(new CommandResult(1, List.of(), List.of(reason))); Server server = server(transport)) { assertEquals("main-horizontal-mirrored", Layouts.require("main-horizontal-m", server, 1)); - assertEquals(List.of("display-message", "-V"), transport.commands); + assertEquals(List.of("run-shell", "-V"), transport.commands); } } @@ -193,7 +205,7 @@ void probeFailuresDoNotFallBackToANewerClient(String reason) { LibTmuxException failure = assertThrows(LibTmuxException.class, () -> Layouts.require("main-horizontal-mirrored", server, 1)); assertTrue(String.valueOf(failure.getMessage()).contains(reason)); - assertEquals(List.of("display-message"), transport.commands); + assertEquals(List.of("run-shell"), transport.commands); } } @@ -223,7 +235,7 @@ void invalidDaemonVersionRepliesNeverSelectTheClient(String reported) { try (VersionTransport transport = new VersionTransport(new CommandResult(0, List.of(reported), List.of())); Server server = server(transport)) { assertThrows(LibTmuxException.class, () -> Layouts.require("main-horizontal-mirrored", server, 1)); - assertEquals(List.of("display-message"), transport.commands); + assertEquals(List.of("run-shell"), transport.commands); } } @@ -237,7 +249,7 @@ void deeplyNestedTreesHaveABoundedParser() { @Test void workspaceJsonPreflightCountsCellsAndRejectsNativeGrammarErrors() { String layout = "{\"V\":2,\"L\":{\"t\":\"p\",\"w\":80,\"h\":24,\"x\":0,\"y\":0,\"i\":0}}"; - try (VersionTransport transport = new VersionTransport(new CommandResult(0, List.of("3.8-rc"), List.of())); + try (VersionTransport transport = new VersionTransport(new CommandResult(0, List.of("3.8-rc2"), List.of())); Server server = server(transport)) { assertEquals(layout, Layouts.require(layout, server, 1)); assertThrows(IllegalArgumentException.class, () -> Layouts.require(layout, server, 2)); @@ -310,24 +322,12 @@ private static final class VersionTransport implements TmuxTransport { public CommandResult execute(CommandRequest request) { List command = request.commands().getFirst(); commands.add(command.getFirst()); - if (command.getFirst().equals("display-message")) { - assertEquals( - List.of( - "display-message", - "-p", - io.github.libtmux.format.RowFormat.of("pid", "version", "start_time") - .template()), - command); - if (!daemon.succeeded()) return daemon; - return new CommandResult( - 0, - List.of(String.join( - io.github.libtmux.format.RowFormat.of("pid", "version", "start_time") - .separator(), - "4242", - String.join("\n", daemon.stdout()), - "1790000000")), - List.of()); + if (command.getFirst().equals("run-shell")) { + assertTrue(VersionProbes.is(request), String.valueOf(command)); + assertTrue(request.endpoint().contains("-N"), "a version probe must not start tmux"); + return daemon.succeeded() + ? VersionProbes.reply(request, daemon.stdout().getFirst()) + : daemon; } assertEquals(List.of("-V"), command); return new CommandResult(0, List.of("tmux 3.7c"), List.of()); diff --git a/libtmux/src/test/java/io/github/libtmux/MinimumTmuxTest.java b/libtmux/src/test/java/io/github/libtmux/MinimumTmuxTest.java new file mode 100644 index 000000000..e4c79da98 --- /dev/null +++ b/libtmux/src/test/java/io/github/libtmux/MinimumTmuxTest.java @@ -0,0 +1,658 @@ +package io.github.libtmux; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.github.libtmux.exception.CommandRejectedException; +import io.github.libtmux.exception.DispatchException; +import io.github.libtmux.exception.MalformedResponseException; +import io.github.libtmux.exception.TargetGoneException; +import io.github.libtmux.exception.UnsupportedFeatureException; +import io.github.libtmux.internal.support.Versions; +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import io.github.libtmux.transport.DispatchOutcome; +import io.github.libtmux.transport.Idempotence; +import io.github.libtmux.transport.TmuxTransport; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +final class MinimumTmuxTest { + + @ParameterizedTest + @ValueSource(strings = {"3.2a", "3.3", "next-3.3", "3.3-rc"}) + void oldDaemonsRejectRawReadsWritesAndGroups(String version) { + FakeTmux tmux = new FakeTmux(version); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows(UnsupportedFeatureException.class, () -> server.cmd("list-sessions")); + assertThrows( + UnsupportedFeatureException.class, () -> server.cmd("set-option", "-g", "@sentinel", "changed")); + assertThrows( + UnsupportedFeatureException.class, + () -> server.batch().add("kill-server").run()); + assertThrows(UnsupportedFeatureException.class, server::killServer); + assertThrows( + UnsupportedFeatureException.class, + () -> server.globalOptions().get("@sentinel")); + assertEquals(0, tmux.effects.get()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"3.3a", "3.3b", "3.4", "3.7a", "3.8", "next-3.9"}) + void supportedDaemonWinsOverAnOlderClient(String version) { + FakeTmux tmux = new FakeTmux(version); + tmux.binaryVersion = "3.2a"; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertTrue(server.cmd("set-option", "-g", "@sentinel", "changed").succeeded()); + assertEquals(1, tmux.effects.get()); + assertEquals(0, tmux.binaryProbes.get()); + } + } + + @Test + void freshSocketChecksTheBinaryBeforeStartingADaemon() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.absent = true; + tmux.binaryVersion = "3.3"; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows(UnsupportedFeatureException.class, () -> server.cmd("new-session", "-d")); + assertEquals(0, tmux.effects.get()); + assertEquals(1, tmux.binaryProbes.get()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"tmux", "configured client"}) + void coldVersionBannerMayNameTheConfiguredExecutable(String name) { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.absent = true; + tmux.binaryVersion = "3.3a"; + tmux.binaryName = name; + try (Server server = Server.using( + ServerConfig.builder().binary("/tmp/configured client").build(), tmux)) { + server.requireCompatibleTmux(); + assertEquals(1, tmux.binaryProbes.get()); + assertEquals(0, tmux.effects.get()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"other", "configured", "configured client-helper"}) + void coldVersionBannerMustNameTmuxOrTheExactConfiguredExecutable(String name) { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.absent = true; + tmux.binaryVersion = "3.3a"; + tmux.binaryName = name; + try (Server server = Server.using( + ServerConfig.builder().binary("/tmp/configured client").build(), tmux)) { + assertThrows(MalformedResponseException.class, server::requireCompatibleTmux); + assertEquals(0, tmux.effects.get()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"3.3rc1", "3.3a+unknown", "3.3a trailing"}) + void renamedExecutableStillRequiresAnExactVersionToken(String version) { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.absent = true; + tmux.binaryVersion = version; + tmux.binaryName = "configured-client"; + try (Server server = + Server.using(ServerConfig.builder().binary("configured-client").build(), tmux)) { + assertThrows(MalformedResponseException.class, server::requireCompatibleTmux); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void anExecutableBannerNeverBecomesADaemonVersionToken() { + FakeTmux tmux = new FakeTmux("configured-client 3.3a"); + try (Server server = + Server.using(ServerConfig.builder().binary("configured-client").build(), tmux)) { + assertThrows(MalformedResponseException.class, server::requireCompatibleTmux); + assertEquals(0, tmux.binaryProbes.get()); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void aDaemonReplacementCannotPassTheVersionProbeThenReceiveAMutation() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.liveVersion = "3.2a"; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows(TargetGoneException.class, () -> server.cmd("set-option", "-g", "@sentinel", "changed")); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void theProbeRunsNoCommandThatFiresAHook() { + FakeTmux tmux = new FakeTmux("3.3a"); + List probes = new ArrayList<>(); + TmuxTransport recording = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) probes.add(request); + return tmux.execute(request); + } + + @Override + public void close() {} + }; + try (Server server = Server.using(ServerConfig.builder().build(), recording)) { + assertTrue(server.cmd("list-sessions").succeeded()); + } + assertEquals(1, probes.size()); + List probe = probes.getFirst().commands().getFirst(); + assertEquals(List.of("run-shell", "-C"), probe.subList(0, 2)); + assertTrue(probes.getFirst().endpoint().contains("-N"), "a probe must never start a daemon"); + assertTrue(probes.getFirst().discardStdout(), "a daemon from before 3.2 holds the probe's stdout open"); + assertTrue(tmux.sawNoCommand("display-message"), "the probe must not use display-message"); + } + + @Test + void anotherRequestsMarkerCannotAnswerTheProbe() { + FakeTmux tmux = new FakeTmux("3.8"); + tmux.probeFailure = + new CommandResult(1, List.of(), List.of("unknown command: libtmux-version-0000000000000000-3.8")); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows(CommandRejectedException.class, () -> server.cmd("set-option", "-g", "@sentinel", "x")); + assertEquals(0, tmux.effects.get()); + } + } + + /** What tmux 3.1, 3.0 and a client from before 3.2 print, none of which names a version. */ + static Stream> beforeThreeTwoAnswers() { + return Stream.of( + List.of("usage: run-shell [-b] [-t target-pane] shell-command"), + List.of("server version is too old for client"), + List.of( + "tmux: unknown option -- N", + "usage: tmux [-2CDlNuVv] [-c shell-command] [-f file] [-L socket-name]")); + } + + @ParameterizedTest + @MethodSource("beforeThreeTwoAnswers") + void anAnswerFromBeforeThreeTwoIsTheVersionRefusal(List stderr) { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.probeFailure = new CommandResult(1, List.of(), stderr); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + UnsupportedFeatureException refused = assertThrows( + UnsupportedFeatureException.class, () -> server.cmd("set-option", "-g", "@sentinel", "x")); + assertTrue(String.valueOf(refused.getMessage()).contains("older than 3.2"), refused.getMessage()); + assertTrue( + String.valueOf(refused.getMessage()).contains("restart any running server"), refused.getMessage()); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void theUsageLineAloneIsNotAnOldTmuxWithoutTheFailureThatGoesWithIt() { + for (CommandResult lookalike : List.of( + new CommandResult(2, List.of(), List.of("usage: run-shell [-b] [-t target-pane] shell-command")), + new CommandResult( + 1, List.of("output"), List.of("usage: run-shell [-b] [-t target-pane] shell-command")))) { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.probeFailure = lookalike; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows(CommandRejectedException.class, () -> server.cmd("set-option", "-g", "@sentinel", "x")); + assertEquals(0, tmux.effects.get()); + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"3.2a", "3.3"}) + void everyRefusalSaysWhatToDo(String version) { + FakeTmux tmux = new FakeTmux(version); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + UnsupportedFeatureException refused = + assertThrows(UnsupportedFeatureException.class, () -> server.cmd("list-sessions")); + assertTrue( + String.valueOf(refused.getMessage()).contains("restart any running server"), refused.getMessage()); + assertTrue(refused.belowFloor(), "a floor refusal is told apart from a missing optional feature"); + } + } + + @Test + void anOptionalFeatureRefusalIsNotBelowTheFloor() { + TmuxVersion old = TmuxVersion.parse("3.4"); + assertFalse(new UnsupportedFeatureException("popups", TmuxVersion.parse("3.7"), old).belowFloor()); + assertFalse(new UnsupportedFeatureException("reading run-shell output on 3.4").belowFloor()); + } + + @ParameterizedTest + @ValueSource(strings = {"master", "next-3.9x", "3.8-beta"}) + void aDevelopmentSpellingTheParserDoesNotKnowIsRefused(String version) { + FakeTmux tmux = new FakeTmux(version); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows(MalformedResponseException.class, () -> server.cmd("set-option", "-g", "@sentinel", "x")); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void theNextDevelopmentBuildIsSupported() { + FakeTmux tmux = new FakeTmux("next-3.9"); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertTrue(server.cmd("set-option", "-g", "@sentinel", "x").succeeded()); + assertEquals(1, tmux.effects.get()); + } + } + + @Test + void versionIsAskedOncePerDaemonAndReadAgainWhenTheFenceRefuses() { + FakeTmux tmux = new FakeTmux("3.3a"); + List probes = new ArrayList<>(); + TmuxTransport recording = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) probes.add(request); + return tmux.execute(request); + } + + @Override + public void close() {} + }; + try (Server server = Server.using(ServerConfig.builder().build(), recording)) { + assertTrue(server.cmd("set-option", "-g", "@sentinel", "one").succeeded()); + assertTrue(server.cmd("set-option", "-g", "@sentinel", "two").succeeded()); + assertEquals(1, probes.size(), "a known daemon is not asked again"); + + tmux.liveVersion = "3.8"; + tmux.reportedVersion = "3.8"; + assertTrue(server.cmd("set-option", "-g", "@sentinel", "three").succeeded()); + assertEquals(2, probes.size(), "a refused fence reads the daemon again"); + assertEquals(3, tmux.effects.get()); + } + } + + @Test + void aDaemonReplacedByAnOlderOneRaisesTheVersionError() { + FakeTmux tmux = new FakeTmux("3.8"); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertTrue(server.cmd("set-option", "-g", "@sentinel", "one").succeeded()); + tmux.liveVersion = "3.2a"; + tmux.reportedVersion = "3.2a"; + assertThrows(UnsupportedFeatureException.class, () -> server.cmd("set-option", "-g", "@sentinel", "two")); + assertEquals(1, tmux.effects.get(), "the refused call ran nothing"); + } + } + + @Test + void aFailedHandshakeNeverFallsBackToTheClientVersion() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.probeFailure = new CommandResult(1, List.of(), List.of("server exited unexpectedly")); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + CommandRejectedException failure = + assertThrows(CommandRejectedException.class, () -> server.cmd("new-session", "-d")); + assertTrue(String.valueOf(failure.getMessage()).contains("server exited unexpectedly")); + assertEquals(0, tmux.effects.get()); + assertEquals(0, tmux.binaryProbes.get()); + } + } + + @Test + void aDaemonThatExitsUnderTheProbeIsNotAlive() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.probeFailure = new CommandResult(1, List.of(), List.of("server exited unexpectedly")); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertFalse(server.isAlive()); + assertThrows(CommandRejectedException.class, () -> server.cmd("list-sessions")); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void unknownDaemonVersionFailsBeforeEffects() { + FakeTmux tmux = new FakeTmux("unknown"); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows( + MalformedResponseException.class, () -> server.cmd("set-option", "-g", "@sentinel", "changed")); + assertEquals(0, tmux.effects.get()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"3.3rc1", "next-3.3a", "3.3a+unknown", "3.3a trailing", "3.3abc"}) + void malformedFullTokensNeverInheritAPrefixsMinimum(String version) { + FakeTmux tmux = new FakeTmux(version); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows( + MalformedResponseException.class, () -> server.cmd("set-option", "-g", "@sentinel", "changed")); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void aCandidateForTheMinimumPatchIsStillOlderThanItsRelease() { + FakeTmux tmux = new FakeTmux("3.3a-rc1"); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertThrows( + UnsupportedFeatureException.class, () -> server.cmd("set-option", "-g", "@sentinel", "changed")); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void rawIfShellFlagsRemainLiteral() { + List dispatched = new ArrayList<>(); + TmuxTransport transport = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.3a"); + } + dispatched.add(request); + return new CommandResult(0, List.of(), List.of()); + } + + @Override + public void close() {} + }; + List original = List.of("if-shell", "-F", "-b", "1", "set-option -g @sentinel changed", ""); + try (Server server = Server.using(ServerConfig.builder().build(), transport)) { + assertTrue(server.cmd(original).succeeded()); + assertTrue(dispatched.getFirst().commands().contains(original)); + } + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void aVersionCheckNoOpDoesNotTurnReadsIntoMutations(String branch) { + CommandRequest request = new CommandRequest( + List.of("tmux"), + List.of(List.of("if-shell", "-F", "1", branch, "libtmux-stale-version"), List.of("list-sessions")), + Duration.ofSeconds(1), + ""); + assertEquals(Idempotence.IDEMPOTENT, request.idempotence()); + } + + @Test + void aRawCommandFailureCannotImpersonateTheVersionMarker() { + TmuxTransport transport = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + return VersionProbes.is(request) + ? VersionProbes.reply(request, "3.3a") + : new CommandResult(1, List.of(), List.of("unknown command: libtmux-stale-version")); + } + + @Override + public void close() {} + }; + try (Server server = Server.using(ServerConfig.builder().build(), transport)) { + CommandResult result = server.cmd("libtmux-stale-version"); + assertFalse(result.succeeded()); + assertEquals(List.of("unknown command: libtmux-stale-version"), result.stderr()); + } + } + + @Test + void clientVersionDiagnosticsRemainAvailableBelowTheCoreFloor() { + FakeTmux tmux = new FakeTmux("3.3"); + tmux.binaryVersion = "3.2a"; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertEquals(List.of("tmux 3.2a"), server.cmd("-V").stdout()); + assertEquals(1, tmux.binaryProbes.get()); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void compatibilityPreflightDoesNotTreatADiagnosticAsAVersionCheck() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.absent = true; + tmux.binaryVersion = "3.3"; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertEquals(List.of("tmux 3.3"), server.cmd("-V").stdout()); + UnsupportedFeatureException failure = + assertThrows(UnsupportedFeatureException.class, server::requireCompatibleTmux); + assertTrue(String.valueOf(failure.getMessage()).contains("configured binary")); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void compatibilityPreflightUsesTheLiveDaemonAndNeverStartsOne() { + FakeTmux live = new FakeTmux("3.3a"); + live.binaryVersion = "3.3"; + try (Server server = Server.using(ServerConfig.builder().build(), live)) { + server.requireCompatibleTmux(); + assertEquals(0, live.binaryProbes.get()); + assertEquals(0, live.effects.get()); + } + FakeTmux fresh = new FakeTmux("3.3a"); + fresh.absent = true; + fresh.binaryVersion = "3.3a"; + try (Server server = Server.using(ServerConfig.builder().build(), fresh)) { + server.requireCompatibleTmux(); + assertEquals(1, fresh.binaryProbes.get()); + assertEquals(0, fresh.effects.get()); + } + } + + @Test + void aSupportedDaemonDoesNotTurnClientVersionInspectionIntoAClientMinimum() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.binaryVersion = "3.3"; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + assertEquals(List.of("tmux 3.3"), server.cmd("-V").stdout()); + } + } + + @Test + void standaloneHelpPreservesTheNativeDiagnosticBelowTheFloor() { + List seen = new ArrayList<>(); + CommandResult help = new CommandResult(1, List.of(), List.of("usage: tmux [-hV]")); + TmuxTransport transport = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + seen.add(request); + return VersionProbes.is(request) ? VersionProbes.reply(request, "3.3") : help; + } + + @Override + public void close() {} + }; + try (Server server = Server.using(ServerConfig.builder().build(), transport)) { + assertEquals(help, server.cmd("-h")); + } + assertEquals(1, seen.size()); + assertEquals(List.of(List.of("-h")), seen.getFirst().commands()); + } + + @Test + void aDisappearingDaemonCannotRestartAnOlderConfiguredBinary() { + AtomicInteger starts = new AtomicInteger(); + TmuxTransport transport = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.3a"); + } + if (request.endpoint().contains("-N")) { + return new CommandResult(1, List.of(), List.of("no server running")); + } + starts.incrementAndGet(); + return new CommandResult(0, List.of(), List.of()); + } + + @Override + public void close() {} + }; + try (Server server = + Server.using(ServerConfig.builder().binary("older-tmux").build(), transport)) { + assertFalse(server.cmd("new-session", "-d", "-s", "blocked").succeeded()); + } + assertEquals(0, starts.get(), "a live version check must not authorize a fresh daemon"); + } + + @Test + void aSocketNameEqualToANoStartFlagDoesNotChangeTheVersionCheck() { + List seen = new ArrayList<>(); + TmuxTransport transport = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + seen.add(request); + if (VersionProbes.is(request)) { + assertEquals("-N", request.endpoint().get(1), "the probe needs a real no-start flag"); + return new CommandResult(1, List.of(), List.of("no server running")); + } + if (request.commands().equals(List.of(List.of("-V")))) { + return new CommandResult(0, List.of("tmux 3.3a"), List.of()); + } + assertEquals( + List.of("tmux", "-u", "-L", "-N"), request.endpoint(), "cold creation retains its selector"); + return new CommandResult(0, List.of("created"), List.of()); + } + + @Override + public void close() {} + }; + try (Server server = Server.using( + ServerConfig.builder() + .endpoint(ServerEndpoint.namedSocket("-N")) + .build(), + transport)) { + assertEquals(List.of("created"), server.cmd("new-session", "-d").stdout()); + } + assertEquals(3, seen.size(), "daemon probe, binary check and creation"); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void anAbsentProbeCannotAdmitANewlyAppearedOldDaemon(boolean explicitNoStart) { + AtomicInteger effects = new AtomicInteger(); + TmuxTransport transport = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return new CommandResult(1, List.of(), List.of("no server running")); + } + return GroupedTmux.execute(request, 4242, "3.2a", command -> { + effects.incrementAndGet(); + return new CommandResult(0, List.of(), List.of()); + }); + } + + @Override + public void close() {} + }; + List endpoint = explicitNoStart ? List.of("tmux", "-N") : List.of("tmux"); + List command = + explicitNoStart ? List.of("set-option", "-g", "@sentinel", "changed") : List.of("list-sessions"); + CommandRequest request = CommandRequest.of(endpoint, command, Duration.ofSeconds(1)); + assertThrows(TargetGoneException.class, () -> Versions.execute(transport, request, false)); + assertEquals(0, effects.get()); + } + + @Test + void absentDaemonReadsAndCleanupPreserveNativeResults() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.absent = true; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + CommandResult result = server.cmd("list-sessions"); + assertFalse(result.succeeded()); + assertEquals(List.of("no server running"), result.stderr()); + server.killServer(); + assertEquals(0, tmux.effects.get()); + assertEquals(0, tmux.binaryProbes.get()); + } + } + + @Test + void versionCheckConsumesTheSameDeadlineAsDispatch() { + FakeTmux tmux = new FakeTmux("3.3a"); + tmux.probeDelay = Duration.ofMillis(30); + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + DispatchException.TimedOut failure = assertThrows( + DispatchException.TimedOut.class, + () -> server.cmd(List.of("set-option", "-g", "@sentinel", "changed"), Duration.ofMillis(10))); + assertEquals(DispatchOutcome.NOT_DISPATCHED, failure.outcome()); + assertTrue(failure.safeToRetry()); + assertEquals(0, tmux.effects.get()); + } + } + + @Test + void probeTimeoutDoesNotClaimTheRequestedMutationWasDispatched() { + FakeTmux tmux = new FakeTmux("3.3a") { + @Override + public CommandResult execute(CommandRequest request) { + throw new DispatchException.TimedOut("probe timed out", DispatchOutcome.UNKNOWN, null); + } + }; + try (Server server = Server.using(ServerConfig.builder().build(), tmux)) { + DispatchException.TimedOut failure = + assertThrows(DispatchException.TimedOut.class, () -> server.cmd("kill-server")); + assertEquals(DispatchOutcome.NOT_DISPATCHED, failure.outcome()); + assertTrue(failure.safeToRetry()); + } + } + + private static class FakeTmux implements TmuxTransport { + private String reportedVersion; + private String liveVersion; + private String binaryVersion = "3.8"; + private String binaryName = "tmux"; + private boolean absent; + private CommandResult probeFailure = new CommandResult(0, List.of(), List.of()); + private Duration probeDelay = Duration.ZERO; + private final AtomicInteger effects = new AtomicInteger(); + private final AtomicInteger binaryProbes = new AtomicInteger(); + private final List verbs = new ArrayList<>(); + + boolean sawNoCommand(String verb) { + return !verbs.contains(verb); + } + + FakeTmux(String version) { + reportedVersion = version; + liveVersion = version; + } + + @Override + public CommandResult execute(CommandRequest request) { + request.commands().forEach(command -> verbs.add(command.getFirst())); + if (VersionProbes.is(request)) { + try { + Thread.sleep(probeDelay); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new AssertionError(interrupted); + } + if (!probeFailure.succeeded()) { + return probeFailure; + } + return absent + ? new CommandResult(1, List.of(), List.of("no server running")) + : VersionProbes.reply(request, reportedVersion); + } + if (request.commands().equals(List.of(List.of("-V")))) { + binaryProbes.incrementAndGet(); + return new CommandResult(0, List.of(binaryName + " " + binaryVersion), List.of()); + } + if (absent) { + return new CommandResult(1, List.of(), List.of("no server running")); + } + return GroupedTmux.execute(request, 4242, liveVersion, command -> { + effects.incrementAndGet(); + return new CommandResult(0, List.of("ok"), List.of()); + }); + } + + @Override + public void close() {} + } +} diff --git a/libtmux/src/test/java/io/github/libtmux/NarrowLookupTest.java b/libtmux/src/test/java/io/github/libtmux/NarrowLookupTest.java index cce5b53d6..1d80b4738 100644 --- a/libtmux/src/test/java/io/github/libtmux/NarrowLookupTest.java +++ b/libtmux/src/test/java/io/github/libtmux/NarrowLookupTest.java @@ -24,6 +24,9 @@ void anAbsentSessionCostsOneFencedRead() { TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } String flat = request.commands().stream() .flatMap(List::stream) .reduce("", (left, right) -> left + " " + right); @@ -57,7 +60,7 @@ void matchingSessionsAreReadInOneFencedCall() { TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - requests.add(request); + if (!VersionProbes.is(request)) requests.add(request); return GroupedTmux.execute(request, 4242L, argv -> { String template = argv.get(argv.indexOf("-F") + 1); return switch (argv.get(0)) { @@ -122,7 +125,7 @@ private static TmuxTransport twoSessions(List requests) { return new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - requests.add(request); + if (!VersionProbes.is(request)) requests.add(request); return GroupedTmux.execute(request, 4242L, argv -> { String template = argv.get(argv.indexOf("-F") + 1); return switch (argv.get(0)) { @@ -197,7 +200,7 @@ void aSafePaneExpressionIsSentAsAFormat() { assertTrue(server.panes(Pane_.command().is("nvim")).isEmpty()); } String all = String.join("\n", sent); - assertTrue(all.contains("-f"), all); + assertTrue(all.contains("'-f'"), all); assertTrue(all.contains("#{==:#{pane_current_command},nvim}"), all); } @@ -230,7 +233,7 @@ void aWindowRelationStaysLocal() { assertTrue(server.windows(Window_.panes().any(Pane_.command().is("nvim"))) .isEmpty()); } - assertFalse(String.join("\n", sent).contains("-f")); + assertFalse(String.join("\n", sent).contains("'-f'")); } @Test @@ -239,7 +242,7 @@ void anUnsafePaneExpressionStaysLocal() { try (Server server = Server.using(ServerConfig.builder().build(), answering(sent))) { assertTrue(server.panes(Pane_.command().is("a,b")).isEmpty()); } - assertFalse(String.join("\n", sent).contains("-f")); + assertFalse(String.join("\n", sent).contains("'-f'")); } private static List commands() { @@ -252,6 +255,9 @@ private static TmuxTransport answering(List sent) { return new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } String flat = request.commands().stream() .flatMap(List::stream) .reduce("", (left, right) -> left + " " + right); diff --git a/libtmux/src/test/java/io/github/libtmux/PaneWaitTest.java b/libtmux/src/test/java/io/github/libtmux/PaneWaitTest.java index 9e5ad75b5..07cc46906 100644 --- a/libtmux/src/test/java/io/github/libtmux/PaneWaitTest.java +++ b/libtmux/src/test/java/io/github/libtmux/PaneWaitTest.java @@ -166,7 +166,9 @@ public CommandResult execute(CommandRequest request) { var _ = server.within(bound).panes().get(0).capture(); assertTrue(seen.size() >= 2, "a listing and a capture both reached tmux"); - assertTrue(seen.stream().allMatch(bound::equals), "every command carried the chosen deadline: " + seen); + assertTrue( + seen.stream().allMatch(timeout -> timeout.isPositive() && timeout.compareTo(bound) <= 0), + "the version check and command share the chosen deadline: " + seen); assertThrows(IllegalArgumentException.class, () -> server.within(Duration.ZERO)); } } diff --git a/libtmux/src/test/java/io/github/libtmux/ServerTest.java b/libtmux/src/test/java/io/github/libtmux/ServerTest.java index 6f2493a08..9d2a70a29 100644 --- a/libtmux/src/test/java/io/github/libtmux/ServerTest.java +++ b/libtmux/src/test/java/io/github/libtmux/ServerTest.java @@ -56,7 +56,7 @@ void optionReadsUseDaemonEncodingAndPreserveMetadataFailures(@TempDir Path direc @Override public CommandResult execute(CommandRequest request) { requests.add(request); - return GroupedTmux.execute(request, 4242L, argv -> switch (argv.getFirst()) { + return GroupedTmux.execute(request, 4242L, "3.4", argv -> switch (argv.getFirst()) { case "display-message" -> argv.getLast().equals("#{version}") ? version.get() @@ -71,7 +71,10 @@ public void close() {} }; try (Server server = Server.using(config(directory), transport)) { assertEquals(java.util.Optional.of("a\rb"), server.globalOptions().get("@value")); - assertEquals(1, requests.size(), "the version and the value come from one invocation"); + assertEquals( + 2, + requests.size(), + "the version check precedes the invocation that reads version and value together"); assertTrue(requests.getLast().commands().contains(List.of("show-options", "-g", "-A", "--", "@value"))); value.set(new CommandResult(0, List.of("@value a\\377\\376é"), List.of())); assertEquals( @@ -88,9 +91,7 @@ public void close() {} assertThrows( ServerUnavailableException.class, () -> server.globalOptions().get("@value")); - assertEquals( - List.of("display-message", "-p", "#{version}"), - requests.getLast().commands().getFirst()); + assertTrue(requests.getLast().commands().contains(List.of("display-message", "-p", "#{version}"))); } } @@ -227,9 +228,12 @@ void aChosenDeadlineBindsOnlyTheCallThatChoseIt(@TempDir Path directory) throws server.killServer(chosen); server.isAlive(); - assertEquals( - List.of(chosen, chosen, server.config().defaultTimeout()), - transport.requests.stream().map(CommandRequest::timeout).toList()); + List budgets = List.of(chosen, chosen, server.config().defaultTimeout()); + assertEquals(budgets.size(), transport.requests.size()); + for (int index = 0; index < budgets.size(); index++) { + Duration sent = transport.requests.get(index).timeout(); + assertTrue(sent.isPositive() && sent.compareTo(budgets.get(index)) <= 0); + } } } @@ -249,10 +253,10 @@ void theConfirmingLookIsBoundedToo(@TempDir Path directory) throws IOException { try (Server server = Server.using(config(directory), refusing)) { server.killServer(chosen); - assertEquals( - List.of(chosen, chosen), - refusing.requests.stream().map(CommandRequest::timeout).toList(), - "the kill and the look that confirms it"); + assertEquals(2, refusing.requests.size(), "the kill and the look that confirms it"); + for (CommandRequest request : refusing.requests) { + assertTrue(request.timeout().isPositive() && request.timeout().compareTo(chosen) <= 0); + } } } @@ -297,7 +301,10 @@ void aWaitPropagatesTransportFailuresThatAreNotItsDeadline(@TempDir Path directo TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).contains("wait-for")) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } + if (request.commands().stream().anyMatch(command -> command.contains("wait-for"))) { throw failure; } return new CommandResult(0, List.of("4242"), List.of()); @@ -325,7 +332,10 @@ void aWaitWithSignalCapacityPreservesAPredispatchTimeout(@TempDir Path directory TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).contains("wait-for")) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } + if (request.commands().stream().anyMatch(command -> command.contains("wait-for"))) { throw failure; } return new CommandResult(1, List.of(), List.of("no server running")); @@ -422,8 +432,14 @@ public CommandResult execute(CommandRequest request) { public void close() {} }; try (Server server = Server.using(config(directory), transport)) { - assertAll(liveReads(server).stream() - .map(read -> () -> assertSame(failure, assertThrows(DispatchException.class, read)))); + assertAll(liveReads(server).stream().map(read -> () -> { + DispatchException seen = assertThrows(DispatchException.class, read); + assertEquals(failure.getClass(), seen.getClass()); + assertEquals(failure.getMessage(), seen.getMessage()); + assertSame(cause, seen.getCause()); + assertEquals(DispatchOutcome.NOT_DISPATCHED, seen.outcome()); + assertTrue(seen.safeToRetry()); + })); } } } @@ -520,11 +536,11 @@ void snapshotKeepsTheIdentityOfALiveServerWithNoSessions(@TempDir Path directory @Override public CommandResult execute(CommandRequest request) { requests.incrementAndGet(); - return GroupedTmux.execute(request, 4242L, "3.2a", argv -> switch (argv.getFirst()) { + return GroupedTmux.execute(request, 4242L, "3.3a", argv -> switch (argv.getFirst()) { case "display-message" -> new CommandResult( 0, - List.of(String.join(RowFormat.of("field").separator(), "4242", "3.2a", "1790000000")), + List.of(String.join(RowFormat.of("field").separator(), "4242", "3.3a", "1790000000")), List.of()); case "list-sessions" -> new CommandResult(0, List.of(), List.of()); // tmux has no current target to list children against, and says so. @@ -540,12 +556,12 @@ public void close() {} var snapshot = server.snapshot(); assertEquals(4242L, snapshot.serverPid().orElseThrow()); - assertEquals(TmuxVersion.parse("3.2a"), snapshot.serverVersion().orElseThrow()); + assertEquals(TmuxVersion.parse("3.3a"), snapshot.serverVersion().orElseThrow()); assertTrue(snapshot.sessions().isEmpty()); assertTrue(snapshot.windows().isEmpty()); assertTrue(snapshot.panes().isEmpty()); assertTrue(snapshot.clients().isEmpty()); - assertEquals(2, requests.get(), "tmux refused the rest of the group, which cost no further request"); + assertEquals(3, requests.get(), "two data invocations share one version probe"); assertEquals(List.of(), server.sessions()); assertEquals(List.of(), server.windows()); assertEquals(List.of(), server.panes()); @@ -749,8 +765,9 @@ void controlRefusesATransportThatStartsNoClient(@TempDir Path directory) throws TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - List argv = request.commands().get(0); - if (argv.stream().anyMatch(word -> word.contains("#{pid} #{version}"))) { + if (request.commands().stream() + .flatMap(List::stream) + .anyMatch(word -> word.contains("#{pid} #{version}"))) { return new CommandResult(0, List.of("4242 3.6"), List.of()); } return GroupedTmux.execute(request, 4242L, "3.6", command -> switch (command.get(0)) { @@ -790,31 +807,12 @@ public void close() {} // --------------------------------------------------------------------------- version gates - /** - * The guard asks the daemon rather than trusting a version string, so it cannot fail the way a - * version predicate can: a floor fitted to the two adjacent releases a matrix happens to run, - * one lettered patch off from what tmux's own history says. Proven by making the two disagree - - * a version the constant would refuse, with {@code list-commands} naming the command anyway, and - * the reverse - and checking the daemon's answer is the one that wins. - */ @Test - void promptHistoryAsksTheDaemonRatherThanTrustingAVersionString(@TempDir Path directory) throws IOException { - try (Server server = - Server.using(config(directory), listingCommands("3.2a", "show-prompt-history (showphist) [-T type]"))) { - assertDoesNotThrow( - () -> server.prompt().history(), "list-commands names it, so a low version must not refuse"); + void promptHistoryUsesTheSupportedCommandDirectly(@TempDir Path directory) throws IOException { + try (Server server = Server.using(config(directory), listingCommands("3.3a"))) { + assertDoesNotThrow(() -> server.prompt().history()); assertDoesNotThrow(() -> server.prompt().clear()); } - try (Server server = Server.using(config(directory), listingCommands("99.0"))) { - UnsupportedFeatureException refused = assertThrows( - UnsupportedFeatureException.class, () -> server.prompt().history()); - - assertTrue( - String.valueOf(refused.getMessage()).contains("3.3"), - "the refusal still names the release most callers will recognise: " + refused.getMessage()); - assertThrows( - UnsupportedFeatureException.class, () -> server.prompt().clear()); - } } /** @@ -1014,19 +1012,15 @@ void aSilentExitUnderAMissingSocketDirectoryNamesTheDirectory(@TempDir Path dire * with {@code lines}, whatever the real relationship between the two would be. */ private static TmuxTransport listingCommands(String version, String... lines) { - String separator = RowFormat.of("field").separator(); return new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - List argv = request.commands().get(0); - if (argv.get(0).equals("display-message")) { - return new CommandResult( - 0, List.of(String.join(separator, "4242", version, "1790000000")), List.of()); - } - if (argv.get(0).equals("list-commands")) { - return new CommandResult(0, List.of(lines), List.of()); - } - return new CommandResult(0, List.of(), List.of()); + return GroupedTmux.execute( + request, + 4242L, + version, + command -> new CommandResult( + 0, command.getFirst().equals("list-commands") ? List.of(lines) : List.of(), List.of())); } @Override @@ -1088,7 +1082,9 @@ void killingThroughAnotherTransportWaitsOnNoLocalProcess(@TempDir Path directory TmuxTransport transport = new TmuxTransport() { @Override public CommandResult execute(CommandRequest request) { - return new CommandResult(0, List.of(Long.toString(sleeper.pid())), List.of()); + return VersionProbes.is(request) + ? VersionProbes.reply(request, "3.6") + : new CommandResult(0, List.of(Long.toString(sleeper.pid())), List.of()); } @Override @@ -1135,6 +1131,9 @@ private record RefusingTransport(String stderr) implements TmuxTransport { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return new CommandResult(1, List.of(), List.of(stderr)); } @@ -1147,6 +1146,9 @@ private static final class SurvivingTransport implements TmuxTransport { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } return request.commands().stream().anyMatch(command -> command.contains("kill-server")) ? new CommandResult(1, List.of(), List.of("permission denied")) : new CommandResult(0, List.of("4242"), List.of()); @@ -1175,6 +1177,9 @@ private static final class RecordingTransport implements TmuxTransport { @Override public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.6"); + } executions.incrementAndGet(); requests.add(request); return new CommandResult(exitCode, List.of("ok"), List.of()); @@ -1223,14 +1228,15 @@ private static final class SnapshotRaceTransport implements TmuxTransport { */ @Override public CommandResult execute(CommandRequest request) { - if (request.commands().get(0).get(0).equals("display-message")) { - return identity(at(2 * identityReads.getAndIncrement())); + if (VersionProbes.is(request)) { + return at(2 * identityReads.get()).isEmpty() ? identity("") : VersionProbes.reply(request, "3.6"); } - String live = at(2 * (identityReads.get() - 1) + 1); + String live = at(Math.max(0, 2 * (identityReads.get() - 1) + 1)); if (live.isEmpty()) { - return new CommandResult(1, List.of(), List.of("no server running on /tmp/s")); + return identity(""); } return GroupedTmux.execute(request, Long.parseLong(live), argv -> switch (argv.get(0)) { + case "display-message" -> identity(at(2 * identityReads.getAndIncrement())); case "list-sessions" -> new CommandResult(0, List.of(sessionRows.get(sessionReads.getAndIncrement())), List.of()); default -> new CommandResult(0, List.of(), List.of()); diff --git a/libtmux/src/test/java/io/github/libtmux/TmuxVersionTest.java b/libtmux/src/test/java/io/github/libtmux/TmuxVersionTest.java index f05a605d5..93507fbe8 100644 --- a/libtmux/src/test/java/io/github/libtmux/TmuxVersionTest.java +++ b/libtmux/src/test/java/io/github/libtmux/TmuxVersionTest.java @@ -68,12 +68,11 @@ void aReleaseCandidateRoundTrips() { } /** - * A release candidate has what its release has — tmux freezes features at the candidate, and a - * 3.8 candidate behaves as 3.8 does on every lane-tested gate — so it compares equal to it, above - * the development build tracking toward it. It is still not the release: not {@code equals} to it. + * A release candidate orders above the development build tracking toward it and below its release, + * so the order agrees with {@code equals}, and a gate on the release is not met by its candidate. */ @Test - void aReleaseCandidateComparesAsItsRelease() { + void aReleaseCandidateOrdersBelowItsReleaseAndFailsItsGates() { TmuxVersion previous = TmuxVersion.parse("3.7c"); TmuxVersion development = TmuxVersion.parse("next-3.8"); TmuxVersion candidate = TmuxVersion.parse("3.8-rc"); @@ -82,9 +81,11 @@ void aReleaseCandidateComparesAsItsRelease() { assertTrue(previous.compareTo(development) < 0); assertTrue(development.compareTo(candidate) < 0, "a development build precedes the candidate"); - assertEquals(0, candidate.compareTo(release), "features are frozen at the candidate"); + assertTrue(candidate.compareTo(release) < 0, "the order agrees with equals"); assertTrue(candidate.compareTo(patch) < 0); - assertTrue(candidate.atLeast(release), "a gate on the release is met by its candidate"); + assertTrue(!candidate.atLeast(release), "a gate on the release is not met by its candidate"); + assertTrue(!development.atLeast(candidate)); + assertTrue(!development.atLeast(release)); assertTrue(!candidate.equals(release), "and the candidate is still not the release"); } } diff --git a/libtmux/src/test/java/io/github/libtmux/VersionGrammarTest.java b/libtmux/src/test/java/io/github/libtmux/VersionGrammarTest.java new file mode 100644 index 000000000..38d69ffd8 --- /dev/null +++ b/libtmux/src/test/java/io/github/libtmux/VersionGrammarTest.java @@ -0,0 +1,147 @@ +package io.github.libtmux; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.github.libtmux.exception.MalformedResponseException; +import io.github.libtmux.exception.UnsupportedFeatureException; +import io.github.libtmux.internal.support.Versions; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * The version strings tmux has ever reported, and what a support policy does with each: the + * canonical vectors every port is held to, with floor 3.3a. + */ +final class VersionGrammarTest { + + @ParameterizedTest + @ValueSource( + strings = { + "3.3a", + "3.4", + "3.7d", + "3.8", + "3.10", + "4.0", + "3.4-rc", + "3.8-rc", + "3.8-rc2", + "next-3.4", + "next-3.9" + }) + void supportedAtTheFloor(String reported) { + assertEquals(reported, Versions.require(reported).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"3.3", "3.2a", "3.3-rc", "3.2-rc5", "next-3.3"}) + void belowTheFloorIsRefusedAsUnsupported(String reported) { + assertThrows(UnsupportedFeatureException.class, () -> Versions.require(reported)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "master", + "openbsd-7.6", + "", + "3", + "garbage", + "3.3a garbage", + "3.3a-master", + "3.3a-dev", + "next-3.3a", + "next-3.9-master", + "next-3.9a", + "next-3.8-rc2", + "3.8-rc0", + "3.8-rc00001", + "3.8-rc99999", + "3.99999", + "99999999999.0", + "03.3a" + }) + void unparseableReportsAreRefusedAsMalformed(String reported) { + assertThrows(MalformedResponseException.class, () -> Versions.require(reported)); + } + + @Test + void orderingFollowsTheReleaseTrain() { + assertAscending("next-3.7", "3.7-rc", "3.7-rc3", "3.7", "3.7a"); + assertAscending("next-3.4", "3.4-rc", "3.4"); + assertAscending("3.8-rc2", "3.8-rc10", "3.8"); + assertAscending("3.9", "3.10", "4.0"); + } + + @Test + void aCandidateOrDevelopmentBuildOfAReleaseIsNotThatRelease() { + TmuxVersion control = new TmuxVersion(3, 7, ""); + for (String below : List.of("next-3.7", "3.7-rc", "3.7-rc2")) { + assertTrue(TmuxVersion.parse(below).compareTo(control) < 0, below); + } + for (String atOrAbove : List.of("3.7", "3.7a", "3.8-rc2", "next-3.8")) { + assertTrue(TmuxVersion.parse(atOrAbove).compareTo(control) >= 0, atOrAbove); + } + } + + @ParameterizedTest + @ValueSource( + strings = { + "3.3a garbage", + "3.3a-master", + "3.3a-dev", + "3.3abc", + "next-3.3a-x", + "next-3.3a", + "next-3.9-rc", + "3.8-rc0", + "3.8-rc99999", + "3.99999", + "", + "3", + "master" + }) + void parseIsAnchoredToTheCanonicalGrammar(String text) { + assertThrows(IllegalArgumentException.class, () -> TmuxVersion.parse(text)); + } + + @Test + void aFeatureGateFollowsTheSameOrderAsTheFloor() { + TmuxVersion release = TmuxVersion.parse("3.8"); + TmuxVersion candidate = TmuxVersion.parse("3.8-rc2"); + assertTrue(candidate.compareTo(release) < 0); + assertTrue(!candidate.equals(release)); + assertTrue(!candidate.atLeast(release), "a candidate does not meet its release's gate"); + assertTrue(release.atLeast(candidate)); + assertTrue(candidate.sameRelease(release)); + assertTrue(!TmuxVersion.parse("next-3.8").sameRelease(release)); + assertTrue(!TmuxVersion.parse("3.8a").sameRelease(release)); + assertEquals(0, release.compareTo(TmuxVersion.parse("3.8"))); + } + + @Test + void aCandidateOrDevelopmentBuildFailsAGateOnItsRelease() { + TmuxVersion gate = new TmuxVersion(3, 4, ""); + for (String below : List.of("next-3.4", "3.4-rc", "3.4-rc2", "3.3a")) { + assertTrue(!TmuxVersion.parse(below).atLeast(gate), below); + } + for (String met : List.of("3.4", "3.4a", "3.5-rc", "next-3.5")) { + assertTrue(TmuxVersion.parse(met).atLeast(gate), met); + } + assertTrue(TmuxVersion.parse("3.8-rc2").atLeast(new TmuxVersion(3, 8, "", false, "rc2"))); + assertTrue(!TmuxVersion.parse("3.8-rc").atLeast(new TmuxVersion(3, 8, "", false, "rc2"))); + } + + private static void assertAscending(String... versions) { + for (int index = 1; index < versions.length; index++) { + TmuxVersion lower = TmuxVersion.parse(versions[index - 1]); + TmuxVersion higher = TmuxVersion.parse(versions[index]); + assertTrue(lower.compareTo(higher) < 0, versions[index - 1] + " < " + versions[index]); + assertTrue(higher.compareTo(lower) > 0, versions[index] + " > " + versions[index - 1]); + } + } +} diff --git a/libtmux/src/test/java/io/github/libtmux/VersionProbes.java b/libtmux/src/test/java/io/github/libtmux/VersionProbes.java new file mode 100644 index 000000000..5eaa99ae7 --- /dev/null +++ b/libtmux/src/test/java/io/github/libtmux/VersionProbes.java @@ -0,0 +1,39 @@ +package io.github.libtmux; + +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import java.util.List; + +/** + * The version probe as a test double sees it, and tmux's answer to it. + * + *

The probe is {@code run-shell -C '#{version}'}: the daemon expands the format, fails to + * parse the result as a command, and spells the version in {@code unknown command: }. + */ +final class VersionProbes { + + private static final String MARKER = "libtmux-version-"; + private static final String FORMAT = "#{version}"; + + private VersionProbes() {} + + /** Whether this request is the version probe and nothing else. */ + static boolean is(CommandRequest request) { + return request.commands().size() == 1 && is(request.commands().getFirst()); + } + + static boolean is(List command) { + return command.size() == 3 + && command.get(0).equals("run-shell") + && command.get(1).equals("-C") + && command.get(2).startsWith(MARKER) + && command.get(2).endsWith(FORMAT); + } + + /** The reply of a daemon that reports this version. */ + static CommandResult reply(CommandRequest request, String version) { + String argument = request.commands().getFirst().get(2); + String marker = argument.substring(0, argument.length() - FORMAT.length()); + return new CommandResult(1, List.of(), List.of("unknown command: " + marker + version)); + } +} diff --git a/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java b/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java index c9383eda7..94d2258fb 100644 --- a/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java +++ b/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java @@ -12,6 +12,7 @@ import io.github.libtmux.exception.ControlEndedException; import io.github.libtmux.exception.DispatchException; import io.github.libtmux.exception.LibTmuxException; +import io.github.libtmux.transport.ControlCarrier; import io.github.libtmux.transport.DispatchOutcome; import java.nio.ByteBuffer; import java.nio.file.Files; @@ -20,10 +21,13 @@ import java.time.Duration; import java.util.List; import java.util.Optional; +import java.util.OptionalLong; import java.util.concurrent.CountDownLatch; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -36,6 +40,32 @@ */ final class ControlClientTest { + @Test + void aCarrierProbeTimeoutDoesNotAcquireControl() { + AtomicInteger starts = new AtomicInteger(); + AtomicReference probe = new AtomicReference<>(); + ControlCarrier carrier = command -> { + starts.incrementAndGet(); + Process started = new ProcessBuilder("/bin/sh", "-c", "exec sleep 30").start(); + probe.set(started); + return started; + }; + DispatchException.TimedOut failure = assertThrows( + DispatchException.TimedOut.class, + () -> ControlClient.attach( + carrier, + ServerConfig.builder().build(), + new SessionId("$0"), + 4242L, + OptionalLong.empty(), + "3.7c", + Duration.ofMillis(250))); + + assertEquals(DispatchOutcome.NOT_DISPATCHED, failure.outcome()); + assertEquals(1, starts.get(), "only the version probe may start"); + assertFalse(probe.get().isAlive(), "the timed-out version probe survived"); + } + /** A pipe write on the calling virtual thread pins the only carrier and starves the sentinel. */ @Test @Tag("carrier") @@ -472,6 +502,13 @@ void extendedOutputIsOutput(@TempDir Path directory) throws Exception { */ private static final String PRELUDE = """ #!/bin/sh + for argument in "$@"; do + case "$argument" in + libtmux-version-*'#{version}') + printf 'unknown command: %s3.7d\\n' "${argument%'#{version}'}" >&2 + exit 1;; + esac + done read_request() { IFS= read -r request && IFS= read -r marker; } answer() { printf '%%begin 1 1 1\\n' diff --git a/libtmux/src/test/java/io/github/libtmux/transport/ProcessTransportTest.java b/libtmux/src/test/java/io/github/libtmux/transport/ProcessTransportTest.java index d0fefdaf2..bcdd9028f 100644 --- a/libtmux/src/test/java/io/github/libtmux/transport/ProcessTransportTest.java +++ b/libtmux/src/test/java/io/github/libtmux/transport/ProcessTransportTest.java @@ -59,6 +59,229 @@ private static CommandRequest bash(String script, Duration timeout) { return CommandRequest.of(List.of("/bin/bash"), List.of("-c", script), timeout); } + // ------------------------------------------------------------------- held standard output + + /** + * tmux 3.0 and 3.1 leave the client's standard output open in the daemon after the client has + * exited, while stderr closes. The double does the same with a background child that keeps stdout. + */ + @Test + void aRequestThatReadsNoStdoutDoesNotWaitForAnOutputPipeSomethingElseHolds() { + CommandRequest request = new CommandRequest( + List.of("/bin/sh"), + List.of(List.of("-c", "sleep 5 2>/dev/null & echo out; exit 1")), + GENEROUS, + "", + true); + try (ProcessTransport transport = new ProcessTransport(1, 4_096)) { + long started = System.nanoTime(); + CommandResult result = transport.execute(request); + Duration took = Duration.ofNanos(System.nanoTime() - started); + assertEquals(1, result.exitCode()); + assertEquals(List.of(), result.stdout(), "the output pipe was never opened"); + assertTrue(took.compareTo(Duration.ofSeconds(3)) < 0, "returned after " + took); + } + } + + /** + * The same pipe held for a caller that does read standard output: what the exited process wrote + * is returned at once, not after the deadline. + */ + @Test + void anExitedProcessWhoseOutputPipeStaysOpenReturnsWhatItWrote() { + // The reader is already waiting on the pipe when the process exits. + CommandRequest request = shell("sleep 5 2>/dev/null & sleep 0.3; echo out; exit 1", GENEROUS); + try (ProcessTransport transport = new ProcessTransport(1, 4_096)) { + long started = System.nanoTime(); + CommandResult result = transport.execute(request); + Duration took = Duration.ofNanos(System.nanoTime() - started); + assertEquals(1, result.exitCode()); + assertEquals(List.of("out"), result.stdout()); + assertTrue(took.compareTo(Duration.ofSeconds(3)) < 0, "returned after " + took); + } + } + + /** + * Whatever a process wrote before it exited is in the pipe, however long the reader was kept from + * a CPU. A reader that first looks at the pipe well after the exit, as one starved by a busy host + * does, still gets every line; a drain that gave up after a quiet interval returned none. + */ + @Test + void aReaderStarvedPastTheExitStillGetsEverythingTheProcessWrote() { + CommandRequest request = shell("i=0; while [ $i -lt 200 ]; do echo line$i; i=$((i+1)); done; exit 0", GENEROUS); + ProcessTransport.ProcessStarter starved = command -> new LateReader(new ProcessBuilder(command).start(), 800); + try (ProcessTransport transport = new ProcessTransport(1, 1 << 20, starved, System::nanoTime)) { + CommandResult result = transport.execute(request); + assertEquals(200, result.stdout().size()); + assertEquals("line199", result.stdout().getLast()); + } + } + + /** + * An old daemon holding the pipe must not keep a library user's JVM alive once its main returns: + * the child here never closes its transport. + */ + @Test + void aJvmWhosePumpsWaitOnAPipeSomethingElseHoldsStillExits() throws Exception { + Process child = new ProcessBuilder( + Path.of(System.getProperty("java.home"), "bin", "java").toString(), + "-cp", + System.getProperty("java.class.path"), + HeldPipeMain.class.getName()) + .redirectErrorStream(true) + .redirectOutput(ProcessBuilder.Redirect.DISCARD) + .start(); + try { + assertTrue(child.waitFor(8, TimeUnit.SECONDS), "the JVM is still alive after its main returned"); + assertEquals(0, child.exitValue()); + } finally { + child.destroyForcibly(); + } + } + + /** + * A pump whose child outlives the kill must stop when its thread is interrupted, whether the + * interrupt arrives while it waits or was already set, and must not spin on the flag: a wait + * that returns at once on an interrupted thread would burn a core until the child died. + */ + @Test + void anInterruptedPumpWhoseChildIsAliveStopsWithoutSpinning() throws Exception { + for (boolean alreadySet : new boolean[] {false, true}) { + Process child = new ProcessBuilder("/bin/sh", "-c", "sleep 20").start(); + try { + ProcessTransport.StdoutPump pump = new ProcessTransport.StdoutPump( + child, 4_096, new CountDownLatch(1), new CompletableFuture<>(), () -> true); + java.lang.management.ThreadMXBean threads = java.lang.management.ManagementFactory.getThreadMXBean(); + CompletableFuture cpuNanos = new CompletableFuture<>(); + CountDownLatch started = new CountDownLatch(1); + Thread worker = new Thread(() -> { + long before = threads.getCurrentThreadCpuTime(); + if (alreadySet) { + Thread.currentThread().interrupt(); + } + started.countDown(); + try { + pump.call(); + cpuNanos.complete(threads.getCurrentThreadCpuTime() - before); + } catch (Exception e) { + cpuNanos.completeExceptionally(e); + } + }); + worker.start(); + assertTrue(started.await(5, TimeUnit.SECONDS)); + if (!alreadySet) { + Thread.sleep(300); + worker.interrupt(); + } + long used = cpuNanos.get(3, TimeUnit.SECONDS); + assertTrue(child.isAlive(), "the child must have outlived the pump for this to mean anything"); + assertTrue( + used < TimeUnit.MILLISECONDS.toNanos(500), + (alreadySet ? "flag already set" : "interrupted while waiting") + " used " + + TimeUnit.NANOSECONDS.toMillis(used) + " ms of CPU"); + } finally { + child.destroyForcibly(); + } + } + } + + /** Runs a command whose background child keeps the output pipe, then returns without closing. */ + public static final class HeldPipeMain { + private HeldPipeMain() {} + + public static void main(String[] args) { + ProcessTransport transport = new ProcessTransport(1, 4_096); + transport.execute(shell("sleep 20 2>/dev/null & echo out; exit 1", GENEROUS)); + } + } + + /** A process whose output is first looked at only after a delay. */ + private static final class LateReader extends Process { + private final Process inner; + private final long delayMillis; + + LateReader(Process inner, long delayMillis) { + this.inner = inner; + this.delayMillis = delayMillis; + } + + @Override + public InputStream getInputStream() { + return new java.io.FilterInputStream(inner.getInputStream()) { + private boolean looked; + + @Override + public int available() throws IOException { + if (!looked) { + looked = true; + try { + Thread.sleep(delayMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + return super.available(); + } + }; + } + + @Override + public OutputStream getOutputStream() { + return inner.getOutputStream(); + } + + @Override + public InputStream getErrorStream() { + return inner.getErrorStream(); + } + + @Override + public int waitFor() throws InterruptedException { + return inner.waitFor(); + } + + @Override + public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { + return inner.waitFor(timeout, unit); + } + + @Override + public int exitValue() { + return inner.exitValue(); + } + + @Override + public void destroy() { + inner.destroy(); + } + + @Override + public Process destroyForcibly() { + inner.destroyForcibly(); + return this; + } + + @Override + public boolean isAlive() { + return inner.isAlive(); + } + + @Override + public CompletableFuture onExit() { + return inner.onExit().thenApply(ignored -> this); + } + + @Override + public long pid() { + return inner.pid(); + } + + @Override + public ProcessHandle toHandle() { + return inner.toHandle(); + } + } + // ----------------------------------------------------------------------------- logging /** @@ -194,6 +417,39 @@ void anExecutableThatDoesNotExistIsNotDispatched() { // --------------------------------------------------------------------------------- deadlines + @Test + void aOneShotCarrierKeepsCallerOwnershipAndTimeoutCertainty() throws Exception { + AtomicReference latest = new AtomicReference<>(); + AtomicInteger starts = new AtomicInteger(); + ControlCarrier carrier = command -> { + starts.incrementAndGet(); + Process started = new ProcessBuilder(command).start(); + latest.set(started); + return started; + }; + Process borrowed = carrier.start(List.of("/bin/sh", "-c", "exec sleep 30")); + try { + DispatchException.TimedOut failure = assertThrows( + DispatchException.TimedOut.class, + () -> ProcessTransport.executeThroughCarrier(carrier, hangingAs(List.of("kill-server")))); + + assertEquals(2, starts.get(), "the request must use the supplied carrier"); + assertEquals(DispatchOutcome.UNKNOWN, failure.outcome()); + assertFalse(failure.safeToRetry(), "the dispatched mutation may have run"); + assertFalse(latest.get().isAlive(), "the request's child survived its deadline"); + assertTrue(borrowed.isAlive(), "one request reclaimed a caller-owned process"); + assertEquals( + List.of("reused"), + ProcessTransport.executeThroughCarrier(carrier, shell("echo reused", Duration.ofSeconds(2))) + .stdout()); + assertEquals(3, starts.get()); + assertTrue(borrowed.isAlive()); + } finally { + borrowed.destroyForcibly(); + borrowed.waitFor(5, TimeUnit.SECONDS); + } + } + @Test void aChildThatOutlivesItsDeadlineIsKilledAndReportedUnknown() { try (ProcessTransport transport = new ProcessTransport()) { @@ -483,6 +739,37 @@ void closeKillingARunningChildReportsUnknownRatherThanASignalExit() throws Inter assertEquals(DispatchOutcome.UNKNOWN, failure.outcome()); } + /** + * Closing kills the child and closes its pipes, and the drain that notices first must not report + * that as its own failure. The child here ignores the termination signal for a moment, so its pipes + * are closed while it is still alive, which is when a drain would report the closed stream; the + * close is repeated across different timings of the drain. + */ + @Test + void closeDuringACommandIsAlwaysReportedAsTheTransportClosing() throws Exception { + int wrong = 0; + for (int round = 0; round < 12; round++) { + ProcessTransport transport = new ProcessTransport(1, 4_096); + BlockingQueue outcome = new ArrayBlockingQueue<>(1); + Thread caller = new Thread(() -> { + try { + outcome.add(transport.execute(shell("trap '' TERM; while :; do sleep 0.05; done", GENEROUS))); + } catch (RuntimeException e) { + outcome.add(e); + } + }); + caller.start(); + Thread.sleep(20 + (round % 6) * 10); + transport.close(); + Object result = outcome.poll(20, TimeUnit.SECONDS); + if (!(result instanceof ServerClosedException)) { + wrong++; + } + caller.join(TimeUnit.SECONDS.toMillis(5)); + } + assertEquals(0, wrong, "closes reported as something other than the transport closing"); + } + @Test void concurrentCloseWaitsForTheFirstCloseToFinish() throws Exception { GatedInputStream stdout = new GatedInputStream(); @@ -949,12 +1236,15 @@ private static boolean awaitTimedWait(Future request, Thread caller) throws I return false; } + /** + * Standard output whose first look blocks until released, as a pump worker stuck in I/O would. + */ private static final class GatedInputStream extends InputStream { private final CountDownLatch readStarted = new CountDownLatch(1); private final CountDownLatch released = new CountDownLatch(1); @Override - public int read() { + public int available() { readStarted.countDown(); boolean interrupted = false; while (released.getCount() > 0) { @@ -967,6 +1257,11 @@ public int read() { if (interrupted) { Thread.currentThread().interrupt(); } + return 1; + } + + @Override + public int read() { return -1; } From 08be463ef273c87acac81c480cb6d00d97938e2b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Oct 2026 21:40:09 -0500 Subject: [PATCH 02/11] CLI(fix): Check tmux and Python backend versions why: Python builders and shells execute outside the Java dispatcher. A Java preflight alone cannot enforce the core floor through an obsolete installed Python libtmux backend, and a script needs a machine code that tells a tmux below the floor from any other tmux failure. what: - Check the selected daemon, or the executable only when absent, before external builder and shell entry. - Require the Python backend's declared core floor to be at least 3.3a before loading builder or shell code. Repeat the handshake in every child process. - Report the selected interpreter and compatible backend requirement without inventing a release pin. - Report a tmux below the floor with the machine code tmux_unsupported, in JSON and in text beside the remedy. A missing optional feature stays tmux_failed. - Install released tmuxp in CI and put Python libtmux source from a commit on a branch of its repository on PYTHONPATH, asserting its import path and floor, because released tmuxp pins an older libtmux than the check needs. The JDK and tmux matrix lanes do the same. - Cover old-client refusal, obsolete backend refusal before effects, qualified backend success and real tmuxp 1.74.0 extensions. --- .github/workflows/ci.yml | 9 ++ .github/workflows/tmux-matrix.yml | 9 ++ docs/guide/workspace-cli.md | 2 + .../libtmux/workspace/cli/Children.java | 22 ++- .../libtmux/workspace/cli/Execution.java | 5 + .../github/libtmux/workspace/cli/Machine.java | 2 + .../io/github/libtmux/workspace/cli/Main.java | 2 + .../workspace/cli/PythonExtensions.java | 15 +- .../libtmux/workspace/cli/ExecutionTest.java | 142 ++++++++++++++++-- .../libtmux/workspace/cli/MainTest.java | 12 ++ 10 files changed, 203 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 680104eb9..eb0a22341 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,15 @@ jobs: run: | python3 -m venv "$RUNNER_TEMP/workspace-python" "$RUNNER_TEMP/workspace-python/bin/python" -m pip install 'tmuxp==1.74.0' + git init --quiet "$RUNNER_TEMP/python-libtmux-core" + git -C "$RUNNER_TEMP/python-libtmux-core" fetch --quiet --depth=1 \ + https://github.com/tmux-python/libtmux.git c27dd929fc25c3a95648759d8c0537a07ef60258 + git -C "$RUNNER_TEMP/python-libtmux-core" checkout --quiet --detach \ + c27dd929fc25c3a95648759d8c0537a07ef60258 + PYTHONPATH="$RUNNER_TEMP/python-libtmux-core/src" "$RUNNER_TEMP/workspace-python/bin/python" -c \ + 'import sys, libtmux; from pathlib import Path; from libtmux.common import TMUX_MIN_VERSION; assert TMUX_MIN_VERSION == "3.3a", TMUX_MIN_VERSION; assert Path(libtmux.__file__).resolve() == Path(sys.argv[1]).resolve() / "libtmux" / "__init__.py", libtmux.__file__; print(TMUX_MIN_VERSION, libtmux.__file__)' \ + "$RUNNER_TEMP/python-libtmux-core/src" + echo "PYTHONPATH=$RUNNER_TEMP/python-libtmux-core/src" >> "$GITHUB_ENV" echo "TMUX_WORKSPACE_TEST_PYTHON=$RUNNER_TEMP/workspace-python/bin/python" >> "$GITHUB_ENV" # Runs every check task instead of stopping at the first failure, so one run names them all. diff --git a/.github/workflows/tmux-matrix.yml b/.github/workflows/tmux-matrix.yml index dd4711594..c0a18335e 100644 --- a/.github/workflows/tmux-matrix.yml +++ b/.github/workflows/tmux-matrix.yml @@ -132,6 +132,15 @@ jobs: run: | python3 -m venv "$RUNNER_TEMP/workspace-python" "$RUNNER_TEMP/workspace-python/bin/python" -m pip install 'tmuxp==1.74.0' + git init --quiet "$RUNNER_TEMP/python-libtmux-core" + git -C "$RUNNER_TEMP/python-libtmux-core" fetch --quiet --depth=1 \ + https://github.com/tmux-python/libtmux.git c27dd929fc25c3a95648759d8c0537a07ef60258 + git -C "$RUNNER_TEMP/python-libtmux-core" checkout --quiet --detach \ + c27dd929fc25c3a95648759d8c0537a07ef60258 + PYTHONPATH="$RUNNER_TEMP/python-libtmux-core/src" "$RUNNER_TEMP/workspace-python/bin/python" -c \ + 'import sys, libtmux; from pathlib import Path; from libtmux.common import TMUX_MIN_VERSION; assert TMUX_MIN_VERSION == "3.3a", TMUX_MIN_VERSION; assert Path(libtmux.__file__).resolve() == Path(sys.argv[1]).resolve() / "libtmux" / "__init__.py", libtmux.__file__; print(TMUX_MIN_VERSION, libtmux.__file__)' \ + "$RUNNER_TEMP/python-libtmux-core/src" + echo "PYTHONPATH=$RUNNER_TEMP/python-libtmux-core/src" >> "$GITHUB_ENV" echo "TMUX_WORKSPACE_TEST_PYTHON=$RUNNER_TEMP/workspace-python/bin/python" >> "$GITHUB_ENV" # One lane rather than testTmuxMatrix: each version builds and runs on its own runner, so the diff --git a/docs/guide/workspace-cli.md b/docs/guide/workspace-cli.md index c91cf936b..dfbb62092 100644 --- a/docs/guide/workspace-cli.md +++ b/docs/guide/workspace-cli.md @@ -185,6 +185,8 @@ one a script calls: - **`session_mismatch`** — the named session exists and is not what the document describes - **`tmux_unavailable`** — tmux could not be found or run +- **`tmux_unsupported`** — tmux is older than 3.3a; the message says to + upgrade tmux or use the previous release - **`tmux_failed`** — tmux ran and refused, or the server changed under the command - **`script_failed`** — a child program could not be run, or ended badly diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Children.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Children.java index e3b180f15..3a13aab54 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Children.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Children.java @@ -291,7 +291,11 @@ static String python(Main.Context context, Reporter report) throws InterruptedEx executable(context, context.environment().getOrDefault("TMUX_WORKSPACE_PYTHON", "python3")); Output version = run( context, - List.of(executable, "-c", "import importlib.metadata; print(importlib.metadata.version('tmuxp'))"), + List.of( + executable, + "-c", + PythonExtensions.CORE_PRELUDE + + "import importlib.metadata; print(importlib.metadata.version('tmuxp'))"), context.directory(), report, Duration.ofSeconds(5), @@ -301,8 +305,9 @@ static String python(Main.Context context, Reporter report) throws InterruptedEx throw new Main.Failure( Machine.Code.SCRIPT_FAILED, 1, - "tmuxp 1.74.0 is required: " + refusal(version) - + "; set TMUX_WORKSPACE_PYTHON to an interpreter with tmuxp 1.74.0 installed"); + "tmuxp 1.74.0 is required with Python libtmux's core version check for tmux 3.3a or newer: " + + refusal(version) + + "; set TMUX_WORKSPACE_PYTHON to an interpreter with tmuxp 1.74.0 and compatible Python libtmux installed"); return executable; } catch (IOException absent) { throw unusableRuntime(absent.getMessage(), absent); @@ -345,8 +350,12 @@ static void shell(Main.Context context, picocli.CommandLine.ParseResult args, Re if (interactive && !Main.controllingTerminal()) throw Main.usage("interactive Python shell requires a terminal; use -c for captured output"); Main.Context bridge = withSelectedTmuxFirstOnPath(context); - var command = new ArrayList<>( - List.of(python(bridge, report), "-u", "-c", "from tmuxp.cli import cli; cli()", "shell")); + var command = new ArrayList<>(List.of( + python(bridge, report), + "-u", + "-c", + PythonExtensions.CORE_PRELUDE + "from tmuxp.cli import cli; cli()", + "shell")); if (!interactive) { command.add("-c"); command.add(code); @@ -371,6 +380,9 @@ static void shell(Main.Context context, picocli.CommandLine.ParseResult args, Re String value = args.matchedPositionalValue(index, ""); if (!value.isEmpty()) command.add(value); } + try (var server = Execution.server(bridge, args)) { + Execution.requireTmuxForExternalPython(server); + } Output output = interactive ? terminal(bridge, command) : run(bridge, command, bridge.directory(), report, Duration.ofHours(24)); diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java index df24e05d1..140decbc8 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java @@ -60,6 +60,11 @@ static String tmuxExecutable(Main.Context context) { } } + /** Validates the selected tmux without starting a daemon before Python runs. */ + static void requireTmuxForExternalPython(Server server) { + server.requireCompatibleTmux(); + } + static void load(Main.Context context, ParseResult args, Reporter report) throws IOException, InterruptedException { if (Main.flag(args, "-8")) throw new Main.Failure( diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Machine.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Machine.java index 9679d5784..3edee0a1b 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Machine.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Machine.java @@ -38,6 +38,8 @@ enum Code { SESSION_MISMATCH("session_mismatch"), /** tmux itself could not be found or run. */ TMUX_UNAVAILABLE("tmux_unavailable"), + /** tmux is older than the minimum this tool supports, so no command can run on it. */ + TMUX_UNSUPPORTED("tmux_unsupported"), /** tmux ran and refused, or the server changed under the command. */ TMUX_FAILED("tmux_failed"), /** A child program the document asked for could not be run, or ended badly. */ diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Main.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Main.java index 721eff5d3..ba2fae658 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Main.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Main.java @@ -131,6 +131,8 @@ static Machine.Code classify(Throwable failure) { if (failure instanceof InterruptedException || failure instanceof InterruptedIOException) return Machine.Code.INTERRUPTED; if (failure instanceof Failure known) return known.code; + if (failure instanceof io.github.libtmux.exception.UnsupportedFeatureException unsupported + && unsupported.belowFloor()) return Machine.Code.TMUX_UNSUPPORTED; if (failure instanceof io.github.libtmux.exception.LibTmuxException) return Machine.Code.TMUX_FAILED; if (failure instanceof IOException || failure instanceof UncheckedIOException diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/PythonExtensions.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/PythonExtensions.java index a68edf529..dbc9338ef 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/PythonExtensions.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/PythonExtensions.java @@ -5,6 +5,7 @@ import io.github.libtmux.Server; import io.github.libtmux.ServerEndpoint; import io.github.libtmux.Session; +import io.github.libtmux.TmuxVersion; import io.github.libtmux.exception.ServerUnavailableException; import io.github.libtmux.snapshot.ServerSnapshot; import java.io.IOException; @@ -19,7 +20,18 @@ final class PythonExtensions { private PythonExtensions() {} - private static final String BUILD = """ + /** Refuses a Python libtmux whose own floor is below this tool's before anything else runs. */ + static final String CORE_PRELUDE = """ + import re as _libtmux_re + from libtmux.common import TMUX_MIN_VERSION as _libtmux_floor + _libtmux_floor = str(_libtmux_floor) + _libtmux_release = _libtmux_re.fullmatch(r'(\\d+)\\.(\\d+)([a-z]?)', _libtmux_floor) + if not _libtmux_release or (int(_libtmux_release[1]), int(_libtmux_release[2]), _libtmux_release[3]) < (%2$d, %3$d, '%4$s'): + raise RuntimeError('Python libtmux with a tmux %1$s minimum is required; install a compatible Python libtmux in TMUX_WORKSPACE_PYTHON (loaded floor: ' + _libtmux_floor + ')') + """.formatted( + TmuxVersion.MINIMUM, TmuxVersion.MINIMUM.major(), TmuxVersion.MINIMUM.minor(), TmuxVersion.MINIMUM.patch()); + + private static final String BUILD = CORE_PRELUDE + """ import importlib, json, sys from pathlib import Path from libtmux import Server @@ -97,6 +109,7 @@ static Session build( Reporter report, ObjectNode effects) throws IOException, InterruptedException { + Execution.requireTmuxForExternalPython(server); Set windows = new HashSet<>(); Set panes = new HashSet<>(); String daemon = ""; diff --git a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java index c4c70fa6d..c30eac01b 100644 --- a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java +++ b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java @@ -72,6 +72,115 @@ private static void assertReportedObjects(Server server, JsonNode effects) { effects.path("pane_ids").valueStream().map(JsonNode::asText).collect(toSet())); } + @ParameterizedTest + @ValueSource(strings = {"load", "shell"}) + void obsoletePythonBackendIsRefusedBeforeExternalCode(String entry) throws Exception { + Path marker = directory.resolve("backend-entered"); + Path backend = pythonBackend("3.2a", marker); + Path source = directory.resolve("external.yaml"); + Files.writeString( + source, + "session_name: external\nplugins: [forbidden.Plugin]\nwindows:\n- window_name: main\n panes: [null]\n"); + String argument = entry.equals("load") ? source.toString() : "-c"; + String value = entry.equals("load") ? "-d" : "print('must-not-run')"; + Result result = invoke( + java.util.Map.of("PYTHONPATH", backend.toString(), "TMUX_WORKSPACE_PYTHON", "python3"), + entry, + "--json", + "-S", + directory.resolve("s").toString(), + argument, + value); + + assertFalse(Files.exists(marker), "obsolete Python code ran before the version check: " + result.err()); + assertTrue(result.code() != 0, result.out()); + assertTrue( + (result.out() + result.err()).contains("Python libtmux with a tmux 3.3a minimum is required"), + result.out() + result.err()); + } + + @Test + void aQualifiedPythonBackendCanEnterTheShell() throws Exception { + Path marker = directory.resolve("qualified-backend-entered"); + Path backend = pythonBackend("3.3a", marker); + Result result = invoke( + java.util.Map.of("PYTHONPATH", backend.toString(), "TMUX_WORKSPACE_PYTHON", "python3"), + "shell", + "--json", + "-S", + directory.resolve("s").toString(), + "-c", + "print('qualified')"); + + assertEquals(0, result.code(), result.out() + result.err()); + assertTrue(Files.exists(marker), "the qualified child was not entered"); + } + + private Path pythonBackend(String floor, Path marker) throws Exception { + Path backend = directory.resolve("python-" + floor); + Files.createDirectories(backend.resolve("libtmux")); + Files.writeString( + backend.resolve("libtmux/__init__.py"), "class Server:\n def __init__(self, **kwargs): pass\n"); + Files.writeString(backend.resolve("libtmux/common.py"), "TMUX_MIN_VERSION = '" + floor + "'\n"); + Files.createDirectories(backend.resolve("tmuxp/workspace")); + Files.writeString(backend.resolve("tmuxp/__init__.py"), ""); + String entered = "from pathlib import Path\nPath(" + Documents.JSON.writeValueAsString(marker.toString()) + + ").write_text('entered')\n"; + Files.writeString(backend.resolve("tmuxp/workspace/__init__.py"), entered); + Files.writeString( + backend.resolve("tmuxp/cli.py"), "def cli():\n" + entered.indent(4) + " print('qualified')\n"); + Files.createDirectories(backend.resolve("tmuxp-1.74.0.dist-info")); + Files.writeString( + backend.resolve("tmuxp-1.74.0.dist-info/METADATA"), + "Metadata-Version: 2.1\nName: tmuxp\nVersion: 1.74.0\n"); + return backend; + } + + @ParameterizedTest + @ValueSource(strings = {"load", "shell"}) + void externalPythonCannotStartAnUnsupportedFreshTmux(String entry) throws Exception { + Path marker = directory.resolve("python-builder-started"); + Path python = directory.resolve("python"); + Files.writeString( + python, + "#!/bin/sh\ncase \"$*\" in *importlib.metadata*) printf '1.74.0\\n'; exit;; esac\nprintf started > '" + + marker + "'\nexit 1\n"); + assertTrue(python.toFile().setExecutable(true)); + Path tmux = directory.resolve("old-tmux"); + Files.writeString( + tmux, + "#!/bin/sh\nfor argument in \"$@\"; do\n if [ \"$argument\" = -V ]; then printf 'tmux 3.3\\n'; exit; fi\ndone\nprintf 'no server running\\n' >&2\nexit 1\n"); + assertTrue(tmux.toFile().setExecutable(true)); + Path source = directory.resolve("external.yaml"); + Files.writeString(source, "session_name: external\nworkspace_builder: external:Builder\nwindows: [{}]\n"); + String[] arguments = entry.equals("load") + ? new String[] { + "load", + source.toString(), + "-d", + "-S", + directory.resolve("s").toString(), + "--json" + } + : new String[] { + "shell", + "-c", + "print('must not run')", + "-S", + directory.resolve("s").toString(), + "--json" + }; + Result result = invoke( + java.util.Map.of("LIBTMUX_TEST_TMUX", tmux.toString(), "TMUX_WORKSPACE_PYTHON", python.toString()), + arguments); + assertEquals(1, result.code(), result.toString()); + assertTrue(result.err().contains("3.3a"), result.toString()); + assertEquals( + "tmux_unsupported", + new ObjectMapper().readTree(result.err()).path("code").asText()); + assertFalse(Files.exists(marker), "external Python started before the minimum-version check"); + } + @ParameterizedTest @CsvSource({"false,false", "false,true", "true,false", "true,true"}) void pythonExtensionsUseTheSelectedServerAndReportObservedEffects(boolean append, boolean fail) throws Exception { @@ -495,9 +604,13 @@ void loadRejectsANonNumericColumnsAsUsage() throws Exception { "usage", new ObjectMapper().readTree(result.err()).path("code").asText()); } - /** An unsupported executable is refused before session creation, not retried without a size. */ - @Test - void loadRejectsAnUnsupportedTmuxInsteadOfRetryingWithoutSize() throws Exception { + /** + * An unsupported executable is refused before session creation. The refusal carries the machine + * code {@code tmux_unsupported} in JSON and the message with its remedy in text. + */ + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void loadRejectsAnUnsupportedTmuxInsteadOfRetryingWithoutSize(boolean json) throws Exception { Path source = directory.resolve("size-old.yaml"); Path socket = directory.resolve("size-old-socket"); Path trace = directory.resolve("size-old-arguments"); @@ -512,18 +625,25 @@ void loadRejectsAnUnsupportedTmuxInsteadOfRetryingWithoutSize() throws Exception try (Server server = server(socket)) { try { assertFalse(server.isAlive()); + java.util.List arguments = new java.util.ArrayList<>( + java.util.List.of("load", source.toString(), "-d", "-S", socket.toString(), "-f", "/dev/null")); + if (json) arguments.add("--json"); Result result = invoke( java.util.Map.of("LIBTMUX_TEST_TMUX", wrapper.toString(), "COLUMNS", "120", "LINES", "40"), - "load", - source.toString(), - "-d", - "-S", - socket.toString(), - "-f", - "/dev/null", - "--json"); + arguments.toArray(String[]::new)); assertEquals(1, result.code(), result.err()); assertTrue(result.err().contains("3.3a"), result.err()); + assertTrue(result.err().contains("upgrade tmux"), result.err()); + if (json) { + assertEquals( + "tmux_unsupported", + new ObjectMapper() + .readTree(result.err()) + .path("code") + .asText()); + } else { + assertTrue(result.err().startsWith("Error: libtmux core requires tmux 3.3a"), result.err()); + } assertFalse(Files.readString(trace).lines().anyMatch("new-session"::equals), Files.readString(trace)); assertFalse(server.isAlive()); } finally { diff --git a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/MainTest.java b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/MainTest.java index 0e89ff615..af42ca54a 100644 --- a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/MainTest.java +++ b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/MainTest.java @@ -54,6 +54,7 @@ void everyMachineFailureCodeComesFromTheSharedSet() throws Exception { "session_not_found", "session_mismatch", "tmux_unavailable", + "tmux_unsupported", "tmux_failed", "script_failed", "destination_exists", @@ -85,6 +86,17 @@ void everyMachineFailureCodeComesFromTheSharedSet() throws Exception { assertEquals(documented, extra, "a code outside the shared set must be one the README names"); } + /** Only a tmux below the floor is `tmux_unsupported`; a missing optional feature is a tmux refusal. */ + @Test + void onlyAFloorRefusalReportsTmuxUnsupported() { + assertEquals( + Machine.Code.TMUX_UNSUPPORTED, + Main.classify(io.github.libtmux.exception.UnsupportedFeatureException.tmuxBelowFloor("too old"))); + assertEquals( + Machine.Code.TMUX_FAILED, + Main.classify(new io.github.libtmux.exception.UnsupportedFeatureException("needs a newer tmux"))); + } + /** A missing workspace file is `workspace_not_found`. */ @Test void missingWorkspaceFileReportsWorkspaceNotFound() throws Exception { From 174b13ef2b0d2c3281cea844ca91f6c963d46414 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 18 Oct 2026 01:37:41 -0500 Subject: [PATCH 03/11] MCP(fix): State the floor and refuse at startup why: The MCP server never said which tmux it needs, and against an older tmux it died at startup with an uncaught exception and a stack trace that buried the remedy. what: - Name the floor in the server instructions, and report it as minimumTmux in tmux://capabilities. - Check the daemon at startup, or the binary when no daemon is running, so a tmux below the floor is refused before the server announces itself and not at the first tool call. - Let get_server_info raise the floor refusal for a live daemon below it, as the other tools do, instead of reporting it as not running. - Catch UnsupportedFeatureException and MalformedResponseException in Main, print one "libtmux-mcp: " line and exit with status 1. Main.run takes its streams so a test can drive it. - Test the startup refusal with a fake tmux that reports 3.2a and master, with a cold binary that reports 3.2a, and a tool call answered by a 3.2a daemon mid-session, which returns UNSUPPORTED_FEATURE with the remedy and sends nothing. Verified: letting the exception escape, exiting 0, skipping the startup check, dropping the floor from the instructions or the capabilities, and classifying the refusal as INTERNAL_ERROR each fails its test. --- libtmux-mcp/README.md | 10 ++- .../io/github/libtmux/mcp/Instructions.java | 5 +- .../main/java/io/github/libtmux/mcp/Main.java | 33 ++++++-- .../io/github/libtmux/mcp/Operations.java | 5 ++ .../io/github/libtmux/mcp/ToolSurface.java | 2 + .../libtmux/mcp/CapabilityRegistryTest.java | 1 + .../github/libtmux/mcp/InstructionsTest.java | 32 ++++++-- .../java/io/github/libtmux/mcp/MainTest.java | 74 +++++++++++++++++ .../github/libtmux/mcp/TmuxMcpServerTest.java | 79 +++++++++++++++++++ 9 files changed, 222 insertions(+), 19 deletions(-) diff --git a/libtmux-mcp/README.md b/libtmux-mcp/README.md index 0570668e3..4867732c8 100644 --- a/libtmux-mcp/README.md +++ b/libtmux-mcp/README.md @@ -450,13 +450,19 @@ Every tool carries MCP's four effect hints and a capability row: ### `tmux://capabilities`: what this process can reach The one resource is static for the process lifetime. It reports the effective -tool surface, its selection provenance, the pinned tmux connection, and the -capability row published on every tool: +tool surface, its selection provenance, the pinned tmux connection, the +capability row published on every tool, and `minimumTmux`, the oldest tmux the +server accepts (3.3a): ```json {"method": "resources/read", "params": {"uri": "tmux://capabilities"}} ``` +A tmux older than that, or one that reports no release, stops startup with one +`libtmux-mcp: ...` line on stderr and a non-zero exit. The same condition in the +middle of a session, such as a daemon replaced by an older one, returns the +`UNSUPPORTED_FEATURE` error code with the same message. + ## The tools diff --git a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Instructions.java b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Instructions.java index a81c01d93..b60e916fa 100644 --- a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Instructions.java +++ b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Instructions.java @@ -1,5 +1,7 @@ package io.github.libtmux.mcp; +import io.github.libtmux.TmuxVersion; + /** * What a client tells the model before it has called anything. * @@ -62,6 +64,7 @@ private static String ending(Connection connection) { return "\nCAPABILITIES\nOperating on selected socket " + socket.get("selector") + " (" + socket.get("selectionProvenance") + "). Enabled toolsets: " + (toolsets.isEmpty() ? "none" : toolsets) + ". Execute tools run with the tmux user's authority. " - + "Tool filtering shapes this advertised interface; it is not authorization or an OS sandbox.\n"; + + "Tool filtering shapes this advertised interface; it is not authorization or an OS sandbox. " + + "Requires tmux " + TmuxVersion.MINIMUM + " or newer.\n"; } } diff --git a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Main.java b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Main.java index 8ffeefdb5..13dba8069 100644 --- a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Main.java +++ b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Main.java @@ -2,6 +2,10 @@ import io.github.libtmux.Server; import io.github.libtmux.ServerConfig; +import io.github.libtmux.exception.MalformedResponseException; +import io.github.libtmux.exception.UnsupportedFeatureException; +import java.io.InputStream; +import java.io.PrintStream; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -23,6 +27,9 @@ * *

The unordered toolset and named-tool environment variables resolve one immutable surface * before tmux is opened. A tool outside it is neither listed nor callable. + * + *

A tmux older than 3.3a, or one that reports no release, is refused at startup: one line on + * stderr naming the version required and what to do, and a non-zero exit, with no stack trace. */ public final class Main { @@ -34,25 +41,31 @@ private Main() {} * @param args {@code --socket PATH}, {@code --socket-name NAME}, {@code --tmux BINARY} */ public static void main(String[] args) { + System.exit(run(args, System.getenv(), System.in, System.err)); + } + + /** The process, with its streams passed in; returns the exit status rather than exiting. */ + static int run(String[] args, Map environment, InputStream stdin, PrintStream err) { LaunchConfiguration launch; - Map environment = System.getenv(); try { List given = List.of(args); ToolSurface.resolve(environment); launch = LaunchConfiguration.resolve(given, environment); } catch (IllegalArgumentException e) { - System.err.println("libtmux-mcp: " + e.getMessage()); - System.err.println("usage: libtmux-mcp [--socket PATH] [--socket-name NAME] [--tmux BINARY]"); - System.exit(2); - return; + err.println("libtmux-mcp: " + e.getMessage()); + err.println("usage: libtmux-mcp [--socket PATH] [--socket-name NAME] [--tmux BINARY]"); + return 2; } // The server outlives setup: the MCP transport reads stdin until the client closes it. // Lexical ownership also releases its process transport when protocol startup fails. try (Server server = Server.open(launch.config())) { + // Checks the daemon, or the binary when no daemon is running, so a tmux below the floor is + // refused here and not at the first tool call. + server.requireCompatibleTmux(); SocketProfile profile = launch.profile(server); ToolSurface surface = ToolSurface.resolve(environment, profile); Runtime.getRuntime().addShutdownHook(new Thread(server::close, "libtmux-mcp-shutdown")); - System.err.println("libtmux-mcp: serving " + server.identity() + " on " + profile.selector() + err.println("libtmux-mcp: serving " + server.identity() + " on " + profile.selector() + " (server_state=" + profile.serverState() + ", configuration_provenance=" + profile.configurationProvenance() + ") with toolsets " + surface.toolsetNames() + " (" + surface.tools().size() @@ -61,7 +74,7 @@ public static void main(String[] args) { // A client that disconnects closes this end. Without noticing that, the process outlives // the client that launched it, and an MCP client leaves one behind every time it restarts. CountDownLatch disconnected = new CountDownLatch(1); - var mcp = TmuxMcpServer.overStdio(server, System.in, surface, disconnected::countDown); + var mcp = TmuxMcpServer.overStdio(server, stdin, surface, disconnected::countDown); Runtime.getRuntime().addShutdownHook(new Thread(mcp::close, "libtmux-mcp-protocol-shutdown")); try { disconnected.await(); @@ -69,8 +82,12 @@ public static void main(String[] args) { Thread.currentThread().interrupt(); } mcp.closeGracefully(); + } catch (UnsupportedFeatureException | MalformedResponseException refused) { + // The message already says what was found, what is required and what to do. + err.println("libtmux-mcp: " + refused.getMessage()); + return 1; } - System.exit(0); + return 0; } static ServerConfig configure(List args) { diff --git a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Operations.java b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Operations.java index f113b0394..cbbb56bfc 100644 --- a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Operations.java +++ b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/Operations.java @@ -10,6 +10,7 @@ import io.github.libtmux.WindowSpec; import io.github.libtmux.exception.DispatchException; import io.github.libtmux.exception.LibTmuxException; +import io.github.libtmux.exception.UnsupportedFeatureException; import io.github.libtmux.snapshot.ServerSnapshot; import java.nio.file.Path; import java.util.ArrayList; @@ -48,6 +49,10 @@ static Object serverInfo(Call call) { snapshot.sessions().size()); } catch (DispatchException transportFailure) { throw transportFailure; + } catch (UnsupportedFeatureException belowFloor) { + // A daemon that answers but is below the floor is running; saying it is stopped would hide + // the one fact the caller can act on. + throw belowFloor; } catch (LibTmuxException captureFailed) { return values( "running", false, "identity", server.identity().toString(), "version", "unknown", "sessions", 0); diff --git a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/ToolSurface.java b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/ToolSurface.java index 922733391..e781273c9 100644 --- a/libtmux-mcp/src/main/java/io/github/libtmux/mcp/ToolSurface.java +++ b/libtmux-mcp/src/main/java/io/github/libtmux/mcp/ToolSurface.java @@ -2,6 +2,7 @@ import io.github.libtmux.Server; import io.github.libtmux.ServerEndpoint; +import io.github.libtmux.TmuxVersion; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; @@ -134,6 +135,7 @@ Map capabilities(Server server) { Map report = new LinkedHashMap<>(); report.put("schemaVersion", 1); report.put("frozen", true); + report.put("minimumTmux", TmuxVersion.MINIMUM.toString()); report.put( "boundary", Map.of( diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java index 68c394ae5..427c3f464 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/CapabilityRegistryTest.java @@ -1162,6 +1162,7 @@ public void close() {} assertEquals(surface.tools().size(), report.get("toolCount")); assertEquals("tmux-user", report.get("executionAuthority")); assertEquals("none", report.get("operatingSystemBoundary")); + assertEquals("3.3a", report.get("minimumTmux")); assertEquals( Map.of( "oneSocketPerProcess", true, diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/InstructionsTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/InstructionsTest.java index a21f605e6..bda408899 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/InstructionsTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/InstructionsTest.java @@ -32,14 +32,7 @@ final class InstructionsTest { void everyToolsetCombinationFitsClaudeCodesInstructionsBudget() { String prefix = "/tmp/libtmux-java-test/"; String stressPath = prefix + "s".repeat(104 - prefix.length()); - SocketProfile stress = new SocketProfile( - "path:" + stressPath, - "operator-current", - "existing", - "unknown", - stressPath, - "tmux -N -S '" + stressPath + "' attach", - false); + SocketProfile stress = profile(stressPath); try (Server server = Server.using(ServerConfig.builder().build(), noOpTransport())) { for (int bits = 0; bits < (1 << TOOLSET_NAMES.length); bits++) { @@ -63,6 +56,29 @@ void everyToolsetCombinationFitsClaudeCodesInstructionsBudget() { } } + @Test + void theInstructionsStateTheTmuxFloor() { + try (Server server = Server.using(ServerConfig.builder().build(), noOpTransport())) { + ToolSurface surface = ToolSurface.resolve(Map.of(), profile("/tmp/libtmux-java-test/s")); + Connection connection = new Connection(server, Caller.nowhere(), surface); + + String instructions = Instructions.forServer(connection); + + assertTrue(instructions.contains("Requires tmux 3.3a or newer"), instructions); + } + } + + private static SocketProfile profile(String path) { + return new SocketProfile( + "path:" + path, + "operator-current", + "existing", + "unknown", + path, + "tmux -N -S '" + path + "' attach", + false); + } + private static TmuxTransport noOpTransport() { return new TmuxTransport() { @Override diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java index 8ae088664..31b376be8 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/MainTest.java @@ -12,7 +12,11 @@ import io.github.libtmux.transport.CommandRequest; import io.github.libtmux.transport.CommandResult; import io.github.libtmux.transport.TmuxTransport; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -21,6 +25,9 @@ import java.util.UUID; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; /** * Which server the launcher was told to expose. A client passes these once and never sees them @@ -39,6 +46,73 @@ void nothingSaidPinsTheDedicatedMinimalServer() throws IOException { assertEquals("tmux", config.binary()); } + /** + * A tmux that reports an old or unreadable release stops startup with one line on stderr and a + * non-zero status. The remedy is in that line; a stack trace would bury it. + */ + @ParameterizedTest + @CsvSource({"3.2a,requires tmux 3.3a or newer", "master,unrecognized version: master"}) + void aTmuxTheFloorRefusesStopsStartupWithOneLine(String reported, String expected, @TempDir Path dir) + throws IOException { + Path binary = dir.resolve("tmux"); + Files.writeString(binary, """ + #!/bin/sh + for argument in "$@"; do + case "$argument" in + -V) echo "tmux %1$s"; exit 0 ;; + libtmux-version-*) + printf 'unknown command: %%s\\n' "${argument%%'#{version}'}%1$s" >&2; exit 1 ;; + esac + done + exit 0 + """.formatted(reported)); + Files.setPosixFilePermissions(binary, java.nio.file.attribute.PosixFilePermissions.fromString("rwxr-xr-x")); + ByteArrayOutputStream captured = new ByteArrayOutputStream(); + + int status = Main.run( + new String[] {"--socket", dir.resolve("socket").toString(), "--tmux", binary.toString()}, + Map.of(), + InputStream.nullInputStream(), + new PrintStream(captured, true, StandardCharsets.UTF_8)); + + String stderr = captured.toString(StandardCharsets.UTF_8); + assertEquals(1, status); + assertTrue(stderr.startsWith("libtmux-mcp: "), stderr); + assertTrue(stderr.contains(expected), stderr); + assertEquals(1, stderr.lines().count(), stderr); + assertFalse(stderr.contains("Exception"), stderr); + } + + /** With no daemon running the binary itself is checked, so the refusal still comes at startup. */ + @Test + void aColdBinaryBelowTheFloorStopsStartupToo(@TempDir Path dir) throws IOException { + Path binary = dir.resolve("tmux"); + Files.writeString(binary, """ + #!/bin/sh + for argument in "$@"; do + case "$argument" in + -V) echo "tmux 3.2a"; exit 0 ;; + esac + done + echo "no server running on socket" >&2 + exit 1 + """); + Files.setPosixFilePermissions(binary, java.nio.file.attribute.PosixFilePermissions.fromString("rwxr-xr-x")); + ByteArrayOutputStream captured = new ByteArrayOutputStream(); + + int status = Main.run( + new String[] {"--socket", dir.resolve("socket").toString(), "--tmux", binary.toString()}, + Map.of(), + InputStream.nullInputStream(), + new PrintStream(captured, true, StandardCharsets.UTF_8)); + + String stderr = captured.toString(StandardCharsets.UTF_8); + assertEquals(1, status); + assertTrue(stderr.contains("requires tmux 3.3a or newer"), stderr); + assertFalse(stderr.contains("serving"), stderr); + assertEquals(1, stderr.lines().count(), stderr); + } + @Test void aSocketPathIsTakenAsAPath() { ServerConfig config = Main.configure(List.of("--socket", "/tmp/libtmux-java-dev/probe/s")); diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java index 39ec181e8..ca9fd2b73 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java @@ -32,6 +32,8 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import reactor.core.publisher.Mono; /** @@ -251,6 +253,83 @@ public void close() {} assertEquals(false, meta.path("retryable").asBoolean()); } + /** + * A daemon below the floor answers a tool call with the version refusal and its remedy, not a + * generic failure, and nothing is sent to it. + */ + @ParameterizedTest + @ValueSource(strings = {"list_sessions", "get_server_info"}) + void aDaemonBelowTheFloorAnswersAToolCallWithTheRemedy(String tool) throws Exception { + AtomicInteger effects = new AtomicInteger(); + TmuxTransport old = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + if (VersionProbes.is(request)) { + return VersionProbes.reply(request, "3.2a"); + } + effects.incrementAndGet(); + return new CommandResult(0, List.of(), List.of()); + } + + @Override + public void close() {} + }; + // The profile was read at startup, while the daemon still passed; the refusal comes later. + ToolSurface surface = ToolSurface.resolve( + Map.of(ToolSurface.TOOLSETS_ENV, "", ToolSurface.TOOLS_ENV, tool), + new SocketProfile( + "path:/tmp/libtmux-java-test/s", + "operator-current", + "existing", + "unknown", + "/tmp/libtmux-java-test/s", + "tmux -N -S '/tmp/libtmux-java-test/s' attach", + false)); + WireOutput output = new WireOutput(); + + try (Server oldServer = Server.using(ServerConfig.builder().build(), old); + PipedInputStream input = new PipedInputStream(); + PipedOutputStream client = new PipedOutputStream(input)) { + McpSyncServer mcp = TmuxMcpServer.overStdio(oldServer, input, output, surface, () -> {}); + try { + client.write(initialize()); + client.flush(); + assertTrue(output.first.await(3, TimeUnit.SECONDS), "initialization did not answer"); + + client.write("{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\n" + .getBytes(StandardCharsets.UTF_8)); + client.write((Answers.JSON.writeValueAsString(Map.of( + "jsonrpc", + "2.0", + "id", + "below-floor", + "method", + "tools/call", + "params", + Map.of("name", tool, "arguments", Map.of()))) + + "\n") + .getBytes(StandardCharsets.UTF_8)); + client.flush(); + assertTrue(output.second.await(5, TimeUnit.SECONDS), tool + " did not answer"); + } finally { + mcp.close(); + } + } + + String response = output.lines().stream() + .filter(line -> line.contains("below-floor")) + .findFirst() + .orElseThrow(() -> new AssertionError(tool + " response is absent")); + var result = Answers.JSON.readTree(response).path("result"); + assertEquals(true, result.path("isError").asBoolean(), response); + String message = result.path("content").get(0).path("text").asText(); + assertTrue(message.contains("requires tmux 3.3a or newer"), message); + assertTrue(message.contains("upgrade tmux"), message); + assertEquals( + "UNSUPPORTED_FEATURE", result.path("_meta").path("error_code").asText()); + assertEquals(0, effects.get(), "a refused call must send nothing"); + } + @Test void oversizedRequestIdFailsBeforeToolDispatch() throws Exception { AtomicInteger calls = new AtomicInteger(); From b08099e1c8a367c3940e28a487650b0ba7129c0b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Oct 2026 21:40:20 -0500 Subject: [PATCH 04/11] Support(chore): Set the tmux floor to 3.3a why: The supported matrix and user-facing requirements must agree with the core version check. The extra version probe also changes process-count guidance, and text that named 3.2a as supported or described behaviour only 3.2a had would be false. what: - Remove 3.2a and unpatched 3.3 from required CI and local matrix lanes; keep the remaining supported and preview lanes. - Update JVM facade, example, workspace CLI and contributor requirements to 3.3a. - Describe version-probe reports and the extra process before ordinary command groups, captures and filtered reads. - Update benchmark generator explanations while leaving historical generated measurements at their recorded commit. - Drop the version gates the floor makes always true: the five built-in layouts, the lower bound of the run-shell output gate, the matching test gates and the 3.2a column of the layout fixture. - Say "every supported tmux" in the streaming guide, "3.3a through 3.7c" in the parity map, and rewrite the tmux-behaviour.md sections and the source and test comments that depended on 3.2a. Verified with :docs:test. --- .github/CONTRIBUTING.md | 2 +- .github/WRITING.md | 4 +- .github/workflows/tmux-matrix.yml | 4 +- README.md | 2 +- .../libtmux/benchmark/OperationBenchmark.java | 20 +- .../kotlin/libtmux.tmux-matrix.gradle.kts | 2 +- docs/guide/batching-and-chaining.md | 20 +- docs/guide/filtering.md | 6 +- docs/guide/operating-a-service.md | 9 +- docs/guide/scala/compatibility.md | 6 +- docs/guide/scala/getting-started.md | 2 +- docs/guide/snapshots-and-handles.md | 27 +- docs/guide/streaming.md | 4 +- docs/guide/workspace-cli.md | 6 +- docs/internals/tmux-behaviour.md | 42 +- docs/parity/test-map.md | 2132 ++++++++--------- examples/api/README.md | 2 +- .../libtmux/it/CaptureIntegrationTest.java | 4 +- .../it/ControlWatchIntegrationTest.java | 2 +- .../libtmux/it/CreationIntegrationTest.java | 4 +- .../libtmux/it/FailedReadIntegrationTest.java | 2 +- .../libtmux/it/HookIntegrationTest.java | 2 +- .../it/LeadingDashIntegrationTest.java | 6 +- .../it/OptionWritesIntegrationTest.java | 2 +- .../it/PaneProcessIntegrationTest.java | 2 +- .../it/PaneWaitFidelityIntegrationTest.java | 5 +- .../it/ServerControlIntegrationTest.java | 4 +- .../it/ServerScriptingIntegrationTest.java | 18 +- .../it/TmuxVersionIntegrationTest.java | 4 +- .../io/github/libtmux/mcp/ReadingTest.java | 7 +- .../libtmux/mcp/ToolsAgainstTmuxTest.java | 6 +- libtmux-scala/README.md | 2 +- .../libtmux/scaladsl/SnapshotSuite.scala | 2 +- libtmux-workspace-cli/README.md | 2 +- .../libtmux/workspace/cli/ExecutionTest.java | 15 +- .../src/test/resources/layout-preflight.json | 39 - libtmux/README.md | 4 +- .../java/io/github/libtmux/Environment.java | 6 +- .../main/java/io/github/libtmux/Layout.java | 17 +- .../main/java/io/github/libtmux/Layouts.java | 10 +- .../java/io/github/libtmux/OptionKey.java | 2 +- .../main/java/io/github/libtmux/Options.java | 2 +- .../java/io/github/libtmux/PaneCommand.java | 8 +- .../io/github/libtmux/SessionCreation.java | 6 +- .../java/io/github/libtmux/SessionLookup.java | 3 +- .../main/java/io/github/libtmux/Shell.java | 9 +- .../io/github/libtmux/SnapshotCapture.java | 2 +- .../java/io/github/libtmux/TmuxFormats.java | 4 +- .../java/io/github/libtmux/TmuxVersion.java | 2 +- .../main/java/io/github/libtmux/Window.java | 2 +- .../java/io/github/libtmux/WindowSpec.java | 2 +- .../github/libtmux/control/ControlClient.java | 4 +- .../github/libtmux/control/Notification.java | 4 +- .../io/github/libtmux/CreationSpecTest.java | 12 +- .../io/github/libtmux/PaneCommandTest.java | 6 +- .../java/io/github/libtmux/ServerTest.java | 9 +- .../java/io/github/libtmux/SplitSpecTest.java | 6 +- .../github/libtmux/TmuxEnvironmentTest.java | 2 +- .../libtmux/control/ControlClientTest.java | 4 +- .../libtmux/exception/ExceptionTreeTest.java | 2 +- 60 files changed, 1251 insertions(+), 1295 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 79b5d0acf..83193b74a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -232,7 +232,7 @@ toolchain and `options.release` in version matrix in the CI workflow, which builds on 25 and 27, and the claim `README.md` makes under `Requirements`. -**tmux 3.2a through 3.7c is the supported range**, and it is not a claim: the +**tmux 3.3a through 3.7c is the supported range**, and it is not a claim: the whole real-tmux suite runs against every one of those releases, and each lane checks it really ran the tmux it is named after. Moving the range means moving `workflows/tmux-matrix.yml` and the README together. diff --git a/.github/WRITING.md b/.github/WRITING.md index cc475ff38..23643b68c 100644 --- a/.github/WRITING.md +++ b/.github/WRITING.md @@ -48,7 +48,7 @@ Four things near the top are compatibility claims, and they move together: exported identifiers without a deprecation period, pin an exact version, not recommended for production. `CHANGELOG.md` and the `Support and status` section say the same thing in the same words. -- **The requirements are JDK 25 or newer, and tmux 3.2a through 3.7c.** The tmux +- **The requirements are JDK 25 or newer, and tmux 3.3a through 3.7c.** The tmux range is not a claim — the matrix runs every lane of it — so it may not drift from `workflows/tmux-matrix.yml`. - **Coordinates are group `io.github.libtmux`, imported through @@ -142,7 +142,7 @@ Order it capability, then consequence, then compatibility: > 0.0.1-alpha.7 adds streaming capture, and rejects `windowId` at pane scope > rather than ignoring it. Pass `scope: window` to read at window scope. JDK 25 -> and tmux 3.2a through 3.7c are unchanged. +> and tmux 3.3a through 3.7c are unchanged. The title is plain — the version, optionally preceded by `libtmux for Java`. Never "we are excited to announce"; state what shipped. diff --git a/.github/workflows/tmux-matrix.yml b/.github/workflows/tmux-matrix.yml index c0a18335e..2f8584014 100644 --- a/.github/workflows/tmux-matrix.yml +++ b/.github/workflows/tmux-matrix.yml @@ -1,6 +1,6 @@ name: tmux matrix -# The README claims tmux 3.2a through 3.7c, and says that range is not a claim because the suite runs +# The README claims tmux 3.3a through 3.7c, and says that range is not a claim because the suite runs # against every one of them. This is where that stops depending on someone running it locally. on: @@ -38,7 +38,7 @@ jobs: # (its `lanes` list), which is the source of truth. The agree step below fails if they drift # apart. This is also what DocumentationFactsTest reads the README's claimed range from, so a # preview lane belongs in `include` below and never here. - tmux: ['3.2a', '3.3', '3.3a', '3.4', '3.5', '3.6', '3.7', '3.7a', '3.7b', '3.7c'] + tmux: ['3.3a', '3.4', '3.5', '3.6', '3.7', '3.7a', '3.7b', '3.7c'] # Ahead of the supported range, matching the plugin's `previewLanes` list: 3.8-rc2 is a # candidate for a release not yet cut, and master is upstream's development branch, not a # version. Both may fail and neither moves the README's claimed range. diff --git a/README.md b/README.md index 4538ddfa0..7c7614b1c 100644 --- a/README.md +++ b/README.md @@ -715,7 +715,7 @@ $ tmux-workspace load \ named module `io.github.libtmux`, and the jars carry no OSGi bundle headers. - **Linux, including WSL.** CI runs on Ubuntu. A macOS job runs the same checks and reports without gating a merge. -- **tmux 3.2a through 3.7c.** The whole real-tmux suite runs against every one +- **tmux 3.3a through 3.7c.** The whole real-tmux suite runs against every one of those releases, and each lane checks it ran the tmux it is named after: ```console diff --git a/benchmarks/src/test/java/io/github/libtmux/benchmark/OperationBenchmark.java b/benchmarks/src/test/java/io/github/libtmux/benchmark/OperationBenchmark.java index 04f67ffc9..3afe865d6 100644 --- a/benchmarks/src/test/java/io/github/libtmux/benchmark/OperationBenchmark.java +++ b/benchmarks/src/test/java/io/github/libtmux/benchmark/OperationBenchmark.java @@ -864,22 +864,23 @@ private String render( .append(" windows, asked for three ways. This is the whole of the answer to ") .append("per-command process cost, so it leads.\n\n"); table(out, "strategy", grouping); - out.append("\nEvery row pays the same four commands for the handle it starts from and the ") - .append("count it ends with, so the ratio between them understates what grouping ") - .append("saves: the work itself is 60 commands against one.\n"); + out.append("\nEvery row also pays for the handle it starts from and the count it ends with. ") + .append("Each ordinary request has its own version probe; grouping reduces ") + .append("the number of those requests as well as the data commands.\n"); out.append("\n## Reading the hierarchy\n\n") .append("`windows()` and `snapshot()` both throw when capture fails. They read who the ") .append("server is, then run the four listings as one group fenced against that ") - .append("answer. Two commands, whatever the hierarchy holds.\n\n"); + .append("answer. Two data requests and their version probes, whatever the hierarchy holds.\n\n"); table(out, "read", reading); out.append("\n## Narrow reads\n\n") .append("Five sessions, one wanted. A lookup by name, by pane id, or by a filter tmux can ") .append("apply reads who the server is, then lists only the sessions it wants, with their ") - .append("windows and panes, as one fenced group: two commands, the same as a snapshot, ") + .append("windows and panes, as one fenced group: two data requests and their version probes, ") + .append("the same as a snapshot, ") .append("over less of the server. A pane or window condition is looped over each ") - .append("session inside tmux, so no probe comes first.\n\n"); + .append("session inside tmux, so no separate pane-existence probe comes first.\n\n"); table(out, "read", narrowing); out.append("\nThe same reads against fifty sessions of three windows each. The commands do not ") .append("change; what a whole-server read pays for is the rows.\n\n"); @@ -923,8 +924,8 @@ private String render( .append("between releases, so names come from the listing and values from ") .append("`show-options -v`, batched.\n\n"); table(out, "read", options); - out.append("\nThat is the cost: one option is one command, and a listing is two whatever ") - .append("its size, until it outgrows what one command may carry.\n"); + out.append("\nOne option is one data request, and a listing is two whatever its size, until ") + .append("it outgrows what one command may carry. Each request has a version probe.\n"); out.append("\n## One command, two transports\n\n") .append("The same read, `display-message -p \"#{session_name}\"`, sent ") @@ -970,7 +971,8 @@ private String render( .append("| p95 | %s |%n".formatted(micros(following.p95Nanos()))) .append("| commands the mirror dispatched per change | %.1f |%n" .formatted(following.dispatchesPerChange())) - .append("\nA rebuild is a snapshot, two commands. A new window is announced more than once, ") + .append("\nA rebuild is a snapshot: two data requests and their version probes. ") + .append("A new window is announced more than once, ") .append("and announcements that arrive during a rebuild fold into one more, so a change ") .append("costs one or two rebuilds rather than one per announcement.\n"); diff --git a/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts b/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts index 1f99e51ad..707fc6e39 100644 --- a/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts +++ b/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts @@ -8,7 +8,7 @@ // Declared so the source-set accessors resolve; the module already has it via the library plugin. plugins { java } -val lanes = listOf("3.2a", "3.3", "3.3a", "3.4", "3.5", "3.6", "3.7", "3.7a", "3.7b", "3.7c") +val lanes = listOf("3.3a", "3.4", "3.5", "3.6", "3.7", "3.7a", "3.7b", "3.7c") // Ahead of the supported range: previews of what tmux ships next, run so a break shows up before the // release that makes it real rather than after. Allowed to fail, and left out of testTmuxMatrix and diff --git a/docs/guide/batching-and-chaining.md b/docs/guide/batching-and-chaining.md index 7bc9ea375..6aedca3f5 100644 --- a/docs/guide/batching-and-chaining.md +++ b/docs/guide/batching-and-chaining.md @@ -2,23 +2,25 @@ Every snippet here is executed by `DocumentationSnippetsTest`. -Each call starts one tmux process, and that process is most of what a call costs. -A batch and a chain both put several commands into one invocation. +The first request to a daemon also starts a tmux process to check its version; +after that an ordinary request starts one tmux process to run its commands. A +batch and a chain put several commands into that one invocation. ## Which path for which job | you want | use | tmux processes | | --- | --- | --- | -| one change or read, typed | a handle's method: `session.rename`, `pane.sendKeys` | one | +| one change or read, typed | a handle's method: `session.rename`, `pane.sendKeys` | one tmux process, after one version probe per daemon | | a command no method covers | `server.cmd(...)` to inspect the answer, `server.run(...)` to require success | one | -| several independent commands, each answered | `batch()` | one, however many | -| steps where each acts on what the last made | `chain()` | one | +| several independent commands, each answered | `batch()` | one, however many commands | +| steps where each acts on what the last made | `chain()` | one for dispatch, plus any capability reads | | to know when something in a pane has happened | `Pane.awaitText`, `Channel.await`, or `Pane.run` | [measured](../benchmarks/operation-costs.md#push-against-poll) | -| output as it arrives | `server.control(session)`, then a subscription | one attached client | +| output as it arrives | `server.control(session)`, then a subscription | one attached client, with startup probes | -The same twenty windows cost 64 processes made one at a time and 5 as a batch or -a chain: [`operation-costs.md`](../benchmarks/operation-costs.md#collapsing-round-trips) -has the rows. +Creating twenty windows one at a time costs one process each; as a batch or a +chain it costs one for the lot, whatever the daemon's first request cost. +[`operation-costs.md`](../benchmarks/operation-costs.md#collapsing-round-trips) +has the measured rows. ## A batch: several commands, each with its own outcome diff --git a/docs/guide/filtering.md b/docs/guide/filtering.md index 56dfbf17c..ad22d3471 100644 --- a/docs/guide/filtering.md +++ b/docs/guide/filtering.md @@ -109,9 +109,9 @@ use a targeted listing when the name or id is safe to put in a format, and a whole-server capture otherwise. `sessions(FilterExpr)`, `windows(FilterExpr)`, and `panes(FilterExpr)` send a safe expression as `list-sessions -f`, `list-windows -f`, or `list-panes -f`, and still apply the expression to what -comes back. Each costs two tmux commands, as a snapshot does, and reads only the -sessions it keeps. A relation, or an expression tmux cannot apply, reads the -whole server. +comes back. Each sends two data requests and their version probes, as a snapshot +does, and reads only the sessions it keeps. A relation, or an expression tmux +cannot apply, reads the whole server. Filtering a list already in hand still issues no commands. ## Writing an expression down diff --git a/docs/guide/operating-a-service.md b/docs/guide/operating-a-service.md index e7a7c3d7f..79e985ae9 100644 --- a/docs/guide/operating-a-service.md +++ b/docs/guide/operating-a-service.md @@ -51,7 +51,7 @@ try (Server observed = Server.open(config.toBuilder().observer(reports::add).bui } OperationReport last = reports.get(reports.size() - 1); -last.verbs(); // → [display-message] +last.verbs(); // → [if-shell, display-message] last.certainty(); // → COMPLETE last.exitCode(); // → OptionalInt[0] ``` @@ -64,6 +64,11 @@ line counts, and at most 240 characters of stderr in `boundedError`, which stack trace and otherwise ignored; the command's own result is unaffected, and nothing is retried. +The version check emits its own `run-shell` probe report. The probe fires none +of a user's `after-*` or `command-error` hooks, so checking a call's daemon has +no effect a hook could see. The request's report includes the `if-shell` version +guard before the requested command. + A report's `id` counts calls on one transport, so two reports can be told apart; it is not a request id for tracing. None is needed: the observer runs on the thread that made the call, so whatever that thread carries - a logging MDC, @@ -74,7 +79,7 @@ an OpenTelemetry context, a thread local - is there to read: ThreadLocal request = new ThreadLocal<>(); Map seen = new ConcurrentHashMap<>(); ServerConfig traced = - config.toBuilder().observer(report -> seen.put(report.verbs().get(0), request.get())).build(); + config.toBuilder().observer(report -> seen.put(report.verbs().getLast(), request.get())).build(); try (Server server = Server.open(traced)) { request.set("req-42"); server.cmd("display-message", "-p", "hi"); diff --git a/docs/guide/scala/compatibility.md b/docs/guide/scala/compatibility.md index d44c29f72..0ec6f6cd0 100644 --- a/docs/guide/scala/compatibility.md +++ b/docs/guide/scala/compatibility.md @@ -10,9 +10,9 @@ the facades' unit suites, their real-tmux suites, the documented Scala examples and the runnable programs on JDK 25 and 27 on Linux, and on macOS. Every lane of the [tmux matrix][tmux-matrix] runs the real-tmux suites against -its own tmux: `3.2a`, `3.3`, `3.3a`, `3.4`, `3.5`, `3.6`, `3.7`, `3.7a`, `3.7b` -and `3.7c`. The Scala fixture takes the lane's tmux the same way the Java one -does. +its own tmux: `3.3a`, `3.4`, `3.5`, `3.6`, `3.7`, `3.7a`, `3.7b` and `3.7c`. +The Scala fixture takes the lane's tmux the same way the Java one does. The +facades inherit Java's version check and reject older daemons. ## Artifacts diff --git a/docs/guide/scala/getting-started.md b/docs/guide/scala/getting-started.md index 7a2697970..2519f80e9 100644 --- a/docs/guide/scala/getting-started.md +++ b/docs/guide/scala/getting-started.md @@ -1,6 +1,6 @@ # Getting started -The Scala facades need Scala 3.9 and JDK 25 or newer, and tmux 3.2a through +The Scala facades need Scala 3.9 and JDK 25 or newer, and tmux 3.3a through 3.7c on the machine they drive. ## Install diff --git a/docs/guide/snapshots-and-handles.md b/docs/guide/snapshots-and-handles.md index 0d530855e..75f687e62 100644 --- a/docs/guide/snapshots-and-handles.md +++ b/docs/guide/snapshots-and-handles.md @@ -22,15 +22,17 @@ That is deliberate. tmux offers no transaction across separate listings, so a traversal that re-queried could observe a hierarchy that never existed — a window in one listing and its panes from after it closed. -A capture costs two tmux commands: one asks which server this is, and one runs the -four listings as a group fenced against that answer. Because tmux runs a group -inside the server, the rows cannot come from two of them, and a server replaced +A capture sends two data requests, each with a separate version probe: one asks +which server this is, and one runs the four listings as a group fenced against +that answer. Because tmux runs a group inside the server, the rows cannot come +from two of them, and a server replaced under the capture is refused rather than half-read. "Which server" is its pid and when it started: a pid can be reused, and a handle is refused by a tmux started since on the same one. tmux reports that start in whole seconds, so a server replaced on the same pid within the same second would pass; nothing short of a token planted in the server closes that gap. What that costs is measured in -[`docs/benchmarks/operation-costs.md`](../benchmarks/operation-costs.md). +[`docs/benchmarks/operation-costs.md`](../benchmarks/operation-costs.md), whose +recorded commit predates the extra version probes. `refresh()` is how to look again. Every live listing, finder and snapshot capture throws when it fails. An absent daemon throws @@ -107,10 +109,8 @@ captured.size(); // → 90x20 captured.refresh().size(); // → 100x30 ``` -The first size is set rather than inherited on purpose. A new window's default -is the client's size, less a row for the status line on tmux 3.2a and not on -3.3 or later, so a snippet that printed that default would be telling the truth -on only some of the releases this supports. +The first size is set so the example does not depend on the client's terminal +dimensions. `Session.rename`, `Window.rename`, `Pane.retitle`, and each handle's `refresh` carry `@CheckReturnValue`. Error Prone rejects a call that discards their results. @@ -170,11 +170,12 @@ recent.size() <= everything.size(); // → true These talk to tmux. They do not update `info()`. `server.shell()` runs a shell command, or chooses one from an exit status. -`server.commands().list()` is the catalog this tmux knows. `server.prompt()` -is prompt history, present since tmux 3.3. `server.messageLog().lines()` is -the server log. Before 3.6 it needs a client attached. `server.keys()` binds -keys. `server.lock()` locks every attached client. It stays a method on -`Server`: it is one command, not a group. The Scala facades call it `lock`. +`server.commands().list()` is the catalog this tmux knows. `server.prompt()` is +prompt history, present on every supported release. +`server.messageLog().lines()` is the server log. Before 3.6 it needs a client +attached. `server.keys()` binds keys. `server.lock()` locks every attached +client. It stays a method on `Server`: it is one command, not a group. The Scala +facades call it `lock`. ```java // Given: Server server diff --git a/docs/guide/streaming.md b/docs/guide/streaming.md index de6f0d67d..a907a1611 100644 --- a/docs/guide/streaming.md +++ b/docs/guide/streaming.md @@ -282,7 +282,7 @@ itself, which a control-mode line needs, since tmux answers a bare `parse error` for an unquoted `%0:off` typed there directly. The four state words are **two independent pairs**, with different loss -behaviour, measured against tmux 3.2a, 3.7c and next-3.9: +behaviour, measured against tmux 3.7c and next-3.9: | stop → resume | recovers? | output produced while stopped | | --- | --- | --- | @@ -308,7 +308,7 @@ tmux can also pause a pane on its own. After `client.send("refresh-client", "-f", "pause-after=5")`, a pane whose output this client is more than five seconds behind on is paused and announced as `Notification.Pause`; output keeps reaching `subscribeOutput` until then, and `refresh-client -A :continue` -resumes it, announced as `Notification.Continue`. tmux 3.2 and later. +resumes it, announced as `Notification.Continue`. Every supported tmux has it. ## Requests are serialized diff --git a/docs/guide/workspace-cli.md b/docs/guide/workspace-cli.md index dfbb62092..52cc7ea4f 100644 --- a/docs/guide/workspace-cli.md +++ b/docs/guide/workspace-cli.md @@ -353,9 +353,9 @@ the `default`, `minimal`, `window`, `pane` and `verbose` presets, or a template. - Python builders and plugins remain executable user code with their own mutation and cleanup behavior. -On tmux 3.2a, the Python classic builder can fail after tmux rewrites a session -name containing `$`. Use a name without `$` for extension workspaces on that -version; the bridge does not alter tmux's naming behavior. +The Python bridge requires tmuxp 1.74.0 and a Python libtmux whose minimum is +tmux 3.3a or newer. It refuses an older backend before running extensions or +shell code; see the [migration notes](../../MIGRATION.md). ## Subprocesses diff --git a/docs/internals/tmux-behaviour.md b/docs/internals/tmux-behaviour.md index 1eae6c731..62e98a35e 100644 --- a/docs/internals/tmux-behaviour.md +++ b/docs/internals/tmux-behaviour.md @@ -1,7 +1,7 @@ # tmux behaviour this library codes around Facts about tmux's own commands, measured against the supported version range -(tmux 3.2a through 3.7c unless a row says otherwise), that the implementation +(tmux 3.3a through 3.7c unless a row says otherwise), that the implementation relies on and that are not self-evident from the tmux manual. A decision that weighs alternatives belongs in [`docs/decisions/`](../decisions/) instead; this page is measurements, kept reachable because source comments cite them. @@ -33,7 +33,7 @@ chosen anyway cannot detect the second failure mode. ## split-window flags across releases -| flag | 3.2a–3.3 | 3.4 | 3.5–3.6 | 3.7+ | +| flag | 3.3a | 3.4 | 3.5–3.6 | 3.7+ | | --- | --- | --- | --- | --- | | `-v -h -b -l -f -Z -c -e` | yes | yes | yes | yes | | `-p` (percentage) | yes | **broken** (`size missing`, an upstream typo reading the wrong flag) | yes | yes | @@ -52,19 +52,16 @@ attribute of a just-created pane must come from a later read. ## new-window and new-session across releases -`new-window` and `new-session` have not gained or lost a flag since 3.2a — the -version-gated concerns are behavioral, not syntactic: - -- **3.2a resolves a relative `-c` against the server's working directory.** - The path reaches the child unchanged, so it lands wherever the server was - started, or in `$HOME` when that directory has no such path, exit `0` - either way. 3.3 onwards resolves it against the requesting client's - directory. `new-window`, `split-window`, `new-session` and `respawn-pane` - share the spawn code, so all four do this. An absolute directory is honored - on every release, and every directory is sent absolute, resolved against the - calling process, so each release starts the process where 3.3 does. -- **3.2a ignores `new-session -x/-y` (sizing)**, silently and with exit `0`. - It is refused before dispatch rather than accepted and ignored. +`new-window` and `new-session` have not gained or lost a flag across the +supported range — the version-gated concerns are behavioral, not syntactic: + +- **A relative `-c` is resolved against the requesting client's directory.** + `new-window`, `split-window`, `new-session` and `respawn-pane` share the + spawn code, so all four do this. An absolute directory is honored on every + release, and every directory is sent absolute, resolved against the calling + process, so the start directory never depends on where the server was + started. +- **`new-session -x/-y` size a detached session** on every supported release. - **`new-window -S` (reuse-if-named) reports nothing.** It correctly selects the existing window rather than making a second one, but its `-P -F` template expands to the empty string on every lane. The window has to be @@ -90,14 +87,13 @@ and are not exposed for that reason, the same as `new-session -A` above. ## run-shell output is missing on 3.3a and 3.4 only -Reading what `run-shell` printed works on 3.2a, silently reports nothing on -3.3a and 3.4, and works again from 3.5 onward — never intermittent, and the -exit status and the command's real effect are correct throughout. This is a -gap with working releases on both sides, not a floor, so the library exposes -two methods: one that runs a command for effect (works everywhere) and one -that runs it and returns its output (refuses on 3.3a and 3.4, rather than -returning an empty list that would be indistinguishable from a command that -printed nothing there). +Reading what `run-shell` printed silently reports nothing on 3.3a and 3.4, and +works from 3.5 onward — never intermittent, and the exit status and the +command's real effect are correct throughout. This is a gap at the start of the +supported range, not a floor, so the library exposes two methods: one that runs +a command for effect (works everywhere) and one that runs it and returns its +output (refuses on 3.3a and 3.4, rather than returning an empty list that would +be indistinguishable from a command that printed nothing there). ## find-window is a chooser, not a search diff --git a/docs/parity/test-map.md b/docs/parity/test-map.md index 9b68ea5c9..79b7c29e4 100644 --- a/docs/parity/test-map.md +++ b/docs/parity/test-map.md @@ -45,198 +45,198 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | Python evidence | contract | Java test kind | Java test | tmux versions | status | |---|---|---|---|---|---| | README.md::README.md[0] | execute `import libtmux` without exception; execute `svr = libtmux.Server()` without exception; execute `svr` and expect `Server(socket_path=/tmp/tmux-.../default)`. | documentation contract test | PythonBehaviorParityTest#README_md_README_md_0_1e0d7220f0 | not tmux-bound | planned parity | -| README.md::README.md[10] | execute `pane.clear()` and expect `Pane(%... ...)`; execute `pane.send_keys("echo 'hello world'", enter=True)` without exception; source-declared `+SKIP`: do not execute `pane.cmd('capture-pane', '-p').stdout # doctest: +SKIP`; its source text shows `["$ echo 'hello world'", 'hello world', '$']` only as illustrative output. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_10_eb2cfcf33e | 3.2a through 3.7b | source-declared partial non-execution | -| README.md::README.md[11] | execute `pane.window` and expect `Window(@... ...:..., Session($... ...))`; execute `pane.window.session` and expect `Session($... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_11_503180862f | 3.2a through 3.7b | planned parity | -| README.md::README.md[12] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")` without exception; execute `monkeypatch.setenv("TMUX_PANE", pane.pane_id)` without exception; execute `Pane.from_env()` and expect `Pane(%... ...)`; execute `Session.from_env().session_name == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_12_db94bdc4ba | 3.2a through 3.7b | planned parity | -| README.md::README.md[1] | execute `server = Server(socket_name='libtmux_doctest')` without exception; execute `server.cmd('display-message', 'hello world')` and expect `<libtmux...>`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_1_79ce674470 | 3.2a through 3.7b | planned parity | -| README.md::README.md[2] | execute `server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]` and expect `'$...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_2_ebabe7cf4c | 3.2a through 3.7b | planned parity | -| README.md::README.md[3] | execute `server.sessions` and expect `[Session($... ...), ...]`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_3_73f42203fb | 3.2a through 3.7b | planned parity | -| README.md::README.md[4] | execute `server.sessions.filter(history_limit='2000')` and expect `[Session($... ...), ...]`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_4_de4cce9671 | 3.2a through 3.7b | planned parity | -| README.md::README.md[5] | execute `server.sessions.get(session_id=session.session_id)` and expect `Session($... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_5_f14b350217 | 3.2a through 3.7b | planned parity | -| README.md::README.md[6] | execute `session.rename_session('my-session')` and expect `Session($... my-session)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_6_d19a3309aa | 3.2a through 3.7b | planned parity | -| README.md::README.md[7] | execute `bg_window = session.new_window(attach=False, window_name="bg-work")` without exception; execute `bg_window` and expect `Window(@... ...:bg-work, Session($... ...))`; execute `session.windows.filter(window_name__startswith="bg")` and expect `[Window(@... ...:bg-work, Session($... ...))]`; execute `session.windows.get(window_name__startswith="bg")` and expect `Window(@... ...:bg-work, Session($... ...))`; execute `bg_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_7_adfdedf79b | 3.2a through 3.7b | planned parity | -| README.md::README.md[8] | execute `pane = window.split(attach=False)` without exception; execute `pane` and expect `Pane(%... Window(@... ...:..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_8_b0888fc33a | 3.2a through 3.7b | planned parity | -| README.md::README.md[9] | execute `pane.send_keys('echo hello')` without exception; execute `pane.send_keys('echo hey', enter=False)` without exception; execute `pane.enter()` and expect `Pane(%... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_9_acbb4d6008 | 3.2a through 3.7b | planned parity | +| README.md::README.md[10] | execute `pane.clear()` and expect `Pane(%... ...)`; execute `pane.send_keys("echo 'hello world'", enter=True)` without exception; source-declared `+SKIP`: do not execute `pane.cmd('capture-pane', '-p').stdout # doctest: +SKIP`; its source text shows `["$ echo 'hello world'", 'hello world', '$']` only as illustrative output. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_10_eb2cfcf33e | 3.3a through 3.7c | source-declared partial non-execution | +| README.md::README.md[11] | execute `pane.window` and expect `Window(@... ...:..., Session($... ...))`; execute `pane.window.session` and expect `Session($... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_11_503180862f | 3.3a through 3.7c | planned parity | +| README.md::README.md[12] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")` without exception; execute `monkeypatch.setenv("TMUX_PANE", pane.pane_id)` without exception; execute `Pane.from_env()` and expect `Pane(%... ...)`; execute `Session.from_env().session_name == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_12_db94bdc4ba | 3.3a through 3.7c | planned parity | +| README.md::README.md[1] | execute `server = Server(socket_name='libtmux_doctest')` without exception; execute `server.cmd('display-message', 'hello world')` and expect `<libtmux...>`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_1_79ce674470 | 3.3a through 3.7c | planned parity | +| README.md::README.md[2] | execute `server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]` and expect `'$...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_2_ebabe7cf4c | 3.3a through 3.7c | planned parity | +| README.md::README.md[3] | execute `server.sessions` and expect `[Session($... ...), ...]`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_3_73f42203fb | 3.3a through 3.7c | planned parity | +| README.md::README.md[4] | execute `server.sessions.filter(history_limit='2000')` and expect `[Session($... ...), ...]`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_4_de4cce9671 | 3.3a through 3.7c | planned parity | +| README.md::README.md[5] | execute `server.sessions.get(session_id=session.session_id)` and expect `Session($... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_5_f14b350217 | 3.3a through 3.7c | planned parity | +| README.md::README.md[6] | execute `session.rename_session('my-session')` and expect `Session($... my-session)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_6_d19a3309aa | 3.3a through 3.7c | planned parity | +| README.md::README.md[7] | execute `bg_window = session.new_window(attach=False, window_name="bg-work")` without exception; execute `bg_window` and expect `Window(@... ...:bg-work, Session($... ...))`; execute `session.windows.filter(window_name__startswith="bg")` and expect `[Window(@... ...:bg-work, Session($... ...))]`; execute `session.windows.get(window_name__startswith="bg")` and expect `Window(@... ...:bg-work, Session($... ...))`; execute `bg_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_7_adfdedf79b | 3.3a through 3.7c | planned parity | +| README.md::README.md[8] | execute `pane = window.split(attach=False)` without exception; execute `pane` and expect `Pane(%... Window(@... ...:..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_8_b0888fc33a | 3.3a through 3.7c | planned parity | +| README.md::README.md[9] | execute `pane.send_keys('echo hello')` without exception; execute `pane.send_keys('echo hey', enter=False)` without exception; execute `pane.enter()` and expect `Pane(%... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#README_md_README_md_9_acbb4d6008 | 3.3a through 3.7c | planned parity | | docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[0] | execute `import libtmux` without exception. | documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_0_30d3d26530 | not tmux-bound | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[10] | execute `pane.pane_current_command` and expect `'...'`; execute `type(pane.pane_current_command)` and expect `<class 'str'>`; execute `pane.pane_height` and expect `'...'`; execute `pane.pane_width` and expect `'...'`; execute `pane.pane_index` and expect `'0'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_10_64f6b0dcbb | 3.2a through 3.7b | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[10] | execute `pane.pane_current_command` and expect `'...'`; execute `type(pane.pane_current_command)` and expect `<class 'str'>`; execute `pane.pane_height` and expect `'...'`; execute `pane.pane_width` and expect `'...'`; execute `pane.pane_index` and expect `'0'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_10_64f6b0dcbb | 3.3a through 3.7c | planned parity | | docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[1] | execute `import libtmux` without exception; execute `t = libtmux.Server()` without exception; execute `t` and expect `Server(socket_path=/tmp/tmux-.../default)`. | documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_1_6866ce96a6 | not tmux-bound | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[2] | execute `session = server.sessions[0]` without exception; execute `session` and expect `Session($1 libtmux_...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_2_c14aa4141f | 3.2a through 3.7b | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[3] | execute `session.session_name` and expect `'libtmux_...'`; execute `session.session_id` and expect `'$1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_3_f64b65ac31 | 3.2a through 3.7b | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[2] | execute `session = server.sessions[0]` without exception; execute `session` and expect `Session($1 libtmux_...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_2_c14aa4141f | 3.3a through 3.7c | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[3] | execute `session.session_name` and expect `'libtmux_...'`; execute `session.session_id` and expect `'$1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_3_f64b65ac31 | 3.3a through 3.7c | planned parity | | docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[4] | execute `from libtmux.neo import Obj` without exception; execute `sorted(Obj.__dataclass_fields__)[:3]` and expect `['active_window_index', 'alternate_saved_x', 'alternate_saved_y']`. | documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_4_2076265703 | not tmux-bound | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[5] | execute `session.session_windows` and expect `'...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_5_7b844bc7cc | 3.2a through 3.7b | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[6] | execute `window = session.active_window` without exception; execute `window` and expect `Window(@1 ...:..., Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_6_ec88d14a85 | 3.2a through 3.7b | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[7] | execute `window.window_name` and expect `'...'`; execute `window.window_id` and expect `'@1'`; execute `window.window_height` and expect `'...'`; execute `window.window_width` and expect `'...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_7_a017914423 | 3.2a through 3.7b | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[8] | execute `window.window_panes` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_8_e39239d3c5 | 3.2a through 3.7b | planned parity | -| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[9] | execute `pane = window.active_pane` without exception; execute `pane` and expect `Pane(%1 Window(@1 ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_9_e4665aaa3b | 3.2a through 3.7b | planned parity | -| docs/api/testing/pytest-plugin/fixtures.md::docs/api/testing/pytest-plugin/fixtures.md[0] | execute `created_session = server.new_session(session_name="my-session")` without exception; execute `created_session is not None` and expect `True`; execute `created_session.kill()` without exception; execute `created_window = session.new_window(window_name="test")` without exception; execute `created_window is not None` and expect `True`; execute `created_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#g_pytest_plugin_fixtures_md_docs_api_testing_pytest_plugin_fixtures_md_0_3f58feb3b4 | 3.2a through 3.7b | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[5] | execute `session.session_windows` and expect `'...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_5_7b844bc7cc | 3.3a through 3.7c | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[6] | execute `window = session.active_window` without exception; execute `window` and expect `Window(@1 ...:..., Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_6_ec88d14a85 | 3.3a through 3.7c | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[7] | execute `window.window_name` and expect `'...'`; execute `window.window_id` and expect `'@1'`; execute `window.window_height` and expect `'...'`; execute `window.window_width` and expect `'...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_7_a017914423 | 3.3a through 3.7c | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[8] | execute `window.window_panes` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_8_e39239d3c5 | 3.3a through 3.7c | planned parity | +| docs/api/libtmux.neo.md::docs/api/libtmux.neo.md[9] | execute `pane = window.active_pane` without exception; execute `pane` and expect `Pane(%1 Window(@1 ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_api_libtmux_neo_md_docs_api_libtmux_neo_md_9_e4665aaa3b | 3.3a through 3.7c | planned parity | +| docs/api/testing/pytest-plugin/fixtures.md::docs/api/testing/pytest-plugin/fixtures.md[0] | execute `created_session = server.new_session(session_name="my-session")` without exception; execute `created_session is not None` and expect `True`; execute `created_session.kill()` without exception; execute `created_window = session.new_window(window_name="test")` without exception; execute `created_window is not None` and expect `True`; execute `created_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#g_pytest_plugin_fixtures_md_docs_api_testing_pytest_plugin_fixtures_md_0_3f58feb3b4 | 3.3a through 3.7c | planned parity | | docs/api/testing/pytest-plugin/usage.md::docs/api/testing/pytest-plugin/usage.md[0] | execute `import pytest` without exception; execute `@pytest.fixture def session_params() -> dict[str, int]: return {"x": 800, "y": 600}` without exception. | documentation contract test | PythonBehaviorParityTest#testing_pytest_plugin_usage_md_docs_api_testing_pytest_plugin_usage_md_0_5015659fc1 | not tmux-bound | planned parity | | docs/api/testing/pytest-plugin/usage.md::docs/api/testing/pytest-plugin/usage.md[1] | execute `temp_server = Server()` without exception; execute `temp_session = temp_server.new_session()` without exception; execute `temp_server.is_alive()` and expect `True`; execute `temp_server.kill()` without exception. | documentation contract test | PythonBehaviorParityTest#testing_pytest_plugin_usage_md_docs_api_testing_pytest_plugin_usage_md_1_170fae20e8 | not tmux-bound | planned parity | | docs/api/testing/pytest-plugin/usage.md::docs/api/testing/pytest-plugin/usage.md[2] | execute `config_path = request.getfixturevalue("tmp_path") / "tmux.conf"` without exception; execute `_ = config_path.write_text("set -g status off")` without exception; execute `configured_server = Server(config_file=str(config_path))` without exception; execute `_ = configured_server.new_session()` without exception; execute `configured_server.is_alive()` and expect `True`; execute `configured_server.kill()` without exception. | documentation contract test | PythonBehaviorParityTest#testing_pytest_plugin_usage_md_docs_api_testing_pytest_plugin_usage_md_2_2c18b42b8b | not tmux-bound | planned parity | | docs/api/testing/pytest-plugin/usage.md::docs/api/testing/pytest-plugin/usage.md[3] | execute `import pathlib` without exception; execute `import pytest` without exception; execute `@pytest.fixture(autouse=True, scope="function") def set_home( monkeypatch: pytest.MonkeyPatch, user_path: pathlib.Path, ) -> None: monkeypatch.setenv("HOME", str(user_path))` without exception. | documentation contract test | PythonBehaviorParityTest#testing_pytest_plugin_usage_md_docs_api_testing_pytest_plugin_usage_md_3_040e32b95f | not tmux-bound | planned parity | -| docs/index.md::docs/index.md[0] | execute `demo_window = session.new_window(window_name="my-project")` without exception; execute `demo_pane = demo_window.active_pane` without exception; execute `demo_pane.send_keys("echo hello")` without exception; execute `demo_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_index_md_docs_index_md_0_5002a6845e | 3.2a through 3.7b | planned parity | -| docs/index.md::docs/index.md[1] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")` without exception; execute `monkeypatch.setenv("TMUX_PANE", pane.pane_id)` without exception; execute `Pane.from_env().pane_id == pane.pane_id` and expect `True`; execute `Session.from_env().session_name == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_index_md_docs_index_md_1_b6f73301e4 | 3.2a through 3.7b | planned parity | -| docs/index.md::docs/index.md[2] | execute `test_window = session.new_window(window_name="test")` without exception; execute `test_pane = test_window.active_pane` without exception; execute `test_pane.send_keys("echo hello")` without exception; execute `test_window.window_name` and expect `'test'`; execute `test_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_index_md_docs_index_md_2_9cef31ef79 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[0] | execute `import libtmux` without exception; execute `server = libtmux.Server()` without exception; execute `server` and expect `Server(socket_path=/tmp/tmux-.../default)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_0_5c4e9027f7 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[10] | execute `server.sessions[0].rename_session('foo')` and expect `Session($1 foo)`; execute `session = server.sessions.get(session_name="foo")` without exception; execute `session` and expect `Session($1 foo)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_10_1ad96b2cc0 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[11] | execute `session.new_window(attach=False, window_name="ha in the bg")` and expect `Window(@2 ...:ha in the bg, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_11_4a56cd3fb5 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[12] | execute `session.kill_window(window.window_id)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_12_b0b3d61124 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[13] | execute `session.new_window(attach=False, window_name="ha in the bg")` and expect `Window(@2 ...:ha in the bg, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_13_94d0ffffb2 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[14] | execute `session.new_window(attach=False, window_name="ha in the bg")` and expect `Window(@2 ...:ha in the bg, Session($1 ...))`; execute `session.kill_window('ha in the bg')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_14_4c829f0a75 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[15] | execute `window = session.new_window(attach=False, window_name="check this out")` without exception; execute `window` and expect `Window(@2 2:check this out, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_15_d3b0a6f018 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[16] | execute `window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_16_e5f13e6688 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[17] | execute `window = session.active_window` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_17_b87610e4e3 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[18] | execute `window.split(attach=False)` and expect `Pane(%2 Window(@1 ...:..., Session($1 ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_18_9d4e813a76 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[19] | execute `window.rename_window('libtmuxower')` and expect `Window(@1 ...:..., Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_19_1ed0784542 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[1] | execute `server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]` and expect `'$2'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_1_c7d3e620a3 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[20] | execute `pane = window.split()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_20_3a6eae8369 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[21] | execute `pane = window.split(attach=False)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_21_7445c300b3 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[22] | execute `pane.select()` and expect `Pane(%1 Window(@1 ...:..., Session($1 ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_22_d8b57efd93 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[23] | execute `window = session.new_window(attach=False, window_name="test")` without exception; execute `pane = window.split(attach=False)` without exception; execute `pane.send_keys('echo hey', enter=False)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_23_1f33e5b331 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[24] | execute `pane.enter()` and expect `Pane(%1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_24_1200606fdf | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[25] | execute `pane.send_keys('echo Howdy', enter=True, suppress_history=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_25_08ec8ee4a0 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[26] | execute `server.show_option('buffer-limit')` and expect `50`; execute `window.show_options() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_26_7798619390 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[27] | execute `window.set_option('automatic-rename', False) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `window.show_option('automatic-rename')` and expect `False`; execute `window.unset_option('automatic-rename') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_27_c16e06c8e8 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[2] | execute `session.cmd('new-window', '-P').stdout[0]` and expect `'libtmux...:2.0'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_2_868a54960d | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[3] | execute `Window.from_window_id(window_id=session.cmd('new-window', '-P', '-F#{window_id}').stdout[0], server=session.server)` and expect `Window(@2 2:..., Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_3_469dba23a7 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[4] | execute `window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0]` and expect `'%2'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_4_f2abec70e2 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[5] | execute `Pane.from_pane_id(pane_id=window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server)` and expect `Pane(%... Window(@1 1:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_5_7bc3e3ea98 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[6] | execute `server.sessions` and expect `[Session($1 ...), Session($0 ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_6_e1cefc99b0 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[7] | execute `server.sessions[0]` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_7_994dd7180c | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[8] | execute `server.sessions.filter(session_id='$1')[0]` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_8_ed04d31709 | 3.2a through 3.7b | planned parity | -| docs/quickstart.md::docs/quickstart.md[9] | execute `server.sessions[0].rename_session('foo')` and expect `Session($1 foo)`; execute `server.sessions.filter(session_name="foo")[0]` and expect `Session($1 foo)`; execute `server.sessions.get(session_name="foo")` and expect `Session($1 foo)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_9_7aae9f19ec | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[0] | execute `import time` without exception; execute `proc_window = session.new_window(window_name='process', attach=False)` without exception; execute `proc_pane = proc_window.active_pane` without exception; execute `proc_pane.send_keys('sleep 2 && echo "Process complete"')` without exception; execute `time.sleep(0.1)` without exception; execute `proc_window.window_name` and expect `'process'`; execute `proc_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_0_887870afdc | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[10] | execute `import time` without exception; execute `retry_window = session.new_window(window_name='retry-demo', attach=False)` without exception; execute `retry_pane = retry_window.active_pane` without exception; execute `def retry_until_success(pane, command, success_marker, max_retries=3, delay=0.5): """Retry command until success marker appears.""" for attempt in range(max_retries): pane.send_keys(command) time.sleep(delay) output = '\\n'.join(pane.capture_pane()) if success_marker in output: return True, attempt + 1 return False, max_retries` without exception; execute `success, attempts = retry_until_success( retry_pane, 'echo "OK"', 'OK', max_retries=3, delay=0.2 )` without exception; execute `success` and expect `True`; execute `attempts` and expect `1`; execute `retry_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_10_4b90629469 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[11] | execute `import time` without exception; execute `queue_window = session.new_window(window_name='queue', attach=False)` without exception; execute `queue_pane = queue_window.active_pane` without exception; execute `def process_task_queue(pane, tasks, completion_marker='TASK_DONE'): """Process a queue of tasks sequentially.""" results = [] for i, task in enumerate(tasks): pane.send_keys(f'{task}; echo "{completion_marker}_{i}"') # Wait for this task to complete start = time.time() while time.time() - start < 5.0: output = '\\n'.join(pane.capture_pane()) if f'{completion_marker}_{i}' in output: results.append((i, True)) break time.sleep(0.1) else: results.append((i, False)) return results` without exception; execute `tasks = ['echo "Step 1"', 'echo "Step 2"', 'echo "Step 3"']` without exception; execute `results = process_task_queue(queue_pane, tasks)` without exception; execute `all(success for _, success in results)` and expect `True`; execute `queue_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_11_9b0ee172d9 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[12] | execute `import time` without exception; execute `state_window = session.new_window(window_name='state-machine', attach=False)` without exception; execute `state_pane = state_window.active_pane` without exception; execute `def run_state_machine(pane, states, timeout_per_state=2.0): """Run through a series of states with transitions.""" current_state = 0 history = [] while current_state < len(states): state_name, command, next_marker = states[current_state] pane.send_keys(command) start = time.time() while time.time() - start < timeout_per_state: output = '\\n'.join(pane.capture_pane()) if next_marker in output: history.append(state_name) current_state += 1 break time.sleep(0.1) else: return history, False # Timeout return history, True` without exception; execute `states = [ ('init', 'echo "INIT_DONE"', 'INIT_DONE'), ('process', 'echo "PROCESS_DONE"', 'PROCESS_DONE'), ('cleanup', 'echo "CLEANUP_DONE"', 'CLEANUP_DONE'), ]` without exception; execute `history, success = run_state_machine(state_pane, states)` without exception; execute `success` and expect `True`; execute `len(history)` and expect `3`; execute `state_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_12_0019f56ba3 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[13] | execute `bp_window = session.new_window(window_name='best-practice', attach=False)` without exception; execute `bp_pane = bp_window.active_pane` without exception; execute `bp_pane.send_keys('long_command; echo "__DONE__"')` without exception; execute `import time` without exception; execute `time.sleep(0.2)` without exception; execute `'__DONE__' in '\\n'.join(bp_pane.capture_pane())` and expect `True`; execute `bp_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_13_263a0a7422 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[14] | execute `cleanup_window = session.new_window(window_name='cleanup-demo', attach=False)` without exception; execute `cleanup_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `cleanup_window.kill()` without exception; execute `cleanup_window not in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_14_51c82f6dd5 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[15] | execute `with session.new_window(window_name='safe-work') as safe_window: pane = safe_window.active_pane # Work happens here pass # Even if exception occurs, window is cleaned up` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_15_d080f809da | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[1] | execute `import time` without exception; execute `status_window = session.new_window(window_name='status-check', attach=False)` without exception; execute `status_pane = status_window.active_pane` without exception; execute `def is_process_running(pane, marker='RUNNING'): """Check if a marker indicates process is still running.""" output = pane.capture_pane() return marker in '\\n'.join(output)` without exception; execute `status_pane.send_keys('echo "RUNNING"; sleep 0.3; echo "DONE"')` without exception; execute `time.sleep(0.1)` without exception; execute `'RUNNING' in '\\n'.join(status_pane.capture_pane())` and expect `True`; execute `time.sleep(0.5)` without exception; execute `'DONE' in '\\n'.join(status_pane.capture_pane())` and expect `True`; execute `status_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_1_091b7723b5 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[2] | execute `import time` without exception; execute `monitor_window = session.new_window(window_name='monitor', attach=False)` without exception; execute `monitor_pane = monitor_window.active_pane` without exception; execute `def wait_for_output(pane, text, timeout=5.0, poll_interval=0.1): """Wait for specific text to appear in pane output.""" start = time.time() while time.time() - start < timeout: output = '\\n'.join(pane.capture_pane()) if text in output: return True time.sleep(poll_interval) return False` without exception; execute `monitor_pane.send_keys('sleep 0.2; echo "READY"')` without exception; execute `wait_for_output(monitor_pane, 'READY', timeout=2.0)` and expect `True`; execute `monitor_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_2_efd039245a | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[3] | execute `import time` without exception; execute `error_window = session.new_window(window_name='error-check', attach=False)` without exception; execute `error_pane = error_window.active_pane` without exception; execute `def check_for_errors(pane, patterns=None): """Check pane output for error patterns.""" if patterns is None: patterns = ['Error:', 'error:', 'ERROR', 'FAILED', 'Exception'] output = '\\n'.join(pane.capture_pane()) for pattern in patterns: if pattern in output: return pattern return None` without exception; execute `error_pane.send_keys('echo "Success!"')` without exception; execute `time.sleep(0.1)` without exception; execute `check_for_errors(error_pane) is None` and expect `True`; execute `error_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_3_962186aff6 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[4] | execute `import time` without exception; execute `capture_window = session.new_window(window_name='capture', attach=False)` without exception; execute `capture_pane = capture_window.active_pane` without exception; execute `def capture_after_marker(pane, marker, timeout=5.0): """Capture output after a marker appears.""" start_time = time.time() while time.time() - start_time < timeout: lines = pane.capture_pane() output = '\\n'.join(lines) if marker in output: # Return all lines after the marker found = False result = [] for line in lines: if marker in line: found = True continue if found: result.append(line) return result time.sleep(0.1) return None` without exception; execute `capture_pane.send_keys('echo "MARKER"; echo "captured data"')` without exception; execute `time.sleep(0.3)` without exception; execute `result = capture_after_marker(capture_pane, 'MARKER', timeout=2.0)` without exception; execute `any('captured' in line for line in (result or []))` and expect `True`; execute `capture_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_4_5d507b840e | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[5] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `parallel_window = session.new_window(window_name='parallel', attach=False)` without exception; execute `parallel_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `pane1 = parallel_window.active_pane` without exception; execute `pane2 = pane1.split(direction=PaneDirection.Right)` without exception; execute `pane3 = pane1.split(direction=PaneDirection.Below)` without exception; execute `tasks = [ (pane1, 'echo "Task 1"; sleep 0.2; echo "DONE1"'), (pane2, 'echo "Task 2"; sleep 0.1; echo "DONE2"'), (pane3, 'echo "Task 3"; sleep 0.3; echo "DONE3"'), ]` without exception; execute `for pane, cmd in tasks: pane.send_keys(cmd)` without exception; execute `time.sleep(0.5)` without exception; execute `all('DONE' in '\\n'.join(p.capture_pane()) for p, _ in tasks)` and expect `True`; execute `parallel_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_5_26c1bc29c6 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[6] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `multi_window = session.new_window(window_name='multi-monitor', attach=False)` without exception; execute `multi_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `panes = [multi_window.active_pane]` without exception; execute `panes.append(panes[0].split(direction=PaneDirection.Right))` without exception; execute `panes.append(panes[0].split(direction=PaneDirection.Below))` without exception; execute `def wait_all_complete(panes, marker='COMPLETE', timeout=10.0): """Wait for all panes to show completion marker.""" start = time.time() remaining = set(range(len(panes))) while remaining and time.time() - start < timeout: for i in list(remaining): if marker in '\\n'.join(panes[i].capture_pane()): remaining.remove(i) time.sleep(0.1) return len(remaining) == 0` without exception; execute `for i, pane in enumerate(panes): pane.send_keys(f'sleep 0.{i+1}; echo "COMPLETE"')` without exception; execute `wait_all_complete(panes, 'COMPLETE', timeout=2.0)` and expect `True`; execute `multi_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_6_431ba17d12 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[7] | execute `with server.new_session(session_name='temp-work') as temp_session: window = temp_session.new_window(window_name='task') pane = window.active_pane pane.send_keys('echo "Isolated work"') # Session exists during work temp_session in server.sessions` and expect `True`; execute `temp_session not in server.sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_7_b244ac24ec | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[8] | execute `import time` without exception; execute `with session.new_window(window_name='subtask') as sub_window: pane = sub_window.active_pane pane.send_keys('echo "Subtask running"') time.sleep(0.1) 'Subtask' in '\\n'.join(pane.capture_pane())` and expect `True`; execute `sub_window not in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_8_f7764b8715 | 3.2a through 3.7b | planned parity | -| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[9] | execute `import time` without exception; execute `timeout_window = session.new_window(window_name='timeout-demo', attach=False)` without exception; execute `timeout_pane = timeout_window.active_pane` without exception; execute `class CommandTimeout(Exception): """Raised when a command times out.""" pass` without exception; execute `def run_with_timeout(pane, command, marker='__DONE__', timeout=5.0): """Run command and wait for completion with timeout.""" pane.send_keys(f'{command}; echo {marker}') start = time.time() while time.time() - start < timeout: output = '\\n'.join(pane.capture_pane()) if marker in output: return output time.sleep(0.1) raise CommandTimeout(f'Command timed out after {timeout}s')` without exception; execute `result = run_with_timeout(timeout_pane, 'echo "fast"', timeout=2.0)` without exception; execute `'fast' in result` and expect `True`; execute `timeout_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_9_d60e07d979 | 3.2a through 3.7b | planned parity | -| docs/topics/clients.md::docs/topics/clients.md[0] | execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) attached = client.attached_session` without exception; execute `attached is not None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_clients_md_docs_topics_clients_md_0_87f0d41a5d | 3.2a through 3.7b | planned parity | -| docs/topics/clients.md::docs/topics/clients.md[1] | execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) pane = client.attached_pane` without exception; execute `pane is None or pane.pane_id.startswith('%')` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_clients_md_docs_topics_clients_md_1_0ff98b00d9 | 3.2a through 3.7b | planned parity | -| docs/topics/clients.md::docs/topics/clients.md[2] | execute `with control_mode() as ctl: attached = [ c for c in server.clients if c.client_name == ctl.client_name ]` without exception; execute `bool(attached)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_clients_md_docs_topics_clients_md_2_99d2e27e61 | 3.2a through 3.7b | planned parity | +| docs/index.md::docs/index.md[0] | execute `demo_window = session.new_window(window_name="my-project")` without exception; execute `demo_pane = demo_window.active_pane` without exception; execute `demo_pane.send_keys("echo hello")` without exception; execute `demo_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_index_md_docs_index_md_0_5002a6845e | 3.3a through 3.7c | planned parity | +| docs/index.md::docs/index.md[1] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")` without exception; execute `monkeypatch.setenv("TMUX_PANE", pane.pane_id)` without exception; execute `Pane.from_env().pane_id == pane.pane_id` and expect `True`; execute `Session.from_env().session_name == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_index_md_docs_index_md_1_b6f73301e4 | 3.3a through 3.7c | planned parity | +| docs/index.md::docs/index.md[2] | execute `test_window = session.new_window(window_name="test")` without exception; execute `test_pane = test_window.active_pane` without exception; execute `test_pane.send_keys("echo hello")` without exception; execute `test_window.window_name` and expect `'test'`; execute `test_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_index_md_docs_index_md_2_9cef31ef79 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[0] | execute `import libtmux` without exception; execute `server = libtmux.Server()` without exception; execute `server` and expect `Server(socket_path=/tmp/tmux-.../default)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_0_5c4e9027f7 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[10] | execute `server.sessions[0].rename_session('foo')` and expect `Session($1 foo)`; execute `session = server.sessions.get(session_name="foo")` without exception; execute `session` and expect `Session($1 foo)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_10_1ad96b2cc0 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[11] | execute `session.new_window(attach=False, window_name="ha in the bg")` and expect `Window(@2 ...:ha in the bg, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_11_4a56cd3fb5 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[12] | execute `session.kill_window(window.window_id)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_12_b0b3d61124 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[13] | execute `session.new_window(attach=False, window_name="ha in the bg")` and expect `Window(@2 ...:ha in the bg, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_13_94d0ffffb2 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[14] | execute `session.new_window(attach=False, window_name="ha in the bg")` and expect `Window(@2 ...:ha in the bg, Session($1 ...))`; execute `session.kill_window('ha in the bg')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_14_4c829f0a75 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[15] | execute `window = session.new_window(attach=False, window_name="check this out")` without exception; execute `window` and expect `Window(@2 2:check this out, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_15_d3b0a6f018 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[16] | execute `window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_16_e5f13e6688 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[17] | execute `window = session.active_window` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_17_b87610e4e3 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[18] | execute `window.split(attach=False)` and expect `Pane(%2 Window(@1 ...:..., Session($1 ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_18_9d4e813a76 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[19] | execute `window.rename_window('libtmuxower')` and expect `Window(@1 ...:..., Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_19_1ed0784542 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[1] | execute `server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]` and expect `'$2'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_1_c7d3e620a3 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[20] | execute `pane = window.split()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_20_3a6eae8369 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[21] | execute `pane = window.split(attach=False)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_21_7445c300b3 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[22] | execute `pane.select()` and expect `Pane(%1 Window(@1 ...:..., Session($1 ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_22_d8b57efd93 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[23] | execute `window = session.new_window(attach=False, window_name="test")` without exception; execute `pane = window.split(attach=False)` without exception; execute `pane.send_keys('echo hey', enter=False)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_23_1f33e5b331 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[24] | execute `pane.enter()` and expect `Pane(%1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_24_1200606fdf | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[25] | execute `pane.send_keys('echo Howdy', enter=True, suppress_history=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_25_08ec8ee4a0 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[26] | execute `server.show_option('buffer-limit')` and expect `50`; execute `window.show_options() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_26_7798619390 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[27] | execute `window.set_option('automatic-rename', False) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `window.show_option('automatic-rename')` and expect `False`; execute `window.unset_option('automatic-rename') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_27_c16e06c8e8 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[2] | execute `session.cmd('new-window', '-P').stdout[0]` and expect `'libtmux...:2.0'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_2_868a54960d | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[3] | execute `Window.from_window_id(window_id=session.cmd('new-window', '-P', '-F#{window_id}').stdout[0], server=session.server)` and expect `Window(@2 2:..., Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_3_469dba23a7 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[4] | execute `window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0]` and expect `'%2'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_4_f2abec70e2 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[5] | execute `Pane.from_pane_id(pane_id=window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server)` and expect `Pane(%... Window(@1 1:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_5_7bc3e3ea98 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[6] | execute `server.sessions` and expect `[Session($1 ...), Session($0 ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_6_e1cefc99b0 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[7] | execute `server.sessions[0]` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_7_994dd7180c | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[8] | execute `server.sessions.filter(session_id='$1')[0]` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_8_ed04d31709 | 3.3a through 3.7c | planned parity | +| docs/quickstart.md::docs/quickstart.md[9] | execute `server.sessions[0].rename_session('foo')` and expect `Session($1 foo)`; execute `server.sessions.filter(session_name="foo")[0]` and expect `Session($1 foo)`; execute `server.sessions.get(session_name="foo")` and expect `Session($1 foo)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_quickstart_md_docs_quickstart_md_9_7aae9f19ec | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[0] | execute `import time` without exception; execute `proc_window = session.new_window(window_name='process', attach=False)` without exception; execute `proc_pane = proc_window.active_pane` without exception; execute `proc_pane.send_keys('sleep 2 && echo "Process complete"')` without exception; execute `time.sleep(0.1)` without exception; execute `proc_window.window_name` and expect `'process'`; execute `proc_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_0_887870afdc | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[10] | execute `import time` without exception; execute `retry_window = session.new_window(window_name='retry-demo', attach=False)` without exception; execute `retry_pane = retry_window.active_pane` without exception; execute `def retry_until_success(pane, command, success_marker, max_retries=3, delay=0.5): """Retry command until success marker appears.""" for attempt in range(max_retries): pane.send_keys(command) time.sleep(delay) output = '\\n'.join(pane.capture_pane()) if success_marker in output: return True, attempt + 1 return False, max_retries` without exception; execute `success, attempts = retry_until_success( retry_pane, 'echo "OK"', 'OK', max_retries=3, delay=0.2 )` without exception; execute `success` and expect `True`; execute `attempts` and expect `1`; execute `retry_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_10_4b90629469 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[11] | execute `import time` without exception; execute `queue_window = session.new_window(window_name='queue', attach=False)` without exception; execute `queue_pane = queue_window.active_pane` without exception; execute `def process_task_queue(pane, tasks, completion_marker='TASK_DONE'): """Process a queue of tasks sequentially.""" results = [] for i, task in enumerate(tasks): pane.send_keys(f'{task}; echo "{completion_marker}_{i}"') # Wait for this task to complete start = time.time() while time.time() - start < 5.0: output = '\\n'.join(pane.capture_pane()) if f'{completion_marker}_{i}' in output: results.append((i, True)) break time.sleep(0.1) else: results.append((i, False)) return results` without exception; execute `tasks = ['echo "Step 1"', 'echo "Step 2"', 'echo "Step 3"']` without exception; execute `results = process_task_queue(queue_pane, tasks)` without exception; execute `all(success for _, success in results)` and expect `True`; execute `queue_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_11_9b0ee172d9 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[12] | execute `import time` without exception; execute `state_window = session.new_window(window_name='state-machine', attach=False)` without exception; execute `state_pane = state_window.active_pane` without exception; execute `def run_state_machine(pane, states, timeout_per_state=2.0): """Run through a series of states with transitions.""" current_state = 0 history = [] while current_state < len(states): state_name, command, next_marker = states[current_state] pane.send_keys(command) start = time.time() while time.time() - start < timeout_per_state: output = '\\n'.join(pane.capture_pane()) if next_marker in output: history.append(state_name) current_state += 1 break time.sleep(0.1) else: return history, False # Timeout return history, True` without exception; execute `states = [ ('init', 'echo "INIT_DONE"', 'INIT_DONE'), ('process', 'echo "PROCESS_DONE"', 'PROCESS_DONE'), ('cleanup', 'echo "CLEANUP_DONE"', 'CLEANUP_DONE'), ]` without exception; execute `history, success = run_state_machine(state_pane, states)` without exception; execute `success` and expect `True`; execute `len(history)` and expect `3`; execute `state_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_12_0019f56ba3 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[13] | execute `bp_window = session.new_window(window_name='best-practice', attach=False)` without exception; execute `bp_pane = bp_window.active_pane` without exception; execute `bp_pane.send_keys('long_command; echo "__DONE__"')` without exception; execute `import time` without exception; execute `time.sleep(0.2)` without exception; execute `'__DONE__' in '\\n'.join(bp_pane.capture_pane())` and expect `True`; execute `bp_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_13_263a0a7422 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[14] | execute `cleanup_window = session.new_window(window_name='cleanup-demo', attach=False)` without exception; execute `cleanup_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `cleanup_window.kill()` without exception; execute `cleanup_window not in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_14_51c82f6dd5 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[15] | execute `with session.new_window(window_name='safe-work') as safe_window: pane = safe_window.active_pane # Work happens here pass # Even if exception occurs, window is cleaned up` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_15_d080f809da | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[1] | execute `import time` without exception; execute `status_window = session.new_window(window_name='status-check', attach=False)` without exception; execute `status_pane = status_window.active_pane` without exception; execute `def is_process_running(pane, marker='RUNNING'): """Check if a marker indicates process is still running.""" output = pane.capture_pane() return marker in '\\n'.join(output)` without exception; execute `status_pane.send_keys('echo "RUNNING"; sleep 0.3; echo "DONE"')` without exception; execute `time.sleep(0.1)` without exception; execute `'RUNNING' in '\\n'.join(status_pane.capture_pane())` and expect `True`; execute `time.sleep(0.5)` without exception; execute `'DONE' in '\\n'.join(status_pane.capture_pane())` and expect `True`; execute `status_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_1_091b7723b5 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[2] | execute `import time` without exception; execute `monitor_window = session.new_window(window_name='monitor', attach=False)` without exception; execute `monitor_pane = monitor_window.active_pane` without exception; execute `def wait_for_output(pane, text, timeout=5.0, poll_interval=0.1): """Wait for specific text to appear in pane output.""" start = time.time() while time.time() - start < timeout: output = '\\n'.join(pane.capture_pane()) if text in output: return True time.sleep(poll_interval) return False` without exception; execute `monitor_pane.send_keys('sleep 0.2; echo "READY"')` without exception; execute `wait_for_output(monitor_pane, 'READY', timeout=2.0)` and expect `True`; execute `monitor_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_2_efd039245a | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[3] | execute `import time` without exception; execute `error_window = session.new_window(window_name='error-check', attach=False)` without exception; execute `error_pane = error_window.active_pane` without exception; execute `def check_for_errors(pane, patterns=None): """Check pane output for error patterns.""" if patterns is None: patterns = ['Error:', 'error:', 'ERROR', 'FAILED', 'Exception'] output = '\\n'.join(pane.capture_pane()) for pattern in patterns: if pattern in output: return pattern return None` without exception; execute `error_pane.send_keys('echo "Success!"')` without exception; execute `time.sleep(0.1)` without exception; execute `check_for_errors(error_pane) is None` and expect `True`; execute `error_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_3_962186aff6 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[4] | execute `import time` without exception; execute `capture_window = session.new_window(window_name='capture', attach=False)` without exception; execute `capture_pane = capture_window.active_pane` without exception; execute `def capture_after_marker(pane, marker, timeout=5.0): """Capture output after a marker appears.""" start_time = time.time() while time.time() - start_time < timeout: lines = pane.capture_pane() output = '\\n'.join(lines) if marker in output: # Return all lines after the marker found = False result = [] for line in lines: if marker in line: found = True continue if found: result.append(line) return result time.sleep(0.1) return None` without exception; execute `capture_pane.send_keys('echo "MARKER"; echo "captured data"')` without exception; execute `time.sleep(0.3)` without exception; execute `result = capture_after_marker(capture_pane, 'MARKER', timeout=2.0)` without exception; execute `any('captured' in line for line in (result or []))` and expect `True`; execute `capture_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_4_5d507b840e | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[5] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `parallel_window = session.new_window(window_name='parallel', attach=False)` without exception; execute `parallel_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `pane1 = parallel_window.active_pane` without exception; execute `pane2 = pane1.split(direction=PaneDirection.Right)` without exception; execute `pane3 = pane1.split(direction=PaneDirection.Below)` without exception; execute `tasks = [ (pane1, 'echo "Task 1"; sleep 0.2; echo "DONE1"'), (pane2, 'echo "Task 2"; sleep 0.1; echo "DONE2"'), (pane3, 'echo "Task 3"; sleep 0.3; echo "DONE3"'), ]` without exception; execute `for pane, cmd in tasks: pane.send_keys(cmd)` without exception; execute `time.sleep(0.5)` without exception; execute `all('DONE' in '\\n'.join(p.capture_pane()) for p, _ in tasks)` and expect `True`; execute `parallel_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_5_26c1bc29c6 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[6] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `multi_window = session.new_window(window_name='multi-monitor', attach=False)` without exception; execute `multi_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `panes = [multi_window.active_pane]` without exception; execute `panes.append(panes[0].split(direction=PaneDirection.Right))` without exception; execute `panes.append(panes[0].split(direction=PaneDirection.Below))` without exception; execute `def wait_all_complete(panes, marker='COMPLETE', timeout=10.0): """Wait for all panes to show completion marker.""" start = time.time() remaining = set(range(len(panes))) while remaining and time.time() - start < timeout: for i in list(remaining): if marker in '\\n'.join(panes[i].capture_pane()): remaining.remove(i) time.sleep(0.1) return len(remaining) == 0` without exception; execute `for i, pane in enumerate(panes): pane.send_keys(f'sleep 0.{i+1}; echo "COMPLETE"')` without exception; execute `wait_all_complete(panes, 'COMPLETE', timeout=2.0)` and expect `True`; execute `multi_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_6_431ba17d12 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[7] | execute `with server.new_session(session_name='temp-work') as temp_session: window = temp_session.new_window(window_name='task') pane = window.active_pane pane.send_keys('echo "Isolated work"') # Session exists during work temp_session in server.sessions` and expect `True`; execute `temp_session not in server.sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_7_b244ac24ec | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[8] | execute `import time` without exception; execute `with session.new_window(window_name='subtask') as sub_window: pane = sub_window.active_pane pane.send_keys('echo "Subtask running"') time.sleep(0.1) 'Subtask' in '\\n'.join(pane.capture_pane())` and expect `True`; execute `sub_window not in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_8_f7764b8715 | 3.3a through 3.7c | planned parity | +| docs/topics/automation_patterns.md::docs/topics/automation_patterns.md[9] | execute `import time` without exception; execute `timeout_window = session.new_window(window_name='timeout-demo', attach=False)` without exception; execute `timeout_pane = timeout_window.active_pane` without exception; execute `class CommandTimeout(Exception): """Raised when a command times out.""" pass` without exception; execute `def run_with_timeout(pane, command, marker='__DONE__', timeout=5.0): """Run command and wait for completion with timeout.""" pane.send_keys(f'{command}; echo {marker}') start = time.time() while time.time() - start < timeout: output = '\\n'.join(pane.capture_pane()) if marker in output: return output time.sleep(0.1) raise CommandTimeout(f'Command timed out after {timeout}s')` without exception; execute `result = run_with_timeout(timeout_pane, 'echo "fast"', timeout=2.0)` without exception; execute `'fast' in result` and expect `True`; execute `timeout_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_automation_patterns_md_docs_topics_automation_patterns_md_9_d60e07d979 | 3.3a through 3.7c | planned parity | +| docs/topics/clients.md::docs/topics/clients.md[0] | execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) attached = client.attached_session` without exception; execute `attached is not None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_clients_md_docs_topics_clients_md_0_87f0d41a5d | 3.3a through 3.7c | planned parity | +| docs/topics/clients.md::docs/topics/clients.md[1] | execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) pane = client.attached_pane` without exception; execute `pane is None or pane.pane_id.startswith('%')` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_clients_md_docs_topics_clients_md_1_0ff98b00d9 | 3.3a through 3.7c | planned parity | +| docs/topics/clients.md::docs/topics/clients.md[2] | execute `with control_mode() as ctl: attached = [ c for c in server.clients if c.client_name == ctl.client_name ]` without exception; execute `bool(attached)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_clients_md_docs_topics_clients_md_2_99d2e27e61 | 3.3a through 3.7c | planned parity | | docs/topics/context_managers.md::docs/topics/context_managers.md[0] | execute `import libtmux` without exception. | documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_0_ed8462c4a7 | not tmux-bound | planned parity | -| docs/topics/context_managers.md::docs/topics/context_managers.md[1] | execute `with Server() as server: session = server.new_session() print(server.is_alive())` and expect `True`; execute `print(server.is_alive()) # Server is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_1_a71200aff7 | 3.2a through 3.7b | planned parity | -| docs/topics/context_managers.md::docs/topics/context_managers.md[2] | execute `server = Server()` without exception; execute `with server.new_session() as session: print(session in server.sessions) window = session.new_window()` and expect `True`; execute `print(session in server.sessions) # Session is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_2_020ff19587 | 3.2a through 3.7b | planned parity | -| docs/topics/context_managers.md::docs/topics/context_managers.md[3] | execute `server = Server()` without exception; execute `session = server.new_session()` without exception; execute `with session.new_window() as window: print(window in session.windows) pane = window.split()` and expect `True`; execute `print(window in session.windows) # Window is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_3_3859334d99 | 3.2a through 3.7b | planned parity | -| docs/topics/context_managers.md::docs/topics/context_managers.md[4] | execute `server = Server()` without exception; execute `session = server.new_session()` without exception; execute `window = session.new_window()` without exception; execute `with window.split() as pane: print(pane in window.panes) pane.send_keys('echo "Hello"')` and expect `True`; execute `print(pane in window.panes) # Pane is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_4_f08e853cd5 | 3.2a through 3.7b | planned parity | -| docs/topics/context_managers.md::docs/topics/context_managers.md[5] | execute `with Server() as server: with server.new_session() as session: with session.new_window() as window: with window.split() as pane: pane.send_keys('echo "Hello"') # Do work with the pane # Everything is cleaned up automatically when exiting contexts` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_5_ea6e5b2860 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[0] | execute `server.sessions # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_0_190d6fc1c9 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[10] | execute `w1 = session.new_window(window_name="feature-login")` without exception; execute `w2 = session.new_window(window_name="feature-signup")` without exception; execute `w3 = session.new_window(window_name="bugfix-typo")` without exception; execute `session.windows.filter( window_name__startswith='feature', window_name__endswith='signup' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:feature-signup, Session($... ...))]`; execute `session.windows.filter( window_name__contains='feature' ).filter( window_name__contains='login' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:feature-login, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_10_bc8123f45b | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[11] | execute `w1 = session.new_window(window_name="MyApp-Server")` without exception; execute `w2 = session.new_window(window_name="myapp-worker")` without exception; execute `myapp_windows = session.windows.filter(window_name__icontains='MYAPP')` without exception; execute `len(myapp_windows) >= 2` and expect `True`; execute `session.windows.filter(window_name__istartswith='myapp') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:MyApp-Server, Session($... ...)), Window(@... ...:myapp-worker, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_11_5e38730037 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[12] | execute `w1 = session.new_window(window_name="app-v1-0")` without exception; execute `w2 = session.new_window(window_name="app-v2-0")` without exception; execute `w3 = session.new_window(window_name="app-beta")` without exception; execute `versioned = session.windows.filter(window_name__regex=r'v\d+-\d+$')` without exception; execute `len(versioned) >= 2` and expect `True`; execute `session.windows.filter(window_name__iregex=r'BETA') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:app-beta, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_12_111e348049 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[13] | execute `w1 = session.new_window(window_name="dev")` without exception; execute `w2 = session.new_window(window_name="staging")` without exception; execute `w3 = session.new_window(window_name="prod")` without exception; execute `target_envs = ["dev", "prod"]` without exception; execute `session.windows.filter(window_name__in=target_envs) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:dev, Session($... ...)), Window(@... ...:prod, Session($... ...))]`; execute `non_prod = session.windows.filter(window_name__nin=["prod"])` without exception; execute `any(w.window_name == "prod" for w in non_prod)` and expect `False`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_13_54dbf98c56 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[14] | execute `server.panes # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Pane(%... Window(@... ..., Session($... ...)))]`; execute `pane = session.active_pane` without exception; execute `pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_14_594195a62b | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[15] | execute `w1 = session.new_window(window_name="vim-main")` without exception; execute `w2 = session.new_window(window_name="nvim-config")` without exception; execute `w3 = session.new_window(window_name="shell")` without exception; execute `editors = session.windows.filter(window_name__iregex=r'n?vim')` without exception; execute `len(editors) >= 2` and expect `True`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_15_4558f80de6 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[16] | execute `w1 = session.new_window(window_name="project-frontend")` without exception; execute `w2 = session.new_window(window_name="project-backend")` without exception; execute `w3 = session.new_window(window_name="logs")` without exception; execute `project_windows = session.windows.filter(window_name__startswith='project-')` without exception; execute `len(project_windows) >= 2` and expect `True`; execute `backend = session.windows.get(window_name='project-backend')` without exception; execute `backend.window_name` and expect `'project-backend'`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_16_6b541e4fe7 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[17] | execute `s_alpha = server.new_session(session_name='alpha-1')` without exception; execute `s_beta = server.new_session(session_name='beta-1')` without exception; execute `alphas = server.search_sessions(filter='#{m:alpha-*,#{session_name}}')` without exception; execute `[s.session_name for s in alphas]` and expect `['alpha-1']`; execute `exact = server.search_sessions( filter='#{==:#{session_name},alpha-1}' )` without exception; execute `[s.session_name for s in exact]` and expect `['alpha-1']`; execute `s_alpha.kill()` without exception; execute `s_beta.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_17_45f423bb54 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[18] | execute `result = server.display_message( '#{m:alpha-*,alpha-1}', get_text=True )` without exception; execute `result[0]` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_18_2084fce1f1 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[1] | execute `server.sessions.filter(session_name=session.session_name) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`; execute `server.sessions.filter(session_name__exact=session.session_name) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_1_f85fcf994b | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[2] | execute `w1 = session.new_window(window_name="api-server")` without exception; execute `w2 = session.new_window(window_name="api-worker")` without exception; execute `w3 = session.new_window(window_name="web-frontend")` without exception; execute `api_windows = session.windows.filter(window_name__contains='api')` without exception; execute `len(api_windows) >= 2` and expect `True`; execute `web_windows = session.windows.filter(window_name__startswith='web')` without exception; execute `len(web_windows) >= 1` and expect `True`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_2_bb23f841ff | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[3] | execute `window = session.windows.get(window_id=session.active_window.window_id)` without exception; execute `window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ..., Session($... ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_3_954d4fbe3a | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[4] | execute `from libtmux import exc` without exception; execute `try: session.windows.get(window_name="nonexistent") except exc.LibTmuxException as e: print(e)` and expect `No objects found: window_name='nonexistent'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_4_b7d4b8d7a5 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[5] | execute `session.windows.get(window_name="nonexistent", default=None) is None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_5_1975da3146 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[6] | execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `len(server.windows.filter(window_id=shared.window_id))` and expect `2`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_6_f8ff4a6d97 | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[7] | execute `from libtmux import exc` without exception; execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `try: server.windows.get(window_id=shared.window_id) except exc.MultipleObjectsReturned as e: print(e) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Multiple objects returned (2): window_id='@...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_7_c12a9f514f | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[8] | execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `sorted(s.session_name for s in shared.linked_sessions)` and expect `['guest', 'home']`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_8_1224a7171f | 3.2a through 3.7b | planned parity | -| docs/topics/filtering.md::docs/topics/filtering.md[9] | execute `from libtmux.window import Window` without exception; execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `Window.from_window_id(server, shared.window_id).window_id == shared.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_9_911ae33a2e | 3.2a through 3.7b | planned parity | -| docs/topics/floating_panes.md::docs/topics/floating_panes.md[0] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): floating = window.new_pane(width=20, height=5, shell="sleep 30") is_floating = floating.pane_floating_flag else: is_floating = "1"` without exception; execute `is_floating` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_0_1fde3715a8 | 3.2a through 3.7b | planned parity | -| docs/topics/floating_panes.md::docs/topics/floating_panes.md[1] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): placed = window.new_pane(width=20, height=5, x=2, y=1, shell="sleep 30") position = (placed.pane_x, placed.pane_y) else: position = ("2", "1")` without exception; execute `position` and expect `('2', '1')`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_1_e785fa5c58 | 3.2a through 3.7b | planned parity | -| docs/topics/floating_panes.md::docs/topics/floating_panes.md[2] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): held = window.new_pane(width=20, height=5, shell="sleep 30", keep=True) remain = held.cmd("show-options", "-p", "-v", "remain-on-exit").stdout else: remain = ["key"]` without exception; execute `remain` and expect `['key']`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_2_6c875da77f | 3.2a through 3.7b | planned parity | -| docs/topics/floating_panes.md::docs/topics/floating_panes.md[3] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): _ = window.new_pane(width=20, height=5, shell="sleep 30") floating = [p for p in window.panes if p.pane_floating_flag == "1"] found = len(floating) >= 1 else: found = True` without exception; execute `found` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_3_30f804e434 | 3.2a through 3.7b | planned parity | -| docs/topics/format-tokens.md::docs/topics/format-tokens.md[0] | execute `session = server.new_session()` without exception; execute `session.pane_id == session.active_window.active_pane.pane_id` and expect `True`; execute `session.window_id == session.active_window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_format_tokens_md_docs_topics_format_tokens_md_0_fcbd5bfda4 | 3.2a through 3.7b | planned parity | -| docs/topics/format-tokens.md::docs/topics/format-tokens.md[1] | execute `from libtmux.neo import get_output_format` without exception; execute `fields, _ = get_output_format("list-sessions", "3.6a")` without exception; execute `'session_id' in fields` and expect `True`; execute `'pane_id' in fields # active pane for the listed session` and expect `True`; execute `'client_name' in fields # client fields require list-clients` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_format_tokens_md_docs_topics_format_tokens_md_1_c73624c189 | 3.2a through 3.7b | planned parity | +| docs/topics/context_managers.md::docs/topics/context_managers.md[1] | execute `with Server() as server: session = server.new_session() print(server.is_alive())` and expect `True`; execute `print(server.is_alive()) # Server is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_1_a71200aff7 | 3.3a through 3.7c | planned parity | +| docs/topics/context_managers.md::docs/topics/context_managers.md[2] | execute `server = Server()` without exception; execute `with server.new_session() as session: print(session in server.sessions) window = session.new_window()` and expect `True`; execute `print(session in server.sessions) # Session is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_2_020ff19587 | 3.3a through 3.7c | planned parity | +| docs/topics/context_managers.md::docs/topics/context_managers.md[3] | execute `server = Server()` without exception; execute `session = server.new_session()` without exception; execute `with session.new_window() as window: print(window in session.windows) pane = window.split()` and expect `True`; execute `print(window in session.windows) # Window is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_3_3859334d99 | 3.3a through 3.7c | planned parity | +| docs/topics/context_managers.md::docs/topics/context_managers.md[4] | execute `server = Server()` without exception; execute `session = server.new_session()` without exception; execute `window = session.new_window()` without exception; execute `with window.split() as pane: print(pane in window.panes) pane.send_keys('echo "Hello"')` and expect `True`; execute `print(pane in window.panes) # Pane is killed after exiting context` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_4_f08e853cd5 | 3.3a through 3.7c | planned parity | +| docs/topics/context_managers.md::docs/topics/context_managers.md[5] | execute `with Server() as server: with server.new_session() as session: with session.new_window() as window: with window.split() as pane: pane.send_keys('echo "Hello"') # Do work with the pane # Everything is cleaned up automatically when exiting contexts` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_context_managers_md_docs_topics_context_managers_md_5_ea6e5b2860 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[0] | execute `server.sessions # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_0_190d6fc1c9 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[10] | execute `w1 = session.new_window(window_name="feature-login")` without exception; execute `w2 = session.new_window(window_name="feature-signup")` without exception; execute `w3 = session.new_window(window_name="bugfix-typo")` without exception; execute `session.windows.filter( window_name__startswith='feature', window_name__endswith='signup' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:feature-signup, Session($... ...))]`; execute `session.windows.filter( window_name__contains='feature' ).filter( window_name__contains='login' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:feature-login, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_10_bc8123f45b | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[11] | execute `w1 = session.new_window(window_name="MyApp-Server")` without exception; execute `w2 = session.new_window(window_name="myapp-worker")` without exception; execute `myapp_windows = session.windows.filter(window_name__icontains='MYAPP')` without exception; execute `len(myapp_windows) >= 2` and expect `True`; execute `session.windows.filter(window_name__istartswith='myapp') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:MyApp-Server, Session($... ...)), Window(@... ...:myapp-worker, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_11_5e38730037 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[12] | execute `w1 = session.new_window(window_name="app-v1-0")` without exception; execute `w2 = session.new_window(window_name="app-v2-0")` without exception; execute `w3 = session.new_window(window_name="app-beta")` without exception; execute `versioned = session.windows.filter(window_name__regex=r'v\d+-\d+$')` without exception; execute `len(versioned) >= 2` and expect `True`; execute `session.windows.filter(window_name__iregex=r'BETA') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:app-beta, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_12_111e348049 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[13] | execute `w1 = session.new_window(window_name="dev")` without exception; execute `w2 = session.new_window(window_name="staging")` without exception; execute `w3 = session.new_window(window_name="prod")` without exception; execute `target_envs = ["dev", "prod"]` without exception; execute `session.windows.filter(window_name__in=target_envs) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:dev, Session($... ...)), Window(@... ...:prod, Session($... ...))]`; execute `non_prod = session.windows.filter(window_name__nin=["prod"])` without exception; execute `any(w.window_name == "prod" for w in non_prod)` and expect `False`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_13_54dbf98c56 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[14] | execute `server.panes # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Pane(%... Window(@... ..., Session($... ...)))]`; execute `pane = session.active_pane` without exception; execute `pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_14_594195a62b | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[15] | execute `w1 = session.new_window(window_name="vim-main")` without exception; execute `w2 = session.new_window(window_name="nvim-config")` without exception; execute `w3 = session.new_window(window_name="shell")` without exception; execute `editors = session.windows.filter(window_name__iregex=r'n?vim')` without exception; execute `len(editors) >= 2` and expect `True`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_15_4558f80de6 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[16] | execute `w1 = session.new_window(window_name="project-frontend")` without exception; execute `w2 = session.new_window(window_name="project-backend")` without exception; execute `w3 = session.new_window(window_name="logs")` without exception; execute `project_windows = session.windows.filter(window_name__startswith='project-')` without exception; execute `len(project_windows) >= 2` and expect `True`; execute `backend = session.windows.get(window_name='project-backend')` without exception; execute `backend.window_name` and expect `'project-backend'`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_16_6b541e4fe7 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[17] | execute `s_alpha = server.new_session(session_name='alpha-1')` without exception; execute `s_beta = server.new_session(session_name='beta-1')` without exception; execute `alphas = server.search_sessions(filter='#{m:alpha-*,#{session_name}}')` without exception; execute `[s.session_name for s in alphas]` and expect `['alpha-1']`; execute `exact = server.search_sessions( filter='#{==:#{session_name},alpha-1}' )` without exception; execute `[s.session_name for s in exact]` and expect `['alpha-1']`; execute `s_alpha.kill()` without exception; execute `s_beta.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_17_45f423bb54 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[18] | execute `result = server.display_message( '#{m:alpha-*,alpha-1}', get_text=True )` without exception; execute `result[0]` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_18_2084fce1f1 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[1] | execute `server.sessions.filter(session_name=session.session_name) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`; execute `server.sessions.filter(session_name__exact=session.session_name) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_1_f85fcf994b | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[2] | execute `w1 = session.new_window(window_name="api-server")` without exception; execute `w2 = session.new_window(window_name="api-worker")` without exception; execute `w3 = session.new_window(window_name="web-frontend")` without exception; execute `api_windows = session.windows.filter(window_name__contains='api')` without exception; execute `len(api_windows) >= 2` and expect `True`; execute `web_windows = session.windows.filter(window_name__startswith='web')` without exception; execute `len(web_windows) >= 1` and expect `True`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_2_bb23f841ff | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[3] | execute `window = session.windows.get(window_id=session.active_window.window_id)` without exception; execute `window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ..., Session($... ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_3_954d4fbe3a | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[4] | execute `from libtmux import exc` without exception; execute `try: session.windows.get(window_name="nonexistent") except exc.LibTmuxException as e: print(e)` and expect `No objects found: window_name='nonexistent'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_4_b7d4b8d7a5 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[5] | execute `session.windows.get(window_name="nonexistent", default=None) is None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_5_1975da3146 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[6] | execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `len(server.windows.filter(window_id=shared.window_id))` and expect `2`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_6_f8ff4a6d97 | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[7] | execute `from libtmux import exc` without exception; execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `try: server.windows.get(window_id=shared.window_id) except exc.MultipleObjectsReturned as e: print(e) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Multiple objects returned (2): window_id='@...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_7_c12a9f514f | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[8] | execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `sorted(s.session_name for s in shared.linked_sessions)` and expect `['guest', 'home']`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_8_1224a7171f | 3.3a through 3.7c | planned parity | +| docs/topics/filtering.md::docs/topics/filtering.md[9] | execute `from libtmux.window import Window` without exception; execute `home = server.new_session(session_name="home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `guest = server.new_session(session_name="guest")` without exception; execute `_ = server.cmd( "link-window", "-d", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `Window.from_window_id(server, shared.window_id).window_id == shared.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_filtering_md_docs_topics_filtering_md_9_911ae33a2e | 3.3a through 3.7c | planned parity | +| docs/topics/floating_panes.md::docs/topics/floating_panes.md[0] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): floating = window.new_pane(width=20, height=5, shell="sleep 30") is_floating = floating.pane_floating_flag else: is_floating = "1"` without exception; execute `is_floating` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_0_1fde3715a8 | 3.3a through 3.7c | planned parity | +| docs/topics/floating_panes.md::docs/topics/floating_panes.md[1] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): placed = window.new_pane(width=20, height=5, x=2, y=1, shell="sleep 30") position = (placed.pane_x, placed.pane_y) else: position = ("2", "1")` without exception; execute `position` and expect `('2', '1')`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_1_e785fa5c58 | 3.3a through 3.7c | planned parity | +| docs/topics/floating_panes.md::docs/topics/floating_panes.md[2] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): held = window.new_pane(width=20, height=5, shell="sleep 30", keep=True) remain = held.cmd("show-options", "-p", "-v", "remain-on-exit").stdout else: remain = ["key"]` without exception; execute `remain` and expect `['key']`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_2_6c875da77f | 3.3a through 3.7c | planned parity | +| docs/topics/floating_panes.md::docs/topics/floating_panes.md[3] | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): _ = window.new_pane(width=20, height=5, shell="sleep 30") floating = [p for p in window.panes if p.pane_floating_flag == "1"] found = len(floating) >= 1 else: found = True` without exception; execute `found` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_floating_panes_md_docs_topics_floating_panes_md_3_30f804e434 | 3.3a through 3.7c | planned parity | +| docs/topics/format-tokens.md::docs/topics/format-tokens.md[0] | execute `session = server.new_session()` without exception; execute `session.pane_id == session.active_window.active_pane.pane_id` and expect `True`; execute `session.window_id == session.active_window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_format_tokens_md_docs_topics_format_tokens_md_0_fcbd5bfda4 | 3.3a through 3.7c | planned parity | +| docs/topics/format-tokens.md::docs/topics/format-tokens.md[1] | execute `from libtmux.neo import get_output_format` without exception; execute `fields, _ = get_output_format("list-sessions", "3.6a")` without exception; execute `'session_id' in fields` and expect `True`; execute `'pane_id' in fields # active pane for the listed session` and expect `True`; execute `'client_name' in fields # client fields require list-clients` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_format_tokens_md_docs_topics_format_tokens_md_1_c73624c189 | 3.3a through 3.7c | planned parity | | docs/topics/format-tokens.md::docs/topics/format-tokens.md[2] | execute `from libtmux.neo import get_output_format` without exception; execute `fields, _ = get_output_format("list-clients", "3.6a")` without exception; execute `all(t in fields for t in ("client_name", "session_id", "pane_id"))` and expect `True`. | documentation contract test | PythonBehaviorParityTest#docs_topics_format_tokens_md_docs_topics_format_tokens_md_2_9e413d8b28 | not tmux-bound | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[0] | execute `session.show_options() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_0_3437dd3c06 | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[1] | execute `server.show_option('buffer-limit')` and expect `50`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_1_34244a97a6 | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[2] | execute `window.set_option('automatic-rename', False) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `window.show_option('automatic-rename')` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_2_a27878544f | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[3] | execute `window.unset_option('automatic-rename') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_3_b5bbcbef53 | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[4] | execute `from libtmux.constants import OptionScope` without exception; execute `session.show_options(scope=OptionScope.Window) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_4_8ff1aac550 | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[5] | execute `server.show_option('buffer-limit', global_=True)` and expect `50`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_5_d4bcae3a85 | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[6] | execute `session.set_hook('session-renamed', 'display-message "Session renamed"') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.show_hook('session-renamed') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{0: 'display-message "Session renamed"'}`; execute `session.show_hooks() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_6_890f6b0ea5 | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[7] | execute `session.unset_hook('session-renamed') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_7_63ed9539ef | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[8] | execute `session.set_hook('after-split-window[0]', 'display-message "Split 0"') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.set_hook('after-split-window[1]', 'display-message "Split 1"') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `hooks = session.show_hook('after-split-window')` without exception; execute `sorted(hooks.keys())` and expect `[0, 1]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_8_5eb13b767c | 3.2a through 3.7b | planned parity | -| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[9] | execute `session.set_hooks('window-linked', { 0: 'display-message "Window linked 0"', 1: 'display-message "Window linked 1"', }) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('after-split-window[0]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('after-split-window[1]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('window-linked[0]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('window-linked[1]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_9_97552747a8 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[0] | execute `pane = window.split(shell='sh')` without exception; execute `pane.send_keys('echo "Hello from libtmux"')` without exception; execute `import time; time.sleep(0.1) # Allow command to execute` without exception; execute `output = pane.capture_pane()` without exception; execute `'Hello from libtmux' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_0_645b933013 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[10] | execute `import time` without exception; execute `pane.send_keys('printf "text \\n"') # 3 trailing spaces` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane(preserve_trailing=True)` without exception; execute `isinstance(output, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_10_1ebe21a3c5 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[11] | execute `pending = pane.capture_pane(pending=True)` without exception; execute `isinstance(pending, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_11_82b8149fda | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[12] | execute `import time` without exception; execute `pane.send_keys('sleep 0.2; echo "TASK_COMPLETE"')` without exception; execute `for _ in range(30): output = pane.capture_pane() if 'TASK_COMPLETE' in '\\n'.join(output): break time.sleep(0.1)` without exception; execute `'TASK_COMPLETE' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_12_77f2508eca | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[13] | execute `import time` without exception; execute `def wait_for_text(pane, text, timeout=5.0): """Wait for text to appear in pane output.""" start = time.time() while time.time() - start < timeout: output = pane.capture_pane() if text in '\\n'.join(output): return True time.sleep(0.1) return False` without exception; execute `pane.send_keys('echo "READY"')` without exception; execute `wait_for_text(pane, 'READY', timeout=2.0)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_13_2289744fea | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[14] | execute `width = pane.display_message('#{pane_width}', get_text=True)` without exception; execute `isinstance(width, list) and len(width) > 0` and expect `True`; execute `height = pane.display_message('#{pane_height}', get_text=True)` without exception; execute `isinstance(height, list) and len(height) > 0` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_14_8f6850cdbd | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[15] | execute `cwd = pane.display_message('#{pane_current_path}', get_text=True)` without exception; execute `isinstance(cwd, list)` and expect `True`; execute `pane_id = pane.display_message('#{pane_id}', get_text=True)` without exception; execute `pane_id[0].startswith('%')` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_15_37a93f3fac | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[16] | execute `pane.resize(height=20, width=80) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_16_9f44e98c9c | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[17] | execute `from libtmux.constants import ResizeAdjustmentDirection` without exception; execute `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Up, adjustment=5) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Left, adjustment=10) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_17_1edb425a8f | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[18] | execute `pane.resize(zoom=True) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `pane.resize(zoom=True) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_18_3cdae56da5 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[19] | execute `pane.clear() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_19_0f33de1765 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[1] | execute `pane.send_keys('echo "waiting"', enter=False)` without exception; execute `output = pane.capture_pane()` without exception; execute `'waiting' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_1_b6a6b530c5 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[20] | execute `temp_pane = pane.split()` without exception; execute `temp_pane in window.panes` and expect `True`; execute `temp_pane.kill()` without exception; execute `temp_pane not in window.panes` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_20_3cadeac64e | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[21] | execute `pane.window.resize(height=60, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `keep_pane = pane.split()` without exception; execute `extra1 = pane.split()` without exception; execute `extra2 = pane.split()` without exception; execute `keep_pane.kill(all_except=True)` without exception; execute `keep_pane in window.panes` and expect `True`; execute `extra1 not in window.panes` and expect `True`; execute `extra2 not in window.panes` and expect `True`; execute `keep_pane.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_21_8fbc703e15 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[22] | execute `import time` without exception; execute `def run_and_capture(pane, command, marker='__DONE__', timeout=5.0): """Run a command and return its output.""" pane.send_keys(f'{command}; echo {marker}') start = time.time() while time.time() - start < timeout: output = pane.capture_pane() output_str = '\\n'.join(output) if marker in output_str: return output # Return all captured output time.sleep(0.1) raise TimeoutError(f'Command did not complete within {timeout}s')` without exception; execute `result = run_and_capture(pane, 'echo "captured text"', timeout=2.0)` without exception; execute `'captured text' in '\\n'.join(result)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_22_bb780f31fc | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[23] | execute `import time` without exception; execute `def check_for_errors(pane, error_patterns=None): """Check pane output for error patterns.""" if error_patterns is None: error_patterns = ['error:', 'Error:', 'ERROR', 'failed', 'FAILED'] output = '\\n'.join(pane.capture_pane()) for pattern in error_patterns: if pattern in output: return True return False` without exception; execute `pane.send_keys('echo "All good"')` without exception; execute `time.sleep(0.1)` without exception; execute `check_for_errors(pane)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_23_edda539e05 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[2] | execute `import time` without exception; execute `pane.send_keys('echo "execute me"', enter=False)` without exception; execute `pane.enter() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `time.sleep(0.2)` without exception; execute `output = pane.capture_pane()` without exception; execute `'execute me' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_2_6bca8e9329 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[3] | execute `import time` without exception; execute `pane.send_keys('echo "Tab:\\tNewline:\\n"', literal=True)` without exception; execute `time.sleep(0.1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_3_36d5ab640c | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[4] | execute `import time` without exception; execute `pane.send_keys('echo "secret command"', suppress_history=True)` without exception; execute `time.sleep(0.1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_4_a8a73dc1c7 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[5] | execute `pane.send_keys(cmd=None, repeat=5)` without exception; execute `pane.send_keys(cmd=None, reset=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_5_70e41a9e8b | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[6] | execute `import time` without exception; execute `pane.send_keys('echo "Line 1"; echo "Line 2"; echo "Line 3"')` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane()` without exception; execute `isinstance(output, list)` and expect `True`; execute `any('Line 2' in line for line in output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_6_188ee9f8fd | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[7] | execute `recent = pane.capture_pane(start=-5, end='-')` without exception; execute `isinstance(recent, list)` and expect `True`; execute `full_history = pane.capture_pane(start='-', end='-')` without exception; execute `len(full_history) >= 0` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_7_6cbf3a638c | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[8] | execute `import time` without exception; execute `pane.send_keys('printf "\\033[31mRED\\033[0m \\033[32mGREEN\\033[0m"')` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane()` without exception; execute `'RED' in '\\n'.join(output)` and expect `True`; execute `colored_output = pane.capture_pane(escape_sequences=True)` without exception; execute `isinstance(colored_output, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_8_7f49ead560 | 3.2a through 3.7b | planned parity | -| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[9] | execute `import time` without exception; execute `pane.send_keys('echo "' + 'x' * 200 + '"')` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane(join_wrapped=True)` without exception; execute `isinstance(output, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_9_0a43f7ce1c | 3.2a through 3.7b | planned parity | -| docs/topics/self_location.md::docs/topics/self_location.md[0] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")` without exception; execute `monkeypatch.setenv("TMUX_PANE", pane.pane_id)` without exception; execute `Pane.from_env().pane_id == pane.pane_id` and expect `True`; execute `Window.from_env().window_id == window.window_id` and expect `True`; execute `Session.from_env().session_id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_0_ff37fbb90b | 3.2a through 3.7b | planned parity | -| docs/topics/self_location.md::docs/topics/self_location.md[1] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", "TMUX_PANE": pane.pane_id, }` without exception; execute `Session.from_env(env).session_id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_1_ac5fbdbdb2 | 3.2a through 3.7b | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[0] | execute `session.show_options() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_0_3437dd3c06 | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[1] | execute `server.show_option('buffer-limit')` and expect `50`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_1_34244a97a6 | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[2] | execute `window.set_option('automatic-rename', False) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `window.show_option('automatic-rename')` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_2_a27878544f | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[3] | execute `window.unset_option('automatic-rename') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_3_b5bbcbef53 | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[4] | execute `from libtmux.constants import OptionScope` without exception; execute `session.show_options(scope=OptionScope.Window) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_4_8ff1aac550 | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[5] | execute `server.show_option('buffer-limit', global_=True)` and expect `50`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_5_d4bcae3a85 | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[6] | execute `session.set_hook('session-renamed', 'display-message "Session renamed"') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.show_hook('session-renamed') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{0: 'display-message "Session renamed"'}`; execute `session.show_hooks() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_6_890f6b0ea5 | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[7] | execute `session.unset_hook('session-renamed') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_7_63ed9539ef | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[8] | execute `session.set_hook('after-split-window[0]', 'display-message "Split 0"') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.set_hook('after-split-window[1]', 'display-message "Split 1"') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `hooks = session.show_hook('after-split-window')` without exception; execute `sorted(hooks.keys())` and expect `[0, 1]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_8_5eb13b767c | 3.3a through 3.7c | planned parity | +| docs/topics/options_and_hooks.md::docs/topics/options_and_hooks.md[9] | execute `session.set_hooks('window-linked', { 0: 'display-message "Window linked 0"', 1: 'display-message "Window linked 1"', }) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('after-split-window[0]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('after-split-window[1]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('window-linked[0]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`; execute `session.unset_hook('window-linked[1]') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_options_and_hooks_md_docs_topics_options_and_hooks_md_9_97552747a8 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[0] | execute `pane = window.split(shell='sh')` without exception; execute `pane.send_keys('echo "Hello from libtmux"')` without exception; execute `import time; time.sleep(0.1) # Allow command to execute` without exception; execute `output = pane.capture_pane()` without exception; execute `'Hello from libtmux' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_0_645b933013 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[10] | execute `import time` without exception; execute `pane.send_keys('printf "text \\n"') # 3 trailing spaces` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane(preserve_trailing=True)` without exception; execute `isinstance(output, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_10_1ebe21a3c5 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[11] | execute `pending = pane.capture_pane(pending=True)` without exception; execute `isinstance(pending, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_11_82b8149fda | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[12] | execute `import time` without exception; execute `pane.send_keys('sleep 0.2; echo "TASK_COMPLETE"')` without exception; execute `for _ in range(30): output = pane.capture_pane() if 'TASK_COMPLETE' in '\\n'.join(output): break time.sleep(0.1)` without exception; execute `'TASK_COMPLETE' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_12_77f2508eca | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[13] | execute `import time` without exception; execute `def wait_for_text(pane, text, timeout=5.0): """Wait for text to appear in pane output.""" start = time.time() while time.time() - start < timeout: output = pane.capture_pane() if text in '\\n'.join(output): return True time.sleep(0.1) return False` without exception; execute `pane.send_keys('echo "READY"')` without exception; execute `wait_for_text(pane, 'READY', timeout=2.0)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_13_2289744fea | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[14] | execute `width = pane.display_message('#{pane_width}', get_text=True)` without exception; execute `isinstance(width, list) and len(width) > 0` and expect `True`; execute `height = pane.display_message('#{pane_height}', get_text=True)` without exception; execute `isinstance(height, list) and len(height) > 0` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_14_8f6850cdbd | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[15] | execute `cwd = pane.display_message('#{pane_current_path}', get_text=True)` without exception; execute `isinstance(cwd, list)` and expect `True`; execute `pane_id = pane.display_message('#{pane_id}', get_text=True)` without exception; execute `pane_id[0].startswith('%')` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_15_37a93f3fac | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[16] | execute `pane.resize(height=20, width=80) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_16_9f44e98c9c | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[17] | execute `from libtmux.constants import ResizeAdjustmentDirection` without exception; execute `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Up, adjustment=5) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Left, adjustment=10) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_17_1edb425a8f | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[18] | execute `pane.resize(zoom=True) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `pane.resize(zoom=True) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_18_3cdae56da5 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[19] | execute `pane.clear() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_19_0f33de1765 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[1] | execute `pane.send_keys('echo "waiting"', enter=False)` without exception; execute `output = pane.capture_pane()` without exception; execute `'waiting' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_1_b6a6b530c5 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[20] | execute `temp_pane = pane.split()` without exception; execute `temp_pane in window.panes` and expect `True`; execute `temp_pane.kill()` without exception; execute `temp_pane not in window.panes` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_20_3cadeac64e | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[21] | execute `pane.window.resize(height=60, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `keep_pane = pane.split()` without exception; execute `extra1 = pane.split()` without exception; execute `extra2 = pane.split()` without exception; execute `keep_pane.kill(all_except=True)` without exception; execute `keep_pane in window.panes` and expect `True`; execute `extra1 not in window.panes` and expect `True`; execute `extra2 not in window.panes` and expect `True`; execute `keep_pane.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_21_8fbc703e15 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[22] | execute `import time` without exception; execute `def run_and_capture(pane, command, marker='__DONE__', timeout=5.0): """Run a command and return its output.""" pane.send_keys(f'{command}; echo {marker}') start = time.time() while time.time() - start < timeout: output = pane.capture_pane() output_str = '\\n'.join(output) if marker in output_str: return output # Return all captured output time.sleep(0.1) raise TimeoutError(f'Command did not complete within {timeout}s')` without exception; execute `result = run_and_capture(pane, 'echo "captured text"', timeout=2.0)` without exception; execute `'captured text' in '\\n'.join(result)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_22_bb780f31fc | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[23] | execute `import time` without exception; execute `def check_for_errors(pane, error_patterns=None): """Check pane output for error patterns.""" if error_patterns is None: error_patterns = ['error:', 'Error:', 'ERROR', 'failed', 'FAILED'] output = '\\n'.join(pane.capture_pane()) for pattern in error_patterns: if pattern in output: return True return False` without exception; execute `pane.send_keys('echo "All good"')` without exception; execute `time.sleep(0.1)` without exception; execute `check_for_errors(pane)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_23_edda539e05 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[2] | execute `import time` without exception; execute `pane.send_keys('echo "execute me"', enter=False)` without exception; execute `pane.enter() # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `time.sleep(0.2)` without exception; execute `output = pane.capture_pane()` without exception; execute `'execute me' in '\\n'.join(output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_2_6bca8e9329 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[3] | execute `import time` without exception; execute `pane.send_keys('echo "Tab:\\tNewline:\\n"', literal=True)` without exception; execute `time.sleep(0.1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_3_36d5ab640c | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[4] | execute `import time` without exception; execute `pane.send_keys('echo "secret command"', suppress_history=True)` without exception; execute `time.sleep(0.1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_4_a8a73dc1c7 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[5] | execute `pane.send_keys(cmd=None, repeat=5)` without exception; execute `pane.send_keys(cmd=None, reset=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_5_70e41a9e8b | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[6] | execute `import time` without exception; execute `pane.send_keys('echo "Line 1"; echo "Line 2"; echo "Line 3"')` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane()` without exception; execute `isinstance(output, list)` and expect `True`; execute `any('Line 2' in line for line in output)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_6_188ee9f8fd | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[7] | execute `recent = pane.capture_pane(start=-5, end='-')` without exception; execute `isinstance(recent, list)` and expect `True`; execute `full_history = pane.capture_pane(start='-', end='-')` without exception; execute `len(full_history) >= 0` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_7_6cbf3a638c | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[8] | execute `import time` without exception; execute `pane.send_keys('printf "\\033[31mRED\\033[0m \\033[32mGREEN\\033[0m"')` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane()` without exception; execute `'RED' in '\\n'.join(output)` and expect `True`; execute `colored_output = pane.capture_pane(escape_sequences=True)` without exception; execute `isinstance(colored_output, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_8_7f49ead560 | 3.3a through 3.7c | planned parity | +| docs/topics/pane_interaction.md::docs/topics/pane_interaction.md[9] | execute `import time` without exception; execute `pane.send_keys('echo "' + 'x' * 200 + '"')` without exception; execute `time.sleep(0.1)` without exception; execute `output = pane.capture_pane(join_wrapped=True)` without exception; execute `isinstance(output, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_pane_interaction_md_docs_topics_pane_interaction_md_9_0a43f7ce1c | 3.3a through 3.7c | planned parity | +| docs/topics/self_location.md::docs/topics/self_location.md[0] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")` without exception; execute `monkeypatch.setenv("TMUX_PANE", pane.pane_id)` without exception; execute `Pane.from_env().pane_id == pane.pane_id` and expect `True`; execute `Window.from_env().window_id == window.window_id` and expect `True`; execute `Session.from_env().session_id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_0_ff37fbb90b | 3.3a through 3.7c | planned parity | +| docs/topics/self_location.md::docs/topics/self_location.md[1] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", "TMUX_PANE": pane.pane_id, }` without exception; execute `Session.from_env(env).session_id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_1_ac5fbdbdb2 | 3.3a through 3.7c | planned parity | | docs/topics/self_location.md::docs/topics/self_location.md[2] | execute `from libtmux import exc` without exception; execute `try: here = Pane.from_env({}) except exc.NotInsideTmux: here = None` without exception; execute `here is None` and expect `True`. | documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_2_1461e160ac | not tmux-bound | planned parity | -| docs/topics/self_location.md::docs/topics/self_location.md[3] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `worker_window = session.new_window(window_name="worker", attach=False)` without exception; execute `worker = worker_window.active_pane` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", "TMUX_PANE": worker.pane_id, }` without exception; execute `session.active_window.window_id == worker_window.window_id` and expect `False`; execute `Window.from_env(env).window_id == worker_window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_3_555e724540 | 3.2a through 3.7b | planned parity | -| docs/topics/self_location.md::docs/topics/self_location.md[4] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `worker_window = session.new_window(window_name="worker", attach=False)` without exception; execute `worker = worker_window.active_pane` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", # names *this* session "TMUX_PANE": worker.pane_id, }` without exception; execute `elsewhere = server.new_session(session_name="elsewhere")` without exception; execute `_ = worker_window.move_window(session=elsewhere.session_id, no_select=True)` without exception; execute `Session.from_env(env).session_name # where the pane is, not where TMUX says` and expect `'elsewhere'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_4_91a24b14b7 | 3.2a through 3.7b | planned parity | -| docs/topics/self_location.md::docs/topics/self_location.md[5] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `home = server.new_session(session_name="aaa-home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `worker = shared.active_pane` without exception; execute `env = { "TMUX": f"{socket_path},1,{home.session_id}", "TMUX_PANE": worker.pane_id, }` without exception; execute `guest = server.new_session(session_name="zzz-guest")` without exception; execute `_ = server.cmd( "link-window", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `holders = {p.session_name for p in server.panes.filter(pane_id=worker.pane_id)}` without exception; execute `holders == {"aaa-home", "zzz-guest"}` and expect `True`; execute `tmux_says = server.cmd( "display-message", "-p", "-t", worker.pane_id, "#{session_name}" ).stdout[0]` without exception; execute `Session.from_env(env).session_name == tmux_says` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_5_4b9b55ee65 | 3.2a through 3.7b | planned parity | -| docs/topics/self_location.md::docs/topics/self_location.md[6] | execute `def announce(env=None): """Report the session this code is running in.""" return Session.from_env(env).session_name` without exception; execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", "TMUX_PANE": pane.pane_id, }` without exception; execute `announce(env) == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_6_5e1cc1853a | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[0] | execute `session = server.new_session() # Create a test session using existing server` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_0_f88b231524 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[10] | execute `window.panes # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Pane(%... Window(@... ..., Session($... ...)))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_10_4ae2d40efe | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[11] | execute `window.active_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_11_8613cf476e | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[12] | execute `pane = window.panes[0]` without exception; execute `pane.window.window_id == window.window_id` and expect `True`; execute `pane.session.session_id == session.session_id` and expect `True`; execute `pane.server is server` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_12_11d6f8ab2f | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[13] | execute `session.windows.filter(window_index=window.window_index) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ..., Session($... ...))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_13_19094dff60 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[14] | execute `window.panes.get(pane_id=pane.pane_id) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_14_9454947fbb | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[15] | execute `w1 = session.new_window(window_name="app-frontend")` without exception; execute `w2 = session.new_window(window_name="app-backend")` without exception; execute `w3 = session.new_window(window_name="logs")` without exception; execute `session.windows.filter(window_name__startswith='app-') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:app-frontend, Session($... ...)), Window(@... ...:app-backend, Session($... ...))]`; execute `session.windows.filter(window_name__contains='end') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:app-frontend, Session($... ...)), Window(@... ...:app-backend, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_15_ffb8f60600 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[16] | execute `w1 = session.new_window(window_name="MyApp")` without exception; execute `w2 = session.new_window(window_name="myapp-worker")` without exception; execute `session.windows.filter(window_name__istartswith='myapp') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:MyApp, Session($... ...)), Window(@... ...:myapp-worker, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_16_ce5ff58725 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[17] | execute `w1 = session.new_window(window_name="release-v1-0")` without exception; execute `w2 = session.new_window(window_name="release-v2-0")` without exception; execute `w3 = session.new_window(window_name="dev")` without exception; execute `session.windows.filter(window_name__regex=r'v\d+-\d+') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:release-v1-0, Session($... ...)), Window(@... ...:release-v2-0, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_17_7ea3dd13ef | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[18] | execute `w1 = session.new_window(window_name="api-prod")` without exception; execute `w2 = session.new_window(window_name="api-staging")` without exception; execute `w3 = session.new_window(window_name="web-prod")` without exception; execute `session.windows.filter( window_name__startswith='api', window_name__endswith='prod' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:api-prod, Session($... ...))]`; execute `session.windows.filter( window_name__contains='api' ).filter( window_name__contains='staging' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:api-staging, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_18_70edda95b8 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[19] | execute `session.windows.get(window_name="nonexistent", default=None) is None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_19_a3c307694b | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[1] | execute `server # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Server(socket_name=...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_1_c1d67d1446 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[20] | execute `window in session.windows` and expect `True`; execute `pane in window.panes` and expect `True`; execute `session in server.sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_20_953a737631 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[21] | execute `window.window_id == session.active_window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_21_7043421454 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[22] | execute `pane.pane_id == window.active_pane.pane_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_22_1a26987032 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[2] | execute `server.sessions # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_2_15eb87751f | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[3] | execute `server.windows # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ..., Session($... ...))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_3_e82a9da6b0 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[4] | execute `server.panes # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Pane(%... Window(@... ..., Session($... ...)))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_4_30f754f9a2 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[5] | execute `session = server.sessions[0]` without exception; execute `session # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session($... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_5_7fb785fe9d | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[6] | execute `session.windows # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ..., Session($... ...))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_6_a18e5f213c | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[7] | execute `session.active_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ..., Session($... ...))`; execute `session.active_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_7_42d2312d36 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[8] | execute `window = session.windows[0]` without exception; execute `window.window_index # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `'...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_8_529cc07680 | 3.2a through 3.7b | planned parity | -| docs/topics/traversal.md::docs/topics/traversal.md[9] | execute `window.session # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session($... ...)`; execute `window.session.session_id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_9_dfba55476c | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[0] | execute `new_window = session.new_window(window_name='workspace')` without exception; execute `new_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:workspace, Session($... ...))`; execute `new_window in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_0_f498347395 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[10] | execute `from libtmux.constants import PaneDirection` without exception; execute `def create_pane_grid(session, rows=2, cols=2, name='grid'): """Create an NxM grid of panes.""" window = session.new_window(window_name=name, attach=False) window.resize(height=50, width=160) panes = [] base_pane = window.active_pane panes.append(base_pane) # Create first row of panes current = base_pane for _ in range(cols - 1): new_pane = current.split(direction=PaneDirection.Right) panes.append(new_pane) current = new_pane # Create additional rows for _ in range(rows - 1): row_start = panes[-cols] current = row_start for col in range(cols): new_pane = panes[-cols + col].split(direction=PaneDirection.Below) panes.append(new_pane) # Apply tiled layout for even distribution window.select_layout('tiled') return window, panes` without exception; execute `grid_window, grid_panes = create_pane_grid(session, rows=2, cols=2, name='test-grid')` without exception; execute `len(grid_panes) >= 4` and expect `True`; execute `grid_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_10_ccd6daaff8 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[11] | execute `import time` without exception; execute `def run_in_panes(panes, commands): """Run different commands in each pane.""" for pane, cmd in zip(panes, commands): pane.send_keys(cmd)` without exception; execute `multi_window = session.new_window(window_name='multi-cmd', attach=False)` without exception; execute `multi_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `pane_a = multi_window.active_pane` without exception; execute `pane_b = multi_window.split()` without exception; execute `pane_c = multi_window.split()` without exception; execute `run_in_panes( [pane_a, pane_b, pane_c], ['echo "Task A"', 'echo "Task B"', 'echo "Task C"'], )` without exception; execute `time.sleep(0.2)` without exception; execute `'Task A' in '\\n'.join(pane_a.capture_pane())` and expect `True`; execute `multi_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_11_55bf63f4b7 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[12] | execute `with session.new_window(window_name='temp-window') as temp_win: pane = temp_win.active_pane pane.send_keys('echo "temporary workspace"') temp_win in session.windows` and expect `True`; execute `temp_win not in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_12_4ce17f6320 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[1] | execute `background_window = session.new_window( window_name='background-task', attach=False, )` without exception; execute `background_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:background-task, Session($... ...))`; execute `background_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_1_39bd95e35f | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[2] | execute `shell_window = session.new_window( window_name='shell-test', attach=False, window_shell='sh -c "echo Hello; exec sh"', )` without exception; execute `shell_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:shell-test, Session($... ...))`; execute `shell_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_2_2571ab047c | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[3] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `v_split_window = session.new_window(window_name='v-split-demo', attach=False)` without exception; execute `v_split_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `top_pane = v_split_window.active_pane` without exception; execute `bottom_pane = v_split_window.split()` without exception; execute `bottom_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `len(v_split_window.panes)` and expect `2`; execute `v_split_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_3_36d833b5c9 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[4] | execute `from libtmux.constants import PaneDirection` without exception; execute `h_split_window = session.new_window(window_name='h-split', attach=False)` without exception; execute `h_split_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `left_pane = h_split_window.active_pane` without exception; execute `right_pane = left_pane.split(direction=PaneDirection.Right)` without exception; execute `right_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `len(h_split_window.panes)` and expect `2`; execute `h_split_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_4_30a3c7caed | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[5] | execute `size_window = session.new_window(window_name='size-demo', attach=False)` without exception; execute `size_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `main_pane = size_window.active_pane` without exception; execute `small_pane = main_pane.split(size='20%')` without exception; execute `small_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `size_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_5_ce8c840d88 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[6] | execute `layout_window = session.new_window(window_name='layout-demo', attach=False)` without exception; execute `layout_window.resize(height=60, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `pane1 = layout_window.active_pane` without exception; execute `pane2 = layout_window.split()` without exception; execute `pane3 = layout_window.split()` without exception; execute `pane4 = layout_window.split()` without exception; execute `layout_window.select_layout('tiled') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `layout_window.select_layout('even-horizontal') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `layout_window.select_layout('main-vertical') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `layout_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_6_23162871d8 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[7] | execute `rename_window = session.new_window(window_name='old-name', attach=False)` without exception; execute `rename_window.rename_window('new-name') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:new-name, Session($... ...))`; execute `rename_window.window_name` and expect `'new-name'`; execute `rename_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_7_103a4558c1 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[8] | execute `demo_window = session.new_window(window_name='props-demo', attach=False)` without exception; execute `demo_window.window_index # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `'...'`; execute `demo_window.window_id # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `'@...'`; execute `demo_window.session # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session($... ...)`; execute `demo_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_8_2358ddc219 | 3.2a through 3.7b | planned parity | -| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[9] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `def create_dev_workspace(session, name='dev'): """Create a typical development workspace layout.""" window = session.new_window(window_name=name, attach=False) window.resize(height=50, width=160) # Main editing pane (large, left side) main_pane = window.active_pane # Terminal pane (bottom) terminal_pane = main_pane.split(size='30%') # Logs pane (right side of terminal) log_pane = terminal_pane.split(direction=PaneDirection.Right) return { 'window': window, 'main': main_pane, 'terminal': terminal_pane, 'logs': log_pane, }` without exception; execute `workspace = create_dev_workspace(session, 'my-project')` without exception; execute `len(workspace['window'].panes)` and expect `3`; execute `workspace['window'].kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_9_21f429a720 | 3.2a through 3.7b | planned parity | -| source-decision::attached-session-count | Given session rows whose session_attached values are '0', '1', and '2', Server.attachedSessions() returns only the '1' and '2' sessions by testing the attached count greater than zero; it never dispatches the Python noeq lookup. | deliberate correction contract | PythonBehaviorParityTest#source_decision_attached_session_count_d43f2e80e4 | 3.2a through 3.7b | deliberate Java correction | -| source-decision::list-accessor-leniency | Live listings, finders and snapshot capture throw when tmux is absent or capture fails. Successful empty captures retain server identity. Raw commands keep nonzero exit status as data. | deliberate correction contract | ServerTest#liveReadsRejectAnAbsentDaemon | 3.2a through 3.7b | deliberate Java correction | -| src/libtmux/_internal/constants.py::libtmux._internal.constants.Hooks | execute `from libtmux._internal.constants import Hooks` without exception; execute `raw = [ "session-renamed[0] set-option -g status-left-style bg=red", "session-renamed[1] display-message 'session renamed'", ]` without exception; execute `hooks = Hooks.from_stdout(raw)` without exception; execute `hooks.session_renamed[0]` and expect `'set-option -g status-left-style bg=red'`; execute `hooks.session_renamed[1]` and expect `"display-message 'session renamed'"`; execute `hooks.session_renamed.as_list()` and expect `['set-option -g status-left-style bg=red', "display-message 'session renamed'"]`; execute `raw_sparse = [ "pane-focus-in[0] refresh-client", "pane-focus-in[5] display-message 'focus'", ]` without exception; execute `hooks_sparse = Hooks.from_stdout(raw_sparse)` without exception; execute `0 in hooks_sparse.pane_focus_in` and expect `True`; execute `5 in hooks_sparse.pane_focus_in` and expect `True`; execute `3 in hooks_sparse.pane_focus_in` and expect `False`; execute `sorted(hooks_sparse.pane_focus_in.keys())` and expect `[0, 5]`; execute `for cmd in hooks_sparse.pane_focus_in.iter_values(): print(cmd)` and expect `refresh-client display-message 'focus'`; execute `raw_multi = [ "after-new-window[0] select-pane -t 0", "after-new-window[1] send-keys 'clear' Enter", "window-renamed[0] refresh-client -S", ]` without exception; execute `hooks_multi = Hooks.from_stdout(raw_multi)` without exception; execute `len(hooks_multi.after_new_window)` and expect `2`; execute `len(hooks_multi.window_renamed)` and expect `1`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_internal_constants_py_libtmux_internal_constants_Hooks_7039df8600 | 3.2a through 3.7b | planned parity | -| src/libtmux/_internal/constants.py::libtmux._internal.constants.Hooks.from_stdout | execute `from libtmux._internal.constants import Hooks` without exception; execute `raw = ["session-renamed[0] display-message 'renamed'"]` without exception; execute `hooks = Hooks.from_stdout(raw)` without exception; execute `hooks.session_renamed[0]` and expect `"display-message 'renamed'"`; execute `raw = [ "after-select-window[0] refresh-client", "after-select-window[10] display-message 'selected'", ]` without exception; execute `hooks = Hooks.from_stdout(raw)` without exception; execute `sorted(hooks.after_select_window.keys())` and expect `[0, 10]`; execute `hooks_empty = Hooks.from_stdout([])` without exception; execute `len(hooks_empty.session_renamed)` and expect `0`; execute `hooks_empty.session_renamed.as_list()` and expect `[]`. | real-tmux documentation contract test | PythonBehaviorParityTest#btmux_internal_constants_py_libtmux_internal_constants_Hooks_from_stdout_8dc0c3b379 | 3.2a through 3.7b | planned parity | -| src/libtmux/_internal/control_mode.py::libtmux._internal.control_mode.ControlMode | execute `with ControlMode(server=server, session=session) as ctl: clients = server.list_clients() assert len(clients) > 0 assert ctl.client_name != ''` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#btmux_internal_control_mode_py_libtmux_internal_control_mode_ControlMode_d7e85867d7 | 3.2a through 3.7b | planned parity | +| docs/topics/self_location.md::docs/topics/self_location.md[3] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `worker_window = session.new_window(window_name="worker", attach=False)` without exception; execute `worker = worker_window.active_pane` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", "TMUX_PANE": worker.pane_id, }` without exception; execute `session.active_window.window_id == worker_window.window_id` and expect `False`; execute `Window.from_env(env).window_id == worker_window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_3_555e724540 | 3.3a through 3.7c | planned parity | +| docs/topics/self_location.md::docs/topics/self_location.md[4] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `worker_window = session.new_window(window_name="worker", attach=False)` without exception; execute `worker = worker_window.active_pane` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", # names *this* session "TMUX_PANE": worker.pane_id, }` without exception; execute `elsewhere = server.new_session(session_name="elsewhere")` without exception; execute `_ = worker_window.move_window(session=elsewhere.session_id, no_select=True)` without exception; execute `Session.from_env(env).session_name # where the pane is, not where TMUX says` and expect `'elsewhere'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_4_91a24b14b7 | 3.3a through 3.7c | planned parity | +| docs/topics/self_location.md::docs/topics/self_location.md[5] | execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `home = server.new_session(session_name="aaa-home")` without exception; execute `shared = home.new_window(window_name="shared", attach=False)` without exception; execute `worker = shared.active_pane` without exception; execute `env = { "TMUX": f"{socket_path},1,{home.session_id}", "TMUX_PANE": worker.pane_id, }` without exception; execute `guest = server.new_session(session_name="zzz-guest")` without exception; execute `_ = server.cmd( "link-window", "-s", shared.window_id, "-t", f"{guest.session_id}:" )` without exception; execute `holders = {p.session_name for p in server.panes.filter(pane_id=worker.pane_id)}` without exception; execute `holders == {"aaa-home", "zzz-guest"}` and expect `True`; execute `tmux_says = server.cmd( "display-message", "-p", "-t", worker.pane_id, "#{session_name}" ).stdout[0]` without exception; execute `Session.from_env(env).session_name == tmux_says` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_5_4b9b55ee65 | 3.3a through 3.7c | planned parity | +| docs/topics/self_location.md::docs/topics/self_location.md[6] | execute `def announce(env=None): """Report the session this code is running in.""" return Session.from_env(env).session_name` without exception; execute `socket_path = server.cmd( "display-message", "-p", "-t", session.session_id, "#{socket_path}" ).stdout[0]` without exception; execute `env = { "TMUX": f"{socket_path},1,{session.session_id}", "TMUX_PANE": pane.pane_id, }` without exception; execute `announce(env) == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_self_location_md_docs_topics_self_location_md_6_5e1cc1853a | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[0] | execute `session = server.new_session() # Create a test session using existing server` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_0_f88b231524 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[10] | execute `window.panes # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Pane(%... Window(@... ..., Session($... ...)))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_10_4ae2d40efe | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[11] | execute `window.active_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_11_8613cf476e | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[12] | execute `pane = window.panes[0]` without exception; execute `pane.window.window_id == window.window_id` and expect `True`; execute `pane.session.session_id == session.session_id` and expect `True`; execute `pane.server is server` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_12_11d6f8ab2f | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[13] | execute `session.windows.filter(window_index=window.window_index) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ..., Session($... ...))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_13_19094dff60 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[14] | execute `window.panes.get(pane_id=pane.pane_id) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_14_9454947fbb | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[15] | execute `w1 = session.new_window(window_name="app-frontend")` without exception; execute `w2 = session.new_window(window_name="app-backend")` without exception; execute `w3 = session.new_window(window_name="logs")` without exception; execute `session.windows.filter(window_name__startswith='app-') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:app-frontend, Session($... ...)), Window(@... ...:app-backend, Session($... ...))]`; execute `session.windows.filter(window_name__contains='end') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:app-frontend, Session($... ...)), Window(@... ...:app-backend, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_15_ffb8f60600 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[16] | execute `w1 = session.new_window(window_name="MyApp")` without exception; execute `w2 = session.new_window(window_name="myapp-worker")` without exception; execute `session.windows.filter(window_name__istartswith='myapp') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:MyApp, Session($... ...)), Window(@... ...:myapp-worker, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_16_ce5ff58725 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[17] | execute `w1 = session.new_window(window_name="release-v1-0")` without exception; execute `w2 = session.new_window(window_name="release-v2-0")` without exception; execute `w3 = session.new_window(window_name="dev")` without exception; execute `session.windows.filter(window_name__regex=r'v\d+-\d+') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:release-v1-0, Session($... ...)), Window(@... ...:release-v2-0, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_17_7ea3dd13ef | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[18] | execute `w1 = session.new_window(window_name="api-prod")` without exception; execute `w2 = session.new_window(window_name="api-staging")` without exception; execute `w3 = session.new_window(window_name="web-prod")` without exception; execute `session.windows.filter( window_name__startswith='api', window_name__endswith='prod' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:api-prod, Session($... ...))]`; execute `session.windows.filter( window_name__contains='api' ).filter( window_name__contains='staging' ) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ...:api-staging, Session($... ...))]`; execute `w1.kill()` without exception; execute `w2.kill()` without exception; execute `w3.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_18_70edda95b8 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[19] | execute `session.windows.get(window_name="nonexistent", default=None) is None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_19_a3c307694b | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[1] | execute `server # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Server(socket_name=...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_1_c1d67d1446 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[20] | execute `window in session.windows` and expect `True`; execute `pane in window.panes` and expect `True`; execute `session in server.sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_20_953a737631 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[21] | execute `window.window_id == session.active_window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_21_7043421454 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[22] | execute `pane.pane_id == window.active_pane.pane_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_22_1a26987032 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[2] | execute `server.sessions # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Session($... ...)]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_2_15eb87751f | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[3] | execute `server.windows # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ..., Session($... ...))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_3_e82a9da6b0 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[4] | execute `server.panes # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Pane(%... Window(@... ..., Session($... ...)))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_4_30f754f9a2 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[5] | execute `session = server.sessions[0]` without exception; execute `session # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session($... ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_5_7fb785fe9d | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[6] | execute `session.windows # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `[Window(@... ..., Session($... ...))]`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_6_a18e5f213c | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[7] | execute `session.active_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ..., Session($... ...))`; execute `session.active_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_7_42d2312d36 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[8] | execute `window = session.windows[0]` without exception; execute `window.window_index # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `'...'`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_8_529cc07680 | 3.3a through 3.7c | planned parity | +| docs/topics/traversal.md::docs/topics/traversal.md[9] | execute `window.session # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session($... ...)`; execute `window.session.session_id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_traversal_md_docs_topics_traversal_md_9_dfba55476c | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[0] | execute `new_window = session.new_window(window_name='workspace')` without exception; execute `new_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:workspace, Session($... ...))`; execute `new_window in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_0_f498347395 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[10] | execute `from libtmux.constants import PaneDirection` without exception; execute `def create_pane_grid(session, rows=2, cols=2, name='grid'): """Create an NxM grid of panes.""" window = session.new_window(window_name=name, attach=False) window.resize(height=50, width=160) panes = [] base_pane = window.active_pane panes.append(base_pane) # Create first row of panes current = base_pane for _ in range(cols - 1): new_pane = current.split(direction=PaneDirection.Right) panes.append(new_pane) current = new_pane # Create additional rows for _ in range(rows - 1): row_start = panes[-cols] current = row_start for col in range(cols): new_pane = panes[-cols + col].split(direction=PaneDirection.Below) panes.append(new_pane) # Apply tiled layout for even distribution window.select_layout('tiled') return window, panes` without exception; execute `grid_window, grid_panes = create_pane_grid(session, rows=2, cols=2, name='test-grid')` without exception; execute `len(grid_panes) >= 4` and expect `True`; execute `grid_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_10_ccd6daaff8 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[11] | execute `import time` without exception; execute `def run_in_panes(panes, commands): """Run different commands in each pane.""" for pane, cmd in zip(panes, commands): pane.send_keys(cmd)` without exception; execute `multi_window = session.new_window(window_name='multi-cmd', attach=False)` without exception; execute `multi_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `pane_a = multi_window.active_pane` without exception; execute `pane_b = multi_window.split()` without exception; execute `pane_c = multi_window.split()` without exception; execute `run_in_panes( [pane_a, pane_b, pane_c], ['echo "Task A"', 'echo "Task B"', 'echo "Task C"'], )` without exception; execute `time.sleep(0.2)` without exception; execute `'Task A' in '\\n'.join(pane_a.capture_pane())` and expect `True`; execute `multi_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_11_55bf63f4b7 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[12] | execute `with session.new_window(window_name='temp-window') as temp_win: pane = temp_win.active_pane pane.send_keys('echo "temporary workspace"') temp_win in session.windows` and expect `True`; execute `temp_win not in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_12_4ce17f6320 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[1] | execute `background_window = session.new_window( window_name='background-task', attach=False, )` without exception; execute `background_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:background-task, Session($... ...))`; execute `background_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_1_39bd95e35f | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[2] | execute `shell_window = session.new_window( window_name='shell-test', attach=False, window_shell='sh -c "echo Hello; exec sh"', )` without exception; execute `shell_window # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:shell-test, Session($... ...))`; execute `shell_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_2_2571ab047c | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[3] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `v_split_window = session.new_window(window_name='v-split-demo', attach=False)` without exception; execute `v_split_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `top_pane = v_split_window.active_pane` without exception; execute `bottom_pane = v_split_window.split()` without exception; execute `bottom_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `len(v_split_window.panes)` and expect `2`; execute `v_split_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_3_36d833b5c9 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[4] | execute `from libtmux.constants import PaneDirection` without exception; execute `h_split_window = session.new_window(window_name='h-split', attach=False)` without exception; execute `h_split_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `left_pane = h_split_window.active_pane` without exception; execute `right_pane = left_pane.split(direction=PaneDirection.Right)` without exception; execute `right_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `len(h_split_window.panes)` and expect `2`; execute `h_split_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_4_30a3c7caed | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[5] | execute `size_window = session.new_window(window_name='size-demo', attach=False)` without exception; execute `size_window.resize(height=40, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `main_pane = size_window.active_pane` without exception; execute `small_pane = main_pane.split(size='20%')` without exception; execute `small_pane # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Pane(%... Window(@... ..., Session($... ...)))`; execute `size_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_5_ce8c840d88 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[6] | execute `layout_window = session.new_window(window_name='layout-demo', attach=False)` without exception; execute `layout_window.resize(height=60, width=120) # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `pane1 = layout_window.active_pane` without exception; execute `pane2 = layout_window.split()` without exception; execute `pane3 = layout_window.split()` without exception; execute `pane4 = layout_window.split()` without exception; execute `layout_window.select_layout('tiled') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `layout_window.select_layout('even-horizontal') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `layout_window.select_layout('main-vertical') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...)`; execute `layout_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_6_23162871d8 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[7] | execute `rename_window = session.new_window(window_name='old-name', attach=False)` without exception; execute `rename_window.rename_window('new-name') # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Window(@... ...:new-name, Session($... ...))`; execute `rename_window.window_name` and expect `'new-name'`; execute `rename_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_7_103a4558c1 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[8] | execute `demo_window = session.new_window(window_name='props-demo', attach=False)` without exception; execute `demo_window.window_index # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `'...'`; execute `demo_window.window_id # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `'@...'`; execute `demo_window.session # doctest: +ELLIPSIS` with doctest options `+ELLIPSIS` and expect `Session($... ...)`; execute `demo_window.kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_8_2358ddc219 | 3.3a through 3.7c | planned parity | +| docs/topics/workspace_setup.md::docs/topics/workspace_setup.md[9] | execute `import time` without exception; execute `from libtmux.constants import PaneDirection` without exception; execute `def create_dev_workspace(session, name='dev'): """Create a typical development workspace layout.""" window = session.new_window(window_name=name, attach=False) window.resize(height=50, width=160) # Main editing pane (large, left side) main_pane = window.active_pane # Terminal pane (bottom) terminal_pane = main_pane.split(size='30%') # Logs pane (right side of terminal) log_pane = terminal_pane.split(direction=PaneDirection.Right) return { 'window': window, 'main': main_pane, 'terminal': terminal_pane, 'logs': log_pane, }` without exception; execute `workspace = create_dev_workspace(session, 'my-project')` without exception; execute `len(workspace['window'].panes)` and expect `3`; execute `workspace['window'].kill()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#docs_topics_workspace_setup_md_docs_topics_workspace_setup_md_9_21f429a720 | 3.3a through 3.7c | planned parity | +| source-decision::attached-session-count | Given session rows whose session_attached values are '0', '1', and '2', Server.attachedSessions() returns only the '1' and '2' sessions by testing the attached count greater than zero; it never dispatches the Python noeq lookup. | deliberate correction contract | PythonBehaviorParityTest#source_decision_attached_session_count_d43f2e80e4 | 3.3a through 3.7c | deliberate Java correction | +| source-decision::list-accessor-leniency | Live listings, finders and snapshot capture throw when tmux is absent or capture fails. Successful empty captures retain server identity. Raw commands keep nonzero exit status as data. | deliberate correction contract | ServerTest#liveReadsRejectAnAbsentDaemon | 3.3a through 3.7c | deliberate Java correction | +| src/libtmux/_internal/constants.py::libtmux._internal.constants.Hooks | execute `from libtmux._internal.constants import Hooks` without exception; execute `raw = [ "session-renamed[0] set-option -g status-left-style bg=red", "session-renamed[1] display-message 'session renamed'", ]` without exception; execute `hooks = Hooks.from_stdout(raw)` without exception; execute `hooks.session_renamed[0]` and expect `'set-option -g status-left-style bg=red'`; execute `hooks.session_renamed[1]` and expect `"display-message 'session renamed'"`; execute `hooks.session_renamed.as_list()` and expect `['set-option -g status-left-style bg=red', "display-message 'session renamed'"]`; execute `raw_sparse = [ "pane-focus-in[0] refresh-client", "pane-focus-in[5] display-message 'focus'", ]` without exception; execute `hooks_sparse = Hooks.from_stdout(raw_sparse)` without exception; execute `0 in hooks_sparse.pane_focus_in` and expect `True`; execute `5 in hooks_sparse.pane_focus_in` and expect `True`; execute `3 in hooks_sparse.pane_focus_in` and expect `False`; execute `sorted(hooks_sparse.pane_focus_in.keys())` and expect `[0, 5]`; execute `for cmd in hooks_sparse.pane_focus_in.iter_values(): print(cmd)` and expect `refresh-client display-message 'focus'`; execute `raw_multi = [ "after-new-window[0] select-pane -t 0", "after-new-window[1] send-keys 'clear' Enter", "window-renamed[0] refresh-client -S", ]` without exception; execute `hooks_multi = Hooks.from_stdout(raw_multi)` without exception; execute `len(hooks_multi.after_new_window)` and expect `2`; execute `len(hooks_multi.window_renamed)` and expect `1`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_internal_constants_py_libtmux_internal_constants_Hooks_7039df8600 | 3.3a through 3.7c | planned parity | +| src/libtmux/_internal/constants.py::libtmux._internal.constants.Hooks.from_stdout | execute `from libtmux._internal.constants import Hooks` without exception; execute `raw = ["session-renamed[0] display-message 'renamed'"]` without exception; execute `hooks = Hooks.from_stdout(raw)` without exception; execute `hooks.session_renamed[0]` and expect `"display-message 'renamed'"`; execute `raw = [ "after-select-window[0] refresh-client", "after-select-window[10] display-message 'selected'", ]` without exception; execute `hooks = Hooks.from_stdout(raw)` without exception; execute `sorted(hooks.after_select_window.keys())` and expect `[0, 10]`; execute `hooks_empty = Hooks.from_stdout([])` without exception; execute `len(hooks_empty.session_renamed)` and expect `0`; execute `hooks_empty.session_renamed.as_list()` and expect `[]`. | real-tmux documentation contract test | PythonBehaviorParityTest#btmux_internal_constants_py_libtmux_internal_constants_Hooks_from_stdout_8dc0c3b379 | 3.3a through 3.7c | planned parity | +| src/libtmux/_internal/control_mode.py::libtmux._internal.control_mode.ControlMode | execute `with ControlMode(server=server, session=session) as ctl: clients = server.list_clients() assert len(clients) > 0 assert ctl.client_name != ''` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#btmux_internal_control_mode_py_libtmux_internal_control_mode_ControlMode_d7e85867d7 | 3.3a through 3.7c | planned parity | | src/libtmux/_internal/dataclasses.py::libtmux._internal.dataclasses.SkipDefaultFieldsReprMixin | execute `@dataclasses.dataclass() class Item: name: str unit_price: float = 1.00 quantity_on_hand: int = 0` without exception; execute `@dataclasses.dataclass(repr=False) class ItemWithMixin(SkipDefaultFieldsReprMixin): name: str unit_price: float = 1.00 quantity_on_hand: int = 0` without exception; execute `Item('Test')` and expect `Item(name='Test', unit_price=1.0, quantity_on_hand=0)`; execute `ItemWithMixin('Test')` and expect `ItemWithMixin(name=Test)`; execute `Item('Test', quantity_on_hand=2)` and expect `Item(name='Test', unit_price=1.0, quantity_on_hand=2)`; execute `ItemWithMixin('Test', quantity_on_hand=2)` and expect `ItemWithMixin(name=Test, quantity_on_hand=2)`; execute `@dataclasses.dataclass class ItemWithMixin(SkipDefaultFieldsReprMixin): name: str unit_price: float = 1.00 quantity_on_hand: int = 0 __repr__ = SkipDefaultFieldsReprMixin.__repr__` without exception; execute `ItemWithMixin('Test')` and expect `ItemWithMixin(name=Test)`; execute `ItemWithMixin('Test', unit_price=2.00)` and expect `ItemWithMixin(name=Test, unit_price=2.0)`; execute `item = ItemWithMixin('Test')` without exception; execute `item.unit_price = 2.05` without exception; execute `item` and expect `ItemWithMixin(name=Test, unit_price=2.05)`. | documentation contract test | PythonBehaviorParityTest#l_dataclasses_py_libtmux_internal_dataclasses_SkipDefaultFieldsReprMixin_defe4814eb | not tmux-bound | planned parity | | src/libtmux/_internal/env.py::libtmux._internal.env.pane_id_from_env | execute `from libtmux._internal.env import pane_id_from_env` without exception; execute `pane_id_from_env({"TMUX_PANE": "%3"})` and expect `'%3'`; execute `pane_id_from_env({})` and expect `libtmux.exc.NotInsideTmux: Not inside a tmux pane: $TMUX_PANE is unset or empty`; execute `pane_id_from_env({"TMUX_PANE": "3"})` and expect `libtmux.exc.NotInsideTmux: Not inside a tmux pane: $TMUX_PANE is not a pane id...`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_internal_env_py_libtmux_internal_env_pane_id_from_env_89d680cb4f | not tmux-bound | planned parity | | src/libtmux/_internal/env.py::libtmux._internal.env.resolve_env | execute `from libtmux._internal.env import resolve_env` without exception; execute `resolve_env({"TMUX_PANE": "%1"})` and expect `{'TMUX_PANE': '%1'}`; execute `resolve_env() is os.environ` and expect `True`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_internal_env_py_libtmux_internal_env_resolve_env_c97bc4cb66 | not tmux-bound | planned parity | @@ -269,177 +269,177 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | src/libtmux/_vendor/version.py::libtmux._vendor.version.Version.public | execute `Version("1.2.3").public` and expect `'1.2.3'`; execute `Version("1.2.3+abc").public` and expect `'1.2.3'`; execute `Version("1.2.3+abc.dev1").public` and expect `'1.2.3'`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_vendor_version_py_libtmux_vendor_version_Version_public_a994276b64 | not tmux-bound | planned parity | | src/libtmux/_vendor/version.py::libtmux._vendor.version.Version.release | execute `Version("1.2.3").release` and expect `(1, 2, 3)`; execute `Version("2.0.0").release` and expect `(2, 0, 0)`; execute `Version("1!2.0.0.post0").release` and expect `(2, 0, 0)`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_vendor_version_py_libtmux_vendor_version_Version_release_1cffcad3ee | not tmux-bound | planned parity | | src/libtmux/_vendor/version.py::libtmux._vendor.version.parse | execute `parse('1.0.dev1')` and expect `<Version('1.0.dev1')>`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_vendor_version_py_libtmux_vendor_version_parse_3ccdd22f60 | not tmux-bound | planned parity | -| src/libtmux/client.py::libtmux.client.Client | execute `with control_mode() as ctl: attached = [ c for c in server.clients if c.client_name == ctl.client_name ]` without exception; execute `bool(attached)` and expect `True`; execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) client.client_readonly in {"0", "1"}` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_client_py_libtmux_client_Client_817a1e37cc | 3.2a through 3.7b | planned parity | -| src/libtmux/client.py::libtmux.client.Client.attached_session | execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) attached = client.attached_session` without exception; execute `attached is not None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_client_py_libtmux_client_Client_attached_session_8a3f340620 | 3.2a through 3.7b | planned parity | +| src/libtmux/client.py::libtmux.client.Client | execute `with control_mode() as ctl: attached = [ c for c in server.clients if c.client_name == ctl.client_name ]` without exception; execute `bool(attached)` and expect `True`; execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) client.client_readonly in {"0", "1"}` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_client_py_libtmux_client_Client_817a1e37cc | 3.3a through 3.7c | planned parity | +| src/libtmux/client.py::libtmux.client.Client.attached_session | execute `with control_mode() as ctl: client = server.clients.get(client_name=ctl.client_name) attached = client.attached_session` without exception; execute `attached is not None` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_client_py_libtmux_client_Client_attached_session_8a3f340620 | 3.3a through 3.7c | planned parity | | src/libtmux/common.py::libtmux.common.get_version_str | execute `isinstance(get_version_str(), str)` and expect `True`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_common_py_libtmux_common_get_version_str_388fd49638 | not tmux-bound | planned parity | -| src/libtmux/common.py::libtmux.common.raise_if_stderr | execute `from libtmux.common import raise_if_stderr` without exception; execute `from libtmux import exc` without exception; execute `proc = session.cmd("display-message", "-p", "#{session_id}")` without exception; execute `raise_if_stderr(proc, "display-message") # no stderr → no raise` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_common_py_libtmux_common_raise_if_stderr_39dfbf9493 | 3.2a through 3.7b | planned parity | -| src/libtmux/common.py::libtmux.common.tmux_cmd | execute `proc = tmux_cmd(f'-L{server.socket_name}', 'new-session', '-d', '-P', '-F#S')` without exception; execute `if proc.stderr: raise exc.LibTmuxException( 'Command: %s returned error: %s' % (proc.cmd, proc.stderr) )` without exception; execute `print(f'tmux command returned {" ".join(proc.stdout)}')` and expect `tmux command returned 2`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_common_py_libtmux_common_tmux_cmd_fdb41cb7c5 | 3.2a through 3.7b | planned parity | +| src/libtmux/common.py::libtmux.common.raise_if_stderr | execute `from libtmux.common import raise_if_stderr` without exception; execute `from libtmux import exc` without exception; execute `proc = session.cmd("display-message", "-p", "#{session_id}")` without exception; execute `raise_if_stderr(proc, "display-message") # no stderr → no raise` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_common_py_libtmux_common_raise_if_stderr_39dfbf9493 | 3.3a through 3.7c | planned parity | +| src/libtmux/common.py::libtmux.common.tmux_cmd | execute `proc = tmux_cmd(f'-L{server.socket_name}', 'new-session', '-d', '-P', '-F#S')` without exception; execute `if proc.stderr: raise exc.LibTmuxException( 'Command: %s returned error: %s' % (proc.cmd, proc.stderr) )` without exception; execute `print(f'tmux command returned {" ".join(proc.stdout)}')` and expect `tmux command returned 2`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_common_py_libtmux_common_tmux_cmd_fdb41cb7c5 | 3.3a through 3.7c | planned parity | | src/libtmux/exc.py::libtmux.exc.MultipleObjectsReturned | execute `from libtmux import exc` without exception; execute `str(exc.MultipleObjectsReturned())` and expect `'Multiple objects returned'`; execute `str(exc.MultipleObjectsReturned(count=2, query={"pane_id": "%0"}))` and expect `"Multiple objects returned (2): pane_id='%0'"`; execute `issubclass(exc.MultipleObjectsReturned, exc.LibTmuxException)` and expect `True`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_exc_py_libtmux_exc_MultipleObjectsReturned_cfd9b5a750 | not tmux-bound | planned parity | -| src/libtmux/exc.py::libtmux.exc.NotInsideTmux | execute `from libtmux import exc` without exception; execute `str(exc.NotInsideTmux("TMUX"))` and expect `'Not inside a tmux pane: $TMUX is unset or empty'`; execute `str(exc.NotInsideTmux("TMUX_PANE", reason="not a pane id"))` and expect `'Not inside a tmux pane: $TMUX_PANE is not a pane id'`; execute `str(exc.NotInsideTmux())` and expect `'Not inside a tmux pane'`; execute `issubclass(exc.NotInsideTmux, exc.LibTmuxException)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_exc_py_libtmux_exc_NotInsideTmux_97adb59e52 | 3.2a through 3.7b | planned parity | +| src/libtmux/exc.py::libtmux.exc.NotInsideTmux | execute `from libtmux import exc` without exception; execute `str(exc.NotInsideTmux("TMUX"))` and expect `'Not inside a tmux pane: $TMUX is unset or empty'`; execute `str(exc.NotInsideTmux("TMUX_PANE", reason="not a pane id"))` and expect `'Not inside a tmux pane: $TMUX_PANE is not a pane id'`; execute `str(exc.NotInsideTmux())` and expect `'Not inside a tmux pane'`; execute `issubclass(exc.NotInsideTmux, exc.LibTmuxException)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_exc_py_libtmux_exc_NotInsideTmux_97adb59e52 | 3.3a through 3.7c | planned parity | | src/libtmux/exc.py::libtmux.exc.ObjectDoesNotExist | execute `from libtmux import exc` without exception; execute `str(exc.ObjectDoesNotExist())` and expect `'No objects found'`; execute `str(exc.ObjectDoesNotExist(query={"pane_id": "%99"}))` and expect `"No objects found: pane_id='%99'"`; execute `issubclass(exc.ObjectDoesNotExist, exc.LibTmuxException)` and expect `True`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_exc_py_libtmux_exc_ObjectDoesNotExist_e6e4f35699 | not tmux-bound | planned parity | | src/libtmux/exc.py::libtmux.exc.TmuxObjectDoesNotExist | execute `from libtmux import exc` without exception; execute `str(exc.TmuxObjectDoesNotExist())` and expect `'Could not find object'`; execute `str( exc.TmuxObjectDoesNotExist( obj_key="pane_id", obj_id="%99", list_cmd="list-panes", list_extra_args=("-t", "%99"), ) )` and expect `"Could not find pane_id=%99 for list-panes ('-t', '%99')"`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_exc_py_libtmux_exc_TmuxObjectDoesNotExist_446e85853e | not tmux-bound | planned parity | | src/libtmux/exc.py::libtmux.exc._format_query | execute `from libtmux.exc import _format_query` without exception; execute `_format_query({"pane_id": "%0"})` and expect `"pane_id='%0'"`; execute `_format_query({"window_name": "shared", "window_index": "1"})` and expect `"window_name='shared', window_index='1'"`; execute `_format_query({})` and expect `''`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_exc_py_libtmux_exc_format_query_ee8f0c47bc | not tmux-bound | planned parity | -| src/libtmux/hooks.py::libtmux.hooks.HooksMixin.set_hooks | execute `session.set_hooks('session-renamed', { 0: 'display-message "hook 0"', 1: 'display-message "hook 1"', })` and expect `Session($...)`; execute `hooks = session.show_hook('session-renamed')` without exception; execute `sorted(hooks.keys())` and expect `[0, 1]`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`; execute `session.set_hooks('after-new-window', [ 'select-pane -t 0', 'send-keys "clear" Enter', ])` and expect `Session($...)`; execute `hooks = session.show_hook('after-new-window')` without exception; execute `sorted(hooks.keys())` and expect `[0, 1]`; execute `session.set_hooks( 'session-renamed', {0: 'display-message "new"'}, clear_existing=True, )` and expect `Session($...)`; execute `hooks = session.show_hook('session-renamed')` without exception; execute `sorted(hooks.keys())` and expect `[0]`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`; execute `session.unset_hook('after-new-window')` and expect `Session($...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_hooks_py_libtmux_hooks_HooksMixin_set_hooks_795d87908d | 3.2a through 3.7b | planned parity | -| src/libtmux/hooks.py::libtmux.hooks.HooksMixin.show_hook | execute `session.set_hook('session-renamed[0]', 'display-message "test"')` and expect `Session($...)`; execute `hooks = session.show_hook('session-renamed')` without exception; execute `isinstance(hooks, SparseArray)` and expect `True`; execute `sorted(hooks.keys())` and expect `[0]`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_hooks_py_libtmux_hooks_HooksMixin_show_hook_a532650d49 | 3.2a through 3.7b | planned parity | -| src/libtmux/hooks.py::libtmux.hooks.HooksMixin.show_hooks | execute `session.set_hook('session-renamed[0]', 'display-message "test"')` and expect `Session($...)`; execute `hooks = session.show_hooks()` without exception; execute `isinstance(hooks, dict)` and expect `True`; execute `'session-renamed[0]' in hooks` and expect `True`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_hooks_py_libtmux_hooks_HooksMixin_show_hooks_25663db72d | 3.2a through 3.7b | planned parity | +| src/libtmux/hooks.py::libtmux.hooks.HooksMixin.set_hooks | execute `session.set_hooks('session-renamed', { 0: 'display-message "hook 0"', 1: 'display-message "hook 1"', })` and expect `Session($...)`; execute `hooks = session.show_hook('session-renamed')` without exception; execute `sorted(hooks.keys())` and expect `[0, 1]`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`; execute `session.set_hooks('after-new-window', [ 'select-pane -t 0', 'send-keys "clear" Enter', ])` and expect `Session($...)`; execute `hooks = session.show_hook('after-new-window')` without exception; execute `sorted(hooks.keys())` and expect `[0, 1]`; execute `session.set_hooks( 'session-renamed', {0: 'display-message "new"'}, clear_existing=True, )` and expect `Session($...)`; execute `hooks = session.show_hook('session-renamed')` without exception; execute `sorted(hooks.keys())` and expect `[0]`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`; execute `session.unset_hook('after-new-window')` and expect `Session($...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_hooks_py_libtmux_hooks_HooksMixin_set_hooks_795d87908d | 3.3a through 3.7c | planned parity | +| src/libtmux/hooks.py::libtmux.hooks.HooksMixin.show_hook | execute `session.set_hook('session-renamed[0]', 'display-message "test"')` and expect `Session($...)`; execute `hooks = session.show_hook('session-renamed')` without exception; execute `isinstance(hooks, SparseArray)` and expect `True`; execute `sorted(hooks.keys())` and expect `[0]`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_hooks_py_libtmux_hooks_HooksMixin_show_hook_a532650d49 | 3.3a through 3.7c | planned parity | +| src/libtmux/hooks.py::libtmux.hooks.HooksMixin.show_hooks | execute `session.set_hook('session-renamed[0]', 'display-message "test"')` and expect `Session($...)`; execute `hooks = session.show_hooks()` without exception; execute `isinstance(hooks, dict)` and expect `True`; execute `'session-renamed[0]' in hooks` and expect `True`; execute `session.unset_hook('session-renamed')` and expect `Session($...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_hooks_py_libtmux_hooks_HooksMixin_show_hooks_25663db72d | 3.3a through 3.7c | planned parity | | src/libtmux/neo.py::libtmux.neo._best_winlink | execute `from libtmux.neo import _best_winlink` without exception; execute `_best_winlink([{"window_id": "@0", "window_index": "1"}])["window_index"]` and expect `'1'`; execute `_best_winlink([ {"window_id": "@0", "window_index": "1", "window_active": "0"}, {"window_id": "@0", "window_index": "5", "window_active": "1"}, ])["window_index"]` and expect `'5'`; execute `_best_winlink([ {"window_id": "@0", "window_index": "1", "window_active": "0"}, {"window_id": "@0", "window_index": "5", "window_active": "0"}, ])["window_index"]` and expect `'1'`; execute `_best_winlink([ {"window_id": "@0", "window_index": "5", "window_active": "0"}, {"window_id": "@0", "window_index": "1", "window_active": "0"}, ])["window_index"]` and expect `'1'`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_best_winlink_7afd65aff1 | not tmux-bound | planned parity | -| src/libtmux/neo.py::libtmux.neo._is_target_not_found_error | execute `from libtmux.neo import _is_target_not_found_error` without exception; execute `_is_target_not_found_error("can't find pane: %99")` and expect `True`; execute `_is_target_not_found_error("can't find window: @99")` and expect `True`; execute `_is_target_not_found_error("can't find session: $99")` and expect `True`; execute `_is_target_not_found_error("no server running on /tmp/tmux-1000/default")` and expect `False`; execute `_is_target_not_found_error( "error connecting to /tmp/tmux-1000/nope (No such file or directory)" )` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_is_target_not_found_error_8297e6b672 | 3.2a through 3.7b | planned parity | +| src/libtmux/neo.py::libtmux.neo._is_target_not_found_error | execute `from libtmux.neo import _is_target_not_found_error` without exception; execute `_is_target_not_found_error("can't find pane: %99")` and expect `True`; execute `_is_target_not_found_error("can't find window: @99")` and expect `True`; execute `_is_target_not_found_error("can't find session: $99")` and expect `True`; execute `_is_target_not_found_error("no server running on /tmp/tmux-1000/default")` and expect `False`; execute `_is_target_not_found_error( "error connecting to /tmp/tmux-1000/nope (No such file or directory)" )` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_is_target_not_found_error_8297e6b672 | 3.3a through 3.7c | planned parity | | src/libtmux/neo.py::libtmux.neo._normalize_tmux_version | execute `from libtmux.neo import _normalize_tmux_version` without exception; execute `_normalize_tmux_version("3.6a") < _normalize_tmux_version("master")` and expect `True`; execute `_normalize_tmux_version("3.2a") < _normalize_tmux_version("3.6a")` and expect `True`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_normalize_tmux_version_b739a9d4d2 | not tmux-bound | planned parity | | src/libtmux/neo.py::libtmux.neo._token_scope | execute `from libtmux.neo import _token_scope` without exception; execute `_token_scope("pane_id")` and expect `'pane'`; execute `_token_scope("window_zoomed_flag")` and expect `'window'`; execute `_token_scope("client_name")` and expect `'client'`; execute `_token_scope("version")` and expect `'universal'`; execute `_token_scope("mouse_x")` and expect `'event'`; execute `_token_scope("mouse_all_flag")` and expect `'pane'`; execute `_token_scope("active_window_index")` and expect `'session'`; execute `_token_scope("command_list_alias")` and expect `'context'`; execute `_token_scope("search_match")` and expect `'context'`; execute `_token_scope("libtmux_test_nonexistent_token")` and expect `'unknown'`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_token_scope_daba4038ac | not tmux-bound | planned parity | -| src/libtmux/neo.py::libtmux.neo.fetch_obj | execute `from libtmux.neo import fetch_obj` without exception; execute `fetch_obj( server=pane.server, obj_key="pane_id", obj_id=pane.pane_id, list_cmd="list-panes", list_extra_args=("-t", pane.pane_id), )["pane_id"] == pane.pane_id` and expect `True`; execute `from libtmux import exc` without exception; execute `try: fetch_obj( server=pane.server, obj_key="pane_id", obj_id="%99999", list_cmd="list-panes", list_extra_args=("-t", "%99999"), ) except exc.TmuxObjectDoesNotExist as e: print(e)` and expect `Could not find pane_id=%99999 for list-panes ('-t', '%99999')`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_fetch_obj_81208b227d | 3.2a through 3.7b | planned parity | -| src/libtmux/neo.py::libtmux.neo.fetch_objs | execute `from libtmux.neo import fetch_objs` without exception; execute `objs = fetch_objs(server=server, list_cmd="list-sessions")` without exception; execute `isinstance(objs, list)` and expect `True`; execute `isinstance(objs[0], dict)` and expect `True`; execute `'session_id' in objs[0]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_fetch_objs_5008431037 | 3.2a through 3.7b | planned parity | -| src/libtmux/neo.py::libtmux.neo.get_output_format | execute `from libtmux.neo import get_output_format` without exception; execute `fields, fmt = get_output_format("list-sessions", "3.6a")` without exception; execute `'session_id' in fields` and expect `True`; execute `'pane_id' in fields # active pane for the listed session` and expect `True`; execute `'client_name' in fields # upward not allowed` and expect `False`; execute `'server' in fields` and expect `False`; execute `fields, _ = get_output_format("list-panes", "3.6a")` without exception; execute `all(t in fields for t in ('pane_id', 'window_id', 'session_id'))` and expect `True`; execute `fields, _ = get_output_format("list-clients", "3.6a")` without exception; execute `'client_name' in fields` and expect `True`; execute `'pane_id' in fields` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_get_output_format_58df0c80be | 3.2a through 3.7b | planned parity | +| src/libtmux/neo.py::libtmux.neo.fetch_obj | execute `from libtmux.neo import fetch_obj` without exception; execute `fetch_obj( server=pane.server, obj_key="pane_id", obj_id=pane.pane_id, list_cmd="list-panes", list_extra_args=("-t", pane.pane_id), )["pane_id"] == pane.pane_id` and expect `True`; execute `from libtmux import exc` without exception; execute `try: fetch_obj( server=pane.server, obj_key="pane_id", obj_id="%99999", list_cmd="list-panes", list_extra_args=("-t", "%99999"), ) except exc.TmuxObjectDoesNotExist as e: print(e)` and expect `Could not find pane_id=%99999 for list-panes ('-t', '%99999')`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_fetch_obj_81208b227d | 3.3a through 3.7c | planned parity | +| src/libtmux/neo.py::libtmux.neo.fetch_objs | execute `from libtmux.neo import fetch_objs` without exception; execute `objs = fetch_objs(server=server, list_cmd="list-sessions")` without exception; execute `isinstance(objs, list)` and expect `True`; execute `isinstance(objs[0], dict)` and expect `True`; execute `'session_id' in objs[0]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_fetch_objs_5008431037 | 3.3a through 3.7c | planned parity | +| src/libtmux/neo.py::libtmux.neo.get_output_format | execute `from libtmux.neo import get_output_format` without exception; execute `fields, fmt = get_output_format("list-sessions", "3.6a")` without exception; execute `'session_id' in fields` and expect `True`; execute `'pane_id' in fields # active pane for the listed session` and expect `True`; execute `'client_name' in fields # upward not allowed` and expect `False`; execute `'server' in fields` and expect `False`; execute `fields, _ = get_output_format("list-panes", "3.6a")` without exception; execute `all(t in fields for t in ('pane_id', 'window_id', 'session_id'))` and expect `True`; execute `fields, _ = get_output_format("list-clients", "3.6a")` without exception; execute `'client_name' in fields` and expect `True`; execute `'pane_id' in fields` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_get_output_format_58df0c80be | 3.3a through 3.7c | planned parity | | src/libtmux/neo.py::libtmux.neo.parse_output | execute `from libtmux.neo import get_output_format, parse_output` without exception; execute `from libtmux.formats import FORMAT_SEPARATOR` without exception; execute `fields, fmt = get_output_format("list-sessions", "3.6a")` without exception; execute `values = [''] * len(fields)` without exception; execute `values[fields.index('session_id')] = '$1'` without exception; execute `result = parse_output( FORMAT_SEPARATOR.join(values) + FORMAT_SEPARATOR, list_cmd="list-sessions", tmux_version="3.6a", )` without exception; execute `result['session_id']` and expect `'$1'`; execute `'pane_id' in result` and expect `False`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_neo_py_libtmux_neo_parse_output_4e6a4ce1c7 | not tmux-bound | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin._show_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().cmd('new-session', '-d')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_option('exit-unattached', global_=True)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_option_e815e7192a | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin._show_option_raw | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().cmd('new-session', '-d')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_option_raw('exit-unattached', global_=True)` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_option_raw('exit-unattached', global_=True).stdout` and expect `['exit-unattached off']`; execute `isinstance(MyServer()._show_option_raw('exit-unattached', global_=True).stdout, list)` and expect `True`; execute `isinstance(MyServer()._show_option_raw('exit-unattached', global_=True).stdout[0], str)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_option_raw_3a88b64fb9 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin._show_options | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer()._show_options()` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_5f74522ece | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin._show_options_dict | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer()._show_options_dict()` and expect `{...}`; execute `isinstance(MyServer()._show_options_dict(), dict)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_dict_66d4563e30 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin._show_options_raw | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer()._show_options_raw()` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_options_raw().stdout` and expect `[...]`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_raw_a787e92168 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin.set_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `from libtmux._internal.sparse_array import SparseArray` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().set_option('escape-time', 1250)` and expect `<libtmux.options.MyServer object at ...>`; execute `MyServer()._show_option('escape-time')` and expect `1250`; execute `MyServer().set_option('escape-time', 495)` and expect `<libtmux.options.MyServer object at ...>`; execute `MyServer()._show_option('escape-time')` and expect `495`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_set_option_51585c6225 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin.show_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().cmd('new-session', '-d')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer().show_option('exit-unattached', global_=True)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_option_a0663b7be1 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin.show_options | execute `options = server.show_options()` without exception; execute `isinstance(options, dict)` and expect `True`; execute `'buffer-limit' in options` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_6c26c4ef55 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.OptionsMixin.unset_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().set_option('escape-time', 1250)` and expect `<libtmux.options.MyServer object at ...>`; execute `MyServer()._show_option('escape-time')` and expect `1250`; execute `MyServer().unset_option('escape-time')` and expect `<libtmux.options.MyServer object at ...>`; execute `isinstance(MyServer()._show_option('escape-time'), int)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_unset_option_19d04a4d69 | 3.2a through 3.7b | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin._show_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().cmd('new-session', '-d')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_option('exit-unattached', global_=True)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_option_e815e7192a | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin._show_option_raw | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().cmd('new-session', '-d')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_option_raw('exit-unattached', global_=True)` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_option_raw('exit-unattached', global_=True).stdout` and expect `['exit-unattached off']`; execute `isinstance(MyServer()._show_option_raw('exit-unattached', global_=True).stdout, list)` and expect `True`; execute `isinstance(MyServer()._show_option_raw('exit-unattached', global_=True).stdout[0], str)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_option_raw_3a88b64fb9 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin._show_options | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer()._show_options()` and expect `{...}`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_5f74522ece | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin._show_options_dict | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer()._show_options_dict()` and expect `{...}`; execute `isinstance(MyServer()._show_options_dict(), dict)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_dict_66d4563e30 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin._show_options_raw | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer()._show_options_raw()` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer()._show_options_raw().stdout` and expect `[...]`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_raw_a787e92168 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin.set_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `from libtmux._internal.sparse_array import SparseArray` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().set_option('escape-time', 1250)` and expect `<libtmux.options.MyServer object at ...>`; execute `MyServer()._show_option('escape-time')` and expect `1250`; execute `MyServer().set_option('escape-time', 495)` and expect `<libtmux.options.MyServer object at ...>`; execute `MyServer()._show_option('escape-time')` and expect `495`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_set_option_51585c6225 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin.show_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().cmd('new-session', '-d')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `MyServer().show_option('exit-unattached', global_=True)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_option_a0663b7be1 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin.show_options | execute `options = server.show_options()` without exception; execute `isinstance(options, dict)` and expect `True`; execute `'buffer-limit' in options` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_show_options_6c26c4ef55 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.OptionsMixin.unset_option | execute `import typing as t` without exception; execute `from libtmux.common import tmux_cmd` without exception; execute `from libtmux.constants import OptionScope` without exception; execute `class MyServer(OptionsMixin): socket_name = server.socket_name def cmd(self, cmd: str, *args: object): cmd_args: t.List[t.Union[str, int]] = [cmd] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") cmd_args.insert(0, "-f/dev/null") return tmux_cmd(*cmd_args, *args) default_option_scope = OptionScope.Server` without exception; execute `MyServer().set_option('escape-time', 1250)` and expect `<libtmux.options.MyServer object at ...>`; execute `MyServer()._show_option('escape-time')` and expect `1250`; execute `MyServer().unset_option('escape-time')` and expect `<libtmux.options.MyServer object at ...>`; execute `isinstance(MyServer()._show_option('escape-time'), int)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_OptionsMixin_unset_option_19d04a4d69 | 3.3a through 3.7c | planned parity | | src/libtmux/options.py::libtmux.options.convert_value | execute `convert_value("on")` and expect `True`; execute `convert_value("off")` and expect `False`; execute `convert_value("1")` and expect `1`; execute `convert_value("50")` and expect `50`; execute `convert_value("%50")` and expect `'%50'`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_convert_value_81c610b6db | not tmux-bound | planned parity | | src/libtmux/options.py::libtmux.options.convert_values | execute `convert_values(None)` without exception; execute `convert_values("on")` and expect `True`; execute `convert_values("off")` and expect `False`; execute `convert_values(["on"])` and expect `[True]`; execute `convert_values(["off"])` and expect `[False]`; execute `convert_values({"window_index": "1"})` and expect `{'window_index': 1}`; execute `convert_values({"visual-bell": "on"})` and expect `{'visual-bell': True}`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_convert_values_fb76ac2ac2 | not tmux-bound | planned parity | -| src/libtmux/options.py::libtmux.options.explode_arrays | execute `import io` without exception; execute `many_more_options = io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[1] screen*:title ''')` without exception; execute `many_more_flat_dict = parse_options_to_dict(many_more_options)` without exception; execute `many_more_flat_dict == { "terminal-features[0]": "xterm*:clipboard:ccolour:cstyle:focus", "terminal-features[1]": "screen*:title",}` and expect `True`; execute `explode_arrays(many_more_flat_dict) == { "terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 1: "screen*:title"}}` and expect `True`; execute `explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[5] screen*:title '''))) == { "terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 5: "screen*:title"}}` and expect `True`; execute `from libtmux._internal.sparse_array import SparseArray` without exception; execute `hooks_output = io.StringIO(r''' session-renamed[0] display-message 'renamed' session-renamed[5] refresh-client pane-focus-in[0] run-shell 'echo focus' ''')` without exception; execute `hooks_exploded = explode_arrays( parse_options_to_dict(hooks_output), force_array=True, )` without exception; execute `isinstance(hooks_exploded["session-renamed"], SparseArray)` and expect `True`; execute `hooks_exploded["session-renamed"][0]` and expect `"display-message 'renamed'"`; execute `hooks_exploded["session-renamed"][5]` and expect `'refresh-client'`; execute `sorted(hooks_exploded["session-renamed"].keys())` and expect `[0, 5]`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_explode_arrays_1e4dfcd9bd | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.explode_complex | execute `import io` without exception; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[5] screen*:title '''))))` and expect `{'terminal-features': {'xterm*': ['clipboard', 'ccolour', 'cstyle', 'focus'], 'screen*': ['title']}}`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[5] screen*:title ''')))) == { "terminal-features": {"xterm*": ["clipboard", "ccolour", "cstyle", "focus"], "screen*": ["title"]}}` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' command-alias[0] split-pane=split-window command-alias[1] splitp=split-window command-alias[2] "server-info=show-messages -JT" ''')))) == { "command-alias": {"split-pane": "split-window", "splitp": "split-window", "server-info": "show-messages -JT"}}` and expect `True`; execute `explode_complex(explode_arrays({"terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 1: "screen*:title"}}))` and expect `{'terminal-features': {0: 'xterm*:clipboard:ccolour:cstyle:focus', 1: 'screen*:title'}}`; execute `explode_complex(explode_arrays({"terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 8: "screen*:title"}})) == SparseArray({'terminal-features': {0: 'xterm*:clipboard:ccolour:cstyle:focus', 8: 'screen*:title'}})` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-overrides[0] xterm-256color:Tc terminal-overrides[1] *:U8=0 ''')))) == { "terminal-overrides": {"xterm-256color": {"Tc": None}, "*": {"U8": 0}}}` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' user-keys[100] "\e[test" user-keys[6] "\e\n" user-keys[0] "\e[5;30012~" ''')))) == { "user-keys": {0: "\\e[5;30012~", 6: "\\e\\n", 100: "\\e[test"}}` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' status-format[0] "#[align=left range=left #{E:status-left-style}]#[push-default]#{T;=/#{status-left-length}:status-left}#[pop-default]#[norange default]#[list=on align=#{status-justify}]#[list=left-marker]<#[list=right-marker]>#[list=on]#{W:#[range=window|#{window_index} #{E:window-status-style}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-format}#[pop-default]#[norange default]#{?window_end_flag,,#{window-status-separator}},#[range=window|#{window_index} list=focus #{?#{!=:#{E:window-status-current-style},default},#{E:window-status-current-style},#{E:window-status-style}}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-current-format}#[pop-default]#[norange list=on default]#{?window_end_flag,,#{window-status-separator}}}#[nolist align=right range=right #{E:status-right-style}]#[push-default]#{T;=/#{status-right-length}:status-right}#[pop-default]#[norange default]" status-format[1] "#[align=centre]#{P:#{?pane_active,#[reverse],}#{pane_index}[#{pane_width}x#{pane_height}]#[default] }" ''')))) == { "status-format": {0: "#[align=left range=left #{E:status-left-style}]#[push-default]#{T;=/#{status-left-length}:status-left}#[pop-default]#[norange default]#[list=on align=#{status-justify}]#[list=left-marker]<#[list=right-marker]>#[list=on]#{W:#[range=window|#{window_index} #{E:window-status-style}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-format}#[pop-default]#[norange default]#{?window_end_flag,,#{window-status-separator}},#[range=window|#{window_index} list=focus #{?#{!=:#{E:window-status-current-style},default},#{E:window-status-current-style},#{E:window-status-style}}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-current-format}#[pop-default]#[norange list=on default]#{?window_end_flag,,#{window-status-separator}}}#[nolist align=right range=right #{E:status-right-style}]#[push-default]#{T;=/#{status-right-length}:status-right}#[pop-default]#[norange default]", 1: "#[align=centre]#{P:#{?pane_active,#[reverse],}#{pane_index}[#{pane_width}x#{pane_height}]#[default] }", }}` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_explode_complex_74022ddf0a | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.handle_option_error | execute `result = server.cmd( 'set-option', 'unknown-option-name', )` without exception; execute `bool(isinstance(result.stderr, list) and len(result.stderr))` and expect `True`; execute `import pytest` without exception; execute `from libtmux import exc` without exception; execute `with pytest.raises(exc.OptionError): handle_option_error(result.stderr[0])` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_handle_option_error_be7cdd74c1 | 3.2a through 3.7b | planned parity | -| src/libtmux/options.py::libtmux.options.parse_options_to_dict | execute `import io` without exception; execute `raw_options = io.StringIO("status-keys vi")` without exception; execute `parse_options_to_dict(raw_options) == {"status-keys": "vi"}` and expect `True`; execute `int_options = io.StringIO("message-limit 50")` without exception; execute `parse_options_to_dict(int_options) == {"message-limit": "50"}` and expect `True`; execute `empty_option = io.StringIO("user-keys")` without exception; execute `parse_options_to_dict(empty_option) == {"user-keys": None}` and expect `True`; execute `array_option = io.StringIO("command-alias[0] split-pane=split-window")` without exception; execute `parse_options_to_dict(array_option) == { "command-alias[0]": "split-pane=split-window"}` and expect `True`; execute `array_option = io.StringIO("command-alias[40] split-pane=split-window")` without exception; execute `parse_options_to_dict(array_option) == { "command-alias[40]": "split-pane=split-window"}` and expect `True`; execute `many_options = io.StringIO(r'''status-keys command-alias[0] split-pane=split-window ''')` without exception; execute `parse_options_to_dict(many_options) == { "command-alias[0]": "split-pane=split-window", "status-keys": None,}` and expect `True`; execute `many_more_options = io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[1] screen*:title ''')` without exception; execute `parse_options_to_dict(many_more_options) == { "terminal-features[0]": "xterm*:clipboard:ccolour:cstyle:focus", "terminal-features[1]": "screen*:title",}` and expect `True`; execute `quoted_option = io.StringIO(r''' command-alias[0] "choose-session=choose-tree -s" ''')` without exception; execute `parse_options_to_dict(quoted_option) == { "command-alias[0]": "choose-session=choose-tree -s", }` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_parse_options_to_dict_c132265e66 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane | execute `pane` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`; execute `pane in window.panes` and expect `True`; execute `pane.window` and expect `Window(@1 1:..., Session($1 ...))`; execute `pane.session` and expect `Session($1 ...)`; execute `with window.split() as pane: pane.send_keys('echo "Hello"') # Do work with the pane # Pane will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_89b5a1a53c | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.at_bottom | execute `pane.pane_at_bottom` and expect `'1'`; execute `pane.at_bottom` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_bottom_6ca2a17fef | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.at_left | execute `pane.pane_at_left` and expect `'1'`; execute `pane.at_left` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_left_4e48fd214b | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.at_right | execute `pane.pane_at_right` and expect `'1'`; execute `pane.at_right` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_right_2dd8e8ca5d | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.at_top | execute `pane.pane_at_top` and expect `'1'`; execute `pane.at_top` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_top_968a50a65b | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.break_pane | execute `pane_to_break = window.split(shell='sleep 1m')` without exception; execute `new_window = pane_to_break.break_pane(window_name='broken')` without exception; execute `new_window.window_name` and expect `'broken'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_break_pane_a7e77fef15 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.capture_pane | execute `pane = window.split(shell='sh')` without exception; execute `pane.capture_pane()` and expect `['$']`; execute `pane.send_keys('echo "Hello world"', enter=True)` without exception; execute `pane.capture_pane()` and expect `['$ echo "Hello world"', 'Hello world', '$']`; execute `print(chr(10).join(pane.capture_pane()))` and expect `$ echo "Hello world" Hello world $`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_capture_pane_8fed04c38b | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.choose_buffer | execute `pane.choose_buffer()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_choose_buffer_f64d2f76c4 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.choose_client | execute `pane.choose_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_choose_client_b4b49269f0 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.choose_tree | execute `pane.choose_tree()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_choose_tree_4d42fc71ef | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.clear_history | execute `pane.clear_history()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_clear_history_28d6d8542e | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.clock_mode | execute `pane.clock_mode()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_clock_mode_634705470c | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.cmd | execute `pane.cmd('split-window', '-P').stdout[0]` and expect `'libtmux...:...'`; execute `Pane.from_pane_id(pane_id=pane.cmd( 'split-window', '-P', '-F#{pane_id}').stdout[0], server=pane.server)` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_cmd_3248ce2f06 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.copy_mode | execute `pane.copy_mode()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_copy_mode_e7635a3cab | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.customize_mode | execute `pane.customize_mode()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_customize_mode_22625c0e22 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.display_panes | execute `with control_mode() as ctl: window.active_pane.display_panes()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_display_panes_be2176f2a4 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.display_popup | execute `with control_mode() as ctl: pane.display_popup(command='true', close_on_exit=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_display_popup_ed5880c930 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.find_window | execute `pane.find_window('sh')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_find_window_fa049abe56 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.from_env | execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = { # doctest: +HIDE "TMUX": f"{socket_path},1,0", "TMUX_PANE": pane.pane_id, }` with doctest options `+ALLOW_UNICODE` without exception; execute `Pane.from_env(env)` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`; execute `Pane.from_env(env).pane_id == pane.pane_id` and expect `True`; execute `from libtmux import exc` without exception; execute `try: Pane.from_env({}) except exc.NotInsideTmux as e: print(e)` and expect `Not inside a tmux pane: $TMUX is unset or empty`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_from_env_752ca3f890 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.from_pane_id | execute `Pane.from_pane_id(server=pane.server, pane_id=pane.pane_id)` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_from_pane_id_5b9ce3a290 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.height | execute `pane.height.isdigit()` and expect `True`; execute `pane.height == pane.pane_height` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_height_2fb5ec6d49 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.id | execute `pane.id` and expect `'%1'`; execute `pane.id == pane.pane_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_id_24dc3473ba | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.index | execute `pane.index` and expect `'0'`; execute `pane.index == pane.pane_index` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_index_f37cfd7ca6 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.join | execute `pane_to_join = window.split(shell='sleep 1m')` without exception; execute `new_window = pane_to_join.break_pane()` without exception; execute `pane_to_join.join(window)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_join_308a926493 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.kill | execute `pane_1 = pane.split()` without exception; execute `pane_1 in window.panes` and expect `True`; execute `pane_1.kill()` without exception; execute `pane_1 not in window.panes` and expect `True`; execute `pane.window.resize(height=100, width=100)` and expect `Window(@1 1...)`; execute `one_pane_to_rule_them_all = pane.split()` without exception; execute `other_panes = pane.split( ), pane.split()` without exception; execute `all([p in window.panes for p in other_panes])` and expect `True`; execute `one_pane_to_rule_them_all.kill(all_except=True)` without exception; execute `all([p not in window.panes for p in other_panes])` and expect `True`; execute `one_pane_to_rule_them_all in window.panes` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_kill_e25c6299dc | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.move | execute `pane_to_move = window.split(shell='sleep 1m')` without exception; execute `w2 = session.new_window(window_name='move_target')` without exception; execute `pane_to_move.move(w2)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_move_b01c6d05c7 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.new_pane | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): floating = pane.new_pane(width=40, height=10, shell="sleep 5") is_floating = floating.pane_floating_flag else: is_floating = "1"` without exception; execute `is_floating` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_new_pane_11b0985861 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.paste_buffer | execute `server.set_buffer('pasted_text')` without exception; execute `pane.paste_buffer()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_paste_buffer_3b3c32fdc0 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.pipe | execute `pane.pipe('cat >> /tmp/output.txt')` without exception; execute `pane.pipe()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_pipe_af350fb3ca | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.refresh | execute `pane.refresh()` without exception; execute `pane.pane_id` and expect `'%1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_refresh_b2091c58de | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.reset | execute `pane.reset()` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_reset_9e06d19ed4 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.respawn | execute `pane = window.split(shell='sleep 1m')` without exception; execute `pane.respawn(kill=True, shell='sh')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_respawn_b7ddfec773 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.select | execute `pane = window.active_pane` without exception; execute `new_pane = window.split()` without exception; execute `pane.refresh()` without exception; execute `active_panes = [p for p in window.panes if p.pane_active == '1']` without exception; execute `pane in active_panes` and expect `True`; execute `new_pane in active_panes` and expect `False`; execute `new_pane.pane_active == '1'` and expect `False`; execute `new_pane.select()` and expect `Pane(...)`; execute `new_pane.pane_active == '1'` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_select_02aaa45270 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.send_keys | execute `pane = window.split(shell='sh')` without exception; execute `pane.capture_pane()` and expect `['$']`; execute `pane.send_keys('echo "Hello world"', enter=True)` without exception; execute `pane.capture_pane()` and expect `['$ echo "Hello world"', 'Hello world', '$']`; execute `print('\n'.join(pane.capture_pane())) # doctest: +NORMALIZE_WHITESPACE` with doctest options `+NORMALIZE_WHITESPACE` and expect `$ echo "Hello world" Hello world $`; execute `pane.send_keys(reset=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_send_keys_bcca63c4d0 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.send_prefix | execute `pane.send_prefix()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_send_prefix_75166732ce | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.set_title | execute `pane.set_title('my-title')` and expect `Pane(...)`; execute `pane.pane_title` and expect `'my-title'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_set_title_3b3cf2e8a5 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.split | execute `(pane.at_left, pane.at_right, pane.at_top, pane.at_bottom)` and expect `(True, True, True, True)`; execute `new_pane = pane.split()` without exception; execute `(new_pane.at_left, new_pane.at_right, new_pane.at_top, new_pane.at_bottom)` and expect `(True, True, False, True)`; execute `right_pane = pane.split(direction=PaneDirection.Right)` without exception; execute `(right_pane.at_left, right_pane.at_right, right_pane.at_top, right_pane.at_bottom)` and expect `(False, True, True, False)`; execute `left_pane = pane.split(direction=PaneDirection.Left)` without exception; execute `(left_pane.at_left, left_pane.at_right, left_pane.at_top, left_pane.at_bottom)` and expect `(True, False, True, False)`; execute `top_pane = pane.split(direction=PaneDirection.Above)` without exception; execute `(top_pane.at_left, top_pane.at_right, top_pane.at_top, top_pane.at_bottom)` and expect `(False, False, True, False)`; execute `pane = session.new_window().active_pane` without exception; execute `top_pane = pane.split(direction=PaneDirection.Above, full_window_split=True)` without exception; execute `(top_pane.at_left, top_pane.at_right, top_pane.at_top, top_pane.at_bottom)` and expect `(True, True, True, False)`; execute `bottom_pane = pane.split( direction=PaneDirection.Below, full_window_split=True)` without exception; execute `(bottom_pane.at_left, bottom_pane.at_right, bottom_pane.at_top, bottom_pane.at_bottom)` and expect `(True, True, False, True)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_split_15e115b719 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.swap | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `pane1_id, pane2_id = pane1.pane_id, pane2.pane_id` without exception; execute `pane1.swap(pane2)` without exception; execute `pane1.refresh()` without exception; execute `pane2.refresh()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_swap_e3427460d0 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.title | execute `pane.set_title('test-alias')` and expect `Pane(...)`; execute `pane.title == pane.pane_title` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_title_6d895c42c3 | 3.2a through 3.7b | planned parity | -| src/libtmux/pane.py::libtmux.pane.Pane.width | execute `pane.width.isdigit()` and expect `True`; execute `pane.width == pane.pane_width` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_width_0c5eae94ee | 3.2a through 3.7b | planned parity | -| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.TestServer | execute `server = Server() # Create server instance` without exception; execute `server.new_session()` and expect `Session($... ...)`; execute `server.is_alive()` and expect `True`; execute `server2 = Server()` without exception; execute `server2.socket_name != server.socket_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_TestServer_b769bdd044 | 3.2a through 3.7b | planned parity | -| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.control_mode | execute `from libtmux._internal.control_mode import ControlMode` without exception; execute `def test_example(control_mode): with control_mode() as ctl: assert ctl.client_name != ''` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_control_mode_bd791351b1 | 3.2a through 3.7b | planned parity | -| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.server | execute `from libtmux.server import Server` without exception; execute `def test_example(server: Server) -> None: assert isinstance(server, Server) session = server.new_session('my session') assert len(server.sessions) == 1 assert [session.name.startswith('my') for session in server.sessions]` without exception; execute `locals().keys()` and expect `dict_keys(...)`; execute `source = ''.join([e.source for e in request._pyfuncitem.dtest.examples][:3])` without exception; execute `pytester = request.getfixturevalue('pytester')` without exception; execute `pytester.makepyfile(**{'whatever.py': source})` and expect `PosixPath(...)`; execute `result = pytester.runpytest('whatever.py', '--disable-warnings')` and expect `===...`; execute `result.assert_outcomes(passed=1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_server_acc91d3b27 | 3.2a through 3.7b | planned parity | -| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.session | execute `from libtmux.session import Session` without exception; execute `def test_example(session: "Session") -> None: assert isinstance(session.name, str) assert session.name.startswith('libtmux_') window = session.new_window(window_name='new one') assert window.name == 'new one'` without exception; execute `locals().keys()` and expect `dict_keys(...)`; execute `source = ''.join([e.source for e in request._pyfuncitem.dtest.examples][:3])` without exception; execute `pytester = request.getfixturevalue('pytester')` without exception; execute `pytester.makepyfile(**{'whatever.py': source})` and expect `PosixPath(...)`; execute `result = pytester.runpytest('whatever.py', '--disable-warnings')` and expect `===...`; execute `result.assert_outcomes(passed=1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_session_cc63e7ba8b | 3.2a through 3.7b | planned parity | -| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.session_params | execute `import pytest` without exception; execute `from libtmux.session import Session` without exception; execute `@pytest.fixture def session_params(session_params): return { 'x': 800, 'y': 600, }` without exception; execute `def test_example(session: "Session") -> None: assert isinstance(session.name, str) assert session.name.startswith('libtmux_') window = session.new_window(window_name='new one') assert window.name == 'new one'` without exception; execute `locals().keys()` and expect `dict_keys(...)`; execute `source = ''.join([e.source for e in request._pyfuncitem.dtest.examples][:4])` without exception; execute `pytester = request.getfixturevalue('pytester')` without exception; execute `pytester.makepyfile(**{'whatever.py': source})` and expect `PosixPath(...)`; execute `result = pytester.runpytest('whatever.py', '--disable-warnings')` and expect `===...`; execute `result.assert_outcomes(passed=1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_session_params_50f29f53fe | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server | execute `server` and expect `Server(socket_name=libtmux_test...)`; execute `server.sessions` and expect `[Session($1 ...)]`; execute `server.sessions[0].windows` and expect `[Window(@1 1:..., Session($1 ...))]`; execute `server.sessions[0].active_window` and expect `Window(@1 1:..., Session($1 ...))`; execute `server.sessions[0].active_pane` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`; execute `with Server() as server: session = server.new_session() # Do work with the session # Server will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_57e38b41fe | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.attached_sessions | Given session rows whose session_attached values are '0', '1', and '2', Server.attachedSessions() returns only the '1' and '2' sessions by testing the attached count greater than zero; it never dispatches the Python noeq lookup. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_attached_sessions_96d2258b22 | 3.2a through 3.7b | deliberate Java correction | -| src/libtmux/server.py::libtmux.server.Server.bind_key | execute `server.bind_key('F12', 'display-message test', key_table='root')` without exception; execute `server.unbind_key('F12', key_table='root')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_bind_key_2ad6079d5e | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.clear_prompt_history | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.3"): server.clear_prompt_history()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_clear_prompt_history_194828cc5f | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.clients | execute `with control_mode() as ctl: names = [c.client_name for c in server.clients] ctl.client_name in names` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_clients_075e2b36ef | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.cmd | execute `server.cmd('display-message', 'hi')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]` and expect `'$2'`; execute `session.cmd('new-window', '-P').stdout[0]` and expect `'libtmux...:2.0'`; execute `Window.from_window_id(window_id=session.cmd( 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server)` and expect `Window(@4 3:..., Session($1 libtmux_...))`; execute `window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0]` and expect `'%5'`; execute `Pane.from_pane_id(pane_id=window.cmd( 'split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server)` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_cmd_ac953d76be | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.command_prompt | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.4"): with control_mode() as ctl: server.command_prompt( "set -g @cp_test '%1'", target_client=ctl.client_name, ) for key in ['h', 'i', 'Enter']: _ = server.cmd('send-keys', '-K', '-c', ctl.client_name, key) result = server.cmd('show-options', '-gv', '@cp_test').stdout[0] else: result = 'hi'` without exception; execute `result` and expect `'hi'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_command_prompt_a66476935c | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.confirm_before | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.4"): with control_mode() as ctl: server.confirm_before( 'set -g @cf_test yes', target_client=ctl.client_name, ) _ = server.cmd('send-keys', '-K', '-c', ctl.client_name, 'y') result = server.cmd('show-options', '-gv', '@cf_test').stdout[0] else: result = 'yes'` without exception; execute `result` and expect `'yes'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_confirm_before_9850ce21b4 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.delete_buffer | execute `server.set_buffer('to_delete', buffer_name='del_buf')` without exception; execute `server.delete_buffer(buffer_name='del_buf')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_delete_buffer_468802c3c4 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.detach_all_clients | execute `with control_mode() as ctl: server.detach_all_clients(keep_client=ctl.client_name)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_detach_all_clients_a8327a2553 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.detach_client | execute `with control_mode() as ctl: server.detach_client(target_client=ctl.client_name)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_detach_client_3ee32134c7 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.display_menu | execute `captured = []` without exception; execute `def fake_cmd(name, *args, **_kw): captured.append((name, args)) return type('R', (), {'stderr': [], 'stdout': []})()` without exception; execute `monkeypatch.setattr(server, 'cmd', fake_cmd)` without exception; execute `server.display_menu('First', '1', 'select-pane', title='menu')` without exception; execute `captured[0][0]` and expect `'display-menu'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_display_menu_13c14bf74d | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.display_message | execute `result = server.display_message("#{version}", get_text=True)` without exception; execute `isinstance(result, list) and len(result) == 1` and expect `True`; execute `result = server.display_message("", get_text=True, all_formats=True)` without exception; execute `any("session_name=" in line for line in result)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_display_message_a97c79d3e0 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.from_env | execute `from libtmux.server import Server as TmuxServer # doctest: +HIDE` with doctest options `+ALLOW_UNICODE` without exception; execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = { # doctest: +HIDE "TMUX": f"{socket_path},1,0", "TMUX_PANE": pane.pane_id, }` with doctest options `+ALLOW_UNICODE` without exception; execute `TmuxServer.from_env(env)` and expect `Server(socket_path=...)`; execute `TmuxServer.from_env(env).is_alive()` and expect `True`; execute `try: TmuxServer.from_env({}) except exc.NotInsideTmux as e: print(e)` and expect `Not inside a tmux pane: $TMUX is unset or empty`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_from_env_b58bfdb55c | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.if_shell | execute `server.if_shell('true', 'set -g @if_test yes')` without exception; execute `server.cmd('show-options', '-gv', '@if_test').stdout[0]` and expect `'yes'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_if_shell_bfdd5e913f | 3.2a through 3.7b | planned parity | +| src/libtmux/options.py::libtmux.options.explode_arrays | execute `import io` without exception; execute `many_more_options = io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[1] screen*:title ''')` without exception; execute `many_more_flat_dict = parse_options_to_dict(many_more_options)` without exception; execute `many_more_flat_dict == { "terminal-features[0]": "xterm*:clipboard:ccolour:cstyle:focus", "terminal-features[1]": "screen*:title",}` and expect `True`; execute `explode_arrays(many_more_flat_dict) == { "terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 1: "screen*:title"}}` and expect `True`; execute `explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[5] screen*:title '''))) == { "terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 5: "screen*:title"}}` and expect `True`; execute `from libtmux._internal.sparse_array import SparseArray` without exception; execute `hooks_output = io.StringIO(r''' session-renamed[0] display-message 'renamed' session-renamed[5] refresh-client pane-focus-in[0] run-shell 'echo focus' ''')` without exception; execute `hooks_exploded = explode_arrays( parse_options_to_dict(hooks_output), force_array=True, )` without exception; execute `isinstance(hooks_exploded["session-renamed"], SparseArray)` and expect `True`; execute `hooks_exploded["session-renamed"][0]` and expect `"display-message 'renamed'"`; execute `hooks_exploded["session-renamed"][5]` and expect `'refresh-client'`; execute `sorted(hooks_exploded["session-renamed"].keys())` and expect `[0, 5]`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_explode_arrays_1e4dfcd9bd | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.explode_complex | execute `import io` without exception; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[5] screen*:title '''))))` and expect `{'terminal-features': {'xterm*': ['clipboard', 'ccolour', 'cstyle', 'focus'], 'screen*': ['title']}}`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[5] screen*:title ''')))) == { "terminal-features": {"xterm*": ["clipboard", "ccolour", "cstyle", "focus"], "screen*": ["title"]}}` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' command-alias[0] split-pane=split-window command-alias[1] splitp=split-window command-alias[2] "server-info=show-messages -JT" ''')))) == { "command-alias": {"split-pane": "split-window", "splitp": "split-window", "server-info": "show-messages -JT"}}` and expect `True`; execute `explode_complex(explode_arrays({"terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 1: "screen*:title"}}))` and expect `{'terminal-features': {0: 'xterm*:clipboard:ccolour:cstyle:focus', 1: 'screen*:title'}}`; execute `explode_complex(explode_arrays({"terminal-features": {0: "xterm*:clipboard:ccolour:cstyle:focus", 8: "screen*:title"}})) == SparseArray({'terminal-features': {0: 'xterm*:clipboard:ccolour:cstyle:focus', 8: 'screen*:title'}})` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' terminal-overrides[0] xterm-256color:Tc terminal-overrides[1] *:U8=0 ''')))) == { "terminal-overrides": {"xterm-256color": {"Tc": None}, "*": {"U8": 0}}}` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' user-keys[100] "\e[test" user-keys[6] "\e\n" user-keys[0] "\e[5;30012~" ''')))) == { "user-keys": {0: "\\e[5;30012~", 6: "\\e\\n", 100: "\\e[test"}}` and expect `True`; execute `explode_complex(explode_arrays(parse_options_to_dict(io.StringIO(r''' status-format[0] "#[align=left range=left #{E:status-left-style}]#[push-default]#{T;=/#{status-left-length}:status-left}#[pop-default]#[norange default]#[list=on align=#{status-justify}]#[list=left-marker]<#[list=right-marker]>#[list=on]#{W:#[range=window|#{window_index} #{E:window-status-style}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-format}#[pop-default]#[norange default]#{?window_end_flag,,#{window-status-separator}},#[range=window|#{window_index} list=focus #{?#{!=:#{E:window-status-current-style},default},#{E:window-status-current-style},#{E:window-status-style}}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-current-format}#[pop-default]#[norange list=on default]#{?window_end_flag,,#{window-status-separator}}}#[nolist align=right range=right #{E:status-right-style}]#[push-default]#{T;=/#{status-right-length}:status-right}#[pop-default]#[norange default]" status-format[1] "#[align=centre]#{P:#{?pane_active,#[reverse],}#{pane_index}[#{pane_width}x#{pane_height}]#[default] }" ''')))) == { "status-format": {0: "#[align=left range=left #{E:status-left-style}]#[push-default]#{T;=/#{status-left-length}:status-left}#[pop-default]#[norange default]#[list=on align=#{status-justify}]#[list=left-marker]<#[list=right-marker]>#[list=on]#{W:#[range=window|#{window_index} #{E:window-status-style}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-format}#[pop-default]#[norange default]#{?window_end_flag,,#{window-status-separator}},#[range=window|#{window_index} list=focus #{?#{!=:#{E:window-status-current-style},default},#{E:window-status-current-style},#{E:window-status-style}}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-current-format}#[pop-default]#[norange list=on default]#{?window_end_flag,,#{window-status-separator}}}#[nolist align=right range=right #{E:status-right-style}]#[push-default]#{T;=/#{status-right-length}:status-right}#[pop-default]#[norange default]", 1: "#[align=centre]#{P:#{?pane_active,#[reverse],}#{pane_index}[#{pane_width}x#{pane_height}]#[default] }", }}` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_explode_complex_74022ddf0a | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.handle_option_error | execute `result = server.cmd( 'set-option', 'unknown-option-name', )` without exception; execute `bool(isinstance(result.stderr, list) and len(result.stderr))` and expect `True`; execute `import pytest` without exception; execute `from libtmux import exc` without exception; execute `with pytest.raises(exc.OptionError): handle_option_error(result.stderr[0])` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_handle_option_error_be7cdd74c1 | 3.3a through 3.7c | planned parity | +| src/libtmux/options.py::libtmux.options.parse_options_to_dict | execute `import io` without exception; execute `raw_options = io.StringIO("status-keys vi")` without exception; execute `parse_options_to_dict(raw_options) == {"status-keys": "vi"}` and expect `True`; execute `int_options = io.StringIO("message-limit 50")` without exception; execute `parse_options_to_dict(int_options) == {"message-limit": "50"}` and expect `True`; execute `empty_option = io.StringIO("user-keys")` without exception; execute `parse_options_to_dict(empty_option) == {"user-keys": None}` and expect `True`; execute `array_option = io.StringIO("command-alias[0] split-pane=split-window")` without exception; execute `parse_options_to_dict(array_option) == { "command-alias[0]": "split-pane=split-window"}` and expect `True`; execute `array_option = io.StringIO("command-alias[40] split-pane=split-window")` without exception; execute `parse_options_to_dict(array_option) == { "command-alias[40]": "split-pane=split-window"}` and expect `True`; execute `many_options = io.StringIO(r'''status-keys command-alias[0] split-pane=split-window ''')` without exception; execute `parse_options_to_dict(many_options) == { "command-alias[0]": "split-pane=split-window", "status-keys": None,}` and expect `True`; execute `many_more_options = io.StringIO(r''' terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus terminal-features[1] screen*:title ''')` without exception; execute `parse_options_to_dict(many_more_options) == { "terminal-features[0]": "xterm*:clipboard:ccolour:cstyle:focus", "terminal-features[1]": "screen*:title",}` and expect `True`; execute `quoted_option = io.StringIO(r''' command-alias[0] "choose-session=choose-tree -s" ''')` without exception; execute `parse_options_to_dict(quoted_option) == { "command-alias[0]": "choose-session=choose-tree -s", }` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_options_py_libtmux_options_parse_options_to_dict_c132265e66 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane | execute `pane` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`; execute `pane in window.panes` and expect `True`; execute `pane.window` and expect `Window(@1 1:..., Session($1 ...))`; execute `pane.session` and expect `Session($1 ...)`; execute `with window.split() as pane: pane.send_keys('echo "Hello"') # Do work with the pane # Pane will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_89b5a1a53c | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.at_bottom | execute `pane.pane_at_bottom` and expect `'1'`; execute `pane.at_bottom` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_bottom_6ca2a17fef | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.at_left | execute `pane.pane_at_left` and expect `'1'`; execute `pane.at_left` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_left_4e48fd214b | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.at_right | execute `pane.pane_at_right` and expect `'1'`; execute `pane.at_right` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_right_2dd8e8ca5d | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.at_top | execute `pane.pane_at_top` and expect `'1'`; execute `pane.at_top` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_at_top_968a50a65b | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.break_pane | execute `pane_to_break = window.split(shell='sleep 1m')` without exception; execute `new_window = pane_to_break.break_pane(window_name='broken')` without exception; execute `new_window.window_name` and expect `'broken'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_break_pane_a7e77fef15 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.capture_pane | execute `pane = window.split(shell='sh')` without exception; execute `pane.capture_pane()` and expect `['$']`; execute `pane.send_keys('echo "Hello world"', enter=True)` without exception; execute `pane.capture_pane()` and expect `['$ echo "Hello world"', 'Hello world', '$']`; execute `print(chr(10).join(pane.capture_pane()))` and expect `$ echo "Hello world" Hello world $`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_capture_pane_8fed04c38b | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.choose_buffer | execute `pane.choose_buffer()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_choose_buffer_f64d2f76c4 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.choose_client | execute `pane.choose_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_choose_client_b4b49269f0 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.choose_tree | execute `pane.choose_tree()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_choose_tree_4d42fc71ef | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.clear_history | execute `pane.clear_history()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_clear_history_28d6d8542e | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.clock_mode | execute `pane.clock_mode()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_clock_mode_634705470c | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.cmd | execute `pane.cmd('split-window', '-P').stdout[0]` and expect `'libtmux...:...'`; execute `Pane.from_pane_id(pane_id=pane.cmd( 'split-window', '-P', '-F#{pane_id}').stdout[0], server=pane.server)` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_cmd_3248ce2f06 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.copy_mode | execute `pane.copy_mode()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_copy_mode_e7635a3cab | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.customize_mode | execute `pane.customize_mode()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_customize_mode_22625c0e22 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.display_panes | execute `with control_mode() as ctl: window.active_pane.display_panes()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_display_panes_be2176f2a4 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.display_popup | execute `with control_mode() as ctl: pane.display_popup(command='true', close_on_exit=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_display_popup_ed5880c930 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.find_window | execute `pane.find_window('sh')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_find_window_fa049abe56 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.from_env | execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = { # doctest: +HIDE "TMUX": f"{socket_path},1,0", "TMUX_PANE": pane.pane_id, }` with doctest options `+ALLOW_UNICODE` without exception; execute `Pane.from_env(env)` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`; execute `Pane.from_env(env).pane_id == pane.pane_id` and expect `True`; execute `from libtmux import exc` without exception; execute `try: Pane.from_env({}) except exc.NotInsideTmux as e: print(e)` and expect `Not inside a tmux pane: $TMUX is unset or empty`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_from_env_752ca3f890 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.from_pane_id | execute `Pane.from_pane_id(server=pane.server, pane_id=pane.pane_id)` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_from_pane_id_5b9ce3a290 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.height | execute `pane.height.isdigit()` and expect `True`; execute `pane.height == pane.pane_height` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_height_2fb5ec6d49 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.id | execute `pane.id` and expect `'%1'`; execute `pane.id == pane.pane_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_id_24dc3473ba | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.index | execute `pane.index` and expect `'0'`; execute `pane.index == pane.pane_index` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_index_f37cfd7ca6 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.join | execute `pane_to_join = window.split(shell='sleep 1m')` without exception; execute `new_window = pane_to_join.break_pane()` without exception; execute `pane_to_join.join(window)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_join_308a926493 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.kill | execute `pane_1 = pane.split()` without exception; execute `pane_1 in window.panes` and expect `True`; execute `pane_1.kill()` without exception; execute `pane_1 not in window.panes` and expect `True`; execute `pane.window.resize(height=100, width=100)` and expect `Window(@1 1...)`; execute `one_pane_to_rule_them_all = pane.split()` without exception; execute `other_panes = pane.split( ), pane.split()` without exception; execute `all([p in window.panes for p in other_panes])` and expect `True`; execute `one_pane_to_rule_them_all.kill(all_except=True)` without exception; execute `all([p not in window.panes for p in other_panes])` and expect `True`; execute `one_pane_to_rule_them_all in window.panes` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_kill_e25c6299dc | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.move | execute `pane_to_move = window.split(shell='sleep 1m')` without exception; execute `w2 = session.new_window(window_name='move_target')` without exception; execute `pane_to_move.move(w2)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_move_b01c6d05c7 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.new_pane | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): floating = pane.new_pane(width=40, height=10, shell="sleep 5") is_floating = floating.pane_floating_flag else: is_floating = "1"` without exception; execute `is_floating` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_new_pane_11b0985861 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.paste_buffer | execute `server.set_buffer('pasted_text')` without exception; execute `pane.paste_buffer()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_paste_buffer_3b3c32fdc0 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.pipe | execute `pane.pipe('cat >> /tmp/output.txt')` without exception; execute `pane.pipe()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_pipe_af350fb3ca | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.refresh | execute `pane.refresh()` without exception; execute `pane.pane_id` and expect `'%1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_refresh_b2091c58de | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.reset | execute `pane.reset()` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_reset_9e06d19ed4 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.respawn | execute `pane = window.split(shell='sleep 1m')` without exception; execute `pane.respawn(kill=True, shell='sh')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_respawn_b7ddfec773 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.select | execute `pane = window.active_pane` without exception; execute `new_pane = window.split()` without exception; execute `pane.refresh()` without exception; execute `active_panes = [p for p in window.panes if p.pane_active == '1']` without exception; execute `pane in active_panes` and expect `True`; execute `new_pane in active_panes` and expect `False`; execute `new_pane.pane_active == '1'` and expect `False`; execute `new_pane.select()` and expect `Pane(...)`; execute `new_pane.pane_active == '1'` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_select_02aaa45270 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.send_keys | execute `pane = window.split(shell='sh')` without exception; execute `pane.capture_pane()` and expect `['$']`; execute `pane.send_keys('echo "Hello world"', enter=True)` without exception; execute `pane.capture_pane()` and expect `['$ echo "Hello world"', 'Hello world', '$']`; execute `print('\n'.join(pane.capture_pane())) # doctest: +NORMALIZE_WHITESPACE` with doctest options `+NORMALIZE_WHITESPACE` and expect `$ echo "Hello world" Hello world $`; execute `pane.send_keys(reset=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_send_keys_bcca63c4d0 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.send_prefix | execute `pane.send_prefix()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_send_prefix_75166732ce | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.set_title | execute `pane.set_title('my-title')` and expect `Pane(...)`; execute `pane.pane_title` and expect `'my-title'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_set_title_3b3cf2e8a5 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.split | execute `(pane.at_left, pane.at_right, pane.at_top, pane.at_bottom)` and expect `(True, True, True, True)`; execute `new_pane = pane.split()` without exception; execute `(new_pane.at_left, new_pane.at_right, new_pane.at_top, new_pane.at_bottom)` and expect `(True, True, False, True)`; execute `right_pane = pane.split(direction=PaneDirection.Right)` without exception; execute `(right_pane.at_left, right_pane.at_right, right_pane.at_top, right_pane.at_bottom)` and expect `(False, True, True, False)`; execute `left_pane = pane.split(direction=PaneDirection.Left)` without exception; execute `(left_pane.at_left, left_pane.at_right, left_pane.at_top, left_pane.at_bottom)` and expect `(True, False, True, False)`; execute `top_pane = pane.split(direction=PaneDirection.Above)` without exception; execute `(top_pane.at_left, top_pane.at_right, top_pane.at_top, top_pane.at_bottom)` and expect `(False, False, True, False)`; execute `pane = session.new_window().active_pane` without exception; execute `top_pane = pane.split(direction=PaneDirection.Above, full_window_split=True)` without exception; execute `(top_pane.at_left, top_pane.at_right, top_pane.at_top, top_pane.at_bottom)` and expect `(True, True, True, False)`; execute `bottom_pane = pane.split( direction=PaneDirection.Below, full_window_split=True)` without exception; execute `(bottom_pane.at_left, bottom_pane.at_right, bottom_pane.at_top, bottom_pane.at_bottom)` and expect `(True, True, False, True)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_split_15e115b719 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.swap | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `pane1_id, pane2_id = pane1.pane_id, pane2.pane_id` without exception; execute `pane1.swap(pane2)` without exception; execute `pane1.refresh()` without exception; execute `pane2.refresh()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_swap_e3427460d0 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.title | execute `pane.set_title('test-alias')` and expect `Pane(...)`; execute `pane.title == pane.pane_title` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_title_6d895c42c3 | 3.3a through 3.7c | planned parity | +| src/libtmux/pane.py::libtmux.pane.Pane.width | execute `pane.width.isdigit()` and expect `True`; execute `pane.width == pane.pane_width` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pane_py_libtmux_pane_Pane_width_0c5eae94ee | 3.3a through 3.7c | planned parity | +| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.TestServer | execute `server = Server() # Create server instance` without exception; execute `server.new_session()` and expect `Session($... ...)`; execute `server.is_alive()` and expect `True`; execute `server2 = Server()` without exception; execute `server2.socket_name != server.socket_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_TestServer_b769bdd044 | 3.3a through 3.7c | planned parity | +| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.control_mode | execute `from libtmux._internal.control_mode import ControlMode` without exception; execute `def test_example(control_mode): with control_mode() as ctl: assert ctl.client_name != ''` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_control_mode_bd791351b1 | 3.3a through 3.7c | planned parity | +| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.server | execute `from libtmux.server import Server` without exception; execute `def test_example(server: Server) -> None: assert isinstance(server, Server) session = server.new_session('my session') assert len(server.sessions) == 1 assert [session.name.startswith('my') for session in server.sessions]` without exception; execute `locals().keys()` and expect `dict_keys(...)`; execute `source = ''.join([e.source for e in request._pyfuncitem.dtest.examples][:3])` without exception; execute `pytester = request.getfixturevalue('pytester')` without exception; execute `pytester.makepyfile(**{'whatever.py': source})` and expect `PosixPath(...)`; execute `result = pytester.runpytest('whatever.py', '--disable-warnings')` and expect `===...`; execute `result.assert_outcomes(passed=1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_server_acc91d3b27 | 3.3a through 3.7c | planned parity | +| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.session | execute `from libtmux.session import Session` without exception; execute `def test_example(session: "Session") -> None: assert isinstance(session.name, str) assert session.name.startswith('libtmux_') window = session.new_window(window_name='new one') assert window.name == 'new one'` without exception; execute `locals().keys()` and expect `dict_keys(...)`; execute `source = ''.join([e.source for e in request._pyfuncitem.dtest.examples][:3])` without exception; execute `pytester = request.getfixturevalue('pytester')` without exception; execute `pytester.makepyfile(**{'whatever.py': source})` and expect `PosixPath(...)`; execute `result = pytester.runpytest('whatever.py', '--disable-warnings')` and expect `===...`; execute `result.assert_outcomes(passed=1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_session_cc63e7ba8b | 3.3a through 3.7c | planned parity | +| src/libtmux/pytest_plugin.py::libtmux.pytest_plugin.session_params | execute `import pytest` without exception; execute `from libtmux.session import Session` without exception; execute `@pytest.fixture def session_params(session_params): return { 'x': 800, 'y': 600, }` without exception; execute `def test_example(session: "Session") -> None: assert isinstance(session.name, str) assert session.name.startswith('libtmux_') window = session.new_window(window_name='new one') assert window.name == 'new one'` without exception; execute `locals().keys()` and expect `dict_keys(...)`; execute `source = ''.join([e.source for e in request._pyfuncitem.dtest.examples][:4])` without exception; execute `pytester = request.getfixturevalue('pytester')` without exception; execute `pytester.makepyfile(**{'whatever.py': source})` and expect `PosixPath(...)`; execute `result = pytester.runpytest('whatever.py', '--disable-warnings')` and expect `===...`; execute `result.assert_outcomes(passed=1)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_pytest_plugin_py_libtmux_pytest_plugin_session_params_50f29f53fe | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server | execute `server` and expect `Server(socket_name=libtmux_test...)`; execute `server.sessions` and expect `[Session($1 ...)]`; execute `server.sessions[0].windows` and expect `[Window(@1 1:..., Session($1 ...))]`; execute `server.sessions[0].active_window` and expect `Window(@1 1:..., Session($1 ...))`; execute `server.sessions[0].active_pane` and expect `Pane(%1 Window(@1 1:..., Session($1 ...)))`; execute `with Server() as server: session = server.new_session() # Do work with the session # Server will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_57e38b41fe | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.attached_sessions | Given session rows whose session_attached values are '0', '1', and '2', Server.attachedSessions() returns only the '1' and '2' sessions by testing the attached count greater than zero; it never dispatches the Python noeq lookup. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_attached_sessions_96d2258b22 | 3.3a through 3.7c | deliberate Java correction | +| src/libtmux/server.py::libtmux.server.Server.bind_key | execute `server.bind_key('F12', 'display-message test', key_table='root')` without exception; execute `server.unbind_key('F12', key_table='root')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_bind_key_2ad6079d5e | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.clear_prompt_history | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.3"): server.clear_prompt_history()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_clear_prompt_history_194828cc5f | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.clients | execute `with control_mode() as ctl: names = [c.client_name for c in server.clients] ctl.client_name in names` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_clients_075e2b36ef | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.cmd | execute `server.cmd('display-message', 'hi')` and expect `<libtmux.common.tmux_cmd object at ...>`; execute `server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]` and expect `'$2'`; execute `session.cmd('new-window', '-P').stdout[0]` and expect `'libtmux...:2.0'`; execute `Window.from_window_id(window_id=session.cmd( 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server)` and expect `Window(@4 3:..., Session($1 libtmux_...))`; execute `window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0]` and expect `'%5'`; execute `Pane.from_pane_id(pane_id=window.cmd( 'split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server)` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_cmd_ac953d76be | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.command_prompt | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.4"): with control_mode() as ctl: server.command_prompt( "set -g @cp_test '%1'", target_client=ctl.client_name, ) for key in ['h', 'i', 'Enter']: _ = server.cmd('send-keys', '-K', '-c', ctl.client_name, key) result = server.cmd('show-options', '-gv', '@cp_test').stdout[0] else: result = 'hi'` without exception; execute `result` and expect `'hi'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_command_prompt_a66476935c | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.confirm_before | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.4"): with control_mode() as ctl: server.confirm_before( 'set -g @cf_test yes', target_client=ctl.client_name, ) _ = server.cmd('send-keys', '-K', '-c', ctl.client_name, 'y') result = server.cmd('show-options', '-gv', '@cf_test').stdout[0] else: result = 'yes'` without exception; execute `result` and expect `'yes'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_confirm_before_9850ce21b4 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.delete_buffer | execute `server.set_buffer('to_delete', buffer_name='del_buf')` without exception; execute `server.delete_buffer(buffer_name='del_buf')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_delete_buffer_468802c3c4 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.detach_all_clients | execute `with control_mode() as ctl: server.detach_all_clients(keep_client=ctl.client_name)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_detach_all_clients_a8327a2553 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.detach_client | execute `with control_mode() as ctl: server.detach_client(target_client=ctl.client_name)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_detach_client_3ee32134c7 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.display_menu | execute `captured = []` without exception; execute `def fake_cmd(name, *args, **_kw): captured.append((name, args)) return type('R', (), {'stderr': [], 'stdout': []})()` without exception; execute `monkeypatch.setattr(server, 'cmd', fake_cmd)` without exception; execute `server.display_menu('First', '1', 'select-pane', title='menu')` without exception; execute `captured[0][0]` and expect `'display-menu'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_display_menu_13c14bf74d | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.display_message | execute `result = server.display_message("#{version}", get_text=True)` without exception; execute `isinstance(result, list) and len(result) == 1` and expect `True`; execute `result = server.display_message("", get_text=True, all_formats=True)` without exception; execute `any("session_name=" in line for line in result)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_display_message_a97c79d3e0 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.from_env | execute `from libtmux.server import Server as TmuxServer # doctest: +HIDE` with doctest options `+ALLOW_UNICODE` without exception; execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = { # doctest: +HIDE "TMUX": f"{socket_path},1,0", "TMUX_PANE": pane.pane_id, }` with doctest options `+ALLOW_UNICODE` without exception; execute `TmuxServer.from_env(env)` and expect `Server(socket_path=...)`; execute `TmuxServer.from_env(env).is_alive()` and expect `True`; execute `try: TmuxServer.from_env({}) except exc.NotInsideTmux as e: print(e)` and expect `Not inside a tmux pane: $TMUX is unset or empty`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_from_env_b58bfdb55c | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.if_shell | execute `server.if_shell('true', 'set -g @if_test yes')` without exception; execute `server.cmd('show-options', '-gv', '@if_test').stdout[0]` and expect `'yes'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_if_shell_bfdd5e913f | 3.3a through 3.7c | planned parity | | src/libtmux/server.py::libtmux.server.Server.is_alive | execute `tmux = Server(socket_name="no_exist")` without exception; execute `assert not tmux.is_alive()` without exception. | documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_is_alive_f9b9133a35 | not tmux-bound | planned parity | | src/libtmux/server.py::libtmux.server.Server.kill | execute `svr = Server(socket_name="testing")` without exception; execute `svr` and expect `Server(socket_name=testing)`; execute `svr.new_session()` and expect `Session(...)`; execute `svr.is_alive()` and expect `True`; execute `svr.kill()` without exception; execute `svr.is_alive()` and expect `False`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_kill_35fdecdce9 | not tmux-bound | planned parity | -| src/libtmux/server.py::libtmux.server.Server.list_buffers | execute `server.set_buffer('buf_data')` without exception; execute `result = server.list_buffers()` without exception; execute `len(result) >= 1` and expect `True`; execute `server.set_buffer('hello', buffer_name='gap6_demo')` without exception; execute `'gap6_demo' in server.list_buffers(format_string='#{buffer_name}')` and expect `True`; execute `matches = server.list_buffers( format_string='#{buffer_name}', filter='#{m:gap6_*,#{buffer_name}}', )` without exception; execute `'gap6_demo' in matches` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_buffers_48773b833a | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.list_clients | execute `isinstance(server.list_clients(), list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_clients_bac98f9236 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.list_commands | execute `result = server.list_commands(command_name='send-keys')` without exception; execute `len(result) >= 1` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_commands_69a447b6cf | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.list_keys | execute `result = server.list_keys()` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_keys_c755b7257d | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.load_buffer | execute `import pathlib` without exception; execute `path = pathlib.Path(request.config.rootdir) / '..' / 'tmp_load.txt'` without exception; execute `_ = path.write_text('loaded')` without exception; execute `server.load_buffer(str(path), buffer_name='loaded_buf')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_load_buffer_b6b92f939b | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.lock_client | execute `with control_mode() as ctl: server.lock_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_lock_client_2e125fb771 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.lock_server | execute `with control_mode() as ctl: server.lock_server()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_lock_server_2297d65e57 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.new_session | execute `server.new_session()` and expect `Session($2 2)`; execute `server.new_session()` and expect `Session($3 3)`; execute `server.new_session(session_name='my session')` and expect `Session($4 my session)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_new_session_08418c7bfe | 3.2a through 3.7b | planned parity | +| src/libtmux/server.py::libtmux.server.Server.list_buffers | execute `server.set_buffer('buf_data')` without exception; execute `result = server.list_buffers()` without exception; execute `len(result) >= 1` and expect `True`; execute `server.set_buffer('hello', buffer_name='gap6_demo')` without exception; execute `'gap6_demo' in server.list_buffers(format_string='#{buffer_name}')` and expect `True`; execute `matches = server.list_buffers( format_string='#{buffer_name}', filter='#{m:gap6_*,#{buffer_name}}', )` without exception; execute `'gap6_demo' in matches` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_buffers_48773b833a | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.list_clients | execute `isinstance(server.list_clients(), list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_clients_bac98f9236 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.list_commands | execute `result = server.list_commands(command_name='send-keys')` without exception; execute `len(result) >= 1` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_commands_69a447b6cf | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.list_keys | execute `result = server.list_keys()` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_list_keys_c755b7257d | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.load_buffer | execute `import pathlib` without exception; execute `path = pathlib.Path(request.config.rootdir) / '..' / 'tmp_load.txt'` without exception; execute `_ = path.write_text('loaded')` without exception; execute `server.load_buffer(str(path), buffer_name='loaded_buf')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_load_buffer_b6b92f939b | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.lock_client | execute `with control_mode() as ctl: server.lock_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_lock_client_2e125fb771 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.lock_server | execute `with control_mode() as ctl: server.lock_server()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_lock_server_2297d65e57 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.new_session | execute `server.new_session()` and expect `Session($2 2)`; execute `server.new_session()` and expect `Session($3 3)`; execute `server.new_session(session_name='my session')` and expect `Session($4 my session)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_new_session_08418c7bfe | 3.3a through 3.7c | planned parity | | src/libtmux/server.py::libtmux.server.Server.raise_if_dead | execute `tmux = Server(socket_name="no_exist")` without exception; execute `try: tmux.raise_if_dead() except Exception as e: print(type(e))` and expect `<class 'subprocess.CalledProcessError'>`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_raise_if_dead_44ee0e8059 | not tmux-bound | planned parity | -| src/libtmux/server.py::libtmux.server.Server.refresh_client | execute `with control_mode() as ctl: server.refresh_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_refresh_client_e5e214385b | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.run_shell | execute `result = server.run_shell('true')` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_run_shell_23c807ff8c | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.save_buffer | execute `import pathlib` without exception; execute `server.set_buffer('save_me')` without exception; execute `path = pathlib.Path(request.config.rootdir) / '..' / 'tmp_save.txt'` without exception; execute `server.save_buffer(str(path))` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_save_buffer_1e163deb5e | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.search_panes | execute `sess = server.new_session(session_name='gap7_pane_demo')` without exception; execute `window = sess.active_window` without exception; execute `target_pane = window.split()` without exception; execute `matches = server.search_panes( filter=f'#{{m:{target_pane.pane_id},#{{pane_id}}}}' )` without exception; execute `[p.pane_id for p in matches] == [target_pane.pane_id]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_search_panes_ace9238c91 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.search_sessions | execute `server.new_session(session_name='gap7_alpha')` and expect `Session($... gap7_alpha)`; execute `server.new_session(session_name='other_beta')` and expect `Session($... other_beta)`; execute `matches = server.search_sessions(filter='#{m:gap7_*,#{session_name}}')` without exception; execute `[s.session_name for s in matches]` and expect `['gap7_alpha']`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_search_sessions_63266e09fc | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.search_windows | execute `sess = server.new_session(session_name='gap7_win_demo')` without exception; execute `_ = sess.new_window(window_name='gap7_target')` without exception; execute `_ = sess.new_window(window_name='other_window')` without exception; execute `matches = server.search_windows(filter='#{m:gap7_*,#{window_name}}')` without exception; execute `any(w.window_name == 'gap7_target' for w in matches)` and expect `True`; execute `any(w.window_name == 'other_window' for w in matches)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_search_windows_989f156b66 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.server_access | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.3"): result = server.server_access(list_access=True) assert isinstance(result, list)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_server_access_1c74bee94f | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.set_buffer | execute `server.set_buffer('hello')` without exception; execute `server.show_buffer()` and expect `'hello'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_set_buffer_135fc0776b | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.show_buffer | execute `server.set_buffer('test_data')` without exception; execute `server.show_buffer()` and expect `'test_data'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_show_buffer_c14fd1870c | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.show_messages | execute `with control_mode() as ctl: result = server.show_messages(target_client=ctl.client_name)` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_show_messages_d9d52ef73f | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.show_prompt_history | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.3"): result = server.show_prompt_history() else: result = []` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_show_prompt_history_a9e4067d38 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.source_file | execute `import pathlib` without exception; execute `conf = pathlib.Path(request.config.rootdir) / '..' / 'tmp_src.conf'` without exception; execute `_ = conf.write_text('set -g @test_source yes')` without exception; execute `server.source_file(str(conf))` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_source_file_9505d86519 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.start_server | execute `server.start_server()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_start_server_6525c77f33 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.suspend_client | execute `with control_mode() as ctl: server.suspend_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_suspend_client_d073b2cc5b | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.unbind_key | execute `server.bind_key('F11', 'display-message test', key_table='root')` without exception; execute `server.unbind_key('F11', key_table='root')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_unbind_key_2b54a48018 | 3.2a through 3.7b | planned parity | -| src/libtmux/server.py::libtmux.server.Server.wait_for | execute `server.new_session(session_name='wait_test')` and expect `Session(...)`; execute `server.wait_for('test_channel', set_flag=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_wait_for_d9ac7b9cee | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session | execute `session` and expect `Session($1 ...)`; execute `session.windows` and expect `[Window(@1 ...:..., Session($1 ...)]`; execute `session.active_window` and expect `Window(@1 ...:..., Session($1 ...))`; execute `session.active_pane` and expect `Pane(%1 Window(@1 ...:..., Session($1 ...)))`; execute `with server.new_session() as session: window = session.new_window() # Do work with the window # Session will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_dd81995c9e | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.attach | execute `session = server.new_session()` without exception; execute `session not in server.attached_sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_attach_89d15c2919 | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.cmd | execute `session.cmd('new-window', '-P').stdout[0]` and expect `'libtmux...:....0'`; execute `Window.from_window_id(window_id=session.cmd( 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server)` and expect `Window(@... ...:..., Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_cmd_cbbe5d98aa | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.detach_client | execute `with control_mode() as ctl: session.detach_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_detach_client_a026827392 | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.from_env | execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = {"TMUX": f"{socket_path},1,999", "TMUX_PANE": pane.pane_id}` without exception; execute `Session.from_env(env).session_id == session.session_id` and expect `True`; execute `Session.from_env(env)` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_from_env_0fe4a8131e | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.from_session_id | execute `Session.from_session_id( server=session.server, session_id=session.session_id )` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_from_session_id_477d46c325 | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.id | execute `session.id` and expect `'$1'`; execute `session.id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_id_3dd701484f | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.kill | execute `session_1 = server.new_session()` without exception; execute `session_1 in server.sessions` and expect `True`; execute `session_1.kill()` without exception; execute `session_1 not in server.sessions` and expect `True`; execute `one_session_to_rule_them_all = server.new_session()` without exception; execute `other_sessions = server.new_session( ), server.new_session()` without exception; execute `all([w in server.sessions for w in other_sessions])` and expect `True`; execute `one_session_to_rule_them_all.kill(all_except=True)` without exception; execute `all([w not in server.sessions for w in other_sessions])` and expect `True`; execute `one_session_to_rule_them_all in server.sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_kill_d778a9ed8d | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.last_window | execute `w1 = session.new_window(window_name='lw_a')` without exception; execute `w2 = session.new_window(window_name='lw_b', attach=True)` without exception; execute `session.last_window()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_last_window_26d2d1488f | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.lock_session | execute `session.lock_session()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_lock_session_0ff9f949d4 | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.name | execute `session.name` and expect `'libtmux_...'`; execute `session.name == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_name_b84ddae345 | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.new_window | execute `window_initial = session.new_window(window_name='Example')` without exception; execute `window_initial` and expect `Window(@... 2:Example, Session($1 libtmux_...))`; execute `window_initial.window_index` and expect `'2'`; execute `window_before = session.new_window( window_name='Window before', direction=WindowDirection.Before)` without exception; execute `window_initial.refresh()` without exception; execute `window_before` and expect `Window(@... 1:Window before, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 3:Example, Session($1 libtmux_...))`; execute `window_after = session.new_window( window_name='Window after', direction=WindowDirection.After)` without exception; execute `window_initial.refresh()` without exception; execute `window_after.refresh()` without exception; execute `window_after` and expect `Window(@... 3:Window after, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 4:Example, Session($1 libtmux_...))`; execute `window_before` and expect `Window(@... 1:Window before, Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_new_window_e4664195cd | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.next_window | execute `w = session.new_window(window_name='nw_test')` without exception; execute `session.next_window()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_next_window_9f16a9321d | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.previous_window | execute `w = session.new_window(window_name='pw_test')` without exception; execute `session.previous_window()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_previous_window_40c7f40e6b | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.search_panes | execute `target_pane = session.active_window.split()` without exception; execute `matches = session.search_panes( filter=f'#{{m:{target_pane.pane_id},#{{pane_id}}}}' )` without exception; execute `[p.pane_id for p in matches] == [target_pane.pane_id]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_search_panes_5d4648af7c | 3.2a through 3.7b | planned parity | -| src/libtmux/session.py::libtmux.session.Session.search_windows | execute `_ = session.new_window(window_name='gap7s_target')` without exception; execute `_ = session.new_window(window_name='other_window')` without exception; execute `matches = session.search_windows(filter='#{m:gap7s_*,#{window_name}}')` without exception; execute `[w.window_name for w in matches]` and expect `['gap7s_target']`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_search_windows_539ba119c0 | 3.2a through 3.7b | planned parity | +| src/libtmux/server.py::libtmux.server.Server.refresh_client | execute `with control_mode() as ctl: server.refresh_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_refresh_client_e5e214385b | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.run_shell | execute `result = server.run_shell('true')` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_run_shell_23c807ff8c | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.save_buffer | execute `import pathlib` without exception; execute `server.set_buffer('save_me')` without exception; execute `path = pathlib.Path(request.config.rootdir) / '..' / 'tmp_save.txt'` without exception; execute `server.save_buffer(str(path))` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_save_buffer_1e163deb5e | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.search_panes | execute `sess = server.new_session(session_name='gap7_pane_demo')` without exception; execute `window = sess.active_window` without exception; execute `target_pane = window.split()` without exception; execute `matches = server.search_panes( filter=f'#{{m:{target_pane.pane_id},#{{pane_id}}}}' )` without exception; execute `[p.pane_id for p in matches] == [target_pane.pane_id]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_search_panes_ace9238c91 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.search_sessions | execute `server.new_session(session_name='gap7_alpha')` and expect `Session($... gap7_alpha)`; execute `server.new_session(session_name='other_beta')` and expect `Session($... other_beta)`; execute `matches = server.search_sessions(filter='#{m:gap7_*,#{session_name}}')` without exception; execute `[s.session_name for s in matches]` and expect `['gap7_alpha']`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_search_sessions_63266e09fc | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.search_windows | execute `sess = server.new_session(session_name='gap7_win_demo')` without exception; execute `_ = sess.new_window(window_name='gap7_target')` without exception; execute `_ = sess.new_window(window_name='other_window')` without exception; execute `matches = server.search_windows(filter='#{m:gap7_*,#{window_name}}')` without exception; execute `any(w.window_name == 'gap7_target' for w in matches)` and expect `True`; execute `any(w.window_name == 'other_window' for w in matches)` and expect `False`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_search_windows_989f156b66 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.server_access | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.3"): result = server.server_access(list_access=True) assert isinstance(result, list)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_server_access_1c74bee94f | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.set_buffer | execute `server.set_buffer('hello')` without exception; execute `server.show_buffer()` and expect `'hello'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_set_buffer_135fc0776b | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.show_buffer | execute `server.set_buffer('test_data')` without exception; execute `server.show_buffer()` and expect `'test_data'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_show_buffer_c14fd1870c | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.show_messages | execute `with control_mode() as ctl: result = server.show_messages(target_client=ctl.client_name)` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_show_messages_d9d52ef73f | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.show_prompt_history | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.3"): result = server.show_prompt_history() else: result = []` without exception; execute `isinstance(result, list)` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_show_prompt_history_a9e4067d38 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.source_file | execute `import pathlib` without exception; execute `conf = pathlib.Path(request.config.rootdir) / '..' / 'tmp_src.conf'` without exception; execute `_ = conf.write_text('set -g @test_source yes')` without exception; execute `server.source_file(str(conf))` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_source_file_9505d86519 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.start_server | execute `server.start_server()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_start_server_6525c77f33 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.suspend_client | execute `with control_mode() as ctl: server.suspend_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_suspend_client_d073b2cc5b | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.unbind_key | execute `server.bind_key('F11', 'display-message test', key_table='root')` without exception; execute `server.unbind_key('F11', key_table='root')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_unbind_key_2b54a48018 | 3.3a through 3.7c | planned parity | +| src/libtmux/server.py::libtmux.server.Server.wait_for | execute `server.new_session(session_name='wait_test')` and expect `Session(...)`; execute `server.wait_for('test_channel', set_flag=True)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_server_py_libtmux_server_Server_wait_for_d9ac7b9cee | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session | execute `session` and expect `Session($1 ...)`; execute `session.windows` and expect `[Window(@1 ...:..., Session($1 ...)]`; execute `session.active_window` and expect `Window(@1 ...:..., Session($1 ...))`; execute `session.active_pane` and expect `Pane(%1 Window(@1 ...:..., Session($1 ...)))`; execute `with server.new_session() as session: window = session.new_window() # Do work with the window # Session will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_dd81995c9e | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.attach | execute `session = server.new_session()` without exception; execute `session not in server.attached_sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_attach_89d15c2919 | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.cmd | execute `session.cmd('new-window', '-P').stdout[0]` and expect `'libtmux...:....0'`; execute `Window.from_window_id(window_id=session.cmd( 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server)` and expect `Window(@... ...:..., Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_cmd_cbbe5d98aa | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.detach_client | execute `with control_mode() as ctl: session.detach_client()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_detach_client_a026827392 | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.from_env | execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = {"TMUX": f"{socket_path},1,999", "TMUX_PANE": pane.pane_id}` without exception; execute `Session.from_env(env).session_id == session.session_id` and expect `True`; execute `Session.from_env(env)` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_from_env_0fe4a8131e | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.from_session_id | execute `Session.from_session_id( server=session.server, session_id=session.session_id )` and expect `Session($1 ...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_from_session_id_477d46c325 | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.id | execute `session.id` and expect `'$1'`; execute `session.id == session.session_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_id_3dd701484f | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.kill | execute `session_1 = server.new_session()` without exception; execute `session_1 in server.sessions` and expect `True`; execute `session_1.kill()` without exception; execute `session_1 not in server.sessions` and expect `True`; execute `one_session_to_rule_them_all = server.new_session()` without exception; execute `other_sessions = server.new_session( ), server.new_session()` without exception; execute `all([w in server.sessions for w in other_sessions])` and expect `True`; execute `one_session_to_rule_them_all.kill(all_except=True)` without exception; execute `all([w not in server.sessions for w in other_sessions])` and expect `True`; execute `one_session_to_rule_them_all in server.sessions` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_kill_d778a9ed8d | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.last_window | execute `w1 = session.new_window(window_name='lw_a')` without exception; execute `w2 = session.new_window(window_name='lw_b', attach=True)` without exception; execute `session.last_window()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_last_window_26d2d1488f | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.lock_session | execute `session.lock_session()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_lock_session_0ff9f949d4 | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.name | execute `session.name` and expect `'libtmux_...'`; execute `session.name == session.session_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_name_b84ddae345 | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.new_window | execute `window_initial = session.new_window(window_name='Example')` without exception; execute `window_initial` and expect `Window(@... 2:Example, Session($1 libtmux_...))`; execute `window_initial.window_index` and expect `'2'`; execute `window_before = session.new_window( window_name='Window before', direction=WindowDirection.Before)` without exception; execute `window_initial.refresh()` without exception; execute `window_before` and expect `Window(@... 1:Window before, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 3:Example, Session($1 libtmux_...))`; execute `window_after = session.new_window( window_name='Window after', direction=WindowDirection.After)` without exception; execute `window_initial.refresh()` without exception; execute `window_after.refresh()` without exception; execute `window_after` and expect `Window(@... 3:Window after, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 4:Example, Session($1 libtmux_...))`; execute `window_before` and expect `Window(@... 1:Window before, Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_new_window_e4664195cd | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.next_window | execute `w = session.new_window(window_name='nw_test')` without exception; execute `session.next_window()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_next_window_9f16a9321d | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.previous_window | execute `w = session.new_window(window_name='pw_test')` without exception; execute `session.previous_window()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_previous_window_40c7f40e6b | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.search_panes | execute `target_pane = session.active_window.split()` without exception; execute `matches = session.search_panes( filter=f'#{{m:{target_pane.pane_id},#{{pane_id}}}}' )` without exception; execute `[p.pane_id for p in matches] == [target_pane.pane_id]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_search_panes_5d4648af7c | 3.3a through 3.7c | planned parity | +| src/libtmux/session.py::libtmux.session.Session.search_windows | execute `_ = session.new_window(window_name='gap7s_target')` without exception; execute `_ = session.new_window(window_name='other_window')` without exception; execute `matches = session.search_windows(filter='#{m:gap7s_*,#{window_name}}')` without exception; execute `[w.window_name for w in matches]` and expect `['gap7s_target']`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_session_py_libtmux_session_Session_search_windows_539ba119c0 | 3.3a through 3.7c | planned parity | | src/libtmux/test/random.py::libtmux.test.random.RandomStrSequence.__init__ | execute `rng = RandomStrSequence() # pragma: no cover` without exception; execute `next(rng) # pragma: no cover` and expect `'...'`; execute `len(next(rng)) # pragma: no cover` and expect `8`; execute `type(next(rng)) # pragma: no cover` and expect `<class 'str'>`. | documentation contract test | PythonBehaviorParityTest#src_libtmux_test_random_py_libtmux_test_random_RandomStrSequence_init_9185b29fc3 | not tmux-bound | planned parity | -| src/libtmux/test/random.py::libtmux.test.random.get_test_session_name | execute `get_test_session_name(server=server) # pragma: no cover` and expect `'libtmux_...'`; execute `name1 = get_test_session_name(server=server) # pragma: no cover` without exception; execute `name2 = get_test_session_name(server=server) # pragma: no cover` without exception; execute `name1 != name2 # pragma: no cover` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_random_py_libtmux_test_random_get_test_session_name_73d632a4e5 | 3.2a through 3.7b | planned parity | -| src/libtmux/test/random.py::libtmux.test.random.get_test_window_name | execute `get_test_window_name(session=session) # pragma: no cover` and expect `'libtmux_...'`; execute `name1 = get_test_window_name(session=session) # pragma: no cover` without exception; execute `name2 = get_test_window_name(session=session) # pragma: no cover` without exception; execute `name1 != name2 # pragma: no cover` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_random_py_libtmux_test_random_get_test_window_name_bd6e68b630 | 3.2a through 3.7b | planned parity | -| src/libtmux/test/retry.py::libtmux.test.retry.retry_until | execute `def fn(): p = session.active_window.active_pane return p.pane_current_path is not None` without exception; execute `retry_until(fn)` and expect `True`; execute `assert retry_until(fn, raises=False)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_retry_py_libtmux_test_retry_retry_until_9aca6b0f7a | 3.2a through 3.7b | planned parity | -| src/libtmux/test/temporary.py::libtmux.test.temporary.temp_session | execute `with temp_session(server) as session: session.new_window(window_name='my window')` and expect `Window(@3 2:my window, Session($... ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_temporary_py_libtmux_test_temporary_temp_session_8f0f5e105b | 3.2a through 3.7b | planned parity | -| src/libtmux/test/temporary.py::libtmux.test.temporary.temp_window | execute `with temp_window(session) as window: window` and expect `Window(@2 2:... Session($1 libtmux_...))`; execute `with temp_window(session) as window: window.split()` and expect `Pane(%4 Window(@3 2:libtmux_..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_temporary_py_libtmux_test_temporary_temp_window_e4d8c7c838 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window | execute `window = session.new_window('My project', attach=True)` without exception; execute `window` and expect `Window(@2 2:My project, Session($... ...))`; execute `window.panes` and expect `[Pane(...)]`; execute `window.active_pane` and expect `Pane(...)`; execute `window.session` and expect `Session(...)`; execute `window.window_id == session.active_window.window_id` and expect `True`; execute `window == session.active_window` and expect `True`; execute `window in session.windows` and expect `True`; execute `with session.new_window() as window: pane = window.split() # Do work with the pane # Window will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_55c6fc7924 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.cmd | execute `window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0]` and expect `'%...'`; execute `Pane.from_pane_id(pane_id=session.cmd( 'split-window', '-P', '-F#{pane_id}').stdout[0], server=session.server)` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_cmd_a1c9fd2353 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.display_message | execute `result = window.display_message("#{window_id}", get_text=True)` without exception; execute `result[0].startswith("@")` and expect `True`; execute `result = window.display_message( "#{window_zoomed_flag}", get_text=True )` without exception; execute `result[0] in {"0", "1"}` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_display_message_7bbef1fecf | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.from_env | execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = { # doctest: +HIDE "TMUX": f"{socket_path},1,0", "TMUX_PANE": pane.pane_id, }` with doctest options `+ALLOW_UNICODE` without exception; execute `Window.from_env(env)` and expect `Window(@1 1:..., Session($1 ...))`; execute `_ = session.new_window(window_name="foreground", attach=True)` without exception; execute `Window.from_env(env).window_id == window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_from_env_d2f7ecc8c4 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.from_window_id | execute `Window.from_window_id(server=window.server, window_id=window.window_id)` and expect `Window(@1 1:..., Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_from_window_id_6b5204f062 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.height | execute `window.height.isdigit()` and expect `True`; execute `window.height == window.window_height` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_height_850114ffbe | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.id | execute `window.id` and expect `'@1'`; execute `window.id == window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_id_50e7f4b6ad | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.index | execute `window.index` and expect `'1'`; execute `window.index == window.window_index` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_index_308add77f4 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.kill | execute `window_1 = session.new_window()` without exception; execute `window_1 in session.windows` and expect `True`; execute `window_1.kill()` without exception; execute `window_1 not in session.windows` and expect `True`; execute `one_window_to_rule_them_all = session.new_window()` without exception; execute `other_windows = session.new_window( ), session.new_window()` without exception; execute `all([w in session.windows for w in other_windows])` and expect `True`; execute `one_window_to_rule_them_all.kill(all_except=True)` without exception; execute `all([w not in session.windows for w in other_windows])` and expect `True`; execute `one_window_to_rule_them_all in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_kill_3a41259e19 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.last_pane | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `pane2.select()` and expect `Pane(...)`; execute `result = window.last_pane()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_last_pane_858a47cafb | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.link | execute `w = session.new_window(window_name='link_test')` without exception; execute `s2 = server.new_session(session_name='link_target')` without exception; execute `w.link(s2)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_link_15453f5dea | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.linked_sessions | execute `window = session.new_window(window_name="solo", attach=False)` without exception; execute `[s.session_name for s in window.linked_sessions] == [session.session_name]` and expect `True`; execute `guest = server.new_session(session_name="guest")` without exception; execute `target = f"{guest.session_id}:"` without exception; execute `_ = server.cmd("link-window", "-d", "-s", window.window_id, "-t", target)` without exception; execute `sorted(s.session_name for s in window.linked_sessions) == sorted( [session.session_name, "guest"] )` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_linked_sessions_a1cc1102f9 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.name | execute `window.name` and expect `'...'`; execute `window.name == window.window_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_name_57ed13f1f9 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.new_pane | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): floating = window.new_pane(width=40, height=10, shell="sleep 5") is_floating = floating.pane_floating_flag else: is_floating = "1"` without exception; execute `is_floating` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_new_pane_f7ca47ae60 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.new_window | execute `window_initial = session.new_window(window_name='Example')` without exception; execute `window_initial` and expect `Window(@... 2:Example, Session($1 libtmux_...))`; execute `window_initial.window_index` and expect `'2'`; execute `window_before = window_initial.new_window( window_name='Window before', direction=WindowDirection.Before)` without exception; execute `window_initial.refresh()` without exception; execute `window_before` and expect `Window(@... 2:Window before, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 3:Example, Session($1 libtmux_...))`; execute `window_after = window_initial.new_window( window_name='Window after', direction=WindowDirection.After)` without exception; execute `window_initial.refresh()` without exception; execute `window_after.refresh()` without exception; execute `window_after` and expect `Window(@... 4:Window after, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 3:Example, Session($1 libtmux_...))`; execute `window_before` and expect `Window(@... 2:Window before, Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_new_window_4e77c412b9 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.next_layout | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `window.next_layout()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_next_layout_63cca8ed9e | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.previous_layout | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `window.previous_layout()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_previous_layout_fa635c6fff | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.refresh | execute `window.refresh()` without exception; execute `window.window_id` and expect `'@1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_refresh_52082da539 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.rename_window | execute `window = session.active_window` without exception; execute `window.rename_window('My project')` and expect `Window(@1 1:My project, Session($1 ...))`; execute `window.rename_window('New name')` and expect `Window(@1 1:New name, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_rename_window_5a09364ebd | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.respawn | execute `window = session.new_window(window_name='respawn_test')` without exception; execute `window.respawn(kill=True, shell='sh')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_respawn_b6f1560fe2 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.rotate | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `window.rotate()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_rotate_ddf3350b16 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.search_panes | execute `target_pane = window.split()` without exception; execute `matches = window.search_panes( filter=f'#{{m:{target_pane.pane_id},#{{pane_id}}}}' )` without exception; execute `[p.pane_id for p in matches] == [target_pane.pane_id]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_search_panes_8a2e34eaaa | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.select | execute `window = session.active_window` without exception; execute `new_window = session.new_window()` without exception; execute `session.refresh()` without exception; execute `active_windows = [w for w in session.windows if w.window_active == '1']` without exception; execute `new_window.window_active == '1'` and expect `False`; execute `new_window.select()` and expect `Window(...)`; execute `new_window.window_active == '1'` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_select_95f3054322 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.swap | execute `w1 = session.new_window(window_name='swap_a')` without exception; execute `w2 = session.new_window(window_name='swap_b')` without exception; execute `w1_idx = w1.window_index` without exception; execute `w2_idx = w2.window_index` without exception; execute `w1.swap(w2)` without exception; execute `w1.window_index == w2_idx` and expect `True`; execute `w2.window_index == w1_idx` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_swap_80dddba00b | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.unlink | execute `w = session.new_window(window_name='unlink_test')` without exception; execute `s2 = server.new_session(session_name='unlink_s2')` without exception; execute `w.link(s2)` without exception; execute `linked = [x for x in s2.windows if x.window_name == 'unlink_test']` without exception; execute `linked[0].unlink()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_unlink_7d34a50a88 | 3.2a through 3.7b | planned parity | -| src/libtmux/window.py::libtmux.window.Window.width | execute `window.width.isdigit()` and expect `True`; execute `window.width == window.window_width` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_width_afa998b606 | 3.2a through 3.7b | planned parity | +| src/libtmux/test/random.py::libtmux.test.random.get_test_session_name | execute `get_test_session_name(server=server) # pragma: no cover` and expect `'libtmux_...'`; execute `name1 = get_test_session_name(server=server) # pragma: no cover` without exception; execute `name2 = get_test_session_name(server=server) # pragma: no cover` without exception; execute `name1 != name2 # pragma: no cover` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_random_py_libtmux_test_random_get_test_session_name_73d632a4e5 | 3.3a through 3.7c | planned parity | +| src/libtmux/test/random.py::libtmux.test.random.get_test_window_name | execute `get_test_window_name(session=session) # pragma: no cover` and expect `'libtmux_...'`; execute `name1 = get_test_window_name(session=session) # pragma: no cover` without exception; execute `name2 = get_test_window_name(session=session) # pragma: no cover` without exception; execute `name1 != name2 # pragma: no cover` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_random_py_libtmux_test_random_get_test_window_name_bd6e68b630 | 3.3a through 3.7c | planned parity | +| src/libtmux/test/retry.py::libtmux.test.retry.retry_until | execute `def fn(): p = session.active_window.active_pane return p.pane_current_path is not None` without exception; execute `retry_until(fn)` and expect `True`; execute `assert retry_until(fn, raises=False)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_retry_py_libtmux_test_retry_retry_until_9aca6b0f7a | 3.3a through 3.7c | planned parity | +| src/libtmux/test/temporary.py::libtmux.test.temporary.temp_session | execute `with temp_session(server) as session: session.new_window(window_name='my window')` and expect `Window(@3 2:my window, Session($... ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_temporary_py_libtmux_test_temporary_temp_session_8f0f5e105b | 3.3a through 3.7c | planned parity | +| src/libtmux/test/temporary.py::libtmux.test.temporary.temp_window | execute `with temp_window(session) as window: window` and expect `Window(@2 2:... Session($1 libtmux_...))`; execute `with temp_window(session) as window: window.split()` and expect `Pane(%4 Window(@3 2:libtmux_..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_test_temporary_py_libtmux_test_temporary_temp_window_e4d8c7c838 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window | execute `window = session.new_window('My project', attach=True)` without exception; execute `window` and expect `Window(@2 2:My project, Session($... ...))`; execute `window.panes` and expect `[Pane(...)]`; execute `window.active_pane` and expect `Pane(...)`; execute `window.session` and expect `Session(...)`; execute `window.window_id == session.active_window.window_id` and expect `True`; execute `window == session.active_window` and expect `True`; execute `window in session.windows` and expect `True`; execute `with session.new_window() as window: pane = window.split() # Do work with the pane # Window will be killed automatically when exiting the context` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_55c6fc7924 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.cmd | execute `window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0]` and expect `'%...'`; execute `Pane.from_pane_id(pane_id=session.cmd( 'split-window', '-P', '-F#{pane_id}').stdout[0], server=session.server)` and expect `Pane(%... Window(@... ...:..., Session($1 libtmux_...)))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_cmd_a1c9fd2353 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.display_message | execute `result = window.display_message("#{window_id}", get_text=True)` without exception; execute `result[0].startswith("@")` and expect `True`; execute `result = window.display_message( "#{window_zoomed_flag}", get_text=True )` without exception; execute `result[0] in {"0", "1"}` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_display_message_7bbef1fecf | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.from_env | execute `socket_path = server.cmd( # doctest: +HIDE "display-message", "-p", "-t", pane.pane_id, "#{socket_path}" ).stdout[0]` with doctest options `+ALLOW_UNICODE` without exception; execute `env = { # doctest: +HIDE "TMUX": f"{socket_path},1,0", "TMUX_PANE": pane.pane_id, }` with doctest options `+ALLOW_UNICODE` without exception; execute `Window.from_env(env)` and expect `Window(@1 1:..., Session($1 ...))`; execute `_ = session.new_window(window_name="foreground", attach=True)` without exception; execute `Window.from_env(env).window_id == window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_from_env_d2f7ecc8c4 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.from_window_id | execute `Window.from_window_id(server=window.server, window_id=window.window_id)` and expect `Window(@1 1:..., Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_from_window_id_6b5204f062 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.height | execute `window.height.isdigit()` and expect `True`; execute `window.height == window.window_height` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_height_850114ffbe | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.id | execute `window.id` and expect `'@1'`; execute `window.id == window.window_id` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_id_50e7f4b6ad | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.index | execute `window.index` and expect `'1'`; execute `window.index == window.window_index` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_index_308add77f4 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.kill | execute `window_1 = session.new_window()` without exception; execute `window_1 in session.windows` and expect `True`; execute `window_1.kill()` without exception; execute `window_1 not in session.windows` and expect `True`; execute `one_window_to_rule_them_all = session.new_window()` without exception; execute `other_windows = session.new_window( ), session.new_window()` without exception; execute `all([w in session.windows for w in other_windows])` and expect `True`; execute `one_window_to_rule_them_all.kill(all_except=True)` without exception; execute `all([w not in session.windows for w in other_windows])` and expect `True`; execute `one_window_to_rule_them_all in session.windows` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_kill_3a41259e19 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.last_pane | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `pane2.select()` and expect `Pane(...)`; execute `result = window.last_pane()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_last_pane_858a47cafb | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.link | execute `w = session.new_window(window_name='link_test')` without exception; execute `s2 = server.new_session(session_name='link_target')` without exception; execute `w.link(s2)` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_link_15453f5dea | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.linked_sessions | execute `window = session.new_window(window_name="solo", attach=False)` without exception; execute `[s.session_name for s in window.linked_sessions] == [session.session_name]` and expect `True`; execute `guest = server.new_session(session_name="guest")` without exception; execute `target = f"{guest.session_id}:"` without exception; execute `_ = server.cmd("link-window", "-d", "-s", window.window_id, "-t", target)` without exception; execute `sorted(s.session_name for s in window.linked_sessions) == sorted( [session.session_name, "guest"] )` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_linked_sessions_a1cc1102f9 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.name | execute `window.name` and expect `'...'`; execute `window.name == window.window_name` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_name_57ed13f1f9 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.new_pane | execute `from libtmux.common import has_gte_version` without exception; execute `if has_gte_version("3.7"): floating = window.new_pane(width=40, height=10, shell="sleep 5") is_floating = floating.pane_floating_flag else: is_floating = "1"` without exception; execute `is_floating` and expect `'1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_new_pane_f7ca47ae60 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.new_window | execute `window_initial = session.new_window(window_name='Example')` without exception; execute `window_initial` and expect `Window(@... 2:Example, Session($1 libtmux_...))`; execute `window_initial.window_index` and expect `'2'`; execute `window_before = window_initial.new_window( window_name='Window before', direction=WindowDirection.Before)` without exception; execute `window_initial.refresh()` without exception; execute `window_before` and expect `Window(@... 2:Window before, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 3:Example, Session($1 libtmux_...))`; execute `window_after = window_initial.new_window( window_name='Window after', direction=WindowDirection.After)` without exception; execute `window_initial.refresh()` without exception; execute `window_after.refresh()` without exception; execute `window_after` and expect `Window(@... 4:Window after, Session($1 libtmux_...))`; execute `window_initial` and expect `Window(@... 3:Example, Session($1 libtmux_...))`; execute `window_before` and expect `Window(@... 2:Window before, Session($1 libtmux_...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_new_window_4e77c412b9 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.next_layout | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `window.next_layout()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_next_layout_63cca8ed9e | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.previous_layout | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `window.previous_layout()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_previous_layout_fa635c6fff | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.refresh | execute `window.refresh()` without exception; execute `window.window_id` and expect `'@1'`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_refresh_52082da539 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.rename_window | execute `window = session.active_window` without exception; execute `window.rename_window('My project')` and expect `Window(@1 1:My project, Session($1 ...))`; execute `window.rename_window('New name')` and expect `Window(@1 1:New name, Session($1 ...))`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_rename_window_5a09364ebd | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.respawn | execute `window = session.new_window(window_name='respawn_test')` without exception; execute `window.respawn(kill=True, shell='sh')` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_respawn_b6f1560fe2 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.rotate | execute `pane1 = window.active_pane` without exception; execute `pane2 = window.split()` without exception; execute `window.rotate()` and expect `Window(...)`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_rotate_ddf3350b16 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.search_panes | execute `target_pane = window.split()` without exception; execute `matches = window.search_panes( filter=f'#{{m:{target_pane.pane_id},#{{pane_id}}}}' )` without exception; execute `[p.pane_id for p in matches] == [target_pane.pane_id]` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_search_panes_8a2e34eaaa | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.select | execute `window = session.active_window` without exception; execute `new_window = session.new_window()` without exception; execute `session.refresh()` without exception; execute `active_windows = [w for w in session.windows if w.window_active == '1']` without exception; execute `new_window.window_active == '1'` and expect `False`; execute `new_window.select()` and expect `Window(...)`; execute `new_window.window_active == '1'` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_select_95f3054322 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.swap | execute `w1 = session.new_window(window_name='swap_a')` without exception; execute `w2 = session.new_window(window_name='swap_b')` without exception; execute `w1_idx = w1.window_index` without exception; execute `w2_idx = w2.window_index` without exception; execute `w1.swap(w2)` without exception; execute `w1.window_index == w2_idx` and expect `True`; execute `w2.window_index == w1_idx` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_swap_80dddba00b | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.unlink | execute `w = session.new_window(window_name='unlink_test')` without exception; execute `s2 = server.new_session(session_name='unlink_s2')` without exception; execute `w.link(s2)` without exception; execute `linked = [x for x in s2.windows if x.window_name == 'unlink_test']` without exception; execute `linked[0].unlink()` without exception. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_unlink_7d34a50a88 | 3.3a through 3.7c | planned parity | +| src/libtmux/window.py::libtmux.window.Window.width | execute `window.width.isdigit()` and expect `True`; execute `window.width == window.window_width` and expect `True`. | real-tmux documentation contract test | PythonBehaviorParityTest#src_libtmux_window_py_libtmux_window_Window_width_afa998b606 | 3.3a through 3.7c | planned parity | | tests/_internal/test_query_list.py::test_filter[items0-None-expected_result0] | case inputs `expected_result=[Obj(test=1, fruit=[])]`, `filter_expr=None`, `items=[Obj(test=1, fruit=[])]`; execute `QueryList(items)`; when `filter_expr is not None and isinstance(filter_expr, dict)`: execute `qs.filter(**filter_expr)`; assert `qs.filter(**filter_expr) == expected_result`; when `filter_expr is not None and not (isinstance(filter_expr, dict))`: execute `qs.filter(filter_expr)`; assert `qs.filter(filter_expr) == expected_result`; when `not (filter_expr is not None)`: execute `qs.filter()`; assert `qs.filter() == expected_result`; when `len(expected_result) == 0`: execute `missing.match('No objects found')`; expect `pytest.raises(ObjectDoesNotExist)`; assert `missing.match('No objects found')`; when `len(expected_result) == 1 and isinstance(filter_expr, dict)`: assert `qs.get(**filter_expr) == expected_result[0]`; when `len(expected_result) == 1 and not (isinstance(filter_expr, dict))`: assert `qs.get(filter_expr) == expected_result[0]`; when `not (len(expected_result) == 1)`: execute `multiple.match('Multiple objects returned')`; expect `pytest.raises(MultipleObjectsReturned)`; assert `multiple.match('Multiple objects returned')`. | unit test | PythonBehaviorParityTest#sts_internal_test_query_list_py_test_filter_items0_None_expected_result0_53c1675603 | not tmux-bound | planned parity | | tests/_internal/test_query_list.py::test_filter[items1-filter_expr1-expected_result1] | case inputs `expected_result=[Obj(test=1, fruit=[])]`, `filter_expr={'test': 1}`, `items=[Obj(test=1, fruit=[])]`; execute `QueryList(items)`; when `filter_expr is not None and isinstance(filter_expr, dict)`: execute `qs.filter(**filter_expr)`; assert `qs.filter(**filter_expr) == expected_result`; when `filter_expr is not None and not (isinstance(filter_expr, dict))`: execute `qs.filter(filter_expr)`; assert `qs.filter(filter_expr) == expected_result`; when `not (filter_expr is not None)`: execute `qs.filter()`; assert `qs.filter() == expected_result`; when `len(expected_result) == 0`: execute `missing.match('No objects found')`; expect `pytest.raises(ObjectDoesNotExist)`; assert `missing.match('No objects found')`; when `len(expected_result) == 1 and isinstance(filter_expr, dict)`: assert `qs.get(**filter_expr) == expected_result[0]`; when `len(expected_result) == 1 and not (isinstance(filter_expr, dict))`: assert `qs.get(filter_expr) == expected_result[0]`; when `not (len(expected_result) == 1)`: execute `multiple.match('Multiple objects returned')`; expect `pytest.raises(MultipleObjectsReturned)`; assert `multiple.match('Multiple objects returned')`. | unit test | PythonBehaviorParityTest#rnal_test_query_list_py_test_filter_items1_filter_expr1_expected_result1_7f67f6fa31 | not tmux-bound | planned parity | | tests/_internal/test_query_list.py::test_filter[items10-filter_expr10-expected_result10] | case inputs `expected_result=[{'fruit': 'apple', 'banana': <object object at 0x...>}]`, `filter_expr={'fruit__exact': 'apple'}`, `items=[{'fruit': 'apple', 'banana': <object object at 0x...>}]`; execute `QueryList(items)`; when `filter_expr is not None and isinstance(filter_expr, dict)`: execute `qs.filter(**filter_expr)`; assert `qs.filter(**filter_expr) == expected_result`; when `filter_expr is not None and not (isinstance(filter_expr, dict))`: execute `qs.filter(filter_expr)`; assert `qs.filter(filter_expr) == expected_result`; when `not (filter_expr is not None)`: execute `qs.filter()`; assert `qs.filter() == expected_result`; when `len(expected_result) == 0`: execute `missing.match('No objects found')`; expect `pytest.raises(ObjectDoesNotExist)`; assert `missing.match('No objects found')`; when `len(expected_result) == 1 and isinstance(filter_expr, dict)`: assert `qs.get(**filter_expr) == expected_result[0]`; when `len(expected_result) == 1 and not (isinstance(filter_expr, dict))`: assert `qs.get(filter_expr) == expected_result[0]`; when `not (len(expected_result) == 1)`: execute `multiple.match('Multiple objects returned')`; expect `pytest.raises(MultipleObjectsReturned)`; assert `multiple.match('Multiple objects returned')`. | unit test | PythonBehaviorParityTest#l_test_query_list_py_test_filter_items10_filter_expr10_expected_result10_fd80c52077 | not tmux-bound | planned parity | @@ -496,53 +496,53 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/legacy_api/test_common.py::test_has_lte_version | Test has_lti_version(); execute `has_lte_version('4.0a')`; `has_lte_version('4.0')`; `has_lte_version(str(get_version()))`; `get_version()`; `has_lte_version('1.7')`; `has_lte_version('1.7b')`; assert `has_lte_version('4.0a')`; `has_lte_version('4.0')`; `has_lte_version(str(get_version()))`; `not has_lte_version('1.7')`; `not has_lte_version('1.7b')`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_has_lte_version_914f2bfbb3 | not tmux-bound | planned parity | | tests/legacy_api/test_common.py::test_has_version | Test has_version(); execute `has_version(str(get_version()))`; `get_version()`; assert `has_version(str(get_version()))`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_has_version_4cd963b9e1 | not tmux-bound | planned parity | | tests/legacy_api/test_common.py::test_ignores_letter_versions | Tests version utilities ignores letters such as 1.8b; fixtures `monkeypatch`; execute `has_minimum_version()`; `has_version('1.8')`; `has_version('1.8a')`; `has_version('1.9a')`; assert `result`; `isinstance(has_version('1.8'), bool)`; `isinstance(has_version('1.8a'), bool)`; `isinstance(has_version('1.9a'), bool)`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_ignores_letter_versions_fdc1319d63 | not tmux-bound | planned parity | -| tests/legacy_api/test_common.py::test_session_check_name[-True-empty] | Verify session_check_name(); case inputs `exc_msg_regex='empty'`, `raises=True`, `session_name=''`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_session_check_name_True_empty_9a40f61272 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_common.py::test_session_check_name[None-True-empty] | Verify session_check_name(); case inputs `exc_msg_regex='empty'`, `raises=True`, `session_name=None`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_session_check_name_None_True_empty_f79de29799 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_common.py::test_session_check_name[ajf8a3fa83fads,,,a-False-None] | Verify session_check_name(); case inputs `exc_msg_regex=None`, `raises=False`, `session_name='ajf8a3fa83fads,,,a'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#y_api_test_common_py_test_session_check_name_ajf8a3fa83fads_a_False_None_6679721f6d | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_common.py::test_session_check_name[my great session.-True-contains periods] | Verify session_check_name(); case inputs `exc_msg_regex='contains periods'`, `raises=True`, `session_name='my great session.'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#common_py_test_session_check_name_my_great_session_True_contains_periods_91c14cb936 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_common.py::test_session_check_name[name: great session-True-contains colons] | Verify session_check_name(); case inputs `exc_msg_regex='contains colons'`, `raises=True`, `session_name='name: great session'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#ommon_py_test_session_check_name_name_great_session_True_contains_colons_f9f12dc54c | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_common.py::test_session_check_name[new great session-False-None] | Verify session_check_name(); case inputs `exc_msg_regex=None`, `raises=False`, `session_name='new great session'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#_api_test_common_py_test_session_check_name_new_great_session_False_None_49fd9371c8 | 3.2a through 3.7b | planned parity | +| tests/legacy_api/test_common.py::test_session_check_name[-True-empty] | Verify session_check_name(); case inputs `exc_msg_regex='empty'`, `raises=True`, `session_name=''`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_session_check_name_True_empty_9a40f61272 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_common.py::test_session_check_name[None-True-empty] | Verify session_check_name(); case inputs `exc_msg_regex='empty'`, `raises=True`, `session_name=None`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_session_check_name_None_True_empty_f79de29799 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_common.py::test_session_check_name[ajf8a3fa83fads,,,a-False-None] | Verify session_check_name(); case inputs `exc_msg_regex=None`, `raises=False`, `session_name='ajf8a3fa83fads,,,a'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#y_api_test_common_py_test_session_check_name_ajf8a3fa83fads_a_False_None_6679721f6d | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_common.py::test_session_check_name[my great session.-True-contains periods] | Verify session_check_name(); case inputs `exc_msg_regex='contains periods'`, `raises=True`, `session_name='my great session.'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#common_py_test_session_check_name_my_great_session_True_contains_periods_91c14cb936 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_common.py::test_session_check_name[name: great session-True-contains colons] | Verify session_check_name(); case inputs `exc_msg_regex='contains colons'`, `raises=True`, `session_name='name: great session'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#ommon_py_test_session_check_name_name_great_session_True_contains_colons_f9f12dc54c | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_common.py::test_session_check_name[new great session-False-None] | Verify session_check_name(); case inputs `exc_msg_regex=None`, `raises=False`, `session_name='new great session'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#_api_test_common_py_test_session_check_name_new_great_session_False_None_49fd9371c8 | 3.3a through 3.7c | planned parity | | tests/legacy_api/test_common.py::test_tmux_cmd_raises_on_not_found | Verify raises if tmux command not found; fixtures `monkeypatch`; execute `tmux_cmd('-V')`; expect `pytest.raises(exc.TmuxCommandNotFound)`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_tmux_cmd_raises_on_not_found_7542dab548 | not tmux-bound | planned parity | -| tests/legacy_api/test_common.py::test_tmux_cmd_unicode | Verify tmux commands with unicode; fixtures `session`; execute `session.cmd('new-window', '-t', 3, '-n', 'юникод', '-F', 'Ελληνικά')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_tmux_cmd_unicode_7fa5256541 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_pane.py::test_pane_get_raises_deprecated_error | Test Pane.get() raises DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.get\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_pane_get_raises_deprecated_error_c685b5f026 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_pane.py::test_pane_getitem_raises_deprecated_error | Test Pane.__getitem__() raises DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\[key\\] lookup was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_pane_getitem_raises_deprecated_error_5db8171483 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_pane.py::test_resize_pane_raises_deprecated_error | Test Pane.resize_pane() raises DeprecatedError; fixtures `session`; execute `pane.resize_pane(height=4)`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.resize_pane\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_resize_pane_raises_deprecated_error_db2eda8365 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_pane.py::test_select_pane_raises_deprecated_error | Test Pane.select_pane() raises DeprecatedError; fixtures `session`; execute `pane.select_pane()`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.select_pane\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_select_pane_raises_deprecated_error_fba5bc5886 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_pane.py::test_split_window_raises_deprecated_error | Test Pane.split_window() raises DeprecatedError; fixtures `session`; execute `pane.split_window()`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.split_window\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_split_window_raises_deprecated_error_1aa48b9172 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_kill_server_raises_deprecated_error | Test Server.kill_server() raises exc.DeprecatedError; fixtures `server`; execute `server.kill_server()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.kill_server\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_server_py_test_kill_server_raises_deprecated_error_83f9f3f7a0 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server__list_panes_raises_deprecated_error | Test Server._list_panes() raises exc.DeprecatedError; fixtures `server`; execute `server._list_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._list_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_server_py_test_server_list_panes_raises_deprecated_error_92490325b3 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server__list_sessions_raises_deprecated_error | Test Server._list_sessions() raises exc.DeprecatedError; fixtures `server`; execute `server._list_sessions()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._list_sessions\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_server_py_test_server_list_sessions_raises_deprecated_error_100f982346 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server__list_windows_raises_deprecated_error | Test Server._list_windows() raises exc.DeprecatedError; fixtures `server`; execute `server._list_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._list_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_server_py_test_server_list_windows_raises_deprecated_error_6dda9a1130 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server__sessions_raises_deprecated_error | Test Server._sessions raises exc.DeprecatedError; fixtures `server`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._sessions was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_server_py_test_server_sessions_raises_deprecated_error_0b0467bd46 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server__update_panes_raises_deprecated_error | Test Server._update_panes() raises exc.DeprecatedError; fixtures `server`; execute `server._update_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._update_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_server_py_test_server_update_panes_raises_deprecated_error_b138b79715 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server__update_windows_raises_deprecated_error | Test Server._update_windows() raises exc.DeprecatedError; fixtures `server`; execute `server._update_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._update_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_server_py_test_server_update_windows_raises_deprecated_error_60e6de39f5 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server_children_raises_deprecated_error | Test Server.children raises exc.DeprecatedError; fixtures `server`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.children was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_server_py_test_server_children_raises_deprecated_error_693a4e97c5 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server_find_where_raises_deprecated_error | Test Server.find_where() raises exc.DeprecatedError; fixtures `server`; execute `server.find_where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_server_py_test_server_find_where_raises_deprecated_error_5d45d1f23e | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server_get_by_id_raises_deprecated_error | Test Server.get_by_id() raises exc.DeprecatedError; fixtures `server`; execute `server.get_by_id('$0')`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_server_py_test_server_get_by_id_raises_deprecated_error_93585abef2 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server_list_sessions_raises_deprecated_error | Test Server.list_sessions() raises exc.DeprecatedError; fixtures `server`; execute `server.list_sessions()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.list_sessions\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_server_py_test_server_list_sessions_raises_deprecated_error_3ea9a309a2 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_server.py::test_server_where_raises_deprecated_error | Test Server.where() raises exc.DeprecatedError; fixtures `server`; execute `server.where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_server_py_test_server_where_raises_deprecated_error_06a79f8f2e | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_attach_session_raises_deprecated_error | Test Session.attach_session() raises exc.DeprecatedError; fixtures `session`; execute `session.attach_session()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.attach_session\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_session_py_test_attach_session_raises_deprecated_error_eb4c32f1c6 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_attached_pane_raises_deprecated_error | Test Session.attached_pane raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.attached_pane was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ts_legacy_api_test_session_py_test_attached_pane_raises_deprecated_error_92b3e0ed25 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_attached_window_raises_deprecated_error | Test Session.attached_window raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.attached_window was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_session_py_test_attached_window_raises_deprecated_error_0e6f0927c5 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_kill_session_raises_deprecated_error | Test Session.kill_session() raises exc.DeprecatedError; fixtures `server`; execute `server.new_session(session_name='test_kill_session', detach=True)`; `new_session.kill_session()`; `new_session.kill()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.kill_session\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#sts_legacy_api_test_session_py_test_kill_session_raises_deprecated_error_bedfb1eae5 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session__list_windows_raises_deprecated_error | Test Session._list_windows() raises exc.DeprecatedError; fixtures `session`; execute `session._list_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\._list_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_session_py_test_session_list_windows_raises_deprecated_error_fd0ed21403 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session__windows_raises_deprecated_error | Test Session._windows raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\._windows was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_session_py_test_session_windows_raises_deprecated_error_b479441f8f | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_children_raises_deprecated_error | Test Session.children raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.children was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_session_py_test_session_children_raises_deprecated_error_7931b2ad06 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_find_where_raises_deprecated_error | Test Session.find_where() raises exc.DeprecatedError; fixtures `session`; execute `session.find_where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_session_py_test_session_find_where_raises_deprecated_error_710a5495c0 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_get_by_id_raises_deprecated_error | Test Session.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `session.get_by_id('@0')`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#egacy_api_test_session_py_test_session_get_by_id_raises_deprecated_error_817a478a6b | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_get_raises_deprecated_error | Test Session.get() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.get\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_session_py_test_session_get_raises_deprecated_error_a4c0492453 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_getitem_raises_deprecated_error | Test Session.__getitem__() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\[key\\] lookup was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_session_py_test_session_getitem_raises_deprecated_error_2f10d5a199 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_list_windows_raises_deprecated_error | Test Session.list_windows() raises exc.DeprecatedError; fixtures `session`; execute `session.list_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.list_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_session_py_test_session_list_windows_raises_deprecated_error_17a8f6dfb4 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_session.py::test_session_where_raises_deprecated_error | Test Session.where() raises exc.DeprecatedError; fixtures `session`; execute `session.where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ts_legacy_api_test_session_py_test_session_where_raises_deprecated_error_56f771dca4 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_server_find_where_raises_deprecated_error | Test Server.find_where() raises exc.DeprecatedError; fixtures `server`; execute `server.find_where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_tmuxobject_py_test_server_find_where_raises_deprecated_error_c760af2b7f | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_server_get_by_id_raises_deprecated_error | Test Server.get_by_id() raises exc.DeprecatedError; fixtures `server`; execute `server.get_by_id('$0')`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_tmuxobject_py_test_server_get_by_id_raises_deprecated_error_3832535554 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_server_where_raises_deprecated_error | Test Server.where() raises exc.DeprecatedError; fixtures `server`; execute `server.where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_tmuxobject_py_test_server_where_raises_deprecated_error_eb953219b3 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_session_find_where_raises_deprecated_error | Test Session.find_where() raises exc.DeprecatedError; fixtures `session`; execute `session.find_where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#y_api_test_tmuxobject_py_test_session_find_where_raises_deprecated_error_8dfea1c4b0 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_session_get_by_id_raises_deprecated_error | Test Session.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `session.get_by_id('@0')`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_tmuxobject_py_test_session_get_by_id_raises_deprecated_error_b641f63804 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_session_where_raises_deprecated_error | Test Session.where() raises exc.DeprecatedError; fixtures `session`; execute `session.where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_tmuxobject_py_test_session_where_raises_deprecated_error_e479b95b22 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_window_find_where_raises_deprecated_error | Test Window.find_where() raises exc.DeprecatedError; fixtures `session`; execute `window.find_where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_tmuxobject_py_test_window_find_where_raises_deprecated_error_644ad807b3 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_window_get_by_id_raises_deprecated_error | Test Window.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `window.get_by_id('%0')`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_tmuxobject_py_test_window_get_by_id_raises_deprecated_error_e0cb1674f3 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_tmuxobject.py::test_window_where_raises_deprecated_error | Test Window.where() raises exc.DeprecatedError; fixtures `session`; execute `window.where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_tmuxobject_py_test_window_where_raises_deprecated_error_22a774544e | 3.2a through 3.7b | planned parity | +| tests/legacy_api/test_common.py::test_tmux_cmd_unicode | Verify tmux commands with unicode; fixtures `session`; execute `session.cmd('new-window', '-t', 3, '-n', 'юникод', '-F', 'Ελληνικά')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_common_py_test_tmux_cmd_unicode_7fa5256541 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_pane.py::test_pane_get_raises_deprecated_error | Test Pane.get() raises DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.get\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_pane_get_raises_deprecated_error_c685b5f026 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_pane.py::test_pane_getitem_raises_deprecated_error | Test Pane.__getitem__() raises DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\[key\\] lookup was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_pane_getitem_raises_deprecated_error_5db8171483 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_pane.py::test_resize_pane_raises_deprecated_error | Test Pane.resize_pane() raises DeprecatedError; fixtures `session`; execute `pane.resize_pane(height=4)`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.resize_pane\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_resize_pane_raises_deprecated_error_db2eda8365 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_pane.py::test_select_pane_raises_deprecated_error | Test Pane.select_pane() raises DeprecatedError; fixtures `session`; execute `pane.select_pane()`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.select_pane\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_select_pane_raises_deprecated_error_fba5bc5886 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_pane.py::test_split_window_raises_deprecated_error | Test Pane.split_window() raises DeprecatedError; fixtures `session`; execute `pane.split_window()`; expect `pytest.raises(exc.DeprecatedError, match='Pane\\.split_window\\(\\) was deprecated')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_pane_py_test_split_window_raises_deprecated_error_1aa48b9172 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_kill_server_raises_deprecated_error | Test Server.kill_server() raises exc.DeprecatedError; fixtures `server`; execute `server.kill_server()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.kill_server\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_server_py_test_kill_server_raises_deprecated_error_83f9f3f7a0 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server__list_panes_raises_deprecated_error | Test Server._list_panes() raises exc.DeprecatedError; fixtures `server`; execute `server._list_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._list_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_server_py_test_server_list_panes_raises_deprecated_error_92490325b3 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server__list_sessions_raises_deprecated_error | Test Server._list_sessions() raises exc.DeprecatedError; fixtures `server`; execute `server._list_sessions()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._list_sessions\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_server_py_test_server_list_sessions_raises_deprecated_error_100f982346 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server__list_windows_raises_deprecated_error | Test Server._list_windows() raises exc.DeprecatedError; fixtures `server`; execute `server._list_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._list_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_server_py_test_server_list_windows_raises_deprecated_error_6dda9a1130 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server__sessions_raises_deprecated_error | Test Server._sessions raises exc.DeprecatedError; fixtures `server`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._sessions was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_server_py_test_server_sessions_raises_deprecated_error_0b0467bd46 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server__update_panes_raises_deprecated_error | Test Server._update_panes() raises exc.DeprecatedError; fixtures `server`; execute `server._update_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._update_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_server_py_test_server_update_panes_raises_deprecated_error_b138b79715 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server__update_windows_raises_deprecated_error | Test Server._update_windows() raises exc.DeprecatedError; fixtures `server`; execute `server._update_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\._update_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_server_py_test_server_update_windows_raises_deprecated_error_60e6de39f5 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server_children_raises_deprecated_error | Test Server.children raises exc.DeprecatedError; fixtures `server`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.children was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_server_py_test_server_children_raises_deprecated_error_693a4e97c5 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server_find_where_raises_deprecated_error | Test Server.find_where() raises exc.DeprecatedError; fixtures `server`; execute `server.find_where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_server_py_test_server_find_where_raises_deprecated_error_5d45d1f23e | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server_get_by_id_raises_deprecated_error | Test Server.get_by_id() raises exc.DeprecatedError; fixtures `server`; execute `server.get_by_id('$0')`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_server_py_test_server_get_by_id_raises_deprecated_error_93585abef2 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server_list_sessions_raises_deprecated_error | Test Server.list_sessions() raises exc.DeprecatedError; fixtures `server`; execute `server.list_sessions()`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.list_sessions\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_server_py_test_server_list_sessions_raises_deprecated_error_3ea9a309a2 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_server.py::test_server_where_raises_deprecated_error | Test Server.where() raises exc.DeprecatedError; fixtures `server`; execute `server.where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_server_py_test_server_where_raises_deprecated_error_06a79f8f2e | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_attach_session_raises_deprecated_error | Test Session.attach_session() raises exc.DeprecatedError; fixtures `session`; execute `session.attach_session()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.attach_session\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_session_py_test_attach_session_raises_deprecated_error_eb4c32f1c6 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_attached_pane_raises_deprecated_error | Test Session.attached_pane raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.attached_pane was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ts_legacy_api_test_session_py_test_attached_pane_raises_deprecated_error_92b3e0ed25 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_attached_window_raises_deprecated_error | Test Session.attached_window raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.attached_window was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_session_py_test_attached_window_raises_deprecated_error_0e6f0927c5 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_kill_session_raises_deprecated_error | Test Session.kill_session() raises exc.DeprecatedError; fixtures `server`; execute `server.new_session(session_name='test_kill_session', detach=True)`; `new_session.kill_session()`; `new_session.kill()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.kill_session\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#sts_legacy_api_test_session_py_test_kill_session_raises_deprecated_error_bedfb1eae5 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session__list_windows_raises_deprecated_error | Test Session._list_windows() raises exc.DeprecatedError; fixtures `session`; execute `session._list_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\._list_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_session_py_test_session_list_windows_raises_deprecated_error_fd0ed21403 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session__windows_raises_deprecated_error | Test Session._windows raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\._windows was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_session_py_test_session_windows_raises_deprecated_error_b479441f8f | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_children_raises_deprecated_error | Test Session.children raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.children was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_session_py_test_session_children_raises_deprecated_error_7931b2ad06 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_find_where_raises_deprecated_error | Test Session.find_where() raises exc.DeprecatedError; fixtures `session`; execute `session.find_where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_session_py_test_session_find_where_raises_deprecated_error_710a5495c0 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_get_by_id_raises_deprecated_error | Test Session.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `session.get_by_id('@0')`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#egacy_api_test_session_py_test_session_get_by_id_raises_deprecated_error_817a478a6b | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_get_raises_deprecated_error | Test Session.get() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.get\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_session_py_test_session_get_raises_deprecated_error_a4c0492453 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_getitem_raises_deprecated_error | Test Session.__getitem__() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Session\\[key\\] lookup was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_session_py_test_session_getitem_raises_deprecated_error_2f10d5a199 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_list_windows_raises_deprecated_error | Test Session.list_windows() raises exc.DeprecatedError; fixtures `session`; execute `session.list_windows()`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.list_windows\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_session_py_test_session_list_windows_raises_deprecated_error_17a8f6dfb4 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_session.py::test_session_where_raises_deprecated_error | Test Session.where() raises exc.DeprecatedError; fixtures `session`; execute `session.where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ts_legacy_api_test_session_py_test_session_where_raises_deprecated_error_56f771dca4 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_server_find_where_raises_deprecated_error | Test Server.find_where() raises exc.DeprecatedError; fixtures `server`; execute `server.find_where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_tmuxobject_py_test_server_find_where_raises_deprecated_error_c760af2b7f | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_server_get_by_id_raises_deprecated_error | Test Server.get_by_id() raises exc.DeprecatedError; fixtures `server`; execute `server.get_by_id('$0')`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_tmuxobject_py_test_server_get_by_id_raises_deprecated_error_3832535554 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_server_where_raises_deprecated_error | Test Server.where() raises exc.DeprecatedError; fixtures `server`; execute `server.where({'session_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Server\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_tmuxobject_py_test_server_where_raises_deprecated_error_eb953219b3 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_session_find_where_raises_deprecated_error | Test Session.find_where() raises exc.DeprecatedError; fixtures `session`; execute `session.find_where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#y_api_test_tmuxobject_py_test_session_find_where_raises_deprecated_error_8dfea1c4b0 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_session_get_by_id_raises_deprecated_error | Test Session.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `session.get_by_id('@0')`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_tmuxobject_py_test_session_get_by_id_raises_deprecated_error_b641f63804 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_session_where_raises_deprecated_error | Test Session.where() raises exc.DeprecatedError; fixtures `session`; execute `session.where({'window_name': 'test'})`; expect `pytest.raises(exc.DeprecatedError, match='Session\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_tmuxobject_py_test_session_where_raises_deprecated_error_e479b95b22 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_window_find_where_raises_deprecated_error | Test Window.find_where() raises exc.DeprecatedError; fixtures `session`; execute `window.find_where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_tmuxobject_py_test_window_find_where_raises_deprecated_error_644ad807b3 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_window_get_by_id_raises_deprecated_error | Test Window.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `window.get_by_id('%0')`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_tmuxobject_py_test_window_get_by_id_raises_deprecated_error_e0cb1674f3 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_tmuxobject.py::test_window_where_raises_deprecated_error | Test Window.where() raises exc.DeprecatedError; fixtures `session`; execute `window.where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_tmuxobject_py_test_window_where_raises_deprecated_error_22a774544e | 3.3a through 3.7c | planned parity | | tests/legacy_api/test_version.py::test_version[1.0.0-next.1] | Assert LooseVersion constructor against various version strings; case inputs `version='1.0.0-next.1'`; execute `LooseVersion(version)`; assert `LooseVersion(version)`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_version_py_test_version_1_0_0_next_1_4240bb7736 | not tmux-bound | planned parity | | tests/legacy_api/test_version.py::test_version[1.0.0-next] | Assert LooseVersion constructor against various version strings; case inputs `version='1.0.0-next'`; execute `LooseVersion(version)`; assert `LooseVersion(version)`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_version_py_test_version_1_0_0_next_d2910c5b41 | not tmux-bound | planned parity | | tests/legacy_api/test_version.py::test_version[1.0.0] | Assert LooseVersion constructor against various version strings; case inputs `version='1.0.0'`; execute `LooseVersion(version)`; assert `LooseVersion(version)`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_version_py_test_version_1_0_0_83b8418b14 | not tmux-bound | planned parity | @@ -560,22 +560,22 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/legacy_api/test_version.py::test_version_compare[1-lt-1.0.0-openbsd-False] | Assert version comparisons; case inputs `a='1'`, `b='1.0.0-openbsd'`, `op=<built-in function lt>`, `raises=False`; execute `op(LooseVersion(a), LooseVersion(b))`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `LooseVersion(a)`; `LooseVersion(b)`; assert `op(LooseVersion(a), LooseVersion(b))`. | unit test | PythonBehaviorParityTest#legacy_api_test_version_py_test_version_compare_1_lt_1_0_0_openbsd_False_0947730d1b | not tmux-bound | planned parity | | tests/legacy_api/test_version.py::test_version_compare[1-lt-1.0.0p1-False] | Assert version comparisons; case inputs `a='1'`, `b='1.0.0p1'`, `op=<built-in function lt>`, `raises=False`; execute `op(LooseVersion(a), LooseVersion(b))`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `LooseVersion(a)`; `LooseVersion(b)`; assert `op(LooseVersion(a), LooseVersion(b))`. | unit test | PythonBehaviorParityTest#tests_legacy_api_test_version_py_test_version_compare_1_lt_1_0_0p1_False_f3a2e23e61 | not tmux-bound | planned parity | | tests/legacy_api/test_version.py::test_version_compare[1.0.0c-gt-1.0.0b-False] | Assert version comparisons; case inputs `a='1.0.0c'`, `b='1.0.0b'`, `op=<built-in function gt>`, `raises=False`; execute `op(LooseVersion(a), LooseVersion(b))`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `LooseVersion(a)`; `LooseVersion(b)`; assert `op(LooseVersion(a), LooseVersion(b))`. | unit test | PythonBehaviorParityTest#s_legacy_api_test_version_py_test_version_compare_1_0_0c_gt_1_0_0b_False_b68f114e02 | not tmux-bound | planned parity | -| tests/legacy_api/test_window.py::test_attached_pane_raises_deprecated_error | Test Window.attached_pane raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.attached_pane was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#sts_legacy_api_test_window_py_test_attached_pane_raises_deprecated_error_cd1f418b96 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_kill_window_raises_deprecated_error | Test Window.kill_window() raises exc.DeprecatedError; fixtures `session`; execute `session.new_window(window_name='extra_window')`; `window.kill_window()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.kill_window\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_window_py_test_kill_window_raises_deprecated_error_2e9f0078b9 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_select_window_raises_deprecated_error | Test Window.select_window() raises exc.DeprecatedError; fixtures `session`; execute `window.select_window()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.select_window\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#sts_legacy_api_test_window_py_test_select_window_raises_deprecated_error_2fb44c2734 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_set_window_option_emits_deprecation_warning | Test Window.set_window_option() emits DeprecationWarning; fixtures `session`; execute `window.set_window_option('main-pane-height', 20)`; expect `pytest.warns(DeprecationWarning, match='Window\\.set_window_option\\(\\) is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_window_py_test_set_window_option_emits_deprecation_warning_8b60198717 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_show_window_option_emits_deprecation_warning | Test Window.show_window_option() emits DeprecationWarning; fixtures `session`; execute `window.show_window_option('main-pane-height')`; expect `pytest.warns(DeprecationWarning, match='Window\\.show_window_option\\(\\) is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_window_py_test_show_window_option_emits_deprecation_warning_51acf1e29b | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_show_window_options_emits_deprecation_warning | Test Window.show_window_options() emits DeprecationWarning; fixtures `session`; execute `window.show_window_options()`; expect `pytest.warns(DeprecationWarning, match='Window\\.show_window_options\\(\\) is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_window_py_test_show_window_options_emits_deprecation_warning_adbdce0fe2 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_split_window_raises_deprecated_error | Test Window.split_window() raises exc.DeprecatedError; fixtures `session`; execute `window.split_window()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.split_window\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_window_py_test_split_window_raises_deprecated_error_635a4ccca5 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window__list_panes_raises_deprecated_error | Test Window._list_panes() raises exc.DeprecatedError; fixtures `session`; execute `window._list_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\._list_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_window_py_test_window_list_panes_raises_deprecated_error_27eecd0fca | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window__panes_raises_deprecated_error | Test Window._panes raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\._panes was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_window_py_test_window_panes_raises_deprecated_error_bf9ac5bdb2 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_children_raises_deprecated_error | Test Window.children raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.children was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_window_py_test_window_children_raises_deprecated_error_f48b74fd8e | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_find_where_raises_deprecated_error | Test Window.find_where() raises exc.DeprecatedError; fixtures `session`; execute `window.find_where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_window_py_test_window_find_where_raises_deprecated_error_db7b512349 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_get_by_id_raises_deprecated_error | Test Window.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `window.get_by_id('%0')`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_window_py_test_window_get_by_id_raises_deprecated_error_f1a001095f | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_get_raises_deprecated_error | Test Window.get() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.get\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_window_py_test_window_get_raises_deprecated_error_91782ec9fe | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_getitem_raises_deprecated_error | Test Window.__getitem__() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\[key\\] lookup was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ts_legacy_api_test_window_py_test_window_getitem_raises_deprecated_error_c34a2392f0 | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_list_panes_raises_deprecated_error | Test Window.list_panes() raises exc.DeprecatedError; fixtures `session`; execute `window.list_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.list_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_window_py_test_window_list_panes_raises_deprecated_error_e8767aea1d | 3.2a through 3.7b | planned parity | -| tests/legacy_api/test_window.py::test_window_where_raises_deprecated_error | Test Window.where() raises exc.DeprecatedError; fixtures `session`; execute `window.where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_window_py_test_window_where_raises_deprecated_error_4ea334bb4b | 3.2a through 3.7b | planned parity | +| tests/legacy_api/test_window.py::test_attached_pane_raises_deprecated_error | Test Window.attached_pane raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.attached_pane was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#sts_legacy_api_test_window_py_test_attached_pane_raises_deprecated_error_cd1f418b96 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_kill_window_raises_deprecated_error | Test Window.kill_window() raises exc.DeprecatedError; fixtures `session`; execute `session.new_window(window_name='extra_window')`; `window.kill_window()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.kill_window\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_window_py_test_kill_window_raises_deprecated_error_2e9f0078b9 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_select_window_raises_deprecated_error | Test Window.select_window() raises exc.DeprecatedError; fixtures `session`; execute `window.select_window()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.select_window\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#sts_legacy_api_test_window_py_test_select_window_raises_deprecated_error_2fb44c2734 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_set_window_option_emits_deprecation_warning | Test Window.set_window_option() emits DeprecationWarning; fixtures `session`; execute `window.set_window_option('main-pane-height', 20)`; expect `pytest.warns(DeprecationWarning, match='Window\\.set_window_option\\(\\) is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#gacy_api_test_window_py_test_set_window_option_emits_deprecation_warning_8b60198717 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_show_window_option_emits_deprecation_warning | Test Window.show_window_option() emits DeprecationWarning; fixtures `session`; execute `window.show_window_option('main-pane-height')`; expect `pytest.warns(DeprecationWarning, match='Window\\.show_window_option\\(\\) is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#acy_api_test_window_py_test_show_window_option_emits_deprecation_warning_51acf1e29b | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_show_window_options_emits_deprecation_warning | Test Window.show_window_options() emits DeprecationWarning; fixtures `session`; execute `window.show_window_options()`; expect `pytest.warns(DeprecationWarning, match='Window\\.show_window_options\\(\\) is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#cy_api_test_window_py_test_show_window_options_emits_deprecation_warning_adbdce0fe2 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_split_window_raises_deprecated_error | Test Window.split_window() raises exc.DeprecatedError; fixtures `session`; execute `window.split_window()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.split_window\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_window_py_test_split_window_raises_deprecated_error_635a4ccca5 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window__list_panes_raises_deprecated_error | Test Window._list_panes() raises exc.DeprecatedError; fixtures `session`; execute `window._list_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\._list_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_window_py_test_window_list_panes_raises_deprecated_error_27eecd0fca | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window__panes_raises_deprecated_error | Test Window._panes raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\._panes was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_window_py_test_window_panes_raises_deprecated_error_bf9ac5bdb2 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_children_raises_deprecated_error | Test Window.children raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.children was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_legacy_api_test_window_py_test_window_children_raises_deprecated_error_f48b74fd8e | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_find_where_raises_deprecated_error | Test Window.find_where() raises exc.DeprecatedError; fixtures `session`; execute `window.find_where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.find_where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_window_py_test_window_find_where_raises_deprecated_error_db7b512349 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_get_by_id_raises_deprecated_error | Test Window.get_by_id() raises exc.DeprecatedError; fixtures `session`; execute `window.get_by_id('%0')`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.get_by_id\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#_legacy_api_test_window_py_test_window_get_by_id_raises_deprecated_error_f1a001095f | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_get_raises_deprecated_error | Test Window.get() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.get\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#tests_legacy_api_test_window_py_test_window_get_raises_deprecated_error_91782ec9fe | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_getitem_raises_deprecated_error | Test Window.__getitem__() raises exc.DeprecatedError; fixtures `session`; expect `pytest.raises(exc.DeprecatedError, match='Window\\[key\\] lookup was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ts_legacy_api_test_window_py_test_window_getitem_raises_deprecated_error_c34a2392f0 | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_list_panes_raises_deprecated_error | Test Window.list_panes() raises exc.DeprecatedError; fixtures `session`; execute `window.list_panes()`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.list_panes\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#legacy_api_test_window_py_test_window_list_panes_raises_deprecated_error_e8767aea1d | 3.3a through 3.7c | planned parity | +| tests/legacy_api/test_window.py::test_window_where_raises_deprecated_error | Test Window.where() raises exc.DeprecatedError; fixtures `session`; execute `window.where({'pane_id': '%0'})`; expect `pytest.raises(exc.DeprecatedError, match='Window\\.where\\(\\) was deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#ests_legacy_api_test_window_py_test_window_where_raises_deprecated_error_4ea334bb4b | 3.3a through 3.7c | planned parity | | tests/test/test_constants.py::test_retry_interval_seconds_default | Test RETRY_INTERVAL_SECONDS default value; assert `RETRY_INTERVAL_SECONDS == 0.05`. | unit test | PythonBehaviorParityTest#tests_test_test_constants_py_test_retry_interval_seconds_default_c96f0d8a4e | not tmux-bound | planned parity | | tests/test/test_constants.py::test_retry_interval_seconds_env | Test RETRY_INTERVAL_SECONDS can be configured via environment variable; fixtures `monkeypatch`; execute `reload(libtmux.test.constants)`; assert `libtmux.test.constants.RETRY_INTERVAL_SECONDS == 0.1`. | unit test | PythonBehaviorParityTest#tests_test_test_constants_py_test_retry_interval_seconds_env_b3c4d57585 | not tmux-bound | planned parity | | tests/test/test_constants.py::test_retry_timeout_seconds_default | Test RETRY_TIMEOUT_SECONDS default value; assert `RETRY_TIMEOUT_SECONDS == 8`. | unit test | PythonBehaviorParityTest#tests_test_test_constants_py_test_retry_timeout_seconds_default_732eff539d | not tmux-bound | planned parity | @@ -588,21 +588,21 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test/test_environment.py::test_environment_var_guard_unset | Test unsetting environment variables with EnvironmentVarGuard; execute `EnvironmentVarGuard()`; `env.unset('TEST_EXISTING_VAR')`; `env.unset('TEST_NON_EXISTENT_VAR')`; `env.__exit__(None, None, None)`; assert `'TEST_EXISTING_VAR' not in os.environ`; `os.environ['TEST_EXISTING_VAR'] == 'original_value'`. | unit test | PythonBehaviorParityTest#tests_test_test_environment_py_test_environment_var_guard_unset_8086242665 | not tmux-bound | planned parity | | tests/test/test_environment.py::test_environment_var_guard_unset_and_reset | Test unsetting and then resetting a variable; execute `EnvironmentVarGuard()`; `env.unset('TEST_VAR1')`; `env.set('TEST_VAR1', 'new_value1')`; `env.set('TEST_VAR2', 'new_value2')`; `env.unset('TEST_VAR2')`; `env.__exit__(None, None, None)`; assert `'TEST_VAR1' not in os.environ`; `os.environ['TEST_VAR1'] == 'new_value1'`; `'TEST_VAR2' not in os.environ`; `os.environ['TEST_VAR1'] == 'value1'`; `os.environ['TEST_VAR2'] == 'value2'`. | unit test | PythonBehaviorParityTest#ests_test_test_environment_py_test_environment_var_guard_unset_and_reset_a9aed2afe7 | not tmux-bound | planned parity | | tests/test/test_environment.py::test_environment_var_guard_unset_previously_set | Test unsetting a variable that was previously set in the same context; execute `EnvironmentVarGuard()`; `env.set('TEST_NEW_VAR', 'new_value')`; `env.unset('TEST_NEW_VAR')`; assert `'TEST_NEW_VAR' in os.environ`; `os.environ['TEST_NEW_VAR'] == 'new_value'`; `'TEST_NEW_VAR' not in os.environ`. | unit test | PythonBehaviorParityTest#test_test_environment_py_test_environment_var_guard_unset_previously_set_c0e53b059c | not tmux-bound | planned parity | -| tests/test/test_random.py::test_collisions_with_real_objects | Test collision behavior using real tmux objects instead of mocks; fixtures `server`, `session`; execute `server.new_session(session_name1)`; `server.new_session(session_name2)`; `server.has_session(session_name1)`; `server.has_session(session_name2)`; `server.has_session(result)`; `session.new_window(window_name=window_name1)`; assert `server.has_session(session_name1)`; `server.has_session(session_name2)`; `result.startswith(prefix)`; `result != session_name1`; `result != session_name2`; `not server.has_session(result)`; `any((w.window_name == window_name1 for w in session.windows))`; `any((w.window_name == window_name2 for w in session.windows))`; `result != window_name1`; `result != window_name2`; `not any((w.window_name == result for w in session.windows))`; when `window1`: execute `window1.kill()`; when `window2`: execute `window2.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_collisions_with_real_objects_c73ef59ae2 | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_session_name_collision_handling | Test that get_test_session_name handles collisions properly; fixtures `server`, `monkeypatch`; execute `get_test_session_name(server)`; `session_name.startswith(TEST_SESSION_PREFIX)`; assert `call_count == 2`; `session_name.startswith(TEST_SESSION_PREFIX)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_collision_handling_44e82833e0 | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_session_name_custom_prefix | Test get_test_session_name with custom prefix; fixtures `server`; execute `server.has_session(result)`; `get_test_session_name(server=server, prefix=prefix)`; `result.startswith(prefix)`; assert `isinstance(result, str)`; `result.startswith(prefix)`; `len(result) == len(prefix) + 8`; `not server.has_session(result)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_custom_prefix_93d8de90ce | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_session_name_default_prefix | Test get_test_session_name with default prefix; fixtures `server`; execute `server.has_session(result)`; `get_test_session_name(server=server)`; `result.startswith(TEST_SESSION_PREFIX)`; assert `isinstance(result, str)`; `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `not server.has_session(result)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_default_prefix_f2e42e2c73 | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_session_name_doctest_examples | Test the doctest examples for get_test_session_name; fixtures `server`; execute `get_test_session_name(server=server)`; `result.startswith(TEST_SESSION_PREFIX)`; assert `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `result1 != result2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_doctest_examples_c6410244df | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_session_name_implementation_details | Test specific implementation details of get_test_session_name function; fixtures `server`, `monkeypatch`; execute `server.new_session(collision_name)`; `get_test_session_name(server, prefix=prefix)`; `result.startswith(prefix)`; assert `result != collision_name`; `result.startswith(prefix)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_test_random_py_test_get_test_session_name_implementation_details_53ddea23bf | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_session_name_loop_behavior | Test the loop behavior in get_test_session_name using real sessions; fixtures `server`; execute `server.new_session(first_name)`; `server.has_session(first_name)`; `server.has_session(second_name)`; `server.new_session(second_name)`; `server.has_session(third_name)`; `get_test_session_name(server=server)`; assert `first_name != second_name`; `server.has_session(first_name)`; `not server.has_session(second_name)`; `first_name != third_name`; `second_name != third_name`; `server.has_session(second_name)`; `not server.has_session(third_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_loop_behavior_401ae81bad | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_branch_coverage | Test branch coverage for get_test_window_name function; fixtures `session`; execute `session.new_window(window_name=collision_name)`; `get_test_window_name(session, prefix=prefix)`; `result.startswith(prefix)`; assert `result != collision_name`; `result.startswith(prefix)`; when `window`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_branch_coverage_fa55610e5b | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_collision_handling | Test that get_test_window_name handles collisions properly; fixtures `session`, `monkeypatch`; execute `session.new_window(window_name=first_name)`; `get_test_window_name(session, prefix=prefix)`; `window_name.startswith(prefix)`; `any((w.window_name == window_name for w in session.windows))`; `any((w.window_name == first_name for w in session.windows))`; assert `window_name != first_name`; `window_name.startswith(prefix)`; `not any((w.window_name == window_name for w in session.windows))`; `any((w.window_name == first_name for w in session.windows))`; when `window`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_collision_handling_daac1a3b01 | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_custom_prefix | Test get_test_window_name with custom prefix; fixtures `session`; execute `get_test_window_name(session=session, prefix=prefix)`; `result.startswith(prefix)`; `any((w.window_name == result for w in session.windows))`; assert `isinstance(result, str)`; `result.startswith(prefix)`; `len(result) == len(prefix) + 8`; `not any((w.window_name == result for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_custom_prefix_0a06addb6e | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_default_prefix | Test get_test_window_name with default prefix; fixtures `session`; execute `get_test_window_name(session=session)`; `result.startswith(TEST_SESSION_PREFIX)`; `any((w.window_name == result for w in session.windows))`; assert `isinstance(result, str)`; `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `not any((w.window_name == result for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_default_prefix_8d00d82cee | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_doctest_examples | Test the doctest examples for get_test_window_name; fixtures `session`; execute `get_test_window_name(session=session)`; `result.startswith(TEST_SESSION_PREFIX)`; assert `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `result1 != result2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_doctest_examples_66bb5af877 | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_loop_behavior | Test the loop behavior in get_test_window_name using real windows; fixtures `session`; execute `session.new_window(window_name=first_name)`; `session.new_window(window_name=second_name)`; `get_test_window_name(session=session)`; `any((w.window_name == first_name for w in session.windows))`; `any((w.window_name == second_name for w in session.windows))`; `any((w.window_name == third_name for w in session.windows))`; assert `first_name != second_name`; `any((w.window_name == first_name for w in session.windows))`; `not any((w.window_name == second_name for w in session.windows))`; `first_name != third_name`; `second_name != third_name`; `any((w.window_name == second_name for w in session.windows))`; `not any((w.window_name == third_name for w in session.windows))`; when `window2`: execute `window2.kill()`; when `window`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_loop_behavior_7941ff860d | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_null_prefix | Test that get_test_window_name with None prefix raises an assertion error; execute `t.cast('Session', object())`; `object()`; `get_test_window_name(mock_session, prefix=None)`; expect `pytest.raises(AssertionError)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_null_prefix_73135fa901 | 3.2a through 3.7b | planned parity | -| tests/test/test_random.py::test_get_test_window_name_requires_prefix | Test that get_test_window_name requires a prefix; execute `get_test_window_name(session=t.cast('Session', object()), prefix=None)`; `t.cast('Session', object())`; `object()`; expect `pytest.raises(AssertionError)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_requires_prefix_7be81373c7 | 3.2a through 3.7b | planned parity | +| tests/test/test_random.py::test_collisions_with_real_objects | Test collision behavior using real tmux objects instead of mocks; fixtures `server`, `session`; execute `server.new_session(session_name1)`; `server.new_session(session_name2)`; `server.has_session(session_name1)`; `server.has_session(session_name2)`; `server.has_session(result)`; `session.new_window(window_name=window_name1)`; assert `server.has_session(session_name1)`; `server.has_session(session_name2)`; `result.startswith(prefix)`; `result != session_name1`; `result != session_name2`; `not server.has_session(result)`; `any((w.window_name == window_name1 for w in session.windows))`; `any((w.window_name == window_name2 for w in session.windows))`; `result != window_name1`; `result != window_name2`; `not any((w.window_name == result for w in session.windows))`; when `window1`: execute `window1.kill()`; when `window2`: execute `window2.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_collisions_with_real_objects_c73ef59ae2 | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_session_name_collision_handling | Test that get_test_session_name handles collisions properly; fixtures `server`, `monkeypatch`; execute `get_test_session_name(server)`; `session_name.startswith(TEST_SESSION_PREFIX)`; assert `call_count == 2`; `session_name.startswith(TEST_SESSION_PREFIX)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_collision_handling_44e82833e0 | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_session_name_custom_prefix | Test get_test_session_name with custom prefix; fixtures `server`; execute `server.has_session(result)`; `get_test_session_name(server=server, prefix=prefix)`; `result.startswith(prefix)`; assert `isinstance(result, str)`; `result.startswith(prefix)`; `len(result) == len(prefix) + 8`; `not server.has_session(result)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_custom_prefix_93d8de90ce | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_session_name_default_prefix | Test get_test_session_name with default prefix; fixtures `server`; execute `server.has_session(result)`; `get_test_session_name(server=server)`; `result.startswith(TEST_SESSION_PREFIX)`; assert `isinstance(result, str)`; `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `not server.has_session(result)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_default_prefix_f2e42e2c73 | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_session_name_doctest_examples | Test the doctest examples for get_test_session_name; fixtures `server`; execute `get_test_session_name(server=server)`; `result.startswith(TEST_SESSION_PREFIX)`; assert `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `result1 != result2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_doctest_examples_c6410244df | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_session_name_implementation_details | Test specific implementation details of get_test_session_name function; fixtures `server`, `monkeypatch`; execute `server.new_session(collision_name)`; `get_test_session_name(server, prefix=prefix)`; `result.startswith(prefix)`; assert `result != collision_name`; `result.startswith(prefix)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_test_random_py_test_get_test_session_name_implementation_details_53ddea23bf | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_session_name_loop_behavior | Test the loop behavior in get_test_session_name using real sessions; fixtures `server`; execute `server.new_session(first_name)`; `server.has_session(first_name)`; `server.has_session(second_name)`; `server.new_session(second_name)`; `server.has_session(third_name)`; `get_test_session_name(server=server)`; assert `first_name != second_name`; `server.has_session(first_name)`; `not server.has_session(second_name)`; `first_name != third_name`; `second_name != third_name`; `server.has_session(second_name)`; `not server.has_session(third_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_session_name_loop_behavior_401ae81bad | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_branch_coverage | Test branch coverage for get_test_window_name function; fixtures `session`; execute `session.new_window(window_name=collision_name)`; `get_test_window_name(session, prefix=prefix)`; `result.startswith(prefix)`; assert `result != collision_name`; `result.startswith(prefix)`; when `window`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_branch_coverage_fa55610e5b | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_collision_handling | Test that get_test_window_name handles collisions properly; fixtures `session`, `monkeypatch`; execute `session.new_window(window_name=first_name)`; `get_test_window_name(session, prefix=prefix)`; `window_name.startswith(prefix)`; `any((w.window_name == window_name for w in session.windows))`; `any((w.window_name == first_name for w in session.windows))`; assert `window_name != first_name`; `window_name.startswith(prefix)`; `not any((w.window_name == window_name for w in session.windows))`; `any((w.window_name == first_name for w in session.windows))`; when `window`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_collision_handling_daac1a3b01 | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_custom_prefix | Test get_test_window_name with custom prefix; fixtures `session`; execute `get_test_window_name(session=session, prefix=prefix)`; `result.startswith(prefix)`; `any((w.window_name == result for w in session.windows))`; assert `isinstance(result, str)`; `result.startswith(prefix)`; `len(result) == len(prefix) + 8`; `not any((w.window_name == result for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_custom_prefix_0a06addb6e | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_default_prefix | Test get_test_window_name with default prefix; fixtures `session`; execute `get_test_window_name(session=session)`; `result.startswith(TEST_SESSION_PREFIX)`; `any((w.window_name == result for w in session.windows))`; assert `isinstance(result, str)`; `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `not any((w.window_name == result for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_default_prefix_8d00d82cee | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_doctest_examples | Test the doctest examples for get_test_window_name; fixtures `session`; execute `get_test_window_name(session=session)`; `result.startswith(TEST_SESSION_PREFIX)`; assert `result.startswith(TEST_SESSION_PREFIX)`; `len(result) == len(TEST_SESSION_PREFIX) + 8`; `result1 != result2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_doctest_examples_66bb5af877 | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_loop_behavior | Test the loop behavior in get_test_window_name using real windows; fixtures `session`; execute `session.new_window(window_name=first_name)`; `session.new_window(window_name=second_name)`; `get_test_window_name(session=session)`; `any((w.window_name == first_name for w in session.windows))`; `any((w.window_name == second_name for w in session.windows))`; `any((w.window_name == third_name for w in session.windows))`; assert `first_name != second_name`; `any((w.window_name == first_name for w in session.windows))`; `not any((w.window_name == second_name for w in session.windows))`; `first_name != third_name`; `second_name != third_name`; `any((w.window_name == second_name for w in session.windows))`; `not any((w.window_name == third_name for w in session.windows))`; when `window2`: execute `window2.kill()`; when `window`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_loop_behavior_7941ff860d | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_null_prefix | Test that get_test_window_name with None prefix raises an assertion error; execute `t.cast('Session', object())`; `object()`; `get_test_window_name(mock_session, prefix=None)`; expect `pytest.raises(AssertionError)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_null_prefix_73135fa901 | 3.3a through 3.7c | planned parity | +| tests/test/test_random.py::test_get_test_window_name_requires_prefix | Test that get_test_window_name requires a prefix; execute `get_test_window_name(session=t.cast('Session', object()), prefix=None)`; `t.cast('Session', object())`; `object()`; expect `pytest.raises(AssertionError)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_random_py_test_get_test_window_name_requires_prefix_7be81373c7 | 3.3a through 3.7c | planned parity | | tests/test/test_random.py::test_import_typing_coverage | Test coverage for typing imports in random.py; execute `dir(random)`; assert `hasattr(t, 'TYPE_CHECKING')`; `'t' in dir(random)`. | unit test | PythonBehaviorParityTest#tests_test_test_random_py_test_import_typing_coverage_911f57b387 | not tmux-bound | planned parity | | tests/test/test_random.py::test_imports_coverage | Test coverage for import statements in random.py; assert `hasattr(random, 'random')`; `hasattr(random, 't')`; `hasattr(random, 'TEST_SESSION_PREFIX')`. | unit test | PythonBehaviorParityTest#tests_test_test_random_py_test_imports_coverage_10fccd6010 | not tmux-bound | planned parity | | tests/test/test_random.py::test_iterator_protocol | Test the complete iterator protocol of RandomStrSequence; execute `RandomStrSequence()`; assert `iterator is rng`; `isinstance(result, str)`; `len(result) == 8`. | unit test | PythonBehaviorParityTest#tests_test_test_random_py_test_iterator_protocol_f1f61f029a | not tmux-bound | planned parity | @@ -639,48 +639,48 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test/test_sparse_array.py::test_sparse_array_iter_values[consecutive] | Test SparseArray.iter_values method; case inputs `test_case=SparseArrayValuesTestCase(test_id='consecutive', adds=[(3, 'three'), (1, 'one'), (2, 'two')], expected_values=['one', 'two', 'three'])`; execute `SparseArray()`; `arr.iter_values()`; assert `list(arr.iter_values()) == test_case.expected_values`. | unit test | PythonBehaviorParityTest#ests_test_test_sparse_array_py_test_sparse_array_iter_values_consecutive_cc3924026a | not tmux-bound | planned parity | | tests/test/test_sparse_array.py::test_sparse_array_iter_values[empty] | Test SparseArray.iter_values method; case inputs `test_case=SparseArrayValuesTestCase(test_id='empty', adds=[], expected_values=[])`; execute `SparseArray()`; `arr.iter_values()`; assert `list(arr.iter_values()) == test_case.expected_values`. | unit test | PythonBehaviorParityTest#tests_test_test_sparse_array_py_test_sparse_array_iter_values_empty_f1bfa05fd9 | not tmux-bound | planned parity | | tests/test/test_sparse_array.py::test_sparse_array_iter_values[sorted_order] | Test SparseArray.iter_values method; case inputs `test_case=SparseArrayValuesTestCase(test_id='sorted_order', adds=[(10, 'ten'), (1, 'one'), (5, 'five')], expected_values=['one', 'five', 'ten'])`; execute `SparseArray()`; `arr.iter_values()`; assert `list(arr.iter_values()) == test_case.expected_values`. | unit test | PythonBehaviorParityTest#sts_test_test_sparse_array_py_test_sparse_array_iter_values_sorted_order_e0f2bfa205 | not tmux-bound | planned parity | -| tests/test/test_temporary.py::test_temp_session_cleanup_on_exception | Test that temp_session cleans up even when an exception occurs; fixtures `server`; execute `server.has_session(session_name)`; `RuntimeError()`; `temp_session(server)`; expect `pytest.raises(RuntimeError)`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_cleanup_on_exception_1333901d0f | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_session_creates_and_destroys | Test that temp_session creates and destroys a session; fixtures `server`; execute `server.has_session(session_name)`; `temp_session(server)`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_creates_and_destroys_dc9a9b32eb | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_session_outside_context | Test that temp_session's finally block handles a session already killed; fixtures `server`; execute `server.has_session(session_name)`; `session.kill()`; `temp_session(server)`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_outside_context_3beb5436cc | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_session_with_name | Test temp_session with a provided session name; fixtures `server`; execute `server.has_session(session_name)`; `temp_session(server, session_name=session_name)`; assert `session.session_name == session_name`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_with_name_66ee13409a | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_window_cleanup_on_exception | Test that temp_window cleans up even when an exception occurs; fixtures `session`; execute `RuntimeError()`; `temp_window(session)`; `any((w.window_id == window_id for w in session.windows))`; expect `pytest.raises(RuntimeError)`; assert `window_id is not None`; `len(session.windows) == initial_windows + 1`; `any((w.window_id == window_id for w in session.windows))`; `len(session.windows) == initial_windows`; `not any((w.window_id == window_id for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_cleanup_on_exception_f789bddb7e | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_window_creates_and_destroys | Test that temp_window creates and destroys a window; fixtures `session`; execute `temp_window(session)`; `any((w.window_id == window_id for w in session.windows))`; assert `window_id is not None`; `len(session.windows) == initial_windows + 1`; `any((w.window_id == window_id for w in session.windows))`; `len(session.windows) == initial_windows`; `not any((w.window_id == window_id for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_creates_and_destroys_66d8452734 | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_window_outside_context | Test that temp_window's finally block handles a window already killed; fixtures `session`; execute `window.kill()`; `temp_window(session)`; `any((w.window_id == window_id for w in session.windows))`; assert `window_id is not None`; `len(session.windows) == initial_windows + 1`; `len(session.windows) == initial_windows`; `not any((w.window_id == window_id for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_outside_context_99af61c3cf | 3.2a through 3.7b | planned parity | -| tests/test/test_temporary.py::test_temp_window_with_name | Test temp_window with a provided window name; fixtures `session`; execute `temp_window(session, window_name=window_name)`; `any((w.window_name == window_name for w in session.windows))`; assert `window.window_name == window_name`; `len(session.windows) == initial_windows + 1`; `any((w.window_name == window_name for w in session.windows))`; `len(session.windows) == initial_windows`; `not any((w.window_name == window_name for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_with_name_fd6ff5e195 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_attached_pane_tracks_active_pane | ``client.attached_pane`` reflects the active pane in the active window; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; assert `client is not None`; `isinstance(attached, Pane)`; `attached.pane_id == session.active_window.active_pane.pane_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_attached_pane_tracks_active_pane_856648b529 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_attached_properties_return_none_after_detach | ``attached_*`` returns ``None`` after the client leaves ``list-clients``; fixtures `control_mode`, `server`; execute `control_mode()`; assert `client is not None`; `client.attached_session is not None`; `client.attached_window is not None`; `client.attached_pane is not None`; `client.attached_session is None`; `client.attached_window is None`; `client.attached_pane is None`. | real-tmux integration test | PythonBehaviorParityTest#_test_client_py_test_client_attached_properties_return_none_after_detach_a94d84646a | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_attached_session_returns_typed_session | ``client.attached_session`` resolves to the live :class:`Session`; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; assert `client is not None`; `isinstance(attached, Session)`; `attached.session_id == session.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_attached_session_returns_typed_session_2db32a30ab | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_attached_window_tracks_active_window | ``client.attached_window`` reflects the live active window; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; `session.new_window(window_name='attached_window_probe')`; `session.select_window(new_window.window_index)`; assert `client is not None`; `new_window.window_index is not None`; `isinstance(attached, Window)`; `attached.window_id == new_window.window_id`; `attached.window_id != snapshot_window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_attached_window_tracks_active_window_acbde7102f | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_readonly_default_zero | A non-readonly attached client reports ``client_readonly == "0"``; fixtures `control_mode`, `server`; execute `control_mode()`; assert `client is not None`; `client.client_readonly == '0'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_readonly_default_zero_d3363faa60 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_refresh_raises_when_client_name_is_none | ``Client.refresh()`` raises ``ValueError`` when ``client_name`` is unset; fixtures `server`; execute `client.refresh()`; `Client(server=server)`; expect `pytest.raises(ValueError, match='client_name')`; assert `client.client_name is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_refresh_raises_when_client_name_is_none_06a9b387e2 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_refresh_rehydrates_fields | ``Client.refresh()`` repopulates fields from tmux's live state; fixtures `control_mode`, `server`; execute `control_mode()`; `client.refresh()`; `Client.from_client_name(server=server, client_name=ctl.client_name)`; assert `client.client_name == ctl.client_name`; `client.client_pid == original_pid`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_refresh_rehydrates_fields_a64a728b4e | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_client_session_reports_attached_session | ``client.client_session`` reports the session this client is attached to; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; assert `client is not None`; `client.client_session == session.session_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_session_reports_attached_session_e2d9e0864a | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_clients_property_hydrates_cross_scope | ``Server.clients`` hydrates the client's active session/window/pane; fixtures `control_mode`, `server`; execute `control_mode()`; assert `client is not None`; `client.session_id is not None`; `client.window_id is not None`; `client.pane_id is not None`; `attached_session is not None`; `active_pane is not None`; `client.window_id == attached_session.active_window.window_id`; `client.pane_id == active_pane.pane_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_clients_property_hydrates_cross_scope_43198e59a0 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_resolve_attached_catches_no_active_window | ``_resolve_attached`` returns ``(session, None, None)`` on NoActiveWindow; fixtures `control_mode`, `server`, `monkeypatch`; execute `control_mode()`; `client._resolve_attached()`; `property(raise_no_active_window)`; assert `client is not None`; `resolved_session is not None`; `resolved_window is None`; `resolved_pane is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_resolve_attached_catches_no_active_window_18b4dc6044 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_resolve_attached_returns_full_triple_for_live_client | ``_resolve_attached`` returns ``(session, window, pane)`` for a live client; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; `client._resolve_attached()`; assert `client is not None`; `resolved_session is not None`; `resolved_session.session_id == session.session_id`; `resolved_window is not None`; `resolved_pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#test_client_py_test_resolve_attached_returns_full_triple_for_live_client_f620ce2cb1 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_resolve_attached_returns_none_triple_after_detach | ``_resolve_attached`` returns ``(None, None, None)`` after detach; fixtures `control_mode`, `server`; execute `control_mode()`; `client._resolve_attached()`; assert `client is not None`; `resolved == (None, None, None)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_client_py_test_resolve_attached_returns_none_triple_after_detach_4759334406 | 3.2a through 3.7b | planned parity | -| tests/test_client.py::test_server_clients_returns_querylist | ``Server.clients`` lists every attached tmux client as a :class:`Client`; fixtures `control_mode`, `server`; execute `control_mode()`; assert `len(clients) >= 1`; `isinstance(client, Client)`; `client.client_name is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_server_clients_returns_querylist_c3651cf820 | 3.2a through 3.7b | planned parity | +| tests/test/test_temporary.py::test_temp_session_cleanup_on_exception | Test that temp_session cleans up even when an exception occurs; fixtures `server`; execute `server.has_session(session_name)`; `RuntimeError()`; `temp_session(server)`; expect `pytest.raises(RuntimeError)`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_cleanup_on_exception_1333901d0f | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_session_creates_and_destroys | Test that temp_session creates and destroys a session; fixtures `server`; execute `server.has_session(session_name)`; `temp_session(server)`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_creates_and_destroys_dc9a9b32eb | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_session_outside_context | Test that temp_session's finally block handles a session already killed; fixtures `server`; execute `server.has_session(session_name)`; `session.kill()`; `temp_session(server)`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_outside_context_3beb5436cc | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_session_with_name | Test temp_session with a provided session name; fixtures `server`; execute `server.has_session(session_name)`; `temp_session(server, session_name=session_name)`; assert `session.session_name == session_name`; `server.has_session(session_name)`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_session_with_name_66ee13409a | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_window_cleanup_on_exception | Test that temp_window cleans up even when an exception occurs; fixtures `session`; execute `RuntimeError()`; `temp_window(session)`; `any((w.window_id == window_id for w in session.windows))`; expect `pytest.raises(RuntimeError)`; assert `window_id is not None`; `len(session.windows) == initial_windows + 1`; `any((w.window_id == window_id for w in session.windows))`; `len(session.windows) == initial_windows`; `not any((w.window_id == window_id for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_cleanup_on_exception_f789bddb7e | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_window_creates_and_destroys | Test that temp_window creates and destroys a window; fixtures `session`; execute `temp_window(session)`; `any((w.window_id == window_id for w in session.windows))`; assert `window_id is not None`; `len(session.windows) == initial_windows + 1`; `any((w.window_id == window_id for w in session.windows))`; `len(session.windows) == initial_windows`; `not any((w.window_id == window_id for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_creates_and_destroys_66d8452734 | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_window_outside_context | Test that temp_window's finally block handles a window already killed; fixtures `session`; execute `window.kill()`; `temp_window(session)`; `any((w.window_id == window_id for w in session.windows))`; assert `window_id is not None`; `len(session.windows) == initial_windows + 1`; `len(session.windows) == initial_windows`; `not any((w.window_id == window_id for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_outside_context_99af61c3cf | 3.3a through 3.7c | planned parity | +| tests/test/test_temporary.py::test_temp_window_with_name | Test temp_window with a provided window name; fixtures `session`; execute `temp_window(session, window_name=window_name)`; `any((w.window_name == window_name for w in session.windows))`; assert `window.window_name == window_name`; `len(session.windows) == initial_windows + 1`; `any((w.window_name == window_name for w in session.windows))`; `len(session.windows) == initial_windows`; `not any((w.window_name == window_name for w in session.windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_test_temporary_py_test_temp_window_with_name_fd6ff5e195 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_attached_pane_tracks_active_pane | ``client.attached_pane`` reflects the active pane in the active window; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; assert `client is not None`; `isinstance(attached, Pane)`; `attached.pane_id == session.active_window.active_pane.pane_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_attached_pane_tracks_active_pane_856648b529 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_attached_properties_return_none_after_detach | ``attached_*`` returns ``None`` after the client leaves ``list-clients``; fixtures `control_mode`, `server`; execute `control_mode()`; assert `client is not None`; `client.attached_session is not None`; `client.attached_window is not None`; `client.attached_pane is not None`; `client.attached_session is None`; `client.attached_window is None`; `client.attached_pane is None`. | real-tmux integration test | PythonBehaviorParityTest#_test_client_py_test_client_attached_properties_return_none_after_detach_a94d84646a | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_attached_session_returns_typed_session | ``client.attached_session`` resolves to the live :class:`Session`; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; assert `client is not None`; `isinstance(attached, Session)`; `attached.session_id == session.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_attached_session_returns_typed_session_2db32a30ab | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_attached_window_tracks_active_window | ``client.attached_window`` reflects the live active window; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; `session.new_window(window_name='attached_window_probe')`; `session.select_window(new_window.window_index)`; assert `client is not None`; `new_window.window_index is not None`; `isinstance(attached, Window)`; `attached.window_id == new_window.window_id`; `attached.window_id != snapshot_window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_attached_window_tracks_active_window_acbde7102f | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_readonly_default_zero | A non-readonly attached client reports ``client_readonly == "0"``; fixtures `control_mode`, `server`; execute `control_mode()`; assert `client is not None`; `client.client_readonly == '0'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_readonly_default_zero_d3363faa60 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_refresh_raises_when_client_name_is_none | ``Client.refresh()`` raises ``ValueError`` when ``client_name`` is unset; fixtures `server`; execute `client.refresh()`; `Client(server=server)`; expect `pytest.raises(ValueError, match='client_name')`; assert `client.client_name is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_refresh_raises_when_client_name_is_none_06a9b387e2 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_refresh_rehydrates_fields | ``Client.refresh()`` repopulates fields from tmux's live state; fixtures `control_mode`, `server`; execute `control_mode()`; `client.refresh()`; `Client.from_client_name(server=server, client_name=ctl.client_name)`; assert `client.client_name == ctl.client_name`; `client.client_pid == original_pid`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_refresh_rehydrates_fields_a64a728b4e | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_client_session_reports_attached_session | ``client.client_session`` reports the session this client is attached to; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; assert `client is not None`; `client.client_session == session.session_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_client_session_reports_attached_session_e2d9e0864a | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_clients_property_hydrates_cross_scope | ``Server.clients`` hydrates the client's active session/window/pane; fixtures `control_mode`, `server`; execute `control_mode()`; assert `client is not None`; `client.session_id is not None`; `client.window_id is not None`; `client.pane_id is not None`; `attached_session is not None`; `active_pane is not None`; `client.window_id == attached_session.active_window.window_id`; `client.pane_id == active_pane.pane_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_clients_property_hydrates_cross_scope_43198e59a0 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_resolve_attached_catches_no_active_window | ``_resolve_attached`` returns ``(session, None, None)`` on NoActiveWindow; fixtures `control_mode`, `server`, `monkeypatch`; execute `control_mode()`; `client._resolve_attached()`; `property(raise_no_active_window)`; assert `client is not None`; `resolved_session is not None`; `resolved_window is None`; `resolved_pane is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_resolve_attached_catches_no_active_window_18b4dc6044 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_resolve_attached_returns_full_triple_for_live_client | ``_resolve_attached`` returns ``(session, window, pane)`` for a live client; fixtures `control_mode`, `server`, `session`; execute `control_mode()`; `client._resolve_attached()`; assert `client is not None`; `resolved_session is not None`; `resolved_session.session_id == session.session_id`; `resolved_window is not None`; `resolved_pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#test_client_py_test_resolve_attached_returns_full_triple_for_live_client_f620ce2cb1 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_resolve_attached_returns_none_triple_after_detach | ``_resolve_attached`` returns ``(None, None, None)`` after detach; fixtures `control_mode`, `server`; execute `control_mode()`; `client._resolve_attached()`; assert `client is not None`; `resolved == (None, None, None)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_client_py_test_resolve_attached_returns_none_triple_after_detach_4759334406 | 3.3a through 3.7c | planned parity | +| tests/test_client.py::test_server_clients_returns_querylist | ``Server.clients`` lists every attached tmux client as a :class:`Client`; fixtures `control_mode`, `server`; execute `control_mode()`; assert `len(clients) >= 1`; `isinstance(client, Client)`; `client.client_name is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_client_py_test_server_clients_returns_querylist_c3651cf820 | 3.3a through 3.7c | planned parity | | tests/test_common.py::test_get_libtmux_version | Verify get_libtmux_version(); execute `get_libtmux_version()`; `LooseVersion(__version__)`; assert `isinstance(version, LooseVersion)`; `LooseVersion(__version__) == version`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_get_libtmux_version_d2b919b70b | not tmux-bound | planned parity | | tests/test_common.py::test_get_version_binary_swap_requires_explicit_cache_clear | Documents the sticky-cache trap when tmux_bin=None and PATH changes; fixtures `monkeypatch`; execute `_MockProc(versions[call_count['n']])`; `get_version.cache_clear()`; `get_version()`; assert `str(first) == '3.2'`; `str(second) == '3.2'`; `call_count['n'] == 1`; `str(third) == '3.6'`; `call_count['n'] == 2`. | unit test | PythonBehaviorParityTest#est_common_py_test_get_version_binary_swap_requires_explicit_cache_clear_20650db80d | not tmux-bound | planned parity | | tests/test_common.py::test_get_version_cache_clear_invalidates | cache_clear() forces a fresh subprocess on the next call; fixtures `monkeypatch`; execute `_MockProc()`; `get_version.cache_clear()`; `get_version()`; assert `call_count['n'] == 1`; `call_count['n'] == 2`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_get_version_cache_clear_invalidates_349ba05954 | not tmux-bound | planned parity | | tests/test_common.py::test_get_version_cache_keyed_by_tmux_bin | Different tmux_bin args cache independently; same arg revisits hit; fixtures `monkeypatch`; execute `_MockProc(versions[kwargs['tmux_bin']])`; `get_version.cache_clear()`; `get_version(tmux_bin='/path/a/tmux')`; `get_version(tmux_bin='/path/b/tmux')`; assert `a1 != b1`; `a1 == a2`; `call_count['n'] == 2`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_get_version_cache_keyed_by_tmux_bin_fd49c906d2 | not tmux-bound | planned parity | | tests/test_common.py::test_get_version_is_memoized_for_same_tmux_bin | Two calls with the same tmux_bin fork tmux -V once; fixtures `monkeypatch`; execute `_MockProc()`; `get_version.cache_clear()`; `get_version()`; assert `v1 == v2`; `call_count['n'] == 1`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_get_version_is_memoized_for_same_tmux_bin_6247d1cd0c | not tmux-bound | planned parity | | tests/test_common.py::test_has_version | Test has_version(); execute `has_version(str(get_version()))`; `get_version()`; assert `has_version(str(get_version()))`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_has_version_b831b79403 | not tmux-bound | planned parity | -| tests/test_common.py::test_libtmux_exception_subcommand_default_none | Backward-compat: existing call sites (no kwarg) get subcommand=None; execute `exc.LibTmuxException(['no last window'])`; assert `err.subcommand is None`; `'no last window' in str(err)`; `not str(err).startswith(':')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_libtmux_exception_subcommand_default_none_617370776f | 3.2a through 3.7b | planned parity | -| tests/test_common.py::test_libtmux_exception_subcommand_tags_str | When ``subcommand`` is set, str(exc) prefixes ``"<subcommand>: …"``; execute `exc.LibTmuxException(['no last window'], subcommand='last-window')`; assert `err.subcommand == 'last-window'`; `str(err).startswith('last-window:')`; `'no last window' in str(err)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_libtmux_exception_subcommand_tags_str_7a2963a317 | 3.2a through 3.7b | planned parity | -| tests/test_common.py::test_raise_if_stderr_no_stderr_is_noop | ``raise_if_stderr`` returns silently when proc.stderr is empty; fixtures `session`; execute `session.cmd('display-message', '-p', '#{version}')`; `raise_if_stderr(proc, 'display-message')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_raise_if_stderr_no_stderr_is_noop_f24c83c29f | 3.2a through 3.7b | planned parity | -| tests/test_common.py::test_raise_if_stderr_raises_with_subcommand_tag | ``raise_if_stderr`` raises ``LibTmuxException`` tagged with subcommand; fixtures `session`; execute `session.server.cmd('list-clients', '-t', '$nonexistent_session_id_for_test')`; `raise_if_stderr(proc, 'list-clients')`; expect `pytest.raises(exc.LibTmuxException)`; assert `proc.stderr`; `excinfo.value.subcommand == 'list-clients'`; `str(excinfo.value).startswith('list-clients:')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_raise_if_stderr_raises_with_subcommand_tag_16c9b0d53a | 3.2a through 3.7b | planned parity | -| tests/test_common.py::test_raise_if_stderr_str_shape_exact | Lock down ``str(exc)`` and ``exc.args[0]`` against future drift; fixtures `session`; execute `session.cmd('last-window')`; `raise_if_stderr(proc, 'last-window')`; expect `pytest.raises(exc.LibTmuxException)`; assert `proc.stderr == ['no last window']`; `str(excinfo.value) == 'last-window: no last window'`; `excinfo.value.args == ('no last window',)`; `excinfo.value.subcommand == 'last-window'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_raise_if_stderr_str_shape_exact_b600864546 | 3.2a through 3.7b | planned parity | +| tests/test_common.py::test_libtmux_exception_subcommand_default_none | Backward-compat: existing call sites (no kwarg) get subcommand=None; execute `exc.LibTmuxException(['no last window'])`; assert `err.subcommand is None`; `'no last window' in str(err)`; `not str(err).startswith(':')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_libtmux_exception_subcommand_default_none_617370776f | 3.3a through 3.7c | planned parity | +| tests/test_common.py::test_libtmux_exception_subcommand_tags_str | When ``subcommand`` is set, str(exc) prefixes ``"<subcommand>: …"``; execute `exc.LibTmuxException(['no last window'], subcommand='last-window')`; assert `err.subcommand == 'last-window'`; `str(err).startswith('last-window:')`; `'no last window' in str(err)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_libtmux_exception_subcommand_tags_str_7a2963a317 | 3.3a through 3.7c | planned parity | +| tests/test_common.py::test_raise_if_stderr_no_stderr_is_noop | ``raise_if_stderr`` returns silently when proc.stderr is empty; fixtures `session`; execute `session.cmd('display-message', '-p', '#{version}')`; `raise_if_stderr(proc, 'display-message')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_raise_if_stderr_no_stderr_is_noop_f24c83c29f | 3.3a through 3.7c | planned parity | +| tests/test_common.py::test_raise_if_stderr_raises_with_subcommand_tag | ``raise_if_stderr`` raises ``LibTmuxException`` tagged with subcommand; fixtures `session`; execute `session.server.cmd('list-clients', '-t', '$nonexistent_session_id_for_test')`; `raise_if_stderr(proc, 'list-clients')`; expect `pytest.raises(exc.LibTmuxException)`; assert `proc.stderr`; `excinfo.value.subcommand == 'list-clients'`; `str(excinfo.value).startswith('list-clients:')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_raise_if_stderr_raises_with_subcommand_tag_16c9b0d53a | 3.3a through 3.7c | planned parity | +| tests/test_common.py::test_raise_if_stderr_str_shape_exact | Lock down ``str(exc)`` and ``exc.args[0]`` against future drift; fixtures `session`; execute `session.cmd('last-window')`; `raise_if_stderr(proc, 'last-window')`; expect `pytest.raises(exc.LibTmuxException)`; assert `proc.stderr == ['no last window']`; `str(excinfo.value) == 'last-window: no last window'`; `excinfo.value.args == ('no last window',)`; `excinfo.value.subcommand == 'last-window'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_raise_if_stderr_str_shape_exact_b600864546 | 3.3a through 3.7c | planned parity | | tests/test_common.py::test_session_check_name[contains_colon] | Verify session_check_name(); case inputs `exc_msg_regex='contains colons'`, `raises=True`, `session_name='name: great session'`, `test_id='contains_colon'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_session_check_name_contains_colon_4ac24afe6e | not tmux-bound | planned parity | | tests/test_common.py::test_session_check_name[contains_period] | Verify session_check_name(); case inputs `exc_msg_regex='contains periods'`, `raises=True`, `session_name='my great session.'`, `test_id='contains_period'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_session_check_name_contains_period_7e14ce5df4 | not tmux-bound | planned parity | | tests/test_common.py::test_session_check_name[empty_string] | Verify session_check_name(); case inputs `exc_msg_regex='empty'`, `raises=True`, `session_name=''`, `test_id='empty_string'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_session_check_name_empty_string_662679af52 | not tmux-bound | planned parity | | tests/test_common.py::test_session_check_name[none_value] | Verify session_check_name(); case inputs `exc_msg_regex='empty'`, `raises=True`, `session_name=None`, `test_id='none_value'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_session_check_name_none_value_68fd2ff3d8 | not tmux-bound | planned parity | | tests/test_common.py::test_session_check_name[valid_name] | Verify session_check_name(); case inputs `exc_msg_regex=None`, `raises=False`, `session_name='new great session'`, `test_id='valid_name'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_session_check_name_valid_name_63cf81f9ed | not tmux-bound | planned parity | | tests/test_common.py::test_session_check_name[valid_with_special_chars] | Verify session_check_name(); case inputs `exc_msg_regex=None`, `raises=False`, `session_name='ajf8a3fa83fads,,,a'`, `test_id='valid_with_special_chars'`; when `raises`: execute `session_check_name(session_name)`; expect `pytest.raises(exc.BadSessionName)`; when `raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; assert `exc_info.match(exc_msg_regex)`; when `not (raises)`: execute `session_check_name(session_name)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_session_check_name_valid_with_special_chars_4ddab0bea8 | not tmux-bound | planned parity | -| tests/test_common.py::test_tmux_cmd_format_separator_survives_non_utf8_locale | FORMAT_SEPARATOR must survive a non-UTF-8 locale round-trip through tmux_cmd; fixtures `session`; execute `server.cmd('list-sessions', f'-F{fmt_str}')`; `get_version(tmux_bin=server.tmux_bin)`; `get_output_format('list-sessions', tmux_version)`; `locale.setlocale(locale.LC_CTYPE)`; `locale.setlocale(locale.LC_CTYPE, 'C')`; `locale.setlocale(locale.LC_CTYPE, old_lc_ctype)`; assert `proc.stdout`; `FORMAT_SEPARATOR in line; message=f'FORMAT_SEPARATOR U+241E not found in output; got {line[:80]!r}... (likely decoded with wrong encoding)'`; `isinstance(result, dict)`; `'session_id' in result`. | real-tmux integration test | PythonBehaviorParityTest#s_test_common_py_test_tmux_cmd_format_separator_survives_non_utf8_locale_c6f0b55d39 | 3.2a through 3.7b | planned parity | -| tests/test_common.py::test_tmux_cmd_pre_execution_logging | Verify tmux_cmd logs command before execution; fixtures `caplog`, `server`; execute `server.cmd('list-sessions')`; assert `len(running_records) > 0`; `'list-sessions' in running_records[0].tmux_cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_tmux_cmd_pre_execution_logging_b0644f6e64 | 3.2a through 3.7b | planned parity | +| tests/test_common.py::test_tmux_cmd_format_separator_survives_non_utf8_locale | FORMAT_SEPARATOR must survive a non-UTF-8 locale round-trip through tmux_cmd; fixtures `session`; execute `server.cmd('list-sessions', f'-F{fmt_str}')`; `get_version(tmux_bin=server.tmux_bin)`; `get_output_format('list-sessions', tmux_version)`; `locale.setlocale(locale.LC_CTYPE)`; `locale.setlocale(locale.LC_CTYPE, 'C')`; `locale.setlocale(locale.LC_CTYPE, old_lc_ctype)`; assert `proc.stdout`; `FORMAT_SEPARATOR in line; message=f'FORMAT_SEPARATOR U+241E not found in output; got {line[:80]!r}... (likely decoded with wrong encoding)'`; `isinstance(result, dict)`; `'session_id' in result`. | real-tmux integration test | PythonBehaviorParityTest#s_test_common_py_test_tmux_cmd_format_separator_survives_non_utf8_locale_c6f0b55d39 | 3.3a through 3.7c | planned parity | +| tests/test_common.py::test_tmux_cmd_pre_execution_logging | Verify tmux_cmd logs command before execution; fixtures `caplog`, `server`; execute `server.cmd('list-sessions')`; assert `len(running_records) > 0`; `'list-sessions' in running_records[0].tmux_cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_tmux_cmd_pre_execution_logging_b0644f6e64 | 3.3a through 3.7c | planned parity | | tests/test_common.py::test_tmux_cmd_raises_on_not_found | Verify raises if tmux command not found; fixtures `monkeypatch`; execute `tmux_cmd('-V')`; expect `pytest.raises(exc.TmuxCommandNotFound)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_tmux_cmd_raises_on_not_found_5194c943e2 | not tmux-bound | planned parity | -| tests/test_common.py::test_tmux_cmd_unicode | Verify tmux commands with unicode; fixtures `session`; execute `session.cmd('new-window', '-n', 'юникод', '-F', 'Ελληνικά', target=3)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_tmux_cmd_unicode_50412fe23b | 3.2a through 3.7b | planned parity | +| tests/test_common.py::test_tmux_cmd_unicode | Verify tmux commands with unicode; fixtures `session`; execute `session.cmd('new-window', '-n', 'юникод', '-F', 'Ελληνικά', target=3)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_common_py_test_tmux_cmd_unicode_50412fe23b | 3.3a through 3.7c | planned parity | | tests/test_common.py::test_version_comparison[gt_newer_version] | Test version comparison functions; case inputs `comparison_type='gt'`, `expected=False`, `test_id='gt_newer_version'`, `version='4.0'`; execute `comparison_funcs[comparison_type](version)`; assert `comparison_funcs[comparison_type](version) == expected`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_version_comparison_gt_newer_version_c68e9dd794 | not tmux-bound | planned parity | | tests/test_common.py::test_version_comparison[gt_newer_version_with_letter] | Test version comparison functions; case inputs `comparison_type='gt'`, `expected=False`, `test_id='gt_newer_version_with_letter'`, `version='4.0b'`; execute `comparison_funcs[comparison_type](version)`; assert `comparison_funcs[comparison_type](version) == expected`. | unit test | PythonBehaviorParityTest#ests_test_common_py_test_version_comparison_gt_newer_version_with_letter_88aa5d1ecd | not tmux-bound | planned parity | | tests/test_common.py::test_version_comparison[gt_older_version] | Test version comparison functions; case inputs `comparison_type='gt'`, `expected=True`, `test_id='gt_older_version'`, `version='1.6'`; execute `comparison_funcs[comparison_type](version)`; assert `comparison_funcs[comparison_type](version) == expected`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_version_comparison_gt_older_version_dadf70bcdb | not tmux-bound | planned parity | @@ -714,34 +714,34 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_common.py::test_version_validation[accepts_version_1_8a] | Test version validation; case inputs `check_type='type_check'`, `exc_msg_regex=None`, `mock_min_version=None`, `mock_version='1.8a'`, `raises=False`, `test_id='accepts_version_1_8a'`; fixtures `monkeypatch`; when `mock_version is not None`: execute `LooseVersion(mock_version)`; when `check_type == 'min_version' and raises`: execute `has_minimum_version()`; expect `pytest.raises(exc.LibTmuxException)`; when `check_type == 'min_version' and raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; when `check_type == 'min_version' and not (raises)`: execute `has_minimum_version()`; assert `has_minimum_version()`; when `not (check_type == 'min_version') and check_type == 'type_check'`: execute `has_version(mock_version)`; assert `mock_version is not None`; `isinstance(has_version(mock_version), bool)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_version_validation_accepts_version_1_8a_25fd520174 | not tmux-bound | planned parity | | tests/test_common.py::test_version_validation[accepts_version_1_9a] | Test version validation; case inputs `check_type='type_check'`, `exc_msg_regex=None`, `mock_min_version=None`, `mock_version='1.9a'`, `raises=False`, `test_id='accepts_version_1_9a'`; fixtures `monkeypatch`; when `mock_version is not None`: execute `LooseVersion(mock_version)`; when `check_type == 'min_version' and raises`: execute `has_minimum_version()`; expect `pytest.raises(exc.LibTmuxException)`; when `check_type == 'min_version' and raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; when `check_type == 'min_version' and not (raises)`: execute `has_minimum_version()`; assert `has_minimum_version()`; when `not (check_type == 'min_version') and check_type == 'type_check'`: execute `has_version(mock_version)`; assert `mock_version is not None`; `isinstance(has_version(mock_version), bool)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_version_validation_accepts_version_1_9a_81515d6c56 | not tmux-bound | planned parity | | tests/test_common.py::test_version_validation[rejects_version_1_7] | Test version validation; case inputs `check_type='min_version'`, `exc_msg_regex='libtmux only supports'`, `mock_min_version=None`, `mock_version='1.7'`, `raises=True`, `test_id='rejects_version_1_7'`; fixtures `monkeypatch`; when `mock_version is not None`: execute `LooseVersion(mock_version)`; when `check_type == 'min_version' and raises`: execute `has_minimum_version()`; expect `pytest.raises(exc.LibTmuxException)`; when `check_type == 'min_version' and raises and exc_msg_regex is not None`: execute `exc_info.match(exc_msg_regex)`; when `check_type == 'min_version' and not (raises)`: execute `has_minimum_version()`; assert `has_minimum_version()`; when `not (check_type == 'min_version') and check_type == 'type_check'`: execute `has_version(mock_version)`; assert `mock_version is not None`; `isinstance(has_version(mock_version), bool)`. | unit test | PythonBehaviorParityTest#tests_test_common_py_test_version_validation_rejects_version_1_7_7d72731841 | not tmux-bound | planned parity | -| tests/test_control_mode.py::test_control_mode_cleanup | Client is removed after ControlMode context exits; fixtures `control_mode`, `server`; execute `control_mode()`; `server.list_clients()`; assert `len(server.list_clients()) > 0`; `len(clients) == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_control_mode_py_test_control_mode_cleanup_46681c6797 | 3.2a through 3.7b | planned parity | -| tests/test_control_mode.py::test_control_mode_client_name | ControlMode.client_name contains the tmux client identifier; fixtures `control_mode`; execute `control_mode()`; assert `'client-' in ctl.client_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_control_mode_py_test_control_mode_client_name_269d4a1f8d | 3.2a through 3.7b | planned parity | -| tests/test_control_mode.py::test_control_mode_client_name_matches_spawned_client | ControlMode records the client name for its own subprocess; fixtures `control_mode`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_pid}\t#{client_name}')`; `line.split('\t', 1)`; assert `first.client_name != second.client_name`; `(str(first._proc.pid), first.client_name) in clients`; `(str(second._proc.pid), second.client_name) in clients`. | real-tmux integration test | PythonBehaviorParityTest#est_control_mode_py_test_control_mode_client_name_matches_spawned_client_f8cc559c57 | 3.2a through 3.7b | planned parity | -| tests/test_control_mode.py::test_control_mode_creates_client | ControlMode creates a client visible in list-clients; fixtures `control_mode`, `server`; execute `control_mode()`; `server.list_clients()`; assert `len(clients) > 0`; `ctl.client_name != ''`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_control_mode_py_test_control_mode_creates_client_f9dd07a48d | 3.2a through 3.7b | planned parity | -| tests/test_control_mode.py::test_control_mode_stdout_preserves_non_ascii_output | Control-mode stdout must preserve non-ASCII tmux output; fixtures `control_mode`; execute `control_mode()`; `locale.setlocale(locale.LC_CTYPE)`; `locale.setlocale(locale.LC_CTYPE, 'C')`; `os.write(ctl._write_fd, f"display-message -p '{FORMAT_SEPARATOR}'\n".encode())`; `f"display-message -p '{FORMAT_SEPARATOR}'\n".encode()`; `range(20)`; assert `ready; message='timed out waiting for control-mode output'`. | real-tmux integration test | PythonBehaviorParityTest#test_control_mode_py_test_control_mode_stdout_preserves_non_ascii_output_60ae2d1c8c | 3.2a through 3.7b | planned parity | -| tests/test_dataclasses.py::test_pane | Verify Pane dataclass object; fixtures `monkeypatch`, `tmp_path`, `server`; execute `server.new_session()`; `pane.refresh()`; `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Down, adjustment=25)`; `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Right, adjustment=25)`; `session.new_window(window_name='test')`; `session.windows.filter(window_name=window.window_name)`; assert `session_ is not None`; `pane_ is not None`; `pane_.pane_id is not None`; `isinstance(pane_.pane_id, str)`; `isinstance(pane, Pane)`; `old_pane_size != pane.pane_height`; `pane.pane_current_command is not None`; `pane.window is not None`; `isinstance(pane.window, Window)`; `pane.window.session is not None`; `isinstance(pane.window.session, Session)`; `pane.session is not None`; `isinstance(pane.session, Session)`; `pane.session.windows`; `isinstance(pane.session.windows, list)`; `len(pane.session.windows) > 0`; `isinstance(w, Window)`; `isinstance(w.session, Session)`; `w.session_id == pane.session.session_id`; `len(pane.session.panes) > 0`; `isinstance(_p, Pane)`; `isinstance(_p.session, Session)`; `_p.session_id == pane.session.session_id`; `len(pane.window.panes) > 0`; `len(session.windows) > 1`; `isinstance(_w, Window)`; `isinstance(_w.session, Session)`; `_w.window_name == window.window_name`; `len(window.panes) > 0`; `_p.window_id == window.window_id`; `new_pane.pane_id != pane.pane_id`; `new_pane.window_id == window.window_id`; `new_pane_2.pane_id != new_pane.pane_id`; `new_pane_2.window_id == new_pane.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_dataclasses_py_test_pane_f73141ec81 | 3.2a through 3.7b | planned parity | -| tests/test_dataclasses.py::test_querylist | Verify QueryList behavior with libtmux object; fixtures `monkeypatch`, `tmp_path`, `session`; execute `session.new_window(window_name='test_2')`; `session.new_window(window_name='test_3')`; `server.new_session('second session')`; `server.windows.filter(window_name='test_2')`; `QueryList(session.windows)`; `qs.count(session.windows[0])`; expect `pytest.raises(ObjectDoesNotExist)`; `pytest.raises(MultipleObjectsReturned)`; assert `qs.count(session.windows[0]) == 1`; `len(qs) == 3`; `isinstance(w, Window)`; `w.window_name == 'test_2'`; `isinstance(w_2, Window)`; `w_2.window_name == 'test_2'`; `result == 'default_value'`; `len(server.windows.filter(window_name='test_2')) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_dataclasses_py_test_querylist_676e64c22f | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_agrees_with_tmux_on_a_linked_window | A window linked into two sessions gets tmux's own canonical answer; fixtures `session`; execute `server.new_session(session_name='zzz-guest')`; `server.cmd('link-window', '-s', window.window_id, '-t', guest.session_name)`; `server.cmd('display-message', '-p', '-t', pane.pane_id, '#{session_id}')`; `env_for(server, pane.pane_id, session.session_id)`; `Session.from_env(env)`; `Window.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `window.window_id is not None`; `session.session_id is not None`; `guest.session_name is not None`; `resolved == {tmux_says}`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_agrees_with_tmux_on_a_linked_window_d485ece78e | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_dead_server[pane] | An unreachable server is a tmux failure, not a missing object; case inputs `from_env=<bound method Pane.from_env of <class 'libtmux.pane.Pane'>>`, `test_id='pane'`; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='doomed')`; `server.kill()`; `from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_dead_server_pane_f66b174e30 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_dead_server[session] | An unreachable server is a tmux failure, not a missing object; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `test_id='session'`; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='doomed')`; `server.kill()`; `from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_dead_server_session_e6fa5dcc19 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_dead_server[window] | An unreachable server is a tmux failure, not a missing object; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `test_id='window'`; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='doomed')`; `server.kill()`; `from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_dead_server_window_b9732d8eb6 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_defaults_to_os_environ | ``env=None`` reads the live process environment, as a real pane would; fixtures `session`, `monkeypatch`; execute `env_for(session.server, pane.pane_id, session.session_id).items()`; `env_for(session.server, pane.pane_id, session.session_id)`; `Pane.from_env()`; `Window.from_env()`; `Session.from_env()`; `Server.from_env().is_alive()`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `Pane.from_env().pane_id == pane.pane_id`; `Window.from_env().window_id == session.active_window.window_id`; `Session.from_env().session_id == session.session_id`; `Server.from_env().is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_defaults_to_os_environ_9336b344e2 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_follows_pane_after_move_window | A moved window resolves to its new session, not the one in ``$TMUX``; fixtures `session`; execute `server.new_session(session_name='destination')`; `session.new_window()`; `window.move_window(destination='99', session=destination.session_id)`; `env_for(server, pane.pane_id, session.session_id)`; `Session.from_env(env)`; `Window.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `destination.session_id is not None`; `Session.from_env(env).session_id == destination.session_id`; `Window.from_env(env).session.session_id == destination.session_id`; `Pane.from_env(env).session.session_id == destination.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_follows_pane_after_move_window_7906d90561 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_ignores_stale_session_id | The session id inside ``$TMUX`` is never read, however wrong it is; fixtures `session`; execute `env_for(session.server, pane.pane_id, '$99999')`; `Session.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `Session.from_env(env).session_id == session.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_ignores_stale_session_id_e89ba6b076 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_missing_pane_on_live_server[pane] | A vanished pane on a reachable server is an object-lookup failure; case inputs `from_env=<bound method Pane.from_env of <class 'libtmux.pane.Pane'>>`, `test_id='pane'`; fixtures `session`; execute `from_env(env)`; `env_for(session.server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_missing_pane_on_live_server_pane_7e4a382775 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_missing_pane_on_live_server[session] | A vanished pane on a reachable server is an object-lookup failure; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `test_id='session'`; fixtures `session`; execute `from_env(env)`; `env_for(session.server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_missing_pane_on_live_server_session_fd3e981d60 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_missing_pane_on_live_server[window] | A vanished pane on a reachable server is an object-lookup failure; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `test_id='window'`; fixtures `session`; execute `from_env(env)`; `env_for(session.server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_missing_pane_on_live_server_window_eb72897570 | 3.2a through 3.7b | planned parity | +| tests/test_control_mode.py::test_control_mode_cleanup | Client is removed after ControlMode context exits; fixtures `control_mode`, `server`; execute `control_mode()`; `server.list_clients()`; assert `len(server.list_clients()) > 0`; `len(clients) == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_control_mode_py_test_control_mode_cleanup_46681c6797 | 3.3a through 3.7c | planned parity | +| tests/test_control_mode.py::test_control_mode_client_name | ControlMode.client_name contains the tmux client identifier; fixtures `control_mode`; execute `control_mode()`; assert `'client-' in ctl.client_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_control_mode_py_test_control_mode_client_name_269d4a1f8d | 3.3a through 3.7c | planned parity | +| tests/test_control_mode.py::test_control_mode_client_name_matches_spawned_client | ControlMode records the client name for its own subprocess; fixtures `control_mode`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_pid}\t#{client_name}')`; `line.split('\t', 1)`; assert `first.client_name != second.client_name`; `(str(first._proc.pid), first.client_name) in clients`; `(str(second._proc.pid), second.client_name) in clients`. | real-tmux integration test | PythonBehaviorParityTest#est_control_mode_py_test_control_mode_client_name_matches_spawned_client_f8cc559c57 | 3.3a through 3.7c | planned parity | +| tests/test_control_mode.py::test_control_mode_creates_client | ControlMode creates a client visible in list-clients; fixtures `control_mode`, `server`; execute `control_mode()`; `server.list_clients()`; assert `len(clients) > 0`; `ctl.client_name != ''`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_control_mode_py_test_control_mode_creates_client_f9dd07a48d | 3.3a through 3.7c | planned parity | +| tests/test_control_mode.py::test_control_mode_stdout_preserves_non_ascii_output | Control-mode stdout must preserve non-ASCII tmux output; fixtures `control_mode`; execute `control_mode()`; `locale.setlocale(locale.LC_CTYPE)`; `locale.setlocale(locale.LC_CTYPE, 'C')`; `os.write(ctl._write_fd, f"display-message -p '{FORMAT_SEPARATOR}'\n".encode())`; `f"display-message -p '{FORMAT_SEPARATOR}'\n".encode()`; `range(20)`; assert `ready; message='timed out waiting for control-mode output'`. | real-tmux integration test | PythonBehaviorParityTest#test_control_mode_py_test_control_mode_stdout_preserves_non_ascii_output_60ae2d1c8c | 3.3a through 3.7c | planned parity | +| tests/test_dataclasses.py::test_pane | Verify Pane dataclass object; fixtures `monkeypatch`, `tmp_path`, `server`; execute `server.new_session()`; `pane.refresh()`; `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Down, adjustment=25)`; `pane.resize(adjustment_direction=ResizeAdjustmentDirection.Right, adjustment=25)`; `session.new_window(window_name='test')`; `session.windows.filter(window_name=window.window_name)`; assert `session_ is not None`; `pane_ is not None`; `pane_.pane_id is not None`; `isinstance(pane_.pane_id, str)`; `isinstance(pane, Pane)`; `old_pane_size != pane.pane_height`; `pane.pane_current_command is not None`; `pane.window is not None`; `isinstance(pane.window, Window)`; `pane.window.session is not None`; `isinstance(pane.window.session, Session)`; `pane.session is not None`; `isinstance(pane.session, Session)`; `pane.session.windows`; `isinstance(pane.session.windows, list)`; `len(pane.session.windows) > 0`; `isinstance(w, Window)`; `isinstance(w.session, Session)`; `w.session_id == pane.session.session_id`; `len(pane.session.panes) > 0`; `isinstance(_p, Pane)`; `isinstance(_p.session, Session)`; `_p.session_id == pane.session.session_id`; `len(pane.window.panes) > 0`; `len(session.windows) > 1`; `isinstance(_w, Window)`; `isinstance(_w.session, Session)`; `_w.window_name == window.window_name`; `len(window.panes) > 0`; `_p.window_id == window.window_id`; `new_pane.pane_id != pane.pane_id`; `new_pane.window_id == window.window_id`; `new_pane_2.pane_id != new_pane.pane_id`; `new_pane_2.window_id == new_pane.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_dataclasses_py_test_pane_f73141ec81 | 3.3a through 3.7c | planned parity | +| tests/test_dataclasses.py::test_querylist | Verify QueryList behavior with libtmux object; fixtures `monkeypatch`, `tmp_path`, `session`; execute `session.new_window(window_name='test_2')`; `session.new_window(window_name='test_3')`; `server.new_session('second session')`; `server.windows.filter(window_name='test_2')`; `QueryList(session.windows)`; `qs.count(session.windows[0])`; expect `pytest.raises(ObjectDoesNotExist)`; `pytest.raises(MultipleObjectsReturned)`; assert `qs.count(session.windows[0]) == 1`; `len(qs) == 3`; `isinstance(w, Window)`; `w.window_name == 'test_2'`; `isinstance(w_2, Window)`; `w_2.window_name == 'test_2'`; `result == 'default_value'`; `len(server.windows.filter(window_name='test_2')) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_dataclasses_py_test_querylist_676e64c22f | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_agrees_with_tmux_on_a_linked_window | A window linked into two sessions gets tmux's own canonical answer; fixtures `session`; execute `server.new_session(session_name='zzz-guest')`; `server.cmd('link-window', '-s', window.window_id, '-t', guest.session_name)`; `server.cmd('display-message', '-p', '-t', pane.pane_id, '#{session_id}')`; `env_for(server, pane.pane_id, session.session_id)`; `Session.from_env(env)`; `Window.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `window.window_id is not None`; `session.session_id is not None`; `guest.session_name is not None`; `resolved == {tmux_says}`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_agrees_with_tmux_on_a_linked_window_d485ece78e | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_dead_server[pane] | An unreachable server is a tmux failure, not a missing object; case inputs `from_env=<bound method Pane.from_env of <class 'libtmux.pane.Pane'>>`, `test_id='pane'`; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='doomed')`; `server.kill()`; `from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_dead_server_pane_f66b174e30 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_dead_server[session] | An unreachable server is a tmux failure, not a missing object; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `test_id='session'`; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='doomed')`; `server.kill()`; `from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_dead_server_session_e6fa5dcc19 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_dead_server[window] | An unreachable server is a tmux failure, not a missing object; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `test_id='window'`; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='doomed')`; `server.kill()`; `from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_dead_server_window_b9732d8eb6 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_defaults_to_os_environ | ``env=None`` reads the live process environment, as a real pane would; fixtures `session`, `monkeypatch`; execute `env_for(session.server, pane.pane_id, session.session_id).items()`; `env_for(session.server, pane.pane_id, session.session_id)`; `Pane.from_env()`; `Window.from_env()`; `Session.from_env()`; `Server.from_env().is_alive()`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `Pane.from_env().pane_id == pane.pane_id`; `Window.from_env().window_id == session.active_window.window_id`; `Session.from_env().session_id == session.session_id`; `Server.from_env().is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_defaults_to_os_environ_9336b344e2 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_follows_pane_after_move_window | A moved window resolves to its new session, not the one in ``$TMUX``; fixtures `session`; execute `server.new_session(session_name='destination')`; `session.new_window()`; `window.move_window(destination='99', session=destination.session_id)`; `env_for(server, pane.pane_id, session.session_id)`; `Session.from_env(env)`; `Window.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `destination.session_id is not None`; `Session.from_env(env).session_id == destination.session_id`; `Window.from_env(env).session.session_id == destination.session_id`; `Pane.from_env(env).session.session_id == destination.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_follows_pane_after_move_window_7906d90561 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_ignores_stale_session_id | The session id inside ``$TMUX`` is never read, however wrong it is; fixtures `session`; execute `env_for(session.server, pane.pane_id, '$99999')`; `Session.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `Session.from_env(env).session_id == session.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_ignores_stale_session_id_e89ba6b076 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_missing_pane_on_live_server[pane] | A vanished pane on a reachable server is an object-lookup failure; case inputs `from_env=<bound method Pane.from_env of <class 'libtmux.pane.Pane'>>`, `test_id='pane'`; fixtures `session`; execute `from_env(env)`; `env_for(session.server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_missing_pane_on_live_server_pane_7e4a382775 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_missing_pane_on_live_server[session] | A vanished pane on a reachable server is an object-lookup failure; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `test_id='session'`; fixtures `session`; execute `from_env(env)`; `env_for(session.server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_missing_pane_on_live_server_session_fd3e981d60 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_missing_pane_on_live_server[window] | A vanished pane on a reachable server is an object-lookup failure; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `test_id='window'`; fixtures `session`; execute `from_env(env)`; `env_for(session.server, pane.pane_id, session.session_id)`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_missing_pane_on_live_server_window_eb72897570 | 3.3a through 3.7c | planned parity | | tests/test_from_env.py::test_from_env_outside_tmux[pane] | Every constructor rejects an environment with no tmux in it; case inputs `from_env=<bound method Pane.from_env of <class 'libtmux.pane.Pane'>>`, `test_id='pane'`; execute `from_env({})`; expect `pytest.raises(exc.NotInsideTmux)`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_outside_tmux_pane_cd8de04a8f | not tmux-bound | planned parity | | tests/test_from_env.py::test_from_env_outside_tmux[server] | Every constructor rejects an environment with no tmux in it; case inputs `from_env=<bound method Server.from_env of <class 'libtmux.server.Server'>>`, `test_id='server'`; execute `from_env({})`; expect `pytest.raises(exc.NotInsideTmux)`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_outside_tmux_server_84aef7c630 | not tmux-bound | planned parity | | tests/test_from_env.py::test_from_env_outside_tmux[session] | Every constructor rejects an environment with no tmux in it; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `test_id='session'`; execute `from_env({})`; expect `pytest.raises(exc.NotInsideTmux)`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_outside_tmux_session_dd04f5eb25 | not tmux-bound | planned parity | | tests/test_from_env.py::test_from_env_outside_tmux[window] | Every constructor rejects an environment with no tmux in it; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `test_id='window'`; execute `from_env({})`; expect `pytest.raises(exc.NotInsideTmux)`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_outside_tmux_window_b080771883 | not tmux-bound | planned parity | -| tests/test_from_env.py::test_from_env_pane_without_parent_id[session] | A pane that names no parent is a ``ValueError`` on both constructors; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `message='Pane must have a session_id to resolve its session'`, `test_id='session'`; fixtures `server`, `monkeypatch`; execute `from_env({})`; `Pane(server=server, pane_id='%0')`; `classmethod(unidentified_pane)`; `re.escape(message)`; expect `pytest.raises(ValueError, match=re.escape(message))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_pane_without_parent_id_session_3d07657187 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_pane_without_parent_id[window] | A pane that names no parent is a ``ValueError`` on both constructors; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `message='Pane must have a window_id to resolve its window'`, `test_id='window'`; fixtures `server`, `monkeypatch`; execute `from_env({})`; `Pane(server=server, pane_id='%0')`; `classmethod(unidentified_pane)`; `re.escape(message)`; expect `pytest.raises(ValueError, match=re.escape(message))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_pane_without_parent_id_window_b75c580ec3 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_resolves_the_calling_pane | The whole family agrees on the pane, window, and session; fixtures `session`; execute `env_for(session.server, pane.pane_id, session.session_id)`; `Pane.from_env(env)`; `Window.from_env(env)`; `Session.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `Pane.from_env(env).pane_id == pane.pane_id`; `Window.from_env(env).window_id == window.window_id`; `Session.from_env(env).session_id == session.session_id`; `Pane.from_env(env).window.window_id == window.window_id`; `Pane.from_env(env).session.session_id == session.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_resolves_the_calling_pane_153bf84a14 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[pane] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MyPane'>`, `test_id='pane'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#s_test_from_env_py_test_from_env_returns_the_class_it_was_called_on_pane_0b13051b18 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[server] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MyServer'>`, `test_id='server'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#test_from_env_py_test_from_env_returns_the_class_it_was_called_on_server_a32ffbdf10 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[session] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MySession'>`, `test_id='session'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#est_from_env_py_test_from_env_returns_the_class_it_was_called_on_session_15125a3b92 | 3.2a through 3.7b | planned parity | -| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[window] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MyWindow'>`, `test_id='window'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#test_from_env_py_test_from_env_returns_the_class_it_was_called_on_window_242d69a16a | 3.2a through 3.7b | planned parity | +| tests/test_from_env.py::test_from_env_pane_without_parent_id[session] | A pane that names no parent is a ``ValueError`` on both constructors; case inputs `from_env=<bound method Session.from_env of <class 'libtmux.session.Session'>>`, `message='Pane must have a session_id to resolve its session'`, `test_id='session'`; fixtures `server`, `monkeypatch`; execute `from_env({})`; `Pane(server=server, pane_id='%0')`; `classmethod(unidentified_pane)`; `re.escape(message)`; expect `pytest.raises(ValueError, match=re.escape(message))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_pane_without_parent_id_session_3d07657187 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_pane_without_parent_id[window] | A pane that names no parent is a ``ValueError`` on both constructors; case inputs `from_env=<bound method Window.from_env of <class 'libtmux.window.Window'>>`, `message='Pane must have a window_id to resolve its window'`, `test_id='window'`; fixtures `server`, `monkeypatch`; execute `from_env({})`; `Pane(server=server, pane_id='%0')`; `classmethod(unidentified_pane)`; `re.escape(message)`; expect `pytest.raises(ValueError, match=re.escape(message))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_pane_without_parent_id_window_b75c580ec3 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_resolves_the_calling_pane | The whole family agrees on the pane, window, and session; fixtures `session`; execute `env_for(session.server, pane.pane_id, session.session_id)`; `Pane.from_env(env)`; `Window.from_env(env)`; `Session.from_env(env)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `Pane.from_env(env).pane_id == pane.pane_id`; `Window.from_env(env).window_id == window.window_id`; `Session.from_env(env).session_id == session.session_id`; `Pane.from_env(env).window.window_id == window.window_id`; `Pane.from_env(env).session.session_id == session.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_from_env_resolves_the_calling_pane_153bf84a14 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[pane] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MyPane'>`, `test_id='pane'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#s_test_from_env_py_test_from_env_returns_the_class_it_was_called_on_pane_0b13051b18 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[server] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MyServer'>`, `test_id='server'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#test_from_env_py_test_from_env_returns_the_class_it_was_called_on_server_a32ffbdf10 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[session] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MySession'>`, `test_id='session'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#est_from_env_py_test_from_env_returns_the_class_it_was_called_on_session_15125a3b92 | 3.3a through 3.7c | planned parity | +| tests/test_from_env.py::test_from_env_returns_the_class_it_was_called_on[window] | ``from_env`` honours ``cls``, so subclasses get their own type back; case inputs `subclass=<class 'tests.test_from_env.MyWindow'>`, `test_id='window'`; fixtures `session`; execute `subclass.from_env(env)`; `env_for(server, pane.pane_id, session.session_id)`; assert `pane is not None`; `pane.pane_id is not None`; `session.session_id is not None`; `type(subclass.from_env(env)) is subclass`. | real-tmux integration test | PythonBehaviorParityTest#test_from_env_py_test_from_env_returns_the_class_it_was_called_on_window_242d69a16a | 3.3a through 3.7c | planned parity | | tests/test_from_env.py::test_pane_from_env_bad_pane_id[empty] | ``$TMUX_PANE`` must carry the ``%`` sigil tmux routes targets by; case inputs `message='$TMUX_PANE is unset or empty'`, `test_id='empty'`, `tmux_pane=''`; execute `re.escape(message)`; `Pane.from_env(env)`; expect `pytest.raises(exc.NotInsideTmux, match=re.escape(message))`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_pane_from_env_bad_pane_id_empty_6ae52c8f18 | not tmux-bound | planned parity | | tests/test_from_env.py::test_pane_from_env_bad_pane_id[session_sigil] | ``$TMUX_PANE`` must carry the ``%`` sigil tmux routes targets by; case inputs `message='$TMUX_PANE is not a pane id'`, `test_id='session_sigil'`, `tmux_pane='$1'`; execute `re.escape(message)`; `Pane.from_env(env)`; expect `pytest.raises(exc.NotInsideTmux, match=re.escape(message))`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_pane_from_env_bad_pane_id_session_sigil_9ab9824b53 | not tmux-bound | planned parity | | tests/test_from_env.py::test_pane_from_env_bad_pane_id[sigil_less] | ``$TMUX_PANE`` must carry the ``%`` sigil tmux routes targets by; case inputs `message='$TMUX_PANE is not a pane id'`, `test_id='sigil_less'`, `tmux_pane='0'`; execute `re.escape(message)`; `Pane.from_env(env)`; expect `pytest.raises(exc.NotInsideTmux, match=re.escape(message))`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_pane_from_env_bad_pane_id_sigil_less_0d668f5d24 | not tmux-bound | planned parity | @@ -754,114 +754,114 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_from_env.py::test_server_from_env_socket_path[default_socket] | ``Server.from_env()`` splits ``$TMUX`` from the right; case inputs `expected_socket_path='/tmp/tmux-1000/default'`, `test_id='default_socket'`, `tmux='/tmp/tmux-1000/default,84215,0'`; execute `Server.from_env({'TMUX': tmux})`; assert `Server.from_env({'TMUX': tmux}).socket_path == expected_socket_path`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_server_from_env_socket_path_default_socket_36d69567ca | not tmux-bound | planned parity | | tests/test_from_env.py::test_server_from_env_socket_path[named_socket] | ``Server.from_env()`` splits ``$TMUX`` from the right; case inputs `expected_socket_path='/tmp/tmux-1000/libtmux_test0'`, `test_id='named_socket'`, `tmux='/tmp/tmux-1000/libtmux_test0,1,47'`; execute `Server.from_env({'TMUX': tmux})`; assert `Server.from_env({'TMUX': tmux}).socket_path == expected_socket_path`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_server_from_env_socket_path_named_socket_606800eb25 | not tmux-bound | planned parity | | tests/test_from_env.py::test_server_from_env_socket_path[no_session] | ``Server.from_env()`` splits ``$TMUX`` from the right; case inputs `expected_socket_path='/tmp/tmux-1000/default'`, `test_id='no_session'`, `tmux='/tmp/tmux-1000/default,84215,-1'`; execute `Server.from_env({'TMUX': tmux})`; assert `Server.from_env({'TMUX': tmux}).socket_path == expected_socket_path`. | unit test | PythonBehaviorParityTest#tests_test_from_env_py_test_server_from_env_socket_path_no_session_b66bbdeb91 | not tmux-bound | planned parity | -| tests/test_from_env.py::test_window_from_env_is_the_containing_window | The caller's window is reported, not the session's active window; fixtures `session`; execute `session.new_window(window_name='foreground', attach=True)`; `env_for(session.server, caller_pane.pane_id, session.session_id)`; `Window.from_env(env)`; assert `caller_pane is not None`; `caller_pane.pane_id is not None`; `session.session_id is not None`; `session.active_window.window_id == foreground.window_id`; `Window.from_env(env).window_id == caller_window.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_window_from_env_is_the_containing_window_300cefa54e | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_scope_handling[pane_scope] | Test hooks at different scopes (session, window, pane); case inputs `test_case=HookScopeTestCase(test_id='pane_scope', scope='pane', scope_flag='-p', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_scope')`; assert `window is not None`; `pane is not None`; `result is not None`; `len(result) > 0`; `'display-message' in result[0]`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.scope == 'session'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd)`; `session._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.set_hook(f'{hook_name}[0]', hook_cmd)`; `window._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.set_hook(f'{hook_name}[0]', hook_cmd)`; `pane._show_hook(f'{hook_name}[0]')`; when `test_case.scope == 'session'`: execute `session.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.unset_hook(hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_scope_handling_pane_scope_0ae0362c90 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_scope_handling[session_scope] | Test hooks at different scopes (session, window, pane); case inputs `test_case=HookScopeTestCase(test_id='session_scope', scope='session', scope_flag='', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_scope')`; assert `window is not None`; `pane is not None`; `result is not None`; `len(result) > 0`; `'display-message' in result[0]`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.scope == 'session'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd)`; `session._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.set_hook(f'{hook_name}[0]', hook_cmd)`; `window._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.set_hook(f'{hook_name}[0]', hook_cmd)`; `pane._show_hook(f'{hook_name}[0]')`; when `test_case.scope == 'session'`: execute `session.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.unset_hook(hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_scope_handling_session_scope_797011f03e | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_scope_handling[window_scope] | Test hooks at different scopes (session, window, pane); case inputs `test_case=HookScopeTestCase(test_id='window_scope', scope='window', scope_flag='-w', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_scope')`; assert `window is not None`; `pane is not None`; `result is not None`; `len(result) > 0`; `'display-message' in result[0]`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.scope == 'session'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd)`; `session._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.set_hook(f'{hook_name}[0]', hook_cmd)`; `window._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.set_hook(f'{hook_name}[0]', hook_cmd)`; `pane._show_hook(f'{hook_name}[0]')`; when `test_case.scope == 'session'`: execute `session.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.unset_hook(hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_scope_handling_window_scope_30bdffa08e | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_bind_key] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_bind_key', hook='after-bind-key', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_bind_key_2c1491243a | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_capture_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_capture_pane', hook='after-capture-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_capture_pane_1c20d0ded1 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_copy_mode] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_copy_mode', hook='after-copy-mode', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_copy_mode_7c66a39a9a | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_display_message] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_display_message', hook='after-display-message', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_display_message_66930d7867 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_display_panes] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_display_panes', hook='after-display-panes', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_display_panes_44d5e873cf | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_kill_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_kill_pane', hook='after-kill-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_kill_pane_1ee798d263 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_buffers] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_buffers', hook='after-list-buffers', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_buffers_6aeca01463 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_clients] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_clients', hook='after-list-clients', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_clients_d37ac3b45c | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_keys] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_keys', hook='after-list-keys', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_keys_574aa657f2 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_panes] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_panes', hook='after-list-panes', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_panes_9a3c42a812 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_sessions] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_sessions', hook='after-list-sessions', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_sessions_5b3a1af1ea | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_windows] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_windows', hook='after-list-windows', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_windows_e5b8040590 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_load_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_load_buffer', hook='after-load-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_load_buffer_abdf518ce9 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_lock_server] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_lock_server', hook='after-lock-server', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_lock_server_b00af3984b | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_new_session] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_new_session', hook='after-new-session', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_new_session_120d8eccad | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_new_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_new_window', hook='after-new-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_new_window_4dd3b3215a | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_paste_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_paste_buffer', hook='after-paste-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_paste_buffer_3b2a414e79 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_pipe_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_pipe_pane', hook='after-pipe-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_pipe_pane_9d8a00a513 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_queue] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_queue', hook='after-queue', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_queue_1b30f9cba2 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_refresh_client] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_refresh_client', hook='after-refresh-client', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_refresh_client_1c630dd3d2 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_rename_session] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_rename_session', hook='after-rename-session', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_rename_session_5a54759861 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_rename_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_rename_window', hook='after-rename-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_rename_window_1f06b456a5 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_resize_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_resize_pane', hook='after-resize-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_resize_pane_10b70feee1 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_resize_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_resize_window', hook='after-resize-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_resize_window_4d67871b80 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_save_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_save_buffer', hook='after-save-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_save_buffer_31e068490b | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_select_layout] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_select_layout', hook='after-select-layout', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_select_layout_74a1f70c57 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_select_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_select_pane', hook='after-select-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_select_pane_5874f76b06 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_select_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_select_window', hook='after-select-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_select_window_1efbcd9248 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_send_keys] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_send_keys', hook='after-send-keys', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_send_keys_103443f062 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_buffer', hook='after-set-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_buffer_76205dc9c9 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_environment] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_environment', hook='after-set-environment', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_environment_189cf1b62a | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_hook] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_hook', hook='after-set-hook', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_hook_0203929ead | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_option] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_option', hook='after-set-option', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_option_5cda0169a3 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_show_environment] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_show_environment', hook='after-show-environment', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_hooks_py_test_hook_set_show_unset_cycle_after_show_environment_1320ed1a70 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_show_messages] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_show_messages', hook='after-show-messages', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_show_messages_e4c7ec5e33 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_show_options] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_show_options', hook='after-show-options', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_show_options_2d663149f8 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_split_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_split_window', hook='after-split-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_split_window_da6e2cb721 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_unbind_key] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_unbind_key', hook='after-unbind-key', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_unbind_key_30ec6b9acf | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[alert_activity] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='alert_activity', hook='alert-activity', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_alert_activity_9729ffdbec | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[alert_bell] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='alert_bell', hook='alert-bell', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_alert_bell_14e21a960a | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[alert_silence] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='alert_silence', hook='alert-silence', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_alert_silence_afe03fe070 | 3.2a through 3.7b | planned parity | +| tests/test_from_env.py::test_window_from_env_is_the_containing_window | The caller's window is reported, not the session's active window; fixtures `session`; execute `session.new_window(window_name='foreground', attach=True)`; `env_for(session.server, caller_pane.pane_id, session.session_id)`; `Window.from_env(env)`; assert `caller_pane is not None`; `caller_pane.pane_id is not None`; `session.session_id is not None`; `session.active_window.window_id == foreground.window_id`; `Window.from_env(env).window_id == caller_window.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_from_env_py_test_window_from_env_is_the_containing_window_300cefa54e | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_scope_handling[pane_scope] | Test hooks at different scopes (session, window, pane); case inputs `test_case=HookScopeTestCase(test_id='pane_scope', scope='pane', scope_flag='-p', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_scope')`; assert `window is not None`; `pane is not None`; `result is not None`; `len(result) > 0`; `'display-message' in result[0]`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.scope == 'session'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd)`; `session._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.set_hook(f'{hook_name}[0]', hook_cmd)`; `window._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.set_hook(f'{hook_name}[0]', hook_cmd)`; `pane._show_hook(f'{hook_name}[0]')`; when `test_case.scope == 'session'`: execute `session.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.unset_hook(hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_scope_handling_pane_scope_0ae0362c90 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_scope_handling[session_scope] | Test hooks at different scopes (session, window, pane); case inputs `test_case=HookScopeTestCase(test_id='session_scope', scope='session', scope_flag='', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_scope')`; assert `window is not None`; `pane is not None`; `result is not None`; `len(result) > 0`; `'display-message' in result[0]`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.scope == 'session'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd)`; `session._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.set_hook(f'{hook_name}[0]', hook_cmd)`; `window._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.set_hook(f'{hook_name}[0]', hook_cmd)`; `pane._show_hook(f'{hook_name}[0]')`; when `test_case.scope == 'session'`: execute `session.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.unset_hook(hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_scope_handling_session_scope_797011f03e | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_scope_handling[window_scope] | Test hooks at different scopes (session, window, pane); case inputs `test_case=HookScopeTestCase(test_id='window_scope', scope='window', scope_flag='-w', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_scope')`; assert `window is not None`; `pane is not None`; `result is not None`; `len(result) > 0`; `'display-message' in result[0]`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.scope == 'session'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd)`; `session._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.set_hook(f'{hook_name}[0]', hook_cmd)`; `window._show_hook(f'{hook_name}[0]')`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.set_hook(f'{hook_name}[0]', hook_cmd)`; `pane._show_hook(f'{hook_name}[0]')`; when `test_case.scope == 'session'`: execute `session.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and test_case.scope == 'window'`: execute `window.unset_hook(hook_name)`; when `not (test_case.scope == 'session') and not (test_case.scope == 'window')`: execute `pane.unset_hook(hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_scope_handling_window_scope_30bdffa08e | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_bind_key] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_bind_key', hook='after-bind-key', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_bind_key_2c1491243a | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_capture_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_capture_pane', hook='after-capture-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_capture_pane_1c20d0ded1 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_copy_mode] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_copy_mode', hook='after-copy-mode', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_copy_mode_7c66a39a9a | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_display_message] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_display_message', hook='after-display-message', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_display_message_66930d7867 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_display_panes] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_display_panes', hook='after-display-panes', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_display_panes_44d5e873cf | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_kill_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_kill_pane', hook='after-kill-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_kill_pane_1ee798d263 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_buffers] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_buffers', hook='after-list-buffers', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_buffers_6aeca01463 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_clients] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_clients', hook='after-list-clients', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_clients_d37ac3b45c | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_keys] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_keys', hook='after-list-keys', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_keys_574aa657f2 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_panes] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_panes', hook='after-list-panes', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_panes_9a3c42a812 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_sessions] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_sessions', hook='after-list-sessions', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_sessions_5b3a1af1ea | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_list_windows] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_list_windows', hook='after-list-windows', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_list_windows_e5b8040590 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_load_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_load_buffer', hook='after-load-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_load_buffer_abdf518ce9 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_lock_server] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_lock_server', hook='after-lock-server', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_lock_server_b00af3984b | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_new_session] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_new_session', hook='after-new-session', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_new_session_120d8eccad | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_new_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_new_window', hook='after-new-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_new_window_4dd3b3215a | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_paste_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_paste_buffer', hook='after-paste-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_paste_buffer_3b2a414e79 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_pipe_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_pipe_pane', hook='after-pipe-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_pipe_pane_9d8a00a513 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_queue] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_queue', hook='after-queue', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_queue_1b30f9cba2 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_refresh_client] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_refresh_client', hook='after-refresh-client', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_refresh_client_1c630dd3d2 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_rename_session] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_rename_session', hook='after-rename-session', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_rename_session_5a54759861 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_rename_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_rename_window', hook='after-rename-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_rename_window_1f06b456a5 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_resize_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_resize_pane', hook='after-resize-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_resize_pane_10b70feee1 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_resize_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_resize_window', hook='after-resize-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_resize_window_4d67871b80 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_save_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_save_buffer', hook='after-save-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_save_buffer_31e068490b | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_select_layout] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_select_layout', hook='after-select-layout', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_select_layout_74a1f70c57 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_select_pane] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_select_pane', hook='after-select-pane', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_select_pane_5874f76b06 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_select_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_select_window', hook='after-select-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_select_window_1efbcd9248 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_send_keys] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_send_keys', hook='after-send-keys', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_send_keys_103443f062 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_buffer] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_buffer', hook='after-set-buffer', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_buffer_76205dc9c9 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_environment] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_environment', hook='after-set-environment', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_environment_189cf1b62a | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_hook] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_hook', hook='after-set-hook', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_hook_0203929ead | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_set_option] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_set_option', hook='after-set-option', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_set_option_5cda0169a3 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_show_environment] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_show_environment', hook='after-show-environment', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_hooks_py_test_hook_set_show_unset_cycle_after_show_environment_1320ed1a70 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_show_messages] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_show_messages', hook='after-show-messages', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_show_messages_e4c7ec5e33 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_show_options] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_show_options', hook='after-show-options', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_show_options_2d663149f8 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_split_window] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_split_window', hook='after-split-window', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_split_window_da6e2cb721 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[after_unbind_key] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='after_unbind_key', hook='after-unbind-key', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_after_unbind_key_30ec6b9acf | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[alert_activity] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='alert_activity', hook='alert-activity', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_alert_activity_9729ffdbec | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[alert_bell] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='alert_bell', hook='alert-bell', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_alert_bell_14e21a960a | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[alert_silence] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='alert_silence', hook='alert-silence', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_alert_silence_afe03fe070 | 3.3a through 3.7c | planned parity | | tests/test_hooks.py::test_hook_set_show_unset_cycle[client_active] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_active', hook='client-active', min_version='3.3', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_active_3b7874f832 | 3.3 through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_attached] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_attached', hook='client-attached', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_attached_0f29c4f5f0 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_detached] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_detached', hook='client-detached', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_detached_c681d32818 | 3.2a through 3.7b | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_attached] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_attached', hook='client-attached', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_attached_0f29c4f5f0 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_detached] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_detached', hook='client-detached', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_detached_c681d32818 | 3.3a through 3.7c | planned parity | | tests/test_hooks.py::test_hook_set_show_unset_cycle[client_focus_in] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_focus_in', hook='client-focus-in', min_version='3.3', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_focus_in_3f3ecd3b15 | 3.3 through 3.7b | planned parity | | tests/test_hooks.py::test_hook_set_show_unset_cycle[client_focus_out] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_focus_out', hook='client-focus-out', min_version='3.3', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_focus_out_41ac8543aa | 3.3 through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_resized] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_resized', hook='client-resized', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_resized_5e79f6adfd | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_session_changed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_session_changed', hook='client-session-changed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_hooks_py_test_hook_set_show_unset_cycle_client_session_changed_0a3b14cc14 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_died] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_died', hook='pane-died', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_died_721b0c9b7e | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_exited] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_exited', hook='pane-exited', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_exited_c0c96cf7af | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_focus_in] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_focus_in', hook='pane-focus-in', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_focus_in_71117656f3 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_focus_out] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_focus_out', hook='pane-focus-out', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_focus_out_e69f0137c3 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_mode_changed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_mode_changed', hook='pane-mode-changed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_mode_changed_cca7b40d6f | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_set_clipboard] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_set_clipboard', hook='pane-set-clipboard', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_set_clipboard_76f45b0002 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_closed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_closed', hook='session-closed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_session_closed_803af103b5 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_created] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_created', hook='session-created', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_session_created_418947221e | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_renamed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_renamed', hook='session-renamed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_session_renamed_876e23b9d2 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_window_changed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_window_changed', hook='session-window-changed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_hooks_py_test_hook_set_show_unset_cycle_session_window_changed_cd7ff74abf | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[window_linked] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_linked', hook='window-linked', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_linked_65f09a3c87 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[window_renamed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_renamed', hook='window-renamed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_renamed_0b579984c6 | 3.2a through 3.7b | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_resized] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_resized', hook='client-resized', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_client_resized_5e79f6adfd | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[client_session_changed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='client_session_changed', hook='client-session-changed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_hooks_py_test_hook_set_show_unset_cycle_client_session_changed_0a3b14cc14 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_died] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_died', hook='pane-died', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_died_721b0c9b7e | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_exited] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_exited', hook='pane-exited', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_exited_c0c96cf7af | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_focus_in] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_focus_in', hook='pane-focus-in', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_focus_in_71117656f3 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_focus_out] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_focus_out', hook='pane-focus-out', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_focus_out_e69f0137c3 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_mode_changed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_mode_changed', hook='pane-mode-changed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_mode_changed_cca7b40d6f | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[pane_set_clipboard] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='pane_set_clipboard', hook='pane-set-clipboard', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_pane_set_clipboard_76f45b0002 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_closed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_closed', hook='session-closed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_session_closed_803af103b5 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_created] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_created', hook='session-created', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_session_created_418947221e | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_renamed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_renamed', hook='session-renamed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_session_renamed_876e23b9d2 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[session_window_changed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='session_window_changed', hook='session-window-changed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_hooks_py_test_hook_set_show_unset_cycle_session_window_changed_cd7ff74abf | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[window_linked] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_linked', hook='window-linked', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_linked_65f09a3c87 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[window_renamed] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_renamed', hook='window-renamed', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_renamed_0b579984c6 | 3.3a through 3.7c | planned parity | | tests/test_hooks.py::test_hook_set_show_unset_cycle[window_resized] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_resized', hook='window-resized', min_version='3.3', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_resized_bf9b817452 | 3.3 through 3.7b | planned parity | -| tests/test_hooks.py::test_hook_set_show_unset_cycle[window_unlinked] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_unlinked', hook='window-unlinked', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_unlinked_8ca5dda260 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hooks_dataclass | Tests for hooks dataclass; fixtures `server`; execute `server.new_session(session_name='test hooks')`; `session.cmd('set-hook', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `server.cmd('show-hooks', 'session-renamed[0]')`; `session.cmd('set-hook', '-p', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `session.cmd('set-hook', 'session-renamed[1]', 'set -g status-left-style bg=white')`; `session.cmd('set-hook', 'session-renamed[2]', 'set -g status-left-style bg=blue')`; assert `pane is not None`; `not set_hook_proc.stdout`; `not set_hook_proc.stderr`; `hooks.session_renamed.as_list() == ['set-option -g status-left-style bg=red']`; `session_out_processed.session_renamed is not None`; `session_out_processed.session_renamed[2] == 'set-option -g status-left-style bg=blue'`; `hooks.session_renamed.as_list() == ['set-option -g status-left-style bg=red', 'set-option -g status-left-style bg=white', 'set-option -g status-left-style bg=blue']`; `hooks.after_set_buffer.as_list() == ['set-option -g status-left-style bg=pink']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hooks_dataclass_46244b6202 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hooks_mixin | Tests for hooks; fixtures `server`; execute `server.new_session(session_name='test hooks')`; `pane.set_hook('session-renamed[0]', 'set -g status-left-style bg=red')`; `pane.show_hooks()`; `pane._show_hook('session-renamed[0]')`; `Hooks.from_stdout(show_hooks_raw)`; `SparseArray({0: 'set-option -g status-left-style bg=red'})`; assert `pane is not None`; `not pane.show_hooks()`; `show_hooks_raw is not None`; `hooks.session_renamed == SparseArray({0: 'set-option -g status-left-style bg=red'})`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hooks_mixin_e114642bf6 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_hooks_raw_cmd | Raw hook set, show, unset via cmd; fixtures `server`; execute `server.new_session(session_name='test hooks')`; `server.cmd('set-hook', '-g', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `server.cmd('show-hooks')`; `server.cmd('show-hooks', '-g', 'session-renamed[0]')`; `server.cmd('set-hook', '-g', '-u', 'session-renamed[0]')`; `server.cmd('set-hook', '-g', '-u', 'session-renamed')`; assert `pane is not None`; `not set_hook_proc.stdout`; `not set_hook_proc.stderr`; `not server.cmd('show-hooks').stdout`; `'set-option -g status-left-style bg=red' in show_hooks_proc.stdout[0]`; `server.cmd('show-hooks', '-g', 'session-renamed[0]').stdout == ['session-renamed[0] ']`; `not session.cmd('show-hooks', '-s').stdout`; `session.cmd('show-hooks', '-s', 'session-renamed[0]').stdout == []`; when `has_gte_version('3.2')`: execute `window.cmd('set-hook', '-w', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `server.cmd('show-hooks', 'session-renamed[0]')`; `window.cmd('set-hook', '-w', '-u', 'session-renamed[0]')`; `window.cmd('show-hooks', '-w', 'session-renamed[0]')`; `window.cmd('set-hook', '-w', '-u', 'session-renamed')`; `pane.cmd('set-hook', '-p', 'session-renamed[0]', 'set -g status-left-style bg=red')`; assert `not set_hook_proc.stdout`; `not set_hook_proc.stderr`; `'set-option -g status-left-style bg=red' in show_hooks_proc.stdout[0]`; `window.cmd('show-hooks', '-w', 'session-renamed[0]').stdout == ['session-renamed[0] ']`; `window.cmd('show-hooks', '-w', 'session-renamed[0]').stdout == []`; `pane.cmd('show-hooks', '-p', 'session-renamed[0]').stdout == ['session-renamed[0] ']`; `pane.cmd('show-hooks', '-p', 'session-renamed[0]').stdout == []`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hooks_raw_cmd_4fc114cddd | 3.2a through 3.7b; conditional boundaries: 3.2 | planned parity | +| tests/test_hooks.py::test_hook_set_show_unset_cycle[window_unlinked] | Test set/show/unset cycle for each hook; case inputs `test_case=HookTestCase(test_id='window_unlinked', hook='window-unlinked', min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_hook_cycle')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `test_case.hook.replace('-', '_')`; `session.unset_hook(f'{test_case.hook}[0]')`; `Hooks.from_stdout(result)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected hook {test_case.hook} to be set'`; `hook_value is not None; message=f'Hook attribute {hook_attr} not found in Hooks'`; `len(hook_value) > 0; message=f'Expected hook {test_case.hook} to have values'`; `'display-message' in hook_value[0]; message=f"Expected 'display-message' in hook value, got: {hook_value[0]}"`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `result_after_unset`: execute `Hooks.from_stdout(result_after_unset)`; when `result_after_unset and hook_value_after`: execute `hook_value_after.as_list()`; assert `len(hook_value_after.as_list()) == 0 or hook_value_after[0] == ''; message=f'Expected hook {test_case.hook} to be unset'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hook_set_show_unset_cycle_window_unlinked_8ca5dda260 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hooks_dataclass | Tests for hooks dataclass; fixtures `server`; execute `server.new_session(session_name='test hooks')`; `session.cmd('set-hook', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `server.cmd('show-hooks', 'session-renamed[0]')`; `session.cmd('set-hook', '-p', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `session.cmd('set-hook', 'session-renamed[1]', 'set -g status-left-style bg=white')`; `session.cmd('set-hook', 'session-renamed[2]', 'set -g status-left-style bg=blue')`; assert `pane is not None`; `not set_hook_proc.stdout`; `not set_hook_proc.stderr`; `hooks.session_renamed.as_list() == ['set-option -g status-left-style bg=red']`; `session_out_processed.session_renamed is not None`; `session_out_processed.session_renamed[2] == 'set-option -g status-left-style bg=blue'`; `hooks.session_renamed.as_list() == ['set-option -g status-left-style bg=red', 'set-option -g status-left-style bg=white', 'set-option -g status-left-style bg=blue']`; `hooks.after_set_buffer.as_list() == ['set-option -g status-left-style bg=pink']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hooks_dataclass_46244b6202 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hooks_mixin | Tests for hooks; fixtures `server`; execute `server.new_session(session_name='test hooks')`; `pane.set_hook('session-renamed[0]', 'set -g status-left-style bg=red')`; `pane.show_hooks()`; `pane._show_hook('session-renamed[0]')`; `Hooks.from_stdout(show_hooks_raw)`; `SparseArray({0: 'set-option -g status-left-style bg=red'})`; assert `pane is not None`; `not pane.show_hooks()`; `show_hooks_raw is not None`; `hooks.session_renamed == SparseArray({0: 'set-option -g status-left-style bg=red'})`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hooks_mixin_e114642bf6 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_hooks_raw_cmd | Raw hook set, show, unset via cmd; fixtures `server`; execute `server.new_session(session_name='test hooks')`; `server.cmd('set-hook', '-g', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `server.cmd('show-hooks')`; `server.cmd('show-hooks', '-g', 'session-renamed[0]')`; `server.cmd('set-hook', '-g', '-u', 'session-renamed[0]')`; `server.cmd('set-hook', '-g', '-u', 'session-renamed')`; assert `pane is not None`; `not set_hook_proc.stdout`; `not set_hook_proc.stderr`; `not server.cmd('show-hooks').stdout`; `'set-option -g status-left-style bg=red' in show_hooks_proc.stdout[0]`; `server.cmd('show-hooks', '-g', 'session-renamed[0]').stdout == ['session-renamed[0] ']`; `not session.cmd('show-hooks', '-s').stdout`; `session.cmd('show-hooks', '-s', 'session-renamed[0]').stdout == []`; when `has_gte_version('3.2')`: execute `window.cmd('set-hook', '-w', 'session-renamed[0]', 'set -g status-left-style bg=red')`; `server.cmd('show-hooks', 'session-renamed[0]')`; `window.cmd('set-hook', '-w', '-u', 'session-renamed[0]')`; `window.cmd('show-hooks', '-w', 'session-renamed[0]')`; `window.cmd('set-hook', '-w', '-u', 'session-renamed')`; `pane.cmd('set-hook', '-p', 'session-renamed[0]', 'set -g status-left-style bg=red')`; assert `not set_hook_proc.stdout`; `not set_hook_proc.stderr`; `'set-option -g status-left-style bg=red' in show_hooks_proc.stdout[0]`; `window.cmd('show-hooks', '-w', 'session-renamed[0]').stdout == ['session-renamed[0] ']`; `window.cmd('show-hooks', '-w', 'session-renamed[0]').stdout == []`; `pane.cmd('show-hooks', '-p', 'session-renamed[0]').stdout == ['session-renamed[0] ']`; `pane.cmd('show-hooks', '-p', 'session-renamed[0]').stdout == []`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_hooks_raw_cmd_4fc114cddd | 3.3a through 3.7c; conditional boundaries: 3.2 | planned parity | | tests/test_hooks.py::test_new_hooks_version_gated[pane_title_changed] | Test new hooks that require tmux 3.5+; case inputs `test_case=HookTestCase(test_id='pane_title_changed', hook='pane-title-changed', min_version='3.5', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_new_hooks')`; `session.set_hook(f'{test_case.hook}[0]', hook_cmd)`; `session._show_hook(f'{test_case.hook}[0]')`; `session.unset_hook(f'{test_case.hook}[0]')`; assert `result is not None; message=f'Expected hook {test_case.hook} to be set'`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_new_hooks_version_gated_pane_title_changed_69b5502353 | 3.5 through 3.7b | planned parity | -| tests/test_hooks.py::test_run_hook_basic | Test run_hook() method exists and can be called without error; fixtures `server`; execute `server.new_session(session_name='test_run_hook')`; `session.set_hook('session-renamed[0]', 'display-message "test"')`; `session.run_hook('session-renamed[0]')`; `session.unset_hook('session-renamed')`; assert `result is session`; when `not has_gte_version('3.2')`: skip via `pytest.skip('Requires tmux >= 3.2')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_run_hook_basic_1e7330eab3 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hook_append_flag | Test set_hook with append=True uses tmux's -a flag; fixtures `server`; execute `server.new_session(session_name='test_append_flag')`; `session.set_hook('session-renamed[0]', "display-message 'initial'")`; `session.set_hook('session-renamed', "display-message 'appended'", append=True)`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `len(hooks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_append_flag_916689e76b | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hook_flag_combinations[append_and_global] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='append_and_global', flag_kwargs={'append': True, 'global_': True}, expected_behavior='appends', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_append_and_global_be3d8a5031 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hook_flag_combinations[append_to_existing] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='append_to_existing', flag_kwargs={'append': True}, expected_behavior='appends', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_append_to_existing_5f0e92ba33 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hook_flag_combinations[global_hook] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='global_hook', flag_kwargs={'global_': True}, expected_behavior='global', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_global_hook_8eb90c674e | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hook_flag_combinations[run_immediately] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='run_immediately', flag_kwargs={'run': True}, expected_behavior='runs_immediately', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_run_immediately_874cdba8df | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hooks[set_hooks_clear_existing] | Test set_hooks operations; case inputs `test_case=SetHooksTestCase(test_id='set_hooks_clear_existing', hook='session-renamed', setup_hooks={0: "display-message 'old 0'", 1: "display-message 'old 1'"}, operation_args={'values': {0: "display-message 'new 0'"}, 'clear_existing': True}, expected_indices=[0], expected_contains=['new 0'])`; fixtures `server`; execute `server.new_session(session_name='test_set_hooks')`; `session.set_hook(f'{test_case.hook}[{idx}]', val)`; `session.set_hooks(test_case.hook, **test_case.operation_args)`; `session.show_hook(test_case.hook)`; `session.unset_hook(test_case.hook)`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == test_case.expected_indices`; when `test_case.expected_contains`: execute `any((expected_str in v for v in hooks.values()))`; assert `any((expected_str in v for v in hooks.values()))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_set_hooks_clear_existing_5256a7be82 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hooks[set_hooks_with_dict] | Test set_hooks operations; case inputs `test_case=SetHooksTestCase(test_id='set_hooks_with_dict', hook='session-renamed', setup_hooks={}, operation_args={'values': {0: "display-message 'hook 0'", 1: "display-message 'hook 1'", 5: "display-message 'hook 5'"}}, expected_indices=[0, 1, 5], expected_contains=['hook 0', 'hook 1', 'hook 5'])`; fixtures `server`; execute `server.new_session(session_name='test_set_hooks')`; `session.set_hook(f'{test_case.hook}[{idx}]', val)`; `session.set_hooks(test_case.hook, **test_case.operation_args)`; `session.show_hook(test_case.hook)`; `session.unset_hook(test_case.hook)`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == test_case.expected_indices`; when `test_case.expected_contains`: execute `any((expected_str in v for v in hooks.values()))`; assert `any((expected_str in v for v in hooks.values()))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_set_hooks_with_dict_1898803e0c | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hooks[set_hooks_with_list] | Test set_hooks operations; case inputs `test_case=SetHooksTestCase(test_id='set_hooks_with_list', hook='session-renamed', setup_hooks={}, operation_args={'values': ["display-message 'hook 0'", "display-message 'hook 1'", "display-message 'hook 2'"]}, expected_indices=[0, 1, 2], expected_contains=None)`; fixtures `server`; execute `server.new_session(session_name='test_set_hooks')`; `session.set_hook(f'{test_case.hook}[{idx}]', val)`; `session.set_hooks(test_case.hook, **test_case.operation_args)`; `session.show_hook(test_case.hook)`; `session.unset_hook(test_case.hook)`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == test_case.expected_indices`; when `test_case.expected_contains`: execute `any((expected_str in v for v in hooks.values()))`; assert `any((expected_str in v for v in hooks.values()))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_set_hooks_with_list_6f6c753f39 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hooks_method_chaining | Test that set_hooks supports method chaining; fixtures `server`; execute `server.new_session(session_name='test_set_hooks_chain')`; `session.set_hooks('session-renamed', ["display-message 'hook 0'"]).set_hook('session-renamed', "display-message 'hook 1'", append=True).set_hook('session-renamed', "display-message 'hook 2'", append=True)`; `session.set_hooks('session-renamed', ["display-message 'hook 0'"]).set_hook('session-renamed', "display-message 'hook 1'", append=True)`; `session.set_hooks('session-renamed', ["display-message 'hook 0'"])`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `result is session`; `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 1, 2]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_method_chaining_d4a4a6d605 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_set_hooks_with_sparse_array | Test set_hooks with SparseArray input; fixtures `server`; execute `server.new_session(session_name='test_set_hooks_sparse')`; `session.set_hooks('session-renamed', sparse)`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; `SparseArray()`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 10]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_with_sparse_array_a69f549411 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hook_indexed_lookup[index_five-session-renamed-5-display-message 'test five'] | Test that show_hook with indexed hook name returns the specific value; case inputs `hook_index=5`, `hook_name='session-renamed'`, `hook_value="display-message 'test five'"`, `test_id='index_five'`; fixtures `server`; execute `server.new_session(session_name='test_indexed_lookup')`; `session.set_hook(indexed_hook, hook_value)`; `session.show_hook(indexed_hook)`; `session.show_hook(hook_name)`; `session.unset_hook(hook_name)`; assert `result is not None`; `isinstance(result, str); message=f'Expected str, got {type(result)}'`; `'display-message' in result`; `isinstance(base_result, SparseArray)`; `hook_index in base_result`. | real-tmux integration test | PythonBehaviorParityTest#ok_indexed_lookup_index_five_session_renamed_5_display_message_test_five_6496fdb088 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hook_indexed_lookup[index_zero-session-renamed-0-display-message 'test zero'] | Test that show_hook with indexed hook name returns the specific value; case inputs `hook_index=0`, `hook_name='session-renamed'`, `hook_value="display-message 'test zero'"`, `test_id='index_zero'`; fixtures `server`; execute `server.new_session(session_name='test_indexed_lookup')`; `session.set_hook(indexed_hook, hook_value)`; `session.show_hook(indexed_hook)`; `session.show_hook(hook_name)`; `session.unset_hook(hook_name)`; assert `result is not None`; `isinstance(result, str); message=f'Expected str, got {type(result)}'`; `'display-message' in result`; `isinstance(base_result, SparseArray)`; `hook_index in base_result`. | real-tmux integration test | PythonBehaviorParityTest#ok_indexed_lookup_index_zero_session_renamed_0_display_message_test_zero_953d5e7188 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hook_indexed_lookup[window_hook-window-renamed-0-display-message 'window test'] | Test that show_hook with indexed hook name returns the specific value; case inputs `hook_index=0`, `hook_name='window-renamed'`, `hook_value="display-message 'window test'"`, `test_id='window_hook'`; fixtures `server`; execute `server.new_session(session_name='test_indexed_lookup')`; `session.set_hook(indexed_hook, hook_value)`; `session.show_hook(indexed_hook)`; `session.show_hook(hook_name)`; `session.unset_hook(hook_name)`; assert `result is not None`; `isinstance(result, str); message=f'Expected str, got {type(result)}'`; `'display-message' in result`; `isinstance(base_result, SparseArray)`; `hook_index in base_result`. | real-tmux integration test | PythonBehaviorParityTest#_indexed_lookup_window_hook_window_renamed_0_display_message_window_test_58f9ead4cd | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hook_returns_sparse_array | Test that show_hook returns SparseArray for array hooks; fixtures `server`; execute `server.new_session(session_name='test_show_hook')`; `session.set_hook('session-renamed[5]', "display-message 'fifth'")`; `session.set_hook('session-renamed[0]', "display-message 'zeroth'")`; `session.set_hook('session-renamed[2]', "display-message 'second'")`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 2, 5]`; `len(value_list) == 3`; `'zeroth' in value_list[0]`; `'second' in value_list[1]`; `'fifth' in value_list[2]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hook_returns_sparse_array_6e3931a6cb | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hooks_empty_result | Test show_hooks returns empty dict when no hooks are set; fixtures `server`; execute `server.new_session(session_name='test_empty_hooks')`; `session.show_hooks()`; assert `isinstance(hooks, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hooks_empty_result_b6cbde6abd | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hooks_parsing_edge_cases[multiple_different_hooks] | Test show_hooks parses various output formats correctly; case inputs `test_case=ShowHooksParseTestCase(test_id='multiple_different_hooks', setup_commands=[('session-renamed[0]', "display-message 'renamed'"), ('after-split-window[0]', "display-message 'split'")], expected_keys=['session-renamed[0]', 'after-split-window[0]'], check_values=True)`; fixtures `server`; execute `server.new_session(session_name='test_parsing')`; `session.set_hook(hook, value)`; `session.show_hooks()`; `session.unset_hook(base_hook)`; `hook.split('[')`; assert `key in hooks; message=f'Expected key {key} not found in {list(hooks.keys())}'`; when `test_case.check_values`: assert `hooks[key] is not None; message=f'Expected value for {key} to be non-None'`. | real-tmux integration test | PythonBehaviorParityTest#est_hooks_py_test_show_hooks_parsing_edge_cases_multiple_different_hooks_d989b9d220 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hooks_parsing_edge_cases[multiple_indexed_hooks] | Test show_hooks parses various output formats correctly; case inputs `test_case=ShowHooksParseTestCase(test_id='multiple_indexed_hooks', setup_commands=[('session-renamed[0]', "display-message 'first'"), ('session-renamed[1]', "display-message 'second'"), ('session-renamed[5]', "display-message 'fifth'")], expected_keys=['session-renamed[0]', 'session-renamed[1]', 'session-renamed[5]'], check_values=True)`; fixtures `server`; execute `server.new_session(session_name='test_parsing')`; `session.set_hook(hook, value)`; `session.show_hooks()`; `session.unset_hook(base_hook)`; `hook.split('[')`; assert `key in hooks; message=f'Expected key {key} not found in {list(hooks.keys())}'`; when `test_case.check_values`: assert `hooks[key] is not None; message=f'Expected value for {key} to be non-None'`. | real-tmux integration test | PythonBehaviorParityTest#_test_hooks_py_test_show_hooks_parsing_edge_cases_multiple_indexed_hooks_58da3ecfdc | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hooks_parsing_edge_cases[normal_hook_value] | Test show_hooks parses various output formats correctly; case inputs `test_case=ShowHooksParseTestCase(test_id='normal_hook_value', setup_commands=[('session-renamed[0]', "display-message 'test'")], expected_keys=['session-renamed[0]'], check_values=True)`; fixtures `server`; execute `server.new_session(session_name='test_parsing')`; `session.set_hook(hook, value)`; `session.show_hooks()`; `session.unset_hook(base_hook)`; `hook.split('[')`; assert `key in hooks; message=f'Expected key {key} not found in {list(hooks.keys())}'`; when `test_case.check_values`: assert `hooks[key] is not None; message=f'Expected value for {key} to be non-None'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hooks_parsing_edge_cases_normal_hook_value_412b6d8628 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hooks_stores_string_values[multiple_hooks] | Test that show_hooks() correctly stores string hook values; case inputs `test_case=ShowHooksTestCase(test_id='multiple_hooks', hook='session-renamed[1]', value='display-message "another"', expected_value='display-message another', expected_type=<class 'str'>, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_hooks')`; `session.set_hook(test_case.hook, test_case.value)`; `session.show_hooks()`; `session.unset_hook(test_case.hook.split('[')[0])`; `test_case.hook.split('[')`; assert `test_case.hook in hooks`; `isinstance(hooks[test_case.hook], test_case.expected_type)`; `hooks[test_case.hook] == test_case.expected_value`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hooks_stores_string_values_multiple_hooks_c283082221 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_show_hooks_stores_string_values[string_hook_value] | Test that show_hooks() correctly stores string hook values; case inputs `test_case=ShowHooksTestCase(test_id='string_hook_value', hook='session-renamed[0]', value='display-message "test"', expected_value='display-message test', expected_type=<class 'str'>, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_hooks')`; `session.set_hook(test_case.hook, test_case.value)`; `session.show_hooks()`; `session.unset_hook(test_case.hook.split('[')[0])`; `test_case.hook.split('[')`; assert `test_case.hook in hooks`; `isinstance(hooks[test_case.hook], test_case.expected_type)`; `hooks[test_case.hook] == test_case.expected_value`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_hooks_py_test_show_hooks_stores_string_values_string_hook_value_dc86738eb8 | 3.2a through 3.7b | planned parity | -| tests/test_hooks.py::test_unset_hook_clears_all_indices | Test that unset_hook without index clears all hook indices; fixtures `server`; execute `server.new_session(session_name='test_unset_hook')`; `session.set_hook('session-renamed[0]', "display-message 'hook 0'")`; `session.set_hook('session-renamed[5]', "display-message 'hook 5'")`; `session.set_hook('session-renamed[10]', "display-message 'hook 10'")`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 5, 10]`; `hooks_after is None or (isinstance(hooks_after, SparseArray) and len(hooks_after) == 0)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_unset_hook_clears_all_indices_782777b361 | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_lifecycle_info_logging_schema | Test that lifecycle operations produce INFO records with str-typed extra; fixtures `session`, `caplog`; execute `session.new_window(window_name='log_test')`; `window.kill()`; `t.cast(t.Any, record)`; assert `len(records) >= 1; message='expected at least one INFO lifecycle record'`; when `val is not None`: assert `isinstance(val, str); message=f'extra key {key!r} should be str, got {type(val).__name__}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_lifecycle_info_logging_schema_8796636f6b | 3.2a through 3.7b | planned parity | +| tests/test_hooks.py::test_run_hook_basic | Test run_hook() method exists and can be called without error; fixtures `server`; execute `server.new_session(session_name='test_run_hook')`; `session.set_hook('session-renamed[0]', 'display-message "test"')`; `session.run_hook('session-renamed[0]')`; `session.unset_hook('session-renamed')`; assert `result is session`; when `not has_gte_version('3.2')`: skip via `pytest.skip('Requires tmux >= 3.2')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_run_hook_basic_1e7330eab3 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hook_append_flag | Test set_hook with append=True uses tmux's -a flag; fixtures `server`; execute `server.new_session(session_name='test_append_flag')`; `session.set_hook('session-renamed[0]', "display-message 'initial'")`; `session.set_hook('session-renamed', "display-message 'appended'", append=True)`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `len(hooks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_append_flag_916689e76b | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hook_flag_combinations[append_and_global] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='append_and_global', flag_kwargs={'append': True, 'global_': True}, expected_behavior='appends', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_append_and_global_be3d8a5031 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hook_flag_combinations[append_to_existing] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='append_to_existing', flag_kwargs={'append': True}, expected_behavior='appends', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_append_to_existing_5f0e92ba33 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hook_flag_combinations[global_hook] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='global_hook', flag_kwargs={'global_': True}, expected_behavior='global', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_global_hook_8eb90c674e | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hook_flag_combinations[run_immediately] | Test set_hook with various flag combinations; case inputs `test_case=SetHookFlagTestCase(test_id='run_immediately', flag_kwargs={'run': True}, expected_behavior='runs_immediately', min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_flags')`; `session.unset_hook(hook_name)`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`; when `test_case.expected_behavior == 'appends'`: execute `session.set_hook(f'{hook_name}[0]', "display-message 'initial'")`; `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `parsed.session_renamed.as_list()`; assert `len(hook_values) >= 1`; when `test_case.expected_behavior == 'appends' and test_case.flag_kwargs.get('global_')`: execute `server.cmd('show-hooks', '-g', hook_name)`; `Hooks.from_stdout(result.stdout)`; assert `result.stdout is not None`; when `test_case.expected_behavior == 'appends' and not (test_case.flag_kwargs.get('global_'))`: execute `session._show_hook(hook_name)`; `Hooks.from_stdout(hooks)`; assert `hooks is not None`; when `not (test_case.expected_behavior == 'appends') and test_case.expected_behavior == 'global'`: execute `session.set_hook(f'{hook_name}[0]', hook_cmd, **test_case.flag_kwargs)`; `server.cmd('show-hooks', '-g', f'{hook_name}[0]')`; assert `result.stdout is not None`; `len(result.stdout) > 0`; `'display-message' in result.stdout[0]`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately'`: execute `session.set_hook(hook_name, hook_cmd, **test_case.flag_kwargs)`; `session._show_hook(hook_name)`; when `not (test_case.expected_behavior == 'appends') and not (test_case.expected_behavior == 'global') and test_case.expected_behavior == 'runs_immediately' and hooks`: execute `Hooks.from_stdout(hooks)`; assert `parsed.session_renamed is None or len(parsed.session_renamed) == 0`; when `test_case.flag_kwargs.get('global_')`: execute `server.cmd('set-hook', '-gu', hook_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hook_flag_combinations_run_immediately_874cdba8df | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hooks[set_hooks_clear_existing] | Test set_hooks operations; case inputs `test_case=SetHooksTestCase(test_id='set_hooks_clear_existing', hook='session-renamed', setup_hooks={0: "display-message 'old 0'", 1: "display-message 'old 1'"}, operation_args={'values': {0: "display-message 'new 0'"}, 'clear_existing': True}, expected_indices=[0], expected_contains=['new 0'])`; fixtures `server`; execute `server.new_session(session_name='test_set_hooks')`; `session.set_hook(f'{test_case.hook}[{idx}]', val)`; `session.set_hooks(test_case.hook, **test_case.operation_args)`; `session.show_hook(test_case.hook)`; `session.unset_hook(test_case.hook)`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == test_case.expected_indices`; when `test_case.expected_contains`: execute `any((expected_str in v for v in hooks.values()))`; assert `any((expected_str in v for v in hooks.values()))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_set_hooks_clear_existing_5256a7be82 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hooks[set_hooks_with_dict] | Test set_hooks operations; case inputs `test_case=SetHooksTestCase(test_id='set_hooks_with_dict', hook='session-renamed', setup_hooks={}, operation_args={'values': {0: "display-message 'hook 0'", 1: "display-message 'hook 1'", 5: "display-message 'hook 5'"}}, expected_indices=[0, 1, 5], expected_contains=['hook 0', 'hook 1', 'hook 5'])`; fixtures `server`; execute `server.new_session(session_name='test_set_hooks')`; `session.set_hook(f'{test_case.hook}[{idx}]', val)`; `session.set_hooks(test_case.hook, **test_case.operation_args)`; `session.show_hook(test_case.hook)`; `session.unset_hook(test_case.hook)`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == test_case.expected_indices`; when `test_case.expected_contains`: execute `any((expected_str in v for v in hooks.values()))`; assert `any((expected_str in v for v in hooks.values()))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_set_hooks_with_dict_1898803e0c | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hooks[set_hooks_with_list] | Test set_hooks operations; case inputs `test_case=SetHooksTestCase(test_id='set_hooks_with_list', hook='session-renamed', setup_hooks={}, operation_args={'values': ["display-message 'hook 0'", "display-message 'hook 1'", "display-message 'hook 2'"]}, expected_indices=[0, 1, 2], expected_contains=None)`; fixtures `server`; execute `server.new_session(session_name='test_set_hooks')`; `session.set_hook(f'{test_case.hook}[{idx}]', val)`; `session.set_hooks(test_case.hook, **test_case.operation_args)`; `session.show_hook(test_case.hook)`; `session.unset_hook(test_case.hook)`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == test_case.expected_indices`; when `test_case.expected_contains`: execute `any((expected_str in v for v in hooks.values()))`; assert `any((expected_str in v for v in hooks.values()))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_set_hooks_with_list_6f6c753f39 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hooks_method_chaining | Test that set_hooks supports method chaining; fixtures `server`; execute `server.new_session(session_name='test_set_hooks_chain')`; `session.set_hooks('session-renamed', ["display-message 'hook 0'"]).set_hook('session-renamed', "display-message 'hook 1'", append=True).set_hook('session-renamed', "display-message 'hook 2'", append=True)`; `session.set_hooks('session-renamed', ["display-message 'hook 0'"]).set_hook('session-renamed', "display-message 'hook 1'", append=True)`; `session.set_hooks('session-renamed', ["display-message 'hook 0'"])`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `result is session`; `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 1, 2]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_method_chaining_d4a4a6d605 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_set_hooks_with_sparse_array | Test set_hooks with SparseArray input; fixtures `server`; execute `server.new_session(session_name='test_set_hooks_sparse')`; `session.set_hooks('session-renamed', sparse)`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; `SparseArray()`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 10]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_set_hooks_with_sparse_array_a69f549411 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hook_indexed_lookup[index_five-session-renamed-5-display-message 'test five'] | Test that show_hook with indexed hook name returns the specific value; case inputs `hook_index=5`, `hook_name='session-renamed'`, `hook_value="display-message 'test five'"`, `test_id='index_five'`; fixtures `server`; execute `server.new_session(session_name='test_indexed_lookup')`; `session.set_hook(indexed_hook, hook_value)`; `session.show_hook(indexed_hook)`; `session.show_hook(hook_name)`; `session.unset_hook(hook_name)`; assert `result is not None`; `isinstance(result, str); message=f'Expected str, got {type(result)}'`; `'display-message' in result`; `isinstance(base_result, SparseArray)`; `hook_index in base_result`. | real-tmux integration test | PythonBehaviorParityTest#ok_indexed_lookup_index_five_session_renamed_5_display_message_test_five_6496fdb088 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hook_indexed_lookup[index_zero-session-renamed-0-display-message 'test zero'] | Test that show_hook with indexed hook name returns the specific value; case inputs `hook_index=0`, `hook_name='session-renamed'`, `hook_value="display-message 'test zero'"`, `test_id='index_zero'`; fixtures `server`; execute `server.new_session(session_name='test_indexed_lookup')`; `session.set_hook(indexed_hook, hook_value)`; `session.show_hook(indexed_hook)`; `session.show_hook(hook_name)`; `session.unset_hook(hook_name)`; assert `result is not None`; `isinstance(result, str); message=f'Expected str, got {type(result)}'`; `'display-message' in result`; `isinstance(base_result, SparseArray)`; `hook_index in base_result`. | real-tmux integration test | PythonBehaviorParityTest#ok_indexed_lookup_index_zero_session_renamed_0_display_message_test_zero_953d5e7188 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hook_indexed_lookup[window_hook-window-renamed-0-display-message 'window test'] | Test that show_hook with indexed hook name returns the specific value; case inputs `hook_index=0`, `hook_name='window-renamed'`, `hook_value="display-message 'window test'"`, `test_id='window_hook'`; fixtures `server`; execute `server.new_session(session_name='test_indexed_lookup')`; `session.set_hook(indexed_hook, hook_value)`; `session.show_hook(indexed_hook)`; `session.show_hook(hook_name)`; `session.unset_hook(hook_name)`; assert `result is not None`; `isinstance(result, str); message=f'Expected str, got {type(result)}'`; `'display-message' in result`; `isinstance(base_result, SparseArray)`; `hook_index in base_result`. | real-tmux integration test | PythonBehaviorParityTest#_indexed_lookup_window_hook_window_renamed_0_display_message_window_test_58f9ead4cd | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hook_returns_sparse_array | Test that show_hook returns SparseArray for array hooks; fixtures `server`; execute `server.new_session(session_name='test_show_hook')`; `session.set_hook('session-renamed[5]', "display-message 'fifth'")`; `session.set_hook('session-renamed[0]', "display-message 'zeroth'")`; `session.set_hook('session-renamed[2]', "display-message 'second'")`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 2, 5]`; `len(value_list) == 3`; `'zeroth' in value_list[0]`; `'second' in value_list[1]`; `'fifth' in value_list[2]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hook_returns_sparse_array_6e3931a6cb | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hooks_empty_result | Test show_hooks returns empty dict when no hooks are set; fixtures `server`; execute `server.new_session(session_name='test_empty_hooks')`; `session.show_hooks()`; assert `isinstance(hooks, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hooks_empty_result_b6cbde6abd | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hooks_parsing_edge_cases[multiple_different_hooks] | Test show_hooks parses various output formats correctly; case inputs `test_case=ShowHooksParseTestCase(test_id='multiple_different_hooks', setup_commands=[('session-renamed[0]', "display-message 'renamed'"), ('after-split-window[0]', "display-message 'split'")], expected_keys=['session-renamed[0]', 'after-split-window[0]'], check_values=True)`; fixtures `server`; execute `server.new_session(session_name='test_parsing')`; `session.set_hook(hook, value)`; `session.show_hooks()`; `session.unset_hook(base_hook)`; `hook.split('[')`; assert `key in hooks; message=f'Expected key {key} not found in {list(hooks.keys())}'`; when `test_case.check_values`: assert `hooks[key] is not None; message=f'Expected value for {key} to be non-None'`. | real-tmux integration test | PythonBehaviorParityTest#est_hooks_py_test_show_hooks_parsing_edge_cases_multiple_different_hooks_d989b9d220 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hooks_parsing_edge_cases[multiple_indexed_hooks] | Test show_hooks parses various output formats correctly; case inputs `test_case=ShowHooksParseTestCase(test_id='multiple_indexed_hooks', setup_commands=[('session-renamed[0]', "display-message 'first'"), ('session-renamed[1]', "display-message 'second'"), ('session-renamed[5]', "display-message 'fifth'")], expected_keys=['session-renamed[0]', 'session-renamed[1]', 'session-renamed[5]'], check_values=True)`; fixtures `server`; execute `server.new_session(session_name='test_parsing')`; `session.set_hook(hook, value)`; `session.show_hooks()`; `session.unset_hook(base_hook)`; `hook.split('[')`; assert `key in hooks; message=f'Expected key {key} not found in {list(hooks.keys())}'`; when `test_case.check_values`: assert `hooks[key] is not None; message=f'Expected value for {key} to be non-None'`. | real-tmux integration test | PythonBehaviorParityTest#_test_hooks_py_test_show_hooks_parsing_edge_cases_multiple_indexed_hooks_58da3ecfdc | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hooks_parsing_edge_cases[normal_hook_value] | Test show_hooks parses various output formats correctly; case inputs `test_case=ShowHooksParseTestCase(test_id='normal_hook_value', setup_commands=[('session-renamed[0]', "display-message 'test'")], expected_keys=['session-renamed[0]'], check_values=True)`; fixtures `server`; execute `server.new_session(session_name='test_parsing')`; `session.set_hook(hook, value)`; `session.show_hooks()`; `session.unset_hook(base_hook)`; `hook.split('[')`; assert `key in hooks; message=f'Expected key {key} not found in {list(hooks.keys())}'`; when `test_case.check_values`: assert `hooks[key] is not None; message=f'Expected value for {key} to be non-None'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hooks_parsing_edge_cases_normal_hook_value_412b6d8628 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hooks_stores_string_values[multiple_hooks] | Test that show_hooks() correctly stores string hook values; case inputs `test_case=ShowHooksTestCase(test_id='multiple_hooks', hook='session-renamed[1]', value='display-message "another"', expected_value='display-message another', expected_type=<class 'str'>, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_hooks')`; `session.set_hook(test_case.hook, test_case.value)`; `session.show_hooks()`; `session.unset_hook(test_case.hook.split('[')[0])`; `test_case.hook.split('[')`; assert `test_case.hook in hooks`; `isinstance(hooks[test_case.hook], test_case.expected_type)`; `hooks[test_case.hook] == test_case.expected_value`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_show_hooks_stores_string_values_multiple_hooks_c283082221 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_show_hooks_stores_string_values[string_hook_value] | Test that show_hooks() correctly stores string hook values; case inputs `test_case=ShowHooksTestCase(test_id='string_hook_value', hook='session-renamed[0]', value='display-message "test"', expected_value='display-message test', expected_type=<class 'str'>, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_hooks')`; `session.set_hook(test_case.hook, test_case.value)`; `session.show_hooks()`; `session.unset_hook(test_case.hook.split('[')[0])`; `test_case.hook.split('[')`; assert `test_case.hook in hooks`; `isinstance(hooks[test_case.hook], test_case.expected_type)`; `hooks[test_case.hook] == test_case.expected_value`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_hooks_py_test_show_hooks_stores_string_values_string_hook_value_dc86738eb8 | 3.3a through 3.7c | planned parity | +| tests/test_hooks.py::test_unset_hook_clears_all_indices | Test that unset_hook without index clears all hook indices; fixtures `server`; execute `server.new_session(session_name='test_unset_hook')`; `session.set_hook('session-renamed[0]', "display-message 'hook 0'")`; `session.set_hook('session-renamed[5]', "display-message 'hook 5'")`; `session.set_hook('session-renamed[10]', "display-message 'hook 10'")`; `session.show_hook('session-renamed')`; `session.unset_hook('session-renamed')`; assert `hooks is not None`; `isinstance(hooks, SparseArray)`; `sorted(hooks.keys()) == [0, 5, 10]`; `hooks_after is None or (isinstance(hooks_after, SparseArray) and len(hooks_after) == 0)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_hooks_py_test_unset_hook_clears_all_indices_782777b361 | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_lifecycle_info_logging_schema | Test that lifecycle operations produce INFO records with str-typed extra; fixtures `session`, `caplog`; execute `session.new_window(window_name='log_test')`; `window.kill()`; `t.cast(t.Any, record)`; assert `len(records) >= 1; message='expected at least one INFO lifecycle record'`; when `val is not None`: assert `isinstance(val, str); message=f'extra key {key!r} should be str, got {type(val).__name__}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_lifecycle_info_logging_schema_8796636f6b | 3.3a through 3.7c | planned parity | | tests/test_logging.py::test_options_warning_logging_schema | Test that options parse warnings produce records with tmux_option_key; fixtures `caplog`; execute `SparseArray()`; `explode_complex({'terminal-features': bad_features})`; `t.cast(t.Any, records[0])`; assert `len(records) >= 1; message='expected WARNING record for option parse failure'`; `isinstance(rec.tmux_option_key, str)`; `rec.exc_info is None`. | unit test | PythonBehaviorParityTest#tests_test_logging_py_test_options_warning_logging_schema_358e8402b3 | not tmux-bound | planned parity | -| tests/test_logging.py::test_pane_kill_all_except_logging | Test that pane.kill(all_except=True) identifies the surviving pane; fixtures `session`, `caplog`; execute `window.resize(height=100, width=100)`; `window.split()`; `survivor.kill(all_except=True)`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; `all((p.pane_id not in remaining_pane_ids for p in other_panes))`; assert `window is not None`; `len(records) >= 1; message='expected INFO record for all-except pane kill'`; `rec.getMessage() == 'other panes killed'`; `rec.tmux_pane == survivor.pane_id`; `rec.tmux_target == survivor.pane_id`; `survivor.pane_id in remaining_pane_ids`; `all((p.pane_id not in remaining_pane_ids for p in other_panes))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_pane_kill_all_except_logging_63ecf7040d | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_pane_split_info_logging | Test that pane.split() produces INFO record with str-typed extra; fixtures `session`, `caplog`; execute `pane.split()`; `t.cast(t.Any, records[0])`; `new_pane.kill()`; assert `window is not None`; `pane is not None`; `len(records) >= 1; message='expected INFO record for pane split'`; `isinstance(rec.tmux_subcommand, str)`; `isinstance(rec.tmux_pane, str)`; when `val is not None`: assert `isinstance(val, str); message=f'extra key {key!r} should be str, got {type(val).__name__}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_pane_split_info_logging_e11a0d5da2 | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_server_kill_info_logging | Test that server.kill() emits a lifecycle INFO record; fixtures `caplog`; execute `Server(socket_name=f'libtmux_log_{next(namer)}')`; `temp_server.new_session(session_name=f'log_session_{next(namer)}')`; `temp_server.kill()`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; assert `len(records) >= 1; message='expected INFO record for server kill'`; `rec.getMessage() == 'server killed'`; `isinstance(rec.tmux_subcommand, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_server_kill_info_logging_638284f100 | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_server_new_session_info_logging | Test that server.new_session() produces INFO record with str-typed extra; fixtures `server`, `caplog`; execute `server.new_session(session_name='log_test_session')`; `t.cast(t.Any, records[0])`; `new_session.kill()`; assert `len(records) >= 1; message='expected INFO record for session creation'`; `isinstance(rec.tmux_subcommand, str)`; `isinstance(rec.tmux_session, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_server_new_session_info_logging_c2eb50abfa | 3.2a through 3.7b | planned parity | +| tests/test_logging.py::test_pane_kill_all_except_logging | Test that pane.kill(all_except=True) identifies the surviving pane; fixtures `session`, `caplog`; execute `window.resize(height=100, width=100)`; `window.split()`; `survivor.kill(all_except=True)`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; `all((p.pane_id not in remaining_pane_ids for p in other_panes))`; assert `window is not None`; `len(records) >= 1; message='expected INFO record for all-except pane kill'`; `rec.getMessage() == 'other panes killed'`; `rec.tmux_pane == survivor.pane_id`; `rec.tmux_target == survivor.pane_id`; `survivor.pane_id in remaining_pane_ids`; `all((p.pane_id not in remaining_pane_ids for p in other_panes))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_pane_kill_all_except_logging_63ecf7040d | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_pane_split_info_logging | Test that pane.split() produces INFO record with str-typed extra; fixtures `session`, `caplog`; execute `pane.split()`; `t.cast(t.Any, records[0])`; `new_pane.kill()`; assert `window is not None`; `pane is not None`; `len(records) >= 1; message='expected INFO record for pane split'`; `isinstance(rec.tmux_subcommand, str)`; `isinstance(rec.tmux_pane, str)`; when `val is not None`: assert `isinstance(val, str); message=f'extra key {key!r} should be str, got {type(val).__name__}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_pane_split_info_logging_e11a0d5da2 | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_server_kill_info_logging | Test that server.kill() emits a lifecycle INFO record; fixtures `caplog`; execute `Server(socket_name=f'libtmux_log_{next(namer)}')`; `temp_server.new_session(session_name=f'log_session_{next(namer)}')`; `temp_server.kill()`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; assert `len(records) >= 1; message='expected INFO record for server kill'`; `rec.getMessage() == 'server killed'`; `isinstance(rec.tmux_subcommand, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_server_kill_info_logging_638284f100 | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_server_new_session_info_logging | Test that server.new_session() produces INFO record with str-typed extra; fixtures `server`, `caplog`; execute `server.new_session(session_name='log_test_session')`; `t.cast(t.Any, records[0])`; `new_session.kill()`; assert `len(records) >= 1; message='expected INFO record for session creation'`; `isinstance(rec.tmux_subcommand, str)`; `isinstance(rec.tmux_session, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_server_new_session_info_logging_c2eb50abfa | 3.3a through 3.7c | planned parity | | tests/test_logging.py::test_server_new_session_surfaces_kill_session_stderr | Test kill-session stderr propagation using monkeypatch for the failure path; fixtures `monkeypatch`; execute `server.new_session(session_name='existing_session', kill_session=True)`; `Server(socket_name=f'libtmux_log_{next(namer)}')`; `types.SimpleNamespace(stderr=['kill failed'])`; expect `pytest.raises(exc.LibTmuxException, match='kill failed')`. | unit test | PythonBehaviorParityTest#sts_test_logging_py_test_server_new_session_surfaces_kill_session_stderr_fce6fd3b89 | not tmux-bound | planned parity | -| tests/test_logging.py::test_session_kill_all_except_logging | Test that session.kill(all_except=True) identifies the surviving session; fixtures `server`, `caplog`; execute `server.new_session(session_name=f'log_survivor_{next(namer)}')`; `server.new_session(session_name=f'log_other_{next(namer)}')`; `survivor.kill(all_except=True)`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; `all((session.session_id not in remaining_session_ids for session in other_sessions))`; assert `len(records) >= 1; message='expected INFO record for all-except session kill'`; `rec.getMessage() == 'other sessions killed'`; `rec.tmux_session == survivor.session_name`; `rec.tmux_target == survivor.session_id`; `survivor.session_id in remaining_session_ids`; `all((session.session_id not in remaining_session_ids for session in other_sessions))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_session_kill_all_except_logging_2a4929ce65 | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_tmux_cmd_debug_logging_schema | Test that tmux_cmd produces structured log records per AGENTS.md; fixtures `server`, `caplog`; execute `server.cmd('list-sessions')`; `t.cast(t.Any, records[0])`; assert `len(records) >= 1`; `isinstance(record.tmux_cmd, str)`; `isinstance(record.tmux_exit_code, int)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_tmux_cmd_debug_logging_schema_ee20f7841a | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_window_kill_all_except_logging | Test that window.kill(all_except=True) identifies the surviving window; fixtures `session`, `caplog`; execute `session.new_window(window_name=f'log_survivor_{next(namer)}')`; `session.new_window(window_name=f'log_other_{next(namer)}')`; `survivor.kill(all_except=True)`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; `all((window.window_id not in remaining_window_ids for window in other_windows))`; assert `len(records) >= 1; message='expected INFO record for all-except window kill'`; `rec.getMessage() == 'other windows killed'`; `rec.tmux_window == survivor.window_name`; `rec.tmux_target == survivor.window_id`; `survivor.window_id in remaining_window_ids`; `all((window.window_id not in remaining_window_ids for window in other_windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_window_kill_all_except_logging_2b0be2bf8e | 3.2a through 3.7b | planned parity | -| tests/test_logging.py::test_window_rename_info_logging | Test that window.rename_window() produces INFO record with str-typed extra; fixtures `session`, `caplog`; execute `window.rename_window('log_renamed')`; `t.cast(t.Any, records[0])`; assert `window is not None`; `len(records) >= 1; message='expected INFO record for window rename'`; `isinstance(rec.tmux_subcommand, str)`; when `val is not None`: assert `isinstance(val, str); message=f'extra key {key!r} should be str, got {type(val).__name__}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_window_rename_info_logging_3dd94c3763 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_context_only_token_scope[command_list_alias] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='command_list_alias'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_command_list_alias_977650294c | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_context_only_token_scope[command_list_name] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='command_list_name'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_command_list_name_5a0bc1f3f5 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_context_only_token_scope[command_list_usage] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='command_list_usage'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_command_list_usage_f1efc2b591 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_context_only_token_scope[current_file] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='current_file'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_current_file_665eda3081 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_context_only_token_scope[search_match] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='search_match'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_search_match_6f9972a6e4 | 3.2a through 3.7b | planned parity | +| tests/test_logging.py::test_session_kill_all_except_logging | Test that session.kill(all_except=True) identifies the surviving session; fixtures `server`, `caplog`; execute `server.new_session(session_name=f'log_survivor_{next(namer)}')`; `server.new_session(session_name=f'log_other_{next(namer)}')`; `survivor.kill(all_except=True)`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; `all((session.session_id not in remaining_session_ids for session in other_sessions))`; assert `len(records) >= 1; message='expected INFO record for all-except session kill'`; `rec.getMessage() == 'other sessions killed'`; `rec.tmux_session == survivor.session_name`; `rec.tmux_target == survivor.session_id`; `survivor.session_id in remaining_session_ids`; `all((session.session_id not in remaining_session_ids for session in other_sessions))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_session_kill_all_except_logging_2a4929ce65 | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_tmux_cmd_debug_logging_schema | Test that tmux_cmd produces structured log records per AGENTS.md; fixtures `server`, `caplog`; execute `server.cmd('list-sessions')`; `t.cast(t.Any, records[0])`; assert `len(records) >= 1`; `isinstance(record.tmux_cmd, str)`; `isinstance(record.tmux_exit_code, int)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_tmux_cmd_debug_logging_schema_ee20f7841a | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_window_kill_all_except_logging | Test that window.kill(all_except=True) identifies the surviving window; fixtures `session`, `caplog`; execute `session.new_window(window_name=f'log_survivor_{next(namer)}')`; `session.new_window(window_name=f'log_other_{next(namer)}')`; `survivor.kill(all_except=True)`; `t.cast(t.Any, records[0])`; `rec.getMessage()`; `all((window.window_id not in remaining_window_ids for window in other_windows))`; assert `len(records) >= 1; message='expected INFO record for all-except window kill'`; `rec.getMessage() == 'other windows killed'`; `rec.tmux_window == survivor.window_name`; `rec.tmux_target == survivor.window_id`; `survivor.window_id in remaining_window_ids`; `all((window.window_id not in remaining_window_ids for window in other_windows))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_window_kill_all_except_logging_2b0be2bf8e | 3.3a through 3.7c | planned parity | +| tests/test_logging.py::test_window_rename_info_logging | Test that window.rename_window() produces INFO record with str-typed extra; fixtures `session`, `caplog`; execute `window.rename_window('log_renamed')`; `t.cast(t.Any, records[0])`; assert `window is not None`; `len(records) >= 1; message='expected INFO record for window rename'`; `isinstance(rec.tmux_subcommand, str)`; when `val is not None`: assert `isinstance(val, str); message=f'extra key {key!r} should be str, got {type(val).__name__}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_logging_py_test_window_rename_info_logging_3dd94c3763 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_context_only_token_scope[command_list_alias] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='command_list_alias'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_command_list_alias_977650294c | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_context_only_token_scope[command_list_name] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='command_list_name'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_command_list_name_5a0bc1f3f5 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_context_only_token_scope[command_list_usage] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='command_list_usage'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_command_list_usage_f1efc2b591 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_context_only_token_scope[current_file] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='current_file'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_current_file_665eda3081 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_context_only_token_scope[search_match] | Tokens registered outside ``format.c`` route to the ``context`` scope; case inputs `token='search_match'`; execute `_token_scope(token)`; assert `_token_scope(token) == 'context'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_context_only_token_scope_search_match_6f9972a6e4 | 3.3a through 3.7c | planned parity | | tests/test_neo.py::test_context_scope_excluded_from_every_list_cmd[list-clients] | ``"context"`` is excluded from every ``SCOPES_BY_LIST_CMD`` entry; case inputs `list_cmd='list-clients'`; assert `'context' not in SCOPES_BY_LIST_CMD[list_cmd]`. | unit test | PythonBehaviorParityTest#test_neo_py_test_context_scope_excluded_from_every_list_cmd_list_clients_6b8176815c | not tmux-bound | planned parity | | tests/test_neo.py::test_context_scope_excluded_from_every_list_cmd[list-panes] | ``"context"`` is excluded from every ``SCOPES_BY_LIST_CMD`` entry; case inputs `list_cmd='list-panes'`; assert `'context' not in SCOPES_BY_LIST_CMD[list_cmd]`. | unit test | PythonBehaviorParityTest#s_test_neo_py_test_context_scope_excluded_from_every_list_cmd_list_panes_9c7b349b83 | not tmux-bound | planned parity | | tests/test_neo.py::test_context_scope_excluded_from_every_list_cmd[list-sessions] | ``"context"`` is excluded from every ``SCOPES_BY_LIST_CMD`` entry; case inputs `list_cmd='list-sessions'`; assert `'context' not in SCOPES_BY_LIST_CMD[list_cmd]`. | unit test | PythonBehaviorParityTest#est_neo_py_test_context_scope_excluded_from_every_list_cmd_list_sessions_fdad81f033 | not tmux-bound | planned parity | @@ -871,19 +871,19 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_neo.py::test_context_tokens_absent_from_every_list_cmd_template[command_list_usage] | Context-only tokens never appear in any ``list-*`` ``-F`` template; case inputs `token='command_list_usage'`; execute `get_output_format(list_cmd, '3.6a')`; assert `token not in fields; message=f'{token!r} (context-only) leaked into {list_cmd} template'`. | unit test | PythonBehaviorParityTest#st_context_tokens_absent_from_every_list_cmd_template_command_list_usage_103322c2d8 | not tmux-bound | planned parity | | tests/test_neo.py::test_context_tokens_absent_from_every_list_cmd_template[current_file] | Context-only tokens never appear in any ``list-*`` ``-F`` template; case inputs `token='current_file'`; execute `get_output_format(list_cmd, '3.6a')`; assert `token not in fields; message=f'{token!r} (context-only) leaked into {list_cmd} template'`. | unit test | PythonBehaviorParityTest#_py_test_context_tokens_absent_from_every_list_cmd_template_current_file_c13dc9f27f | not tmux-bound | planned parity | | tests/test_neo.py::test_context_tokens_absent_from_every_list_cmd_template[search_match] | Context-only tokens never appear in any ``list-*`` ``-F`` template; case inputs `token='search_match'`; execute `get_output_format(list_cmd, '3.6a')`; assert `token not in fields; message=f'{token!r} (context-only) leaked into {list_cmd} template'`. | unit test | PythonBehaviorParityTest#_py_test_context_tokens_absent_from_every_list_cmd_template_search_match_0c631dffc4 | not tmux-bound | planned parity | -| tests/test_neo.py::test_every_obj_field_classifies_to_known_scope | Every declared ``Obj`` field must classify to a known scope; assert `not unclassified; message=f'Obj fields with no scope classification (add them to _SCOPE_OVERRIDES, _SCOPE_PREFIXES, _UNIVERSAL_TOKENS, or _CONTEXT_ONLY_TOKENS): {unclassified}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_every_obj_field_classifies_to_known_scope_c39ac6288c | 3.2a through 3.7b | planned parity | +| tests/test_neo.py::test_every_obj_field_classifies_to_known_scope | Every declared ``Obj`` field must classify to a known scope; assert `not unclassified; message=f'Obj fields with no scope classification (add them to _SCOPE_OVERRIDES, _SCOPE_PREFIXES, _UNIVERSAL_TOKENS, or _CONTEXT_ONLY_TOKENS): {unclassified}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_every_obj_field_classifies_to_known_scope_c39ac6288c | 3.3a through 3.7c | planned parity | | tests/test_neo.py::test_field_version_keys_are_obj_fields | Every gated field name must exist on :class:`libtmux.neo.Obj`; assert `token in obj_fields; message=f'{token!r} in FIELD_VERSION but not declared on Obj'`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_field_version_keys_are_obj_fields_6ffa97bc75 | not tmux-bound | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[bracket_paste_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='bracket_paste_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_bracket_paste_flag_227b867afb | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_flags] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_flags'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_flags_bda1b12178 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_floating_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_floating_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_floating_flag_a2d37a85c3 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_pb_progress] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_pb_progress'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_pb_progress_06eabfa637 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_pb_state] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_pb_state'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_pb_state_42a8aa832a | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_pipe_pid] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_pipe_pid'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_pipe_pid_33affb61ec | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_x] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_x'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_x_ecadb0feed | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_y] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_y'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_y_dbe34d50e5 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_z] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_z'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_z_433a952e9a | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[pane_zoomed_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_zoomed_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_zoomed_flag_a4cefd32be | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_format_token_gated_to_3_7[synchronized_output_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='synchronized_output_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_neo_py_test_format_token_gated_to_3_7_synchronized_output_flag_aa6751d603 | 3.2a through 3.7b | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[bracket_paste_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='bracket_paste_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_bracket_paste_flag_227b867afb | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_flags] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_flags'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_flags_bda1b12178 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_floating_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_floating_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_floating_flag_a2d37a85c3 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_pb_progress] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_pb_progress'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_pb_progress_06eabfa637 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_pb_state] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_pb_state'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_pb_state_42a8aa832a | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_pipe_pid] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_pipe_pid'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_pipe_pid_33affb61ec | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_x] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_x'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_x_ecadb0feed | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_y] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_y'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_y_dbe34d50e5 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_z] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_z'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_z_433a952e9a | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[pane_zoomed_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='pane_zoomed_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_format_token_gated_to_3_7_pane_zoomed_flag_a4cefd32be | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_format_token_gated_to_3_7[synchronized_output_flag] | Tmux 3.7 pane format tokens must not leak into older -F templates; case inputs `token='synchronized_output_flag'`; execute `get_output_format('list-panes', '3.6')`; `get_output_format('list-panes', '3.7')`; assert `FIELD_VERSION[token] == '3.7'`; `token not in fields_old`; `token in fields_new`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_neo_py_test_format_token_gated_to_3_7_synchronized_output_flag_aa6751d603 | 3.3a through 3.7c | planned parity | | tests/test_neo.py::test_is_target_not_found_error[daemon-not-running] | Only an unknown ``-t`` target means the object is gone; case inputs `expected=False`, `stderr_text='no server running on /tmp/tmux-1000/default'`, `test_id='daemon-not-running'`; execute `_is_target_not_found_error(stderr_text)`; assert `_is_target_not_found_error(stderr_text) is expected`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_is_target_not_found_error_daemon_not_running_8b7c1f4171 | not tmux-bound | planned parity | | tests/test_neo.py::test_is_target_not_found_error[missing-pane] | Only an unknown ``-t`` target means the object is gone; case inputs `expected=True`, `stderr_text="can't find pane: %99"`, `test_id='missing-pane'`; execute `_is_target_not_found_error(stderr_text)`; assert `_is_target_not_found_error(stderr_text) is expected`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_is_target_not_found_error_missing_pane_522c8bebd4 | not tmux-bound | planned parity | | tests/test_neo.py::test_is_target_not_found_error[missing-session] | Only an unknown ``-t`` target means the object is gone; case inputs `expected=True`, `stderr_text="can't find session: $99"`, `test_id='missing-session'`; execute `_is_target_not_found_error(stderr_text)`; assert `_is_target_not_found_error(stderr_text) is expected`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_is_target_not_found_error_missing_session_302b38ca8f | not tmux-bound | planned parity | @@ -892,134 +892,134 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_neo.py::test_is_target_not_found_error[socket-missing] | Only an unknown ``-t`` target means the object is gone; case inputs `expected=False`, `stderr_text='error connecting to /tmp/tmux-1000/nope (No such file or directory)'`, `test_id='socket-missing'`; execute `_is_target_not_found_error(stderr_text)`; assert `_is_target_not_found_error(stderr_text) is expected`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_is_target_not_found_error_socket_missing_29b84c5f80 | not tmux-bound | planned parity | | tests/test_neo.py::test_pane_dead_signal_gated_to_3_3 | ``pane_dead_signal`` first registered in tmux 3.3; execute `get_output_format('list-panes', '3.2a')`; `get_output_format('list-panes', '3.3')`; assert `FIELD_VERSION['pane_dead_signal'] == '3.3'`; `'pane_dead_signal' not in fields_old`; `'pane_dead_signal' in fields_new`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_pane_dead_signal_gated_to_3_3_b9712c9fa1 | not tmux-bound | planned parity | | tests/test_neo.py::test_pane_dead_time_gated_to_3_3 | ``pane_dead_time`` first registered in tmux 3.3; execute `get_output_format('list-panes', '3.2a')`; `get_output_format('list-panes', '3.3')`; assert `FIELD_VERSION['pane_dead_time'] == '3.3'`; `'pane_dead_time' not in fields_old`; `'pane_dead_time' in fields_new`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_pane_dead_time_gated_to_3_3_a6b2c34832 | not tmux-bound | planned parity | -| tests/test_neo.py::test_scopes_by_list_cmd_downward_cascade | Every ``list-*`` admits universal+session+window+pane scopes; assert `{'universal', 'session', 'window', 'pane'} <= scopes; message=f'{cmd} should admit the downward-cascade core scopes'`; `'client' in SCOPES_BY_LIST_CMD['list-clients']`; `'client' not in SCOPES_BY_LIST_CMD['list-sessions']`; `'client' not in SCOPES_BY_LIST_CMD['list-windows']`; `'client' not in SCOPES_BY_LIST_CMD['list-panes']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_scopes_by_list_cmd_downward_cascade_2241a7dbf4 | 3.2a through 3.7b | planned parity | +| tests/test_neo.py::test_scopes_by_list_cmd_downward_cascade | Every ``list-*`` admits universal+session+window+pane scopes; assert `{'universal', 'session', 'window', 'pane'} <= scopes; message=f'{cmd} should admit the downward-cascade core scopes'`; `'client' in SCOPES_BY_LIST_CMD['list-clients']`; `'client' not in SCOPES_BY_LIST_CMD['list-sessions']`; `'client' not in SCOPES_BY_LIST_CMD['list-windows']`; `'client' not in SCOPES_BY_LIST_CMD['list-panes']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_neo_py_test_scopes_by_list_cmd_downward_cascade_2241a7dbf4 | 3.3a through 3.7c | planned parity | | tests/test_neo.py::test_token_scope_unknown_for_unclassified_field | ``_token_scope`` returns ``"unknown"`` for any unrecognized field; execute `_token_scope('libtmux_test_nonexistent_token')`; assert `_token_scope('libtmux_test_nonexistent_token') == 'unknown'`; `'unknown' not in allowed`. | unit test | PythonBehaviorParityTest#tests_test_neo_py_test_token_scope_unknown_for_unclassified_field_65925616d7 | not tmux-bound | planned parity | -| tests/test_neo.py::test_unprefixed_3_7_tokens_are_pane_scope[bracket_paste_flag] | The 3.7 pane tokens without a ``pane_`` prefix need scope overrides; case inputs `token='bracket_paste_flag'`; execute `_token_scope(token)`; `get_output_format('list-panes', '3.7')`; assert `_token_scope(token) == 'pane'`; `token in fields`. | real-tmux integration test | PythonBehaviorParityTest#test_neo_py_test_unprefixed_3_7_tokens_are_pane_scope_bracket_paste_flag_9f9e5c0997 | 3.2a through 3.7b | planned parity | -| tests/test_neo.py::test_unprefixed_3_7_tokens_are_pane_scope[synchronized_output_flag] | The 3.7 pane tokens without a ``pane_`` prefix need scope overrides; case inputs `token='synchronized_output_flag'`; execute `_token_scope(token)`; `get_output_format('list-panes', '3.7')`; assert `_token_scope(token) == 'pane'`; `token in fields`. | real-tmux integration test | PythonBehaviorParityTest#eo_py_test_unprefixed_3_7_tokens_are_pane_scope_synchronized_output_flag_3c87f2a083 | 3.2a through 3.7b | planned parity | +| tests/test_neo.py::test_unprefixed_3_7_tokens_are_pane_scope[bracket_paste_flag] | The 3.7 pane tokens without a ``pane_`` prefix need scope overrides; case inputs `token='bracket_paste_flag'`; execute `_token_scope(token)`; `get_output_format('list-panes', '3.7')`; assert `_token_scope(token) == 'pane'`; `token in fields`. | real-tmux integration test | PythonBehaviorParityTest#test_neo_py_test_unprefixed_3_7_tokens_are_pane_scope_bracket_paste_flag_9f9e5c0997 | 3.3a through 3.7c | planned parity | +| tests/test_neo.py::test_unprefixed_3_7_tokens_are_pane_scope[synchronized_output_flag] | The 3.7 pane tokens without a ``pane_`` prefix need scope overrides; case inputs `token='synchronized_output_flag'`; execute `_token_scope(token)`; `get_output_format('list-panes', '3.7')`; assert `_token_scope(token) == 'pane'`; `token in fields`. | real-tmux integration test | PythonBehaviorParityTest#eo_py_test_unprefixed_3_7_tokens_are_pane_scope_synchronized_output_flag_3c87f2a083 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_command_alias | Test tmux's command-alias option destructuring; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `any(('command-alias' in k for k in options_))`; `Options(**options_)`; assert `any(('command-alias' in k for k in options_))`; `options`; `options.command_alias`; `isinstance(options_['command-alias'], dict)`; `not isinstance(options_['command-alias'], SparseArray)`; `isinstance(options_['command-alias']['split-pane'], str)`; `options_['command-alias']['split-pane'] == 'split-window'`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_command_alias_950b7b6c08 | not tmux-bound | planned parity | -| tests/test_options.py::test_complex_option_values | Test complex option values and edge cases; fixtures `server`; execute `server.new_session(session_name='test')`; `session.set_option('@complex-option', 'value with spaces')`; `session.show_option('@complex-option')`; `session.set_option('@escaped-option', 'line1\\nline2')`; `session.show_option('@escaped-option')`; `session.set_option('@empty-option', '')`; assert `session.show_option('@complex-option') == 'value with spaces'`; `session.show_option('@escaped-option') == 'line1\\nline2'`; `session.show_option('@empty-option') == ''`; when `has_gte_version('3.0')`: execute `session.show_option('status-style', include_inherited=True)`; `session.set_option('status-style', 'fg=red')`; `session.show_option('status-style')`; `session.unset_option('status-style')`; assert `session.show_option('status-style') == 'fg=red'`; `session.show_option('status-style', include_inherited=True) == 'fg=red'`; `session.show_option('status-style', include_inherited=True) == inherited_val`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_complex_option_values_7c482e4804 | 3.2a through 3.7b; conditional boundaries: 3.0 | planned parity | +| tests/test_options.py::test_complex_option_values | Test complex option values and edge cases; fixtures `server`; execute `server.new_session(session_name='test')`; `session.set_option('@complex-option', 'value with spaces')`; `session.show_option('@complex-option')`; `session.set_option('@escaped-option', 'line1\\nline2')`; `session.show_option('@escaped-option')`; `session.set_option('@empty-option', '')`; assert `session.show_option('@complex-option') == 'value with spaces'`; `session.show_option('@escaped-option') == 'line1\\nline2'`; `session.show_option('@empty-option') == ''`; when `has_gte_version('3.0')`: execute `session.show_option('status-style', include_inherited=True)`; `session.set_option('status-style', 'fg=red')`; `session.show_option('status-style')`; `session.unset_option('status-style')`; assert `session.show_option('status-style') == 'fg=red'`; `session.show_option('status-style', include_inherited=True) == 'fg=red'`; `session.show_option('status-style', include_inherited=True) == inherited_val`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_complex_option_values_7c482e4804 | 3.3a through 3.7c; conditional boundaries: 3.0 | planned parity | | tests/test_options.py::test_convert_values_preserves_sparse_keys | Test convert_values preserves sparse array indices; execute `SparseArray()`; `convert_values(sparse)`; assert `isinstance(result, SparseArray)`; `sorted(result.keys()) == [0, 5, 99]`; `result[0] is True`; `result[5] is False`; `result[99] == 100`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_convert_values_preserves_sparse_keys_b5de8b093c | not tmux-bound | planned parity | | tests/test_options.py::test_convert_values_sparse_array[boolean_on_off] | Test convert_values handles SparseArray via dict branch; case inputs `test_case=ConvertValuesSparseTestCase(test_id='boolean_on_off', initial_values={0: 'on', 1: 'off'}, expected_converted={0: True, 1: False})`; execute `SparseArray()`; `convert_values(sparse)`; assert `isinstance(result, SparseArray)`; `result[idx] == expected; message=f'Index {idx}: {result[idx]} != {expected}'`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_convert_values_sparse_array_boolean_on_off_056ccd9607 | not tmux-bound | planned parity | | tests/test_options.py::test_convert_values_sparse_array[mixed_values] | Test convert_values handles SparseArray via dict branch; case inputs `test_case=ConvertValuesSparseTestCase(test_id='mixed_values', initial_values={0: 'on', 1: '50', 2: 'text'}, expected_converted={0: True, 1: 50, 2: 'text'})`; execute `SparseArray()`; `convert_values(sparse)`; assert `isinstance(result, SparseArray)`; `result[idx] == expected; message=f'Index {idx}: {result[idx]} != {expected}'`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_convert_values_sparse_array_mixed_values_5c77be6d70 | not tmux-bound | planned parity | | tests/test_options.py::test_convert_values_sparse_array[numeric_conversion] | Test convert_values handles SparseArray via dict branch; case inputs `test_case=ConvertValuesSparseTestCase(test_id='numeric_conversion', initial_values={0: '50', 5: '100'}, expected_converted={0: 50, 5: 100})`; execute `SparseArray()`; `convert_values(sparse)`; assert `isinstance(result, SparseArray)`; `result[idx] == expected; message=f'Index {idx}: {result[idx]} != {expected}'`. | unit test | PythonBehaviorParityTest#ests_test_options_py_test_convert_values_sparse_array_numeric_conversion_a99850dc83 | not tmux-bound | planned parity | | tests/test_options.py::test_convert_values_sparse_array[sparse_indices] | Test convert_values handles SparseArray via dict branch; case inputs `test_case=ConvertValuesSparseTestCase(test_id='sparse_indices', initial_values={0: 'on', 10: 'off', 100: '42'}, expected_converted={0: True, 10: False, 100: 42})`; execute `SparseArray()`; `convert_values(sparse)`; assert `isinstance(result, SparseArray)`; `result[idx] == expected; message=f'Index {idx}: {result[idx]} != {expected}'`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_convert_values_sparse_array_sparse_indices_9dc8b27008 | not tmux-bound | planned parity | -| tests/test_options.py::test_custom_options | Test tmux's user (custom) options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.set_option('@custom-option', 'test')`; `session._show_option('@custom-option')`; assert `session._show_option('@custom-option') == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_custom_options_98004aaa03 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_custom_options | Test tmux's user (custom) options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.set_option('@custom-option', 'test')`; `session._show_option('@custom-option')`; assert `session._show_option('@custom-option') == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_custom_options_98004aaa03 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_explode_arrays_preserves_inherited_marker[inherited_array_marker_preserved] | Test that explode_arrays preserves the inherited marker (*) for array options; case inputs `test_case=ExplodeArraysInheritedCase(test_id='inherited_array_marker_preserved', raw_input={'status-format[0]*': 'fmt0', 'status-format[1]*': 'fmt1'}, expected_key='status-format*', expected_indices={0: 'fmt0', 1: 'fmt1'})`; execute `explode_arrays(test_case.raw_input)`; assert `test_case.expected_key in result; message=f"Expected key '{test_case.expected_key}' not found in result. Got keys: {list(result.keys())}"`; `isinstance(array_value, SparseArray); message=f'Expected SparseArray for array option, got {type(array_value).__name__}'`; `idx in array_value; message=f'Expected index {idx} not found in array. Got indices: {list(array_value.keys())}'`; `array_value[idx] == expected_val; message=f"Value at index {idx}: expected '{expected_val}', got '{array_value[idx]}'"`. | unit test | PythonBehaviorParityTest#plode_arrays_preserves_inherited_marker_inherited_array_marker_preserved_619b7cbc84 | not tmux-bound | planned parity | | tests/test_options.py::test_explode_arrays_preserves_inherited_marker[mixed_inherited_indices] | Test that explode_arrays preserves the inherited marker (*) for array options; case inputs `test_case=ExplodeArraysInheritedCase(test_id='mixed_inherited_indices', raw_input={'opt[0]*': 'v0', 'opt[5]*': 'v5', 'opt[10]*': 'v10'}, expected_key='opt*', expected_indices={0: 'v0', 5: 'v5', 10: 'v10'})`; execute `explode_arrays(test_case.raw_input)`; assert `test_case.expected_key in result; message=f"Expected key '{test_case.expected_key}' not found in result. Got keys: {list(result.keys())}"`; `isinstance(array_value, SparseArray); message=f'Expected SparseArray for array option, got {type(array_value).__name__}'`; `idx in array_value; message=f'Expected index {idx} not found in array. Got indices: {list(array_value.keys())}'`; `array_value[idx] == expected_val; message=f"Value at index {idx}: expected '{expected_val}', got '{array_value[idx]}'"`. | unit test | PythonBehaviorParityTest#y_test_explode_arrays_preserves_inherited_marker_mixed_inherited_indices_0515665813 | not tmux-bound | planned parity | | tests/test_options.py::test_explode_arrays_preserves_inherited_marker[non_inherited_array_no_marker] | Test that explode_arrays preserves the inherited marker (*) for array options; case inputs `test_case=ExplodeArraysInheritedCase(test_id='non_inherited_array_no_marker', raw_input={'status-format[0]': 'fmt0', 'status-format[1]': 'fmt1'}, expected_key='status-format', expected_indices={0: 'fmt0', 1: 'fmt1'})`; execute `explode_arrays(test_case.raw_input)`; assert `test_case.expected_key in result; message=f"Expected key '{test_case.expected_key}' not found in result. Got keys: {list(result.keys())}"`; `isinstance(array_value, SparseArray); message=f'Expected SparseArray for array option, got {type(array_value).__name__}'`; `idx in array_value; message=f'Expected index {idx} not found in array. Got indices: {list(array_value.keys())}'`; `array_value[idx] == expected_val; message=f"Value at index {idx}: expected '{expected_val}', got '{array_value[idx]}'"`. | unit test | PythonBehaviorParityTest#_explode_arrays_preserves_inherited_marker_non_inherited_array_no_marker_5d4bf6d8c0 | not tmux-bound | planned parity | -| tests/test_options.py::test_high_level_api_expectations | Ensure options and hooks behave as expected; fixtures `server`; the collected body has no executable statement beyond its docstring, so invocation returns normally without tmux calls or state changes. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_high_level_api_expectations_09a250503a | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_high_level_api_expectations | Ensure options and hooks behave as expected; fixtures `server`; the collected body has no executable statement beyond its docstring, so invocation returns normally without tmux calls or state changes. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_high_level_api_expectations_09a250503a | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_mocked_cmd_stdoutclass_fixture[command-alias] | Parametrized test grid for options; case inputs `dataclass_attribute='command_alias'`, `expected={'split-pane': 'split-window', 'splitp': 'split-window', 'server-info': 'show-messages -JT', 'info': 'show-messages -JT', 'choose-window': 'choose-tree -w', 'choose-session': 'choose-tree -s'}`, `mocked_cmd_stdout=['command-alias[0] split-pane=split-window', 'command-alias[1] splitp=split-window', 'command-alias[2] "server-info=show-messages -JT"', 'command-alias[3] "info=show-messages -JT"', 'command-alias[4] "choose-window=choose-tree -w"', 'command-alias[5] "choose-session=choose-tree -s"']`, `test_id='command-alias'`, `tmux_option='command-alias'`; fixtures `monkeypatch`, `server`; execute `server._show_options()`; `fake_cmd(stdout=mocked_cmd_stdout)`; `any((tmux_option in k for k in options_))`; `Options(**options_)`; assert `any((tmux_option in k for k in options_))`; `options`; `hasattr(options, dataclass_attribute)`; `getattr(options, dataclass_attribute, None) == expected`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_mocked_cmd_stdoutclass_fixture_command_alias_8d2f0c459e | not tmux-bound | planned parity | | tests/test_options.py::test_mocked_cmd_stdoutclass_fixture[terminal-features] | Parametrized test grid for options; case inputs `dataclass_attribute='terminal_features'`, `expected={'screen*': ['title'], 'xterm*': ['clipboard', 'ccolour', 'cstyle', 'focus']}`, `mocked_cmd_stdout=['terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus', 'terminal-features[1] screen*:title']`, `test_id='terminal-features'`, `tmux_option='terminal-features'`; fixtures `monkeypatch`, `server`; execute `server._show_options()`; `fake_cmd(stdout=mocked_cmd_stdout)`; `any((tmux_option in k for k in options_))`; `Options(**options_)`; assert `any((tmux_option in k for k in options_))`; `options`; `hasattr(options, dataclass_attribute)`; `getattr(options, dataclass_attribute, None) == expected`. | unit test | PythonBehaviorParityTest#ts_test_options_py_test_mocked_cmd_stdoutclass_fixture_terminal_features_4ef3a61813 | not tmux-bound | planned parity | -| tests/test_options.py::test_option_error_handling | Test error handling for options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.show_option('non-existent-option')`; `session.set_option('aggressive-resize', 'invalid')`; `any((msg in error_msg for msg in ['unknown option', 'invalid option']))`; expect `pytest.raises(exc.OptionError)`; assert `any((msg in error_msg for msg in ['unknown option', 'invalid option']))`; when `has_gte_version('2.4')`: execute `session.show_option('window-')`; expect `pytest.raises(exc.OptionError)`; assert `'ambiguous option' in str(exc_info.value).lower()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_error_handling_ffb224428f | 3.2a through 3.7b; conditional boundaries: 2.4 | planned parity | +| tests/test_options.py::test_option_error_handling | Test error handling for options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.show_option('non-existent-option')`; `session.set_option('aggressive-resize', 'invalid')`; `any((msg in error_msg for msg in ['unknown option', 'invalid option']))`; expect `pytest.raises(exc.OptionError)`; assert `any((msg in error_msg for msg in ['unknown option', 'invalid option']))`; when `has_gte_version('2.4')`: execute `session.show_option('window-')`; expect `pytest.raises(exc.OptionError)`; assert `'ambiguous option' in str(exc_info.value).lower()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_error_handling_ffb224428f | 3.3a through 3.7c; conditional boundaries: 2.4 | planned parity | | tests/test_options.py::test_option_set_show_cycle[pane_allow_passthrough] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_allow_passthrough', option='allow-passthrough', scope=<OptionScope.Pane: 'PANE'>, test_value='all', expected_type=<class 'str'>, min_version='3.4', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_allow_passthrough_12a06d295a | 3.4 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[pane_allow_rename] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_allow_rename', option='allow-rename', scope=<OptionScope.Pane: 'PANE'>, test_value='off', expected_type=<class 'bool'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_allow_rename_3e104b71cb | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[pane_alternate_screen] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_alternate_screen', option='alternate-screen', scope=<OptionScope.Pane: 'PANE'>, test_value='on', expected_type=<class 'bool'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_alternate_screen_664fc6e203 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[pane_allow_rename] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_allow_rename', option='allow-rename', scope=<OptionScope.Pane: 'PANE'>, test_value='off', expected_type=<class 'bool'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_allow_rename_3e104b71cb | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[pane_alternate_screen] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_alternate_screen', option='alternate-screen', scope=<OptionScope.Pane: 'PANE'>, test_value='on', expected_type=<class 'bool'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_alternate_screen_664fc6e203 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[pane_pane_active_border_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_pane_active_border_style', option='pane-active-border-style', scope=<OptionScope.Pane: 'PANE'>, test_value='fg=green', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#test_options_py_test_option_set_show_cycle_pane_pane_active_border_style_37f4c6d2f6 | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[pane_pane_border_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_pane_border_style', option='pane-border-style', scope=<OptionScope.Pane: 'PANE'>, test_value='fg=red', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_pane_border_style_76e9dfba40 | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[pane_remain_on_exit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_remain_on_exit', option='remain-on-exit', scope=<OptionScope.Pane: 'PANE'>, test_value='failed', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_remain_on_exit_e37d2f7371 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[pane_remain_on_exit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_remain_on_exit', option='remain-on-exit', scope=<OptionScope.Pane: 'PANE'>, test_value='failed', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_remain_on_exit_e37d2f7371 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[pane_remain_on_exit_key] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_remain_on_exit_key', option='remain-on-exit', scope=<OptionScope.Pane: 'PANE'>, test_value='key', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_remain_on_exit_key_558aee681d | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[pane_scroll_on_clear] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_scroll_on_clear', option='scroll-on-clear', scope=<OptionScope.Pane: 'PANE'>, test_value='on', expected_type=<class 'bool'>, min_version='3.3', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_scroll_on_clear_79b6bc2569 | 3.3 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[pane_synchronize_panes] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_synchronize_panes', option='synchronize-panes', scope=<OptionScope.Pane: 'PANE'>, test_value='off', expected_type=<class 'bool'>, min_version='3.1', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_synchronize_panes_be414060d5 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[pane_synchronize_panes] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_synchronize_panes', option='synchronize-panes', scope=<OptionScope.Pane: 'PANE'>, test_value='off', expected_type=<class 'bool'>, min_version='3.1', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_synchronize_panes_be414060d5 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[pane_tree_mode_preview_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_tree_mode_preview_format', option='tree-mode-preview-format', scope=<OptionScope.Pane: 'PANE'>, test_value='#{pane_index}', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#test_options_py_test_option_set_show_cycle_pane_tree_mode_preview_format_f2b345143c | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[pane_window_active_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_window_active_style', option='window-active-style', scope=<OptionScope.Pane: 'PANE'>, test_value='default', expected_type=<class 'str'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_pane_window_active_style_c4141066c8 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[pane_window_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_window_style', option='window-style', scope=<OptionScope.Pane: 'PANE'>, test_value='default', expected_type=<class 'str'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_window_style_e5a06d3db8 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_buffer_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_buffer_limit', option='buffer-limit', scope=<OptionScope.Server: 'SERVER'>, test_value=100, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_buffer_limit_fde518f49f | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_default_terminal] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_default_terminal', option='default-terminal', scope=<OptionScope.Server: 'SERVER'>, test_value='screen-256color', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_default_terminal_c68280f866 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_editor] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_editor', option='editor', scope=<OptionScope.Server: 'SERVER'>, test_value='vim', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_editor_486f671cd0 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_escape_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_escape_time', option='escape-time', scope=<OptionScope.Server: 'SERVER'>, test_value=50, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_escape_time_2242ffb026 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_exit_empty] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_exit_empty', option='exit-empty', scope=<OptionScope.Server: 'SERVER'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_exit_empty_1e6a27b2f8 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_exit_unattached] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_exit_unattached', option='exit-unattached', scope=<OptionScope.Server: 'SERVER'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_exit_unattached_8f0b340992 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_extended_keys] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_extended_keys', option='extended-keys', scope=<OptionScope.Server: 'SERVER'>, test_value='always', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_extended_keys_c42e8b82ab | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_focus_events] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_focus_events', option='focus-events', scope=<OptionScope.Server: 'SERVER'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_focus_events_191244c0ad | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[pane_window_active_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_window_active_style', option='window-active-style', scope=<OptionScope.Pane: 'PANE'>, test_value='default', expected_type=<class 'str'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_pane_window_active_style_c4141066c8 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[pane_window_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='pane_window_style', option='window-style', scope=<OptionScope.Pane: 'PANE'>, test_value='default', expected_type=<class 'str'>, min_version='3.0', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_pane_window_style_e5a06d3db8 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_buffer_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_buffer_limit', option='buffer-limit', scope=<OptionScope.Server: 'SERVER'>, test_value=100, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_buffer_limit_fde518f49f | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_default_terminal] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_default_terminal', option='default-terminal', scope=<OptionScope.Server: 'SERVER'>, test_value='screen-256color', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_default_terminal_c68280f866 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_editor] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_editor', option='editor', scope=<OptionScope.Server: 'SERVER'>, test_value='vim', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_editor_486f671cd0 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_escape_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_escape_time', option='escape-time', scope=<OptionScope.Server: 'SERVER'>, test_value=50, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_escape_time_2242ffb026 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_exit_empty] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_exit_empty', option='exit-empty', scope=<OptionScope.Server: 'SERVER'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_exit_empty_1e6a27b2f8 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_exit_unattached] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_exit_unattached', option='exit-unattached', scope=<OptionScope.Server: 'SERVER'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_exit_unattached_8f0b340992 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_extended_keys] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_extended_keys', option='extended-keys', scope=<OptionScope.Server: 'SERVER'>, test_value='always', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_extended_keys_c42e8b82ab | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_focus_events] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_focus_events', option='focus-events', scope=<OptionScope.Server: 'SERVER'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_focus_events_191244c0ad | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[server_get_clipboard] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_get_clipboard', option='get-clipboard', scope=<OptionScope.Server: 'SERVER'>, test_value='buffer', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_get_clipboard_e757f39410 | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_message_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_message_limit', option='message-limit', scope=<OptionScope.Server: 'SERVER'>, test_value=500, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_message_limit_36843a9ec1 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_message_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_message_limit', option='message-limit', scope=<OptionScope.Server: 'SERVER'>, test_value=500, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_message_limit_36843a9ec1 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[server_prompt_history_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_prompt_history_limit', option='prompt-history-limit', scope=<OptionScope.Server: 'SERVER'>, test_value=50, expected_type=<class 'int'>, min_version='3.3', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_option_set_show_cycle_server_prompt_history_limit_a55725a367 | 3.3 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[server_set_clipboard] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_set_clipboard', option='set-clipboard', scope=<OptionScope.Server: 'SERVER'>, test_value='external', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_set_clipboard_263616c43a | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_activity_action] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_activity_action', option='activity-action', scope=<OptionScope.Session: 'SESSION'>, test_value='any', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_activity_action_6ec8a6e029 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_base_index] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_base_index', option='base-index', scope=<OptionScope.Session: 'SESSION'>, test_value=1, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_base_index_867d8579bf | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_bell_action] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_bell_action', option='bell-action', scope=<OptionScope.Session: 'SESSION'>, test_value='any', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_bell_action_8cbfcb6638 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_default_command] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_default_command', option='default-command', scope=<OptionScope.Session: 'SESSION'>, test_value='', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_default_command_8b2a0d1b6a | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_destroy_unattached] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_destroy_unattached', option='destroy-unattached', scope=<OptionScope.Session: 'SESSION'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_destroy_unattached_16858a4b4d | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_detach_on_destroy] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_detach_on_destroy', option='detach-on-destroy', scope=<OptionScope.Session: 'SESSION'>, test_value='no-detached', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_session_detach_on_destroy_6023bb9baa | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_display_panes_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_display_panes_time', option='display-panes-time', scope=<OptionScope.Session: 'SESSION'>, test_value=2000, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_display_panes_time_9e7dab4a62 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_display_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_display_time', option='display-time', scope=<OptionScope.Session: 'SESSION'>, test_value=1000, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_display_time_e418680538 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[server_set_clipboard] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='server_set_clipboard', option='set-clipboard', scope=<OptionScope.Server: 'SERVER'>, test_value='external', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_server_set_clipboard_263616c43a | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_activity_action] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_activity_action', option='activity-action', scope=<OptionScope.Session: 'SESSION'>, test_value='any', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_activity_action_6ec8a6e029 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_base_index] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_base_index', option='base-index', scope=<OptionScope.Session: 'SESSION'>, test_value=1, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_base_index_867d8579bf | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_bell_action] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_bell_action', option='bell-action', scope=<OptionScope.Session: 'SESSION'>, test_value='any', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_bell_action_8cbfcb6638 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_default_command] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_default_command', option='default-command', scope=<OptionScope.Session: 'SESSION'>, test_value='', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_default_command_8b2a0d1b6a | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_destroy_unattached] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_destroy_unattached', option='destroy-unattached', scope=<OptionScope.Session: 'SESSION'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_destroy_unattached_16858a4b4d | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_detach_on_destroy] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_detach_on_destroy', option='detach-on-destroy', scope=<OptionScope.Session: 'SESSION'>, test_value='no-detached', expected_type=<class 'str'>, min_version='3.2', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_session_detach_on_destroy_6023bb9baa | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_display_panes_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_display_panes_time', option='display-panes-time', scope=<OptionScope.Session: 'SESSION'>, test_value=2000, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_display_panes_time_9e7dab4a62 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_display_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_display_time', option='display-time', scope=<OptionScope.Session: 'SESSION'>, test_value=1000, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_display_time_e418680538 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[session_focus_follows_mouse] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_focus_follows_mouse', option='focus-follows-mouse', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_option_set_show_cycle_session_focus_follows_mouse_a5adeb878d | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_history_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_history_limit', option='history-limit', scope=<OptionScope.Session: 'SESSION'>, test_value=5000, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_history_limit_47a8ea057b | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_lock_after_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_lock_after_time', option='lock-after-time', scope=<OptionScope.Session: 'SESSION'>, test_value=300, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_lock_after_time_4a657dbcc5 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_history_limit] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_history_limit', option='history-limit', scope=<OptionScope.Session: 'SESSION'>, test_value=5000, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_history_limit_47a8ea057b | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_lock_after_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_lock_after_time', option='lock-after-time', scope=<OptionScope.Session: 'SESSION'>, test_value=300, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_lock_after_time_4a657dbcc5 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[session_message_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_message_format', option='message-format', scope=<OptionScope.Session: 'SESSION'>, test_value='#[align=centre]#{message}', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_message_format_1864643569 | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_message_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_message_style', option='message-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=red', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_message_style_a256c72f06 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_mouse] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_mouse', option='mouse', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_mouse_ff6214a68c | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_message_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_message_style', option='message-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=red', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_message_style_a256c72f06 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_mouse] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_mouse', option='mouse', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_mouse_ff6214a68c | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[session_prompt_command_cursor_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_prompt_command_cursor_style', option='prompt-command-cursor-style', scope=<OptionScope.Session: 'SESSION'>, test_value='blinking-block', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ptions_py_test_option_set_show_cycle_session_prompt_command_cursor_style_31ed24c94b | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_renumber_windows] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_renumber_windows', option='renumber-windows', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_session_renumber_windows_8be38a4fdf | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_repeat_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_repeat_time', option='repeat-time', scope=<OptionScope.Session: 'SESSION'>, test_value=500, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_repeat_time_55f36ff80e | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_set_titles] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_set_titles', option='set-titles', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_set_titles_54cd8c349d | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_silence_action] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_silence_action', option='silence-action', scope=<OptionScope.Session: 'SESSION'>, test_value='none', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_silence_action_ce97da6076 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_interval] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_interval', option='status-interval', scope=<OptionScope.Session: 'SESSION'>, test_value=5, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_interval_1880b698af | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_justify] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_justify', option='status-justify', scope=<OptionScope.Session: 'SESSION'>, test_value='left', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_justify_06b13901fd | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_keys] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_keys', option='status-keys', scope=<OptionScope.Session: 'SESSION'>, test_value='vi', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_keys_1211187a15 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_left] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_left', option='status-left', scope=<OptionScope.Session: 'SESSION'>, test_value='[#S]', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_left_743bba617f | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_left_length] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_left_length', option='status-left-length', scope=<OptionScope.Session: 'SESSION'>, test_value=20, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_status_left_length_56b8b180d1 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_left_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_left_style', option='status-left-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=blue', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_session_status_left_style_cca978a727 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_position] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_position', option='status-position', scope=<OptionScope.Session: 'SESSION'>, test_value='bottom', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_position_76f3498d14 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_right] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_right', option='status-right', scope=<OptionScope.Session: 'SESSION'>, test_value='%H:%M', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_right_913acd3558 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_right_length] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_right_length', option='status-right-length', scope=<OptionScope.Session: 'SESSION'>, test_value=30, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_option_set_show_cycle_session_status_right_length_14dba990ab | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_right_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_right_style', option='status-right-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=yellow', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_status_right_style_b2f65c65b6 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_status_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_style', option='status-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=green', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_style_d25802d7b3 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_visual_activity] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_visual_activity', option='visual-activity', scope=<OptionScope.Session: 'SESSION'>, test_value='both', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_visual_activity_8f9110a162 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_visual_bell] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_visual_bell', option='visual-bell', scope=<OptionScope.Session: 'SESSION'>, test_value='both', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_visual_bell_93d992471b | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[session_visual_silence] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_visual_silence', option='visual-silence', scope=<OptionScope.Session: 'SESSION'>, test_value='both', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_visual_silence_2b3bab3d1b | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_aggressive_resize] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_aggressive_resize', option='aggressive-resize', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_window_aggressive_resize_34a4e0a35f | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_automatic_rename] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_automatic_rename', option='automatic-rename', scope=<OptionScope.Window: 'WINDOW'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_automatic_rename_9cdbdcfde2 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_clock_mode_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_clock_mode_style', option='clock-mode-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='24', expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_clock_mode_style_7f71d3b7b2 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_renumber_windows] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_renumber_windows', option='renumber-windows', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_session_renumber_windows_8be38a4fdf | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_repeat_time] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_repeat_time', option='repeat-time', scope=<OptionScope.Session: 'SESSION'>, test_value=500, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_repeat_time_55f36ff80e | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_set_titles] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_set_titles', option='set-titles', scope=<OptionScope.Session: 'SESSION'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_set_titles_54cd8c349d | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_silence_action] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_silence_action', option='silence-action', scope=<OptionScope.Session: 'SESSION'>, test_value='none', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_silence_action_ce97da6076 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_interval] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_interval', option='status-interval', scope=<OptionScope.Session: 'SESSION'>, test_value=5, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_interval_1880b698af | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_justify] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_justify', option='status-justify', scope=<OptionScope.Session: 'SESSION'>, test_value='left', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_justify_06b13901fd | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_keys] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_keys', option='status-keys', scope=<OptionScope.Session: 'SESSION'>, test_value='vi', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_keys_1211187a15 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_left] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_left', option='status-left', scope=<OptionScope.Session: 'SESSION'>, test_value='[#S]', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_left_743bba617f | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_left_length] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_left_length', option='status-left-length', scope=<OptionScope.Session: 'SESSION'>, test_value=20, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_status_left_length_56b8b180d1 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_left_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_left_style', option='status-left-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=blue', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_session_status_left_style_cca978a727 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_position] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_position', option='status-position', scope=<OptionScope.Session: 'SESSION'>, test_value='bottom', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_position_76f3498d14 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_right] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_right', option='status-right', scope=<OptionScope.Session: 'SESSION'>, test_value='%H:%M', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_right_913acd3558 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_right_length] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_right_length', option='status-right-length', scope=<OptionScope.Session: 'SESSION'>, test_value=30, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_option_set_show_cycle_session_status_right_length_14dba990ab | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_right_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_right_style', option='status-right-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=yellow', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_session_status_right_style_b2f65c65b6 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_status_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_status_style', option='status-style', scope=<OptionScope.Session: 'SESSION'>, test_value='fg=green', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_status_style_d25802d7b3 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_visual_activity] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_visual_activity', option='visual-activity', scope=<OptionScope.Session: 'SESSION'>, test_value='both', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_visual_activity_8f9110a162 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_visual_bell] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_visual_bell', option='visual-bell', scope=<OptionScope.Session: 'SESSION'>, test_value='both', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_visual_bell_93d992471b | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[session_visual_silence] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='session_visual_silence', option='visual-silence', scope=<OptionScope.Session: 'SESSION'>, test_value='both', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_session_visual_silence_2b3bab3d1b | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_aggressive_resize] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_aggressive_resize', option='aggressive-resize', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_window_aggressive_resize_34a4e0a35f | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_automatic_rename] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_automatic_rename', option='automatic-rename', scope=<OptionScope.Window: 'WINDOW'>, test_value='off', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_automatic_rename_9cdbdcfde2 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_clock_mode_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_clock_mode_style', option='clock-mode-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='24', expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_clock_mode_style_7f71d3b7b2 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_copy_mode_current_line_number_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_copy_mode_current_line_number_style', option='copy-mode-current-line-number-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=cyan', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#py_test_option_set_show_cycle_window_copy_mode_current_line_number_style_e873b68304 | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_copy_mode_line_number_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_copy_mode_line_number_style', option='copy-mode-line-number-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=cyan', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#options_py_test_option_set_show_cycle_window_copy_mode_line_number_style_ffc6b1354c | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_copy_mode_line_numbers] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_copy_mode_line_numbers', option='copy-mode-line-numbers', scope=<OptionScope.Window: 'WINDOW'>, test_value='absolute', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#test_options_py_test_option_set_show_cycle_window_copy_mode_line_numbers_121d412219 | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_mode_keys] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_mode_keys', option='mode-keys', scope=<OptionScope.Window: 'WINDOW'>, test_value='vi', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_mode_keys_8ab71db3dc | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_mode_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_mode_style', option='mode-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=white', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_mode_style_f5dce16645 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_monitor_activity] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_monitor_activity', option='monitor-activity', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_monitor_activity_61be93bd2a | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_monitor_bell] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_monitor_bell', option='monitor-bell', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_monitor_bell_7a8ffb195a | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_monitor_silence] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_monitor_silence', option='monitor-silence', scope=<OptionScope.Window: 'WINDOW'>, test_value=10, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_monitor_silence_c959269f1e | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_pane_active_border_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_active_border_style', option='pane-active-border-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=red', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#st_options_py_test_option_set_show_cycle_window_pane_active_border_style_081cc7791f | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_pane_base_index] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_base_index', option='pane-base-index', scope=<OptionScope.Window: 'WINDOW'>, test_value=1, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_pane_base_index_43b173517e | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_pane_border_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_border_format', option='pane-border-format', scope=<OptionScope.Window: 'WINDOW'>, test_value='#{pane_index}', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_window_pane_border_format_e577a5cdd1 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_pane_border_status] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_border_status', option='pane-border-status', scope=<OptionScope.Window: 'WINDOW'>, test_value='top', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_window_pane_border_status_6de6e0f39d | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_pane_border_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_border_style', option='pane-border-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=green', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_window_pane_border_style_545969255a | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_mode_keys] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_mode_keys', option='mode-keys', scope=<OptionScope.Window: 'WINDOW'>, test_value='vi', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_mode_keys_8ab71db3dc | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_mode_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_mode_style', option='mode-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=white', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_mode_style_f5dce16645 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_monitor_activity] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_monitor_activity', option='monitor-activity', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_monitor_activity_61be93bd2a | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_monitor_bell] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_monitor_bell', option='monitor-bell', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_monitor_bell_7a8ffb195a | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_monitor_silence] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_monitor_silence', option='monitor-silence', scope=<OptionScope.Window: 'WINDOW'>, test_value=10, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_monitor_silence_c959269f1e | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_pane_active_border_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_active_border_style', option='pane-active-border-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=red', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#st_options_py_test_option_set_show_cycle_window_pane_active_border_style_081cc7791f | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_pane_base_index] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_base_index', option='pane-base-index', scope=<OptionScope.Window: 'WINDOW'>, test_value=1, expected_type=<class 'int'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_pane_base_index_43b173517e | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_pane_border_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_border_format', option='pane-border-format', scope=<OptionScope.Window: 'WINDOW'>, test_value='#{pane_index}', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_window_pane_border_format_e577a5cdd1 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_pane_border_status] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_border_status', option='pane-border-status', scope=<OptionScope.Window: 'WINDOW'>, test_value='top', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_option_set_show_cycle_window_pane_border_status_6de6e0f39d | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_pane_border_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_pane_border_style', option='pane-border-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=green', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_options_py_test_option_set_show_cycle_window_pane_border_style_545969255a | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_tree_mode_preview_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_tree_mode_preview_format', option='tree-mode-preview-format', scope=<OptionScope.Window: 'WINDOW'>, test_value='#{window_name}', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#st_options_py_test_option_set_show_cycle_window_tree_mode_preview_format_b5a4de9145 | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_tree_mode_preview_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_tree_mode_preview_style', option='tree-mode-preview-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=red', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#est_options_py_test_option_set_show_cycle_window_tree_mode_preview_style_dcbea25680 | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_window_pane_current_status_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_pane_current_status_format', option='window-pane-current-status-format', scope=<OptionScope.Window: 'WINDOW'>, test_value='#{pane_index}', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#s_py_test_option_set_show_cycle_window_window_pane_current_status_format_be9fe0c7a6 | 3.7 through 3.7b | planned parity | | tests/test_options.py::test_option_set_show_cycle[window_window_pane_status_format] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_pane_status_format', option='window-pane-status-format', scope=<OptionScope.Window: 'WINDOW'>, test_value='#{pane_index}', expected_type=<class 'str'>, min_version='3.7', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#t_options_py_test_option_set_show_cycle_window_window_pane_status_format_162f7613b7 | 3.7 through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_window_size] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_size', option='window-size', scope=<OptionScope.Window: 'WINDOW'>, test_value='latest', expected_type=<class 'str'>, min_version='3.1', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_window_size_0372372133 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_window_status_current_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_status_current_style', option='window-status-current-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=magenta', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#options_py_test_option_set_show_cycle_window_window_status_current_style_b56a680eb1 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_window_status_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_status_style', option='window-status-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=cyan', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_window_window_status_style_dddbcc656b | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_option_set_show_cycle[window_wrap_search] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_wrap_search', option='wrap-search', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_wrap_search_1349e42c1d | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_options | Test basic options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `obj._show_options()`; `obj._show_options(global_=True)`; `obj._show_options(include_inherited=True)`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.0')`: execute `obj._show_option('test', ignore_errors=True)`; when `isinstance(obj, Pane) and has_gte_version('3.0')`: execute `obj.set_option('test', 'invalid', ignore_errors=True)`; when `isinstance(obj, Pane) and not (has_gte_version('3.0'))`: execute `obj.set_option('test', 'invalid', ignore_errors=True)`; expect `pytest.raises(exc.OptionError)`; when `not (isinstance(obj, Pane))`: execute `obj.set_option('test', 'invalid', ignore_errors=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_60e34563cc | 3.2a through 3.7b; conditional boundaries: 3.0 | planned parity | -| tests/test_options.py::test_options_grid | Test options against grid; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `obj._show_options(scope=scope, include_inherited=include_inherited, global_=global_)`; `Options(**objglobal__options_)`; `dataclasses.fields(objglobal__options)`; assert `getattr(objglobal__options, field.name, default_value) == expected; message=f'Expect {field.name} to be {expected} when ' + f'scope={scope}, global_={global_}'`; when `include_inherited and expected is None`: execute `field.name.replace('_', '-')`; when `field.default_factory is not dataclasses.MISSING`: execute `field.default_factory()`; when `has_gte_version('3.0') and scope == OptionScope.Window and global_`: assert `objglobal__options.pane_base_index == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_grid_56d3bbfac3 | 3.2a through 3.7b; conditional boundaries: 3.0 | planned parity | -| tests/test_options.py::test_options_pane | Test pane options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `pane._show_options(scope=OptionScope.Pane)`; `PaneOptions(**pane_options_)`; assert `pane_options.window_active_style == pane_options_.get('window-active-style')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_pane_9775209292 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_options_server | Test server options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `server.set_option('buffer-limit', 100)`; `server._show_option('buffer-limit')`; `server.set_option('buffer-limit', 150, scope=OptionScope.Server)`; assert `server._show_option('buffer-limit') == 100`; when `has_gte_version('3.0')`: execute `server.set_option('buffer-limit', 150, scope=OptionScope.Pane)`; when `has_gte_version('3.0')`: execute `server.set_option('buffer-limit', 150, scope=OptionScope.Pane, ignore_errors=True)`; when `has_gte_version('3.0')`: execute `session._show_option('buffer-limit')`; assert `session._show_option('buffer-limit') == 150`; when `has_gte_version('3.0')`: execute `pane.set_option('buffer-limit', 100)`; when `not (has_gte_version('3.0'))`: execute `pane.set_option('buffer-limit', 100)`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.0')`: execute `pane._show_option('buffer-limit')`; `window._show_option('buffer-limit')`; `server._show_option('buffer-limit')`; assert `pane._show_option('buffer-limit') == 100`; `window._show_option('buffer-limit') == 100`; `server._show_option('buffer-limit') == 100`; when `has_gte_version('3.0')`: execute `server._show_option('buffer-limit')`; `server.set_option('buffer-limit', 150, scope=OptionScope.Server)`; `server.unset_option('buffer-limit')`; assert `server._show_option('buffer-limit') == 100`; `server_options.buffer_limit == 100`; `server._show_option('buffer-limit') == 150`; `server._show_option('buffer-limit') == 50`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_server_a45f3cd180 | 3.2a through 3.7b; conditional boundaries: 3.0 | planned parity | -| tests/test_options.py::test_options_session | Test session options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `session._show_options(scope=OptionScope.Session)`; `SessionOptions(**session_options_)`; assert `session_options.default_size == session_options_.get('default-size')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_session_8228c4200a | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_options_window | Test window options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `window._show_options(scope=OptionScope.Window)`; `WindowOptions(**window_options_)`; assert `window_options.automatic_rename == window_options_.get('automatic-rename')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_window_c2401e6cd2 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_pane_options_declares_tmux_3_7_shared_fields[pane_active_border_style] | Tmux 3.7 options shared with window scope must type on panes too; case inputs `field_name='pane_active_border_style'`; assert `field_name in PaneOptions.__dataclass_fields__`; `field_name in Options.__dataclass_fields__`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_options_declares_tmux_3_7_shared_fields_pane_active_border_style_a990236eb7 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_pane_options_declares_tmux_3_7_shared_fields[pane_border_style] | Tmux 3.7 options shared with window scope must type on panes too; case inputs `field_name='pane_border_style'`; assert `field_name in PaneOptions.__dataclass_fields__`; `field_name in Options.__dataclass_fields__`. | real-tmux integration test | PythonBehaviorParityTest#s_py_test_pane_options_declares_tmux_3_7_shared_fields_pane_border_style_7a6b256c0c | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_pane_options_declares_tmux_3_7_shared_fields[tree_mode_preview_format] | Tmux 3.7 options shared with window scope must type on panes too; case inputs `field_name='tree_mode_preview_format'`; assert `field_name in PaneOptions.__dataclass_fields__`; `field_name in Options.__dataclass_fields__`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_options_declares_tmux_3_7_shared_fields_tree_mode_preview_format_2da75694e9 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_set_environment_expand_format | Test set_environment with expand_format flag; fixtures `session`; execute `session.set_environment('FORMAT_TEST_VAR', '#{session_name}', expand_format=True)`; `session.show_environment()`; assert `'FORMAT_TEST_VAR' in env`; `env['FORMAT_TEST_VAR'] != '#{session_name}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_set_environment_expand_format_f4e7cbe5b8 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_set_environment_hidden | Test set_environment with hidden flag; fixtures `session`; execute `session.set_environment('HIDDEN_TEST_VAR', 'secret', hidden=True)`; `session.show_environment()`; assert `'HIDDEN_TEST_VAR' not in env`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_set_environment_hidden_d679ff730d | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_option_g_parameter_emits_deprecation_warning | Test show_option() emits DeprecationWarning when g parameter is used; fixtures `session`; execute `server.show_option('buffer-limit', g=True)`; expect `pytest.warns(DeprecationWarning, match='g argument is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_show_option_g_parameter_emits_deprecation_warning_487469f9b6 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_option_indexed_array[base_name_returns_sparse_array] | Test show_option handles bracketed array indices correctly; case inputs `test_case=ShowOptionIndexedTestCase(test_id='base_name_returns_sparse_array', option='status-format', expect_sparse_array=True)`; fixtures `session`; execute `session.show_option(test_case.option, global_=True)`; when `test_case.expect_sparse_array`: assert `isinstance(result, SparseArray); message=f"Expected SparseArray for '{test_case.option}', got {type(result).__name__}"`; when `not (test_case.expect_sparse_array)`: assert `result is not None; message=f"Expected a value for '{test_case.option}', got None"`; `not isinstance(result, SparseArray); message=f"Expected single value for '{test_case.option}', got SparseArray"`. | real-tmux integration test | PythonBehaviorParityTest#options_py_test_show_option_indexed_array_base_name_returns_sparse_array_5b7214defc | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_option_indexed_array[indexed_returns_value] | Test show_option handles bracketed array indices correctly; case inputs `test_case=ShowOptionIndexedTestCase(test_id='indexed_returns_value', option='status-format[0]', expect_sparse_array=False)`; fixtures `session`; execute `session.show_option(test_case.option, global_=True)`; when `test_case.expect_sparse_array`: assert `isinstance(result, SparseArray); message=f"Expected SparseArray for '{test_case.option}', got {type(result).__name__}"`; when `not (test_case.expect_sparse_array)`: assert `result is not None; message=f"Expected a value for '{test_case.option}', got None"`; `not isinstance(result, SparseArray); message=f"Expected single value for '{test_case.option}', got SparseArray"`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_show_option_indexed_array_indexed_returns_value_f76c7e2a78 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_window_size] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_size', option='window-size', scope=<OptionScope.Window: 'WINDOW'>, test_value='latest', expected_type=<class 'str'>, min_version='3.1', xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_window_size_0372372133 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_window_status_current_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_status_current_style', option='window-status-current-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=magenta', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#options_py_test_option_set_show_cycle_window_window_status_current_style_b56a680eb1 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_window_status_style] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_window_status_style', option='window-status-style', scope=<OptionScope.Window: 'WINDOW'>, test_value='fg=cyan', expected_type=<class 'str'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_option_set_show_cycle_window_window_status_style_dddbcc656b | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_option_set_show_cycle[window_wrap_search] | Test set/show cycle for each option type; case inputs `test_case=OptionTestCase(test_id='window_wrap_search', option='wrap-search', scope=<OptionScope.Window: 'WINDOW'>, test_value='on', expected_type=<class 'bool'>, min_version=None, xfail_reason=None)`; fixtures `server`; execute `server.new_session(session_name='test_option_cycle')`; `target.set_option(test_case.option, test_case.test_value)`; `target.show_option(test_case.option)`; assert `window is not None`; `pane is not None`; `result is not None; message=f'Expected {test_case.option} to be set, got None'`; `isinstance(result, test_case.expected_type); message=f'Expected {test_case.expected_type.__name__}, got {type(result).__name__}'`; when `not has_gte_version('3.0')`: skip via `pytest.skip('Option tests require tmux 3.0+')`; when `test_case.min_version and (not has_gte_version(test_case.min_version))`: skip via `pytest.skip(f'Requires tmux {test_case.min_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_option_set_show_cycle_window_wrap_search_1349e42c1d | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_options | Test basic options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `obj._show_options()`; `obj._show_options(global_=True)`; `obj._show_options(include_inherited=True)`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.0')`: execute `obj._show_option('test', ignore_errors=True)`; when `isinstance(obj, Pane) and has_gte_version('3.0')`: execute `obj.set_option('test', 'invalid', ignore_errors=True)`; when `isinstance(obj, Pane) and not (has_gte_version('3.0'))`: execute `obj.set_option('test', 'invalid', ignore_errors=True)`; expect `pytest.raises(exc.OptionError)`; when `not (isinstance(obj, Pane))`: execute `obj.set_option('test', 'invalid', ignore_errors=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_60e34563cc | 3.3a through 3.7c; conditional boundaries: 3.0 | planned parity | +| tests/test_options.py::test_options_grid | Test options against grid; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `obj._show_options(scope=scope, include_inherited=include_inherited, global_=global_)`; `Options(**objglobal__options_)`; `dataclasses.fields(objglobal__options)`; assert `getattr(objglobal__options, field.name, default_value) == expected; message=f'Expect {field.name} to be {expected} when ' + f'scope={scope}, global_={global_}'`; when `include_inherited and expected is None`: execute `field.name.replace('_', '-')`; when `field.default_factory is not dataclasses.MISSING`: execute `field.default_factory()`; when `has_gte_version('3.0') and scope == OptionScope.Window and global_`: assert `objglobal__options.pane_base_index == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_grid_56d3bbfac3 | 3.3a through 3.7c; conditional boundaries: 3.0 | planned parity | +| tests/test_options.py::test_options_pane | Test pane options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `pane._show_options(scope=OptionScope.Pane)`; `PaneOptions(**pane_options_)`; assert `pane_options.window_active_style == pane_options_.get('window-active-style')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_pane_9775209292 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_options_server | Test server options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `server.set_option('buffer-limit', 100)`; `server._show_option('buffer-limit')`; `server.set_option('buffer-limit', 150, scope=OptionScope.Server)`; assert `server._show_option('buffer-limit') == 100`; when `has_gte_version('3.0')`: execute `server.set_option('buffer-limit', 150, scope=OptionScope.Pane)`; when `has_gte_version('3.0')`: execute `server.set_option('buffer-limit', 150, scope=OptionScope.Pane, ignore_errors=True)`; when `has_gte_version('3.0')`: execute `session._show_option('buffer-limit')`; assert `session._show_option('buffer-limit') == 150`; when `has_gte_version('3.0')`: execute `pane.set_option('buffer-limit', 100)`; when `not (has_gte_version('3.0'))`: execute `pane.set_option('buffer-limit', 100)`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.0')`: execute `pane._show_option('buffer-limit')`; `window._show_option('buffer-limit')`; `server._show_option('buffer-limit')`; assert `pane._show_option('buffer-limit') == 100`; `window._show_option('buffer-limit') == 100`; `server._show_option('buffer-limit') == 100`; when `has_gte_version('3.0')`: execute `server._show_option('buffer-limit')`; `server.set_option('buffer-limit', 150, scope=OptionScope.Server)`; `server.unset_option('buffer-limit')`; assert `server._show_option('buffer-limit') == 100`; `server_options.buffer_limit == 100`; `server._show_option('buffer-limit') == 150`; `server._show_option('buffer-limit') == 50`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_server_a45f3cd180 | 3.3a through 3.7c; conditional boundaries: 3.0 | planned parity | +| tests/test_options.py::test_options_session | Test session options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `session._show_options(scope=OptionScope.Session)`; `SessionOptions(**session_options_)`; assert `session_options.default_size == session_options_.get('default-size')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_session_8228c4200a | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_options_window | Test window options; fixtures `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `window._show_options(scope=OptionScope.Window)`; `WindowOptions(**window_options_)`; assert `window_options.automatic_rename == window_options_.get('automatic-rename')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_options_window_c2401e6cd2 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_pane_options_declares_tmux_3_7_shared_fields[pane_active_border_style] | Tmux 3.7 options shared with window scope must type on panes too; case inputs `field_name='pane_active_border_style'`; assert `field_name in PaneOptions.__dataclass_fields__`; `field_name in Options.__dataclass_fields__`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_options_declares_tmux_3_7_shared_fields_pane_active_border_style_a990236eb7 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_pane_options_declares_tmux_3_7_shared_fields[pane_border_style] | Tmux 3.7 options shared with window scope must type on panes too; case inputs `field_name='pane_border_style'`; assert `field_name in PaneOptions.__dataclass_fields__`; `field_name in Options.__dataclass_fields__`. | real-tmux integration test | PythonBehaviorParityTest#s_py_test_pane_options_declares_tmux_3_7_shared_fields_pane_border_style_7a6b256c0c | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_pane_options_declares_tmux_3_7_shared_fields[tree_mode_preview_format] | Tmux 3.7 options shared with window scope must type on panes too; case inputs `field_name='tree_mode_preview_format'`; assert `field_name in PaneOptions.__dataclass_fields__`; `field_name in Options.__dataclass_fields__`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_options_declares_tmux_3_7_shared_fields_tree_mode_preview_format_2da75694e9 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_set_environment_expand_format | Test set_environment with expand_format flag; fixtures `session`; execute `session.set_environment('FORMAT_TEST_VAR', '#{session_name}', expand_format=True)`; `session.show_environment()`; assert `'FORMAT_TEST_VAR' in env`; `env['FORMAT_TEST_VAR'] != '#{session_name}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_set_environment_expand_format_f4e7cbe5b8 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_set_environment_hidden | Test set_environment with hidden flag; fixtures `session`; execute `session.set_environment('HIDDEN_TEST_VAR', 'secret', hidden=True)`; `session.show_environment()`; assert `'HIDDEN_TEST_VAR' not in env`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_set_environment_hidden_d679ff730d | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_option_g_parameter_emits_deprecation_warning | Test show_option() emits DeprecationWarning when g parameter is used; fixtures `session`; execute `server.show_option('buffer-limit', g=True)`; expect `pytest.warns(DeprecationWarning, match='g argument is deprecated')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_show_option_g_parameter_emits_deprecation_warning_487469f9b6 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_option_indexed_array[base_name_returns_sparse_array] | Test show_option handles bracketed array indices correctly; case inputs `test_case=ShowOptionIndexedTestCase(test_id='base_name_returns_sparse_array', option='status-format', expect_sparse_array=True)`; fixtures `session`; execute `session.show_option(test_case.option, global_=True)`; when `test_case.expect_sparse_array`: assert `isinstance(result, SparseArray); message=f"Expected SparseArray for '{test_case.option}', got {type(result).__name__}"`; when `not (test_case.expect_sparse_array)`: assert `result is not None; message=f"Expected a value for '{test_case.option}', got None"`; `not isinstance(result, SparseArray); message=f"Expected single value for '{test_case.option}', got SparseArray"`. | real-tmux integration test | PythonBehaviorParityTest#options_py_test_show_option_indexed_array_base_name_returns_sparse_array_5b7214defc | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_option_indexed_array[indexed_returns_value] | Test show_option handles bracketed array indices correctly; case inputs `test_case=ShowOptionIndexedTestCase(test_id='indexed_returns_value', option='status-format[0]', expect_sparse_array=False)`; fixtures `session`; execute `session.show_option(test_case.option, global_=True)`; when `test_case.expect_sparse_array`: assert `isinstance(result, SparseArray); message=f"Expected SparseArray for '{test_case.option}', got {type(result).__name__}"`; when `not (test_case.expect_sparse_array)`: assert `result is not None; message=f"Expected a value for '{test_case.option}', got None"`; `not isinstance(result, SparseArray); message=f"Expected single value for '{test_case.option}', got SparseArray"`. | real-tmux integration test | PythonBehaviorParityTest#sts_test_options_py_test_show_option_indexed_array_indexed_returns_value_f76c7e2a78 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_show_option_pane_fixture[command-alias] | Test Pane.show_option(s)?; case inputs `dataclass_attribute='command_alias'`, `expected={'split-pane': 'split-window', 'splitp': 'split-window', 'server-info': 'show-messages -JT', 'info': 'show-messages -JT', 'choose-window': 'choose-tree -w', 'choose-session': 'choose-tree -s'}`, `mocked_cmd_stdout=['command-alias[0] split-pane=split-window', 'command-alias[1] splitp=split-window', 'command-alias[2] "server-info=show-messages -JT"', 'command-alias[3] "info=show-messages -JT"', 'command-alias[4] "choose-window=choose-tree -w"', 'command-alias[5] "choose-session=choose-tree -s"']`, `test_id='command-alias'`, `tmux_option='command-alias'`; fixtures `monkeypatch`, `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `pane.show_option(tmux_option)`; `fake_cmd(stdout=mocked_cmd_stdout)`; assert `result == expected`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_show_option_pane_fixture_command_alias_bbe2bb81f5 | not tmux-bound | planned parity | | tests/test_options.py::test_show_option_pane_fixture[terminal-features] | Test Pane.show_option(s)?; case inputs `dataclass_attribute='terminal_features'`, `expected={'screen*': ['title'], 'xterm*': ['clipboard', 'ccolour', 'cstyle', 'focus']}`, `mocked_cmd_stdout=['terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus', 'terminal-features[1] screen*:title']`, `test_id='terminal-features'`, `tmux_option='terminal-features'`; fixtures `monkeypatch`, `server`; execute `server.new_session(session_name='test')`; `session.new_window(window_name='test')`; `window.split(attach=False)`; `pane.show_option(tmux_option)`; `fake_cmd(stdout=mocked_cmd_stdout)`; assert `result == expected`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_show_option_pane_fixture_terminal_features_72f6b9092e | not tmux-bound | planned parity | -| tests/test_options.py::test_show_options_quiet | Test _show_options_raw with quiet flag suppresses errors; fixtures `server`; execute `server._show_options_raw(scope=OptionScope.Server, quiet=True)`; assert `isinstance(result.stdout, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_show_options_quiet_e3cdb42710 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_quiet_public | Public show_options() forwards quiet= to _show_options_raw(); fixtures `server`; execute `server.show_options(quiet=True)`; assert `isinstance(options, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_show_options_quiet_public_6820ad9146 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_returns_expected_keys[server_global] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='server_global', scope=<OptionScope.Server: 'SERVER'>, expected_keys=['buffer-limit', 'escape-time', 'exit-empty', 'focus-events'], global_=True, include_inherited=False, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_show_options_returns_expected_keys_server_global_b69dd85c17 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_returns_expected_keys[server_global_inherited] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='server_global_inherited', scope=<OptionScope.Server: 'SERVER'>, expected_keys=['buffer-limit', 'escape-time', 'exit-empty', 'focus-events'], global_=True, include_inherited=True, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#tions_py_test_show_options_returns_expected_keys_server_global_inherited_a3c8277ec0 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_returns_expected_keys[session_global] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='session_global', scope=<OptionScope.Session: 'SESSION'>, expected_keys=['base-index', 'history-limit', 'status'], global_=True, include_inherited=False, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_show_options_returns_expected_keys_session_global_9a7596348c | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_returns_expected_keys[session_global_inherited] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='session_global_inherited', scope=<OptionScope.Session: 'SESSION'>, expected_keys=['base-index', 'history-limit', 'status'], global_=True, include_inherited=True, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#ions_py_test_show_options_returns_expected_keys_session_global_inherited_208500143a | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_returns_expected_keys[window_global] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='window_global', scope=<OptionScope.Window: 'WINDOW'>, expected_keys=['mode-keys', 'pane-base-index', 'automatic-rename'], global_=True, include_inherited=False, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_show_options_returns_expected_keys_window_global_269e877f5b | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_show_options_returns_expected_keys[window_global_inherited] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='window_global_inherited', scope=<OptionScope.Window: 'WINDOW'>, expected_keys=['mode-keys', 'pane-base-index', 'automatic-rename'], global_=True, include_inherited=True, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#tions_py_test_show_options_returns_expected_keys_window_global_inherited_4ffb5247d1 | 3.2a through 3.7b | planned parity | -| tests/test_options.py::test_stable_baseline_options_and_hooks | Ensure stable baseline across tmux versions; fixtures `server`; execute `server.new_session(session_name='test', detach=True)`; `server.show_option('command-alias')`; `server.show_option('user-keys')`; assert `server.show_option('command-alias') == {'choose-session': 'choose-tree -s', 'choose-window': 'choose-tree -w', 'info': 'show-messages -JT', 'server-info': 'show-messages -JT', 'split-pane': 'split-window', 'splitp': 'split-window'}`; `server.show_option('user-keys') is None`; `pane is not None`; when `has_gte_version('3.2')`: execute `server.show_option('terminal-features')`; assert `isinstance(terminal_features, dict)`; `'screen*' in terminal_features`; `terminal_features['screen*'] == ['title']`; `'xterm*' in terminal_features`; `terminal_features['xterm*'] == ['clipboard', 'ccolour', 'cstyle', 'focus', 'title']`; when `has_gte_version('3.2') and has_gte_version('3.4')`: assert `'rxvt*' in terminal_features`; `terminal_features['rxvt*'] == ['ignorefkeys']`; when `has_gte_version('3.2')`: execute `server.show_option('terminal-overrides')`; assert `isinstance(terminal_overrides, (dict, type(None)))`; when `not (has_gte_version('3.2')) and has_gte_version('3.0')`: execute `server.show_option('terminal-overrides')`; assert `isinstance(terminal_overrides, dict)`; `'screen*' in terminal_overrides or 'xterm*' in terminal_overrides`; when `not (has_gte_version('3.2')) and not (has_gte_version('3.0'))`: execute `server.show_option('terminal-overrides')`; assert `isinstance(terminal_overrides, dict)`; `'screen*' in terminal_overrides`; `'xterm*' in terminal_overrides`; when `has_gte_version('2.9')`: execute `server.show_option('status-format')`; assert `isinstance(status_format, (dict, type(None)))`; when `not (has_gte_version('2.9'))`: execute `server.show_option('status-format')`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.0')`: execute `server.show_option('update-environment')`; assert `isinstance(update_env, (list, type(None)))`; when `not (has_gte_version('3.0'))`: execute `server.show_option('update-environment')`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.3')`: execute `pane.show_option('pane-colours')`; assert `pane.show_option('pane-colours') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_stable_baseline_options_and_hooks_4f936586b7 | 3.2a through 3.7b; conditional boundaries: 3.2, 3.4, 3.0, 2.9, 3.3 | planned parity | -| tests/test_options.py::test_style_option_validation | Test style option validation; fixtures `server`; execute `server.new_session(session_name='test')`; `session.set_option('status-style', 'fg=red,bg=default,bold')`; `session.show_option('status-style')`; `session.set_option('status-style', 'invalid-style')`; expect `pytest.raises(exc.OptionError)`; assert `isinstance(style, str)`; `'fg=red' in str(style)`; when `has_gte_version('3.2')`: assert `'bg=default' in str(style)`; `'bold' in str(style)`; when `not (has_gte_version('3.2'))`: assert `'bright' in str(style)`; `'bg=default' not in str(style)`; when `has_gte_version('3.0')`: execute `session.set_option('status-style', 'fg=colour240,bg=#525252,bold,underscore')`; `session.show_option('status-style')`; assert `isinstance(style, str)`; when `has_gte_version('3.0') and has_gte_version('3.2')`: assert `style == 'fg=colour240,bg=#525252,bold,underscore'`; when `has_gte_version('3.0') and not (has_gte_version('3.2'))`: assert `style == 'fg=colour240,bg=#525252,bright,underscore'`; when `has_gte_version('3.0') and has_gte_version('3.2')`: execute `session.set_option('status-style', 'fg=#{?pane_in_mode,red,green}')`; `session.show_option('status-style')`; assert `isinstance(style, str)`; `style == 'fg=#{?pane_in_mode,red,green}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_style_option_validation_029080827c | 3.2a through 3.7b; conditional boundaries: 3.2, 3.0 | planned parity | +| tests/test_options.py::test_show_options_quiet | Test _show_options_raw with quiet flag suppresses errors; fixtures `server`; execute `server._show_options_raw(scope=OptionScope.Server, quiet=True)`; assert `isinstance(result.stdout, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_show_options_quiet_e3cdb42710 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_quiet_public | Public show_options() forwards quiet= to _show_options_raw(); fixtures `server`; execute `server.show_options(quiet=True)`; assert `isinstance(options, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_show_options_quiet_public_6820ad9146 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_returns_expected_keys[server_global] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='server_global', scope=<OptionScope.Server: 'SERVER'>, expected_keys=['buffer-limit', 'escape-time', 'exit-empty', 'focus-events'], global_=True, include_inherited=False, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_show_options_returns_expected_keys_server_global_b69dd85c17 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_returns_expected_keys[server_global_inherited] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='server_global_inherited', scope=<OptionScope.Server: 'SERVER'>, expected_keys=['buffer-limit', 'escape-time', 'exit-empty', 'focus-events'], global_=True, include_inherited=True, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#tions_py_test_show_options_returns_expected_keys_server_global_inherited_a3c8277ec0 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_returns_expected_keys[session_global] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='session_global', scope=<OptionScope.Session: 'SESSION'>, expected_keys=['base-index', 'history-limit', 'status'], global_=True, include_inherited=False, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_options_py_test_show_options_returns_expected_keys_session_global_9a7596348c | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_returns_expected_keys[session_global_inherited] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='session_global_inherited', scope=<OptionScope.Session: 'SESSION'>, expected_keys=['base-index', 'history-limit', 'status'], global_=True, include_inherited=True, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#ions_py_test_show_options_returns_expected_keys_session_global_inherited_208500143a | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_returns_expected_keys[window_global] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='window_global', scope=<OptionScope.Window: 'WINDOW'>, expected_keys=['mode-keys', 'pane-base-index', 'automatic-rename'], global_=True, include_inherited=False, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_options_py_test_show_options_returns_expected_keys_window_global_269e877f5b | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_show_options_returns_expected_keys[window_global_inherited] | Test that show_options() returns dict with expected scope-specific keys; case inputs `test_case=ShowOptionsTestCase(test_id='window_global_inherited', scope=<OptionScope.Window: 'WINDOW'>, expected_keys=['mode-keys', 'pane-base-index', 'automatic-rename'], global_=True, include_inherited=True, min_version='3.2')`; fixtures `server`; execute `server.new_session(session_name='test_show_options')`; `server.show_options(global_=test_case.global_, scope=test_case.scope, include_inherited=test_case.include_inherited)`; assert `pane is not None`; `isinstance(options, dict)`; `len(options) > 0; message=f'Expected non-empty dict for scope={test_case.scope.name}, global_={test_case.global_}'`; `key in options or f'{key}*' in options; message=f"Expected '{key}' in {test_case.scope.name} options, got keys: {list(options.keys())[:10]}..."`; when `not has_gte_version(test_case.min_version)`: skip via `pytest.skip(f'Requires tmux >= {test_case.min_version}')`. | real-tmux integration test | PythonBehaviorParityTest#tions_py_test_show_options_returns_expected_keys_window_global_inherited_4ffb5247d1 | 3.3a through 3.7c | planned parity | +| tests/test_options.py::test_stable_baseline_options_and_hooks | Ensure stable baseline across tmux versions; fixtures `server`; execute `server.new_session(session_name='test', detach=True)`; `server.show_option('command-alias')`; `server.show_option('user-keys')`; assert `server.show_option('command-alias') == {'choose-session': 'choose-tree -s', 'choose-window': 'choose-tree -w', 'info': 'show-messages -JT', 'server-info': 'show-messages -JT', 'split-pane': 'split-window', 'splitp': 'split-window'}`; `server.show_option('user-keys') is None`; `pane is not None`; when `has_gte_version('3.2')`: execute `server.show_option('terminal-features')`; assert `isinstance(terminal_features, dict)`; `'screen*' in terminal_features`; `terminal_features['screen*'] == ['title']`; `'xterm*' in terminal_features`; `terminal_features['xterm*'] == ['clipboard', 'ccolour', 'cstyle', 'focus', 'title']`; when `has_gte_version('3.2') and has_gte_version('3.4')`: assert `'rxvt*' in terminal_features`; `terminal_features['rxvt*'] == ['ignorefkeys']`; when `has_gte_version('3.2')`: execute `server.show_option('terminal-overrides')`; assert `isinstance(terminal_overrides, (dict, type(None)))`; when `not (has_gte_version('3.2')) and has_gte_version('3.0')`: execute `server.show_option('terminal-overrides')`; assert `isinstance(terminal_overrides, dict)`; `'screen*' in terminal_overrides or 'xterm*' in terminal_overrides`; when `not (has_gte_version('3.2')) and not (has_gte_version('3.0'))`: execute `server.show_option('terminal-overrides')`; assert `isinstance(terminal_overrides, dict)`; `'screen*' in terminal_overrides`; `'xterm*' in terminal_overrides`; when `has_gte_version('2.9')`: execute `server.show_option('status-format')`; assert `isinstance(status_format, (dict, type(None)))`; when `not (has_gte_version('2.9'))`: execute `server.show_option('status-format')`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.0')`: execute `server.show_option('update-environment')`; assert `isinstance(update_env, (list, type(None)))`; when `not (has_gte_version('3.0'))`: execute `server.show_option('update-environment')`; expect `pytest.raises(exc.OptionError)`; when `has_gte_version('3.3')`: execute `pane.show_option('pane-colours')`; assert `pane.show_option('pane-colours') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_stable_baseline_options_and_hooks_4f936586b7 | 3.3a through 3.7c; conditional boundaries: 3.2, 3.4, 3.0, 2.9, 3.3 | planned parity | +| tests/test_options.py::test_style_option_validation | Test style option validation; fixtures `server`; execute `server.new_session(session_name='test')`; `session.set_option('status-style', 'fg=red,bg=default,bold')`; `session.show_option('status-style')`; `session.set_option('status-style', 'invalid-style')`; expect `pytest.raises(exc.OptionError)`; assert `isinstance(style, str)`; `'fg=red' in str(style)`; when `has_gte_version('3.2')`: assert `'bg=default' in str(style)`; `'bold' in str(style)`; when `not (has_gte_version('3.2'))`: assert `'bright' in str(style)`; `'bg=default' not in str(style)`; when `has_gte_version('3.0')`: execute `session.set_option('status-style', 'fg=colour240,bg=#525252,bold,underscore')`; `session.show_option('status-style')`; assert `isinstance(style, str)`; when `has_gte_version('3.0') and has_gte_version('3.2')`: assert `style == 'fg=colour240,bg=#525252,bold,underscore'`; when `has_gte_version('3.0') and not (has_gte_version('3.2'))`: assert `style == 'fg=colour240,bg=#525252,bright,underscore'`; when `has_gte_version('3.0') and has_gte_version('3.2')`: execute `session.set_option('status-style', 'fg=#{?pane_in_mode,red,green}')`; `session.show_option('status-style')`; assert `isinstance(style, str)`; `style == 'fg=#{?pane_in_mode,red,green}'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_options_py_test_style_option_validation_029080827c | 3.3a through 3.7c; conditional boundaries: 3.2, 3.0 | planned parity | | tests/test_options.py::test_terminal_features | Test tmux's terminal-feature option destructuring; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `fake_cmd(stdout=MOCKED_GLOBAL_OPTIONS)`; `any(('terminal-features' in k for k in options_))`; `Options(**options_)`; assert `any(('terminal-features' in k for k in options_))`; `options`; `options.terminal_features`; `options.terminal_features['screen*'] == ['title']`; `options.terminal_features['xterm*'] == ['clipboard', 'ccolour', 'cstyle', 'focus']`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_terminal_features_fb34d208ca | not tmux-bound | planned parity | -| tests/test_options.py::test_terminal_features_edge_cases | Test edge cases for terminal-features option; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `fake_cmd(stdout=['terminal-features[0] xterm*:'])`; `t.cast(dict[str, list[str]], options['terminal-features'])`; `fake_cmd(stdout=['terminal-features[0] xterm*:invalid:feature:with:colons'])`; `any((f == 'invalid' for f in xterm_features))`; `any((f == 'feature' for f in xterm_features))`; assert `isinstance(terminal_features, dict)`; `isinstance(xterm_features, list)`; `xterm_features == ['']`; `any((f == 'invalid' for f in xterm_features))`; `any((f == 'feature' for f in xterm_features))`; `any((f == 'feature with space' for f in xterm_features))`; `any((f == 'special*char' for f in xterm_features))`; when `not has_gte_version('3.2')`: skip via `pytest.skip('terminal-features requires tmux >= 3.2')`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_terminal_features_edge_cases_edde661357 | 3.2a through 3.7b | planned parity | +| tests/test_options.py::test_terminal_features_edge_cases | Test edge cases for terminal-features option; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `fake_cmd(stdout=['terminal-features[0] xterm*:'])`; `t.cast(dict[str, list[str]], options['terminal-features'])`; `fake_cmd(stdout=['terminal-features[0] xterm*:invalid:feature:with:colons'])`; `any((f == 'invalid' for f in xterm_features))`; `any((f == 'feature' for f in xterm_features))`; assert `isinstance(terminal_features, dict)`; `isinstance(xterm_features, list)`; `xterm_features == ['']`; `any((f == 'invalid' for f in xterm_features))`; `any((f == 'feature' for f in xterm_features))`; `any((f == 'feature with space' for f in xterm_features))`; `any((f == 'special*char' for f in xterm_features))`; when `not has_gte_version('3.2')`: skip via `pytest.skip('terminal-features requires tmux >= 3.2')`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_terminal_features_edge_cases_edde661357 | 3.3a through 3.7c | planned parity | | tests/test_options.py::test_terminal_overrides | Test tmux's terminal-overrides option destructuring; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `any(('terminal-overrides' in k for k in options_))`; `Options(**options_)`; assert `any(('terminal-overrides' in k for k in options_))`; `options`; `options.terminal_overrides`; `options_['terminal-overrides'] is not None`; `isinstance(options_['terminal-overrides'], dict)`; `not isinstance(options_['terminal-overrides'], SparseArray)`; `'xterm-256color' in options_['terminal-overrides']`; `isinstance(options_['terminal-overrides']['xterm-256color'], dict)`; `options_['terminal-overrides']['xterm-256color'] == {'Tc': None}`. | unit test | PythonBehaviorParityTest#tests_test_options_py_test_terminal_overrides_e3dccbff7a | not tmux-bound | planned parity | | tests/test_options.py::test_terminal_overrides_multi_feature[flag_and_keyval-xterm*:XT:Ms=clipboard-xterm*-expected_features1] | Test terminal-overrides parsing with multiple features per terminal; case inputs `expected_features={'XT': None, 'Ms': 'clipboard'}`, `expected_term='xterm*'`, `raw_entry='xterm*:XT:Ms=clipboard'`, `test_id='flag_and_keyval'`; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `fake_cmd(stdout=mock_options)`; `t.cast(TerminalOverrides, options_['terminal-overrides'])`; assert `'terminal-overrides' in options_`; `expected_term in term_overrides`; `term_overrides[expected_term] == expected_features`. | unit test | PythonBehaviorParityTest#i_feature_flag_and_keyval_xterm_XT_Ms_clipboard_xterm_expected_features1_561bc3e455 | not tmux-bound | planned parity | | tests/test_options.py::test_terminal_overrides_multi_feature[integer_value-tmux*:colors=256:Tc-tmux*-expected_features3] | Test terminal-overrides parsing with multiple features per terminal; case inputs `expected_features={'colors': 256, 'Tc': None}`, `expected_term='tmux*'`, `raw_entry='tmux*:colors=256:Tc'`, `test_id='integer_value'`; fixtures `server`, `monkeypatch`; execute `server._show_options()`; `fake_cmd(stdout=mock_options)`; `t.cast(TerminalOverrides, options_['terminal-overrides'])`; assert `'terminal-overrides' in options_`; `expected_term in term_overrides`; `term_overrides[expected_term] == expected_features`. | unit test | PythonBehaviorParityTest#s_multi_feature_integer_value_tmux_colors_256_Tc_tmux_expected_features3_4bfd500687 | not tmux-bound | planned parity | @@ -1032,213 +1032,213 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_options.py::test_window_only_3_7_options_absent_from_pane[tree_mode_preview_style] | Window-only tmux 3.7 options must not be typed on PaneOptions; case inputs `field_name='tree_mode_preview_style'`; assert `field_name in WindowOptions.__dataclass_fields__`; `field_name not in PaneOptions.__dataclass_fields__`. | unit test | PythonBehaviorParityTest#py_test_window_only_3_7_options_absent_from_pane_tree_mode_preview_style_75d3564fd0 | not tmux-bound | planned parity | | tests/test_options.py::test_window_only_3_7_options_absent_from_pane[window_pane_current_status_format] | Window-only tmux 3.7 options must not be typed on PaneOptions; case inputs `field_name='window_pane_current_status_format'`; assert `field_name in WindowOptions.__dataclass_fields__`; `field_name not in PaneOptions.__dataclass_fields__`. | unit test | PythonBehaviorParityTest#ndow_only_3_7_options_absent_from_pane_window_pane_current_status_format_efbcedc6a3 | not tmux-bound | planned parity | | tests/test_options.py::test_window_only_3_7_options_absent_from_pane[window_pane_status_format] | Window-only tmux 3.7 options must not be typed on PaneOptions; case inputs `field_name='window_pane_status_format'`; assert `field_name in WindowOptions.__dataclass_fields__`; `field_name not in PaneOptions.__dataclass_fields__`. | unit test | PythonBehaviorParityTest#_test_window_only_3_7_options_absent_from_pane_window_pane_status_format_46da9681d0 | not tmux-bound | planned parity | -| tests/test_pane.py::test_break_pane_basic | Test Pane.break_pane() creates a new window; fixtures `session`; execute `session.new_window(window_name='test_break')`; `pane.split(shell='sleep 1m')`; `session.refresh()`; `window.refresh()`; `new_pane.break_pane()`; assert `pane is not None`; `len(window.panes) == 2`; `len(session.windows) == initial_window_count + 1`; `len(window.panes) == 1`; `new_window.window_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_break_pane_basic_88615b93f8 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_break_pane_no_name_uses_natural_name | Pane.break_pane() without a name keeps tmux's default window name; fixtures `session`; execute `session.new_window(window_name='test_break_natural')`; `pane.split(shell='sleep 123')`; `new_pane.break_pane()`; assert `pane is not None`; `broken.window_name == 'sleep'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_break_pane_no_name_uses_natural_name_460bb1fba9 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_break_pane_with_name | Test Pane.break_pane() with window_name; fixtures `session`; execute `session.new_window(window_name='test_break_name')`; `pane.split(shell='sleep 1m')`; `new_pane.break_pane(window_name='my_broken')`; assert `pane is not None`; `new_window.window_name == 'my_broken'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_break_pane_with_name_5e3188fbf6 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_capture_pane | Verify Pane.capture_pane(); fixtures `session`; execute `session.new_window(attach=True, window_name='capture_pane', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('printf "\\n%s\\n" "Hello World !"', literal=True, suppress_history=False)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; `'$ printf "\\n%s\\n" "Hello World !"{}'.format('\n\nHello World !\n$')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane_contents == '$'`; `pane_contents == '$ printf "\\n%s\\n" "Hello World !"{}'.format('\n\nHello World !\n$')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_5c79c5858d | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_capture_pane_3_7_flags[hyperlinks] | Pane.capture_pane() -H/-L/-F on tmux 3.7+; warn-and-ignore below; case inputs `kwargs={'hyperlinks': True}`, `match='hyperlinks requires tmux 3.7'`, `test_id='hyperlinks'`; fixtures `session`; execute `session.new_window(window_name='capture_37')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.capture_pane(**kwargs)`; assert `isinstance(pane.capture_pane(**kwargs), list)`; when `not (has_gte_version('3.7'))`: execute `pane.capture_pane(**kwargs)`; expect `pytest.warns(UserWarning, match=match)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_3_7_flags_hyperlinks_459c25abda | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_capture_pane_3_7_flags[line_flags] | Pane.capture_pane() -H/-L/-F on tmux 3.7+; warn-and-ignore below; case inputs `kwargs={'line_flags': True}`, `match='line_flags requires tmux 3.7'`, `test_id='line_flags'`; fixtures `session`; execute `session.new_window(window_name='capture_37')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.capture_pane(**kwargs)`; assert `isinstance(pane.capture_pane(**kwargs), list)`; when `not (has_gte_version('3.7'))`: execute `pane.capture_pane(**kwargs)`; expect `pytest.warns(UserWarning, match=match)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_3_7_flags_line_flags_b3fe130bf7 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_capture_pane_3_7_flags[line_numbers] | Pane.capture_pane() -H/-L/-F on tmux 3.7+; warn-and-ignore below; case inputs `kwargs={'line_numbers': True}`, `match='line_numbers requires tmux 3.7'`, `test_id='line_numbers'`; fixtures `session`; execute `session.new_window(window_name='capture_37')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.capture_pane(**kwargs)`; assert `isinstance(pane.capture_pane(**kwargs), list)`; when `not (has_gte_version('3.7'))`: execute `pane.capture_pane(**kwargs)`; expect `pytest.warns(UserWarning, match=match)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_3_7_flags_line_numbers_cbeafcfc86 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_capture_pane_end | Assert Pane.capture_pane() with ``end`` param; fixtures `session`; execute `session.new_window(attach=True, window_name='capture_pane_end', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('printf "%s"', literal=True, suppress_history=False)`; `pane.capture_pane(end=0)`; `pane.capture_pane(end='-')`; `shutil.which('env')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane_contents == '$'`; `pane_contents == '$ printf "%s"\n$'`; `pane_contents == '$ printf "%s"'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_end_87c7445a38 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_capture_pane_start | Assert Pane.capture_pane() with ``start`` param; fixtures `session`; execute `session.new_window(attach=True, window_name='capture_pane_start', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('printf "%s"', literal=True, suppress_history=False)`; `pane.send_keys('clear -x', literal=True, suppress_history=False)`; `pane.capture_pane(start=-2)`; `pane.send_keys('')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane_contents == '$'`; `pane_contents == '$ printf "%s"\n$'`; `pane_contents_history_start[0] == '$ printf "%s"'`; `pane_contents_history_start[1] == '$ clear -x'`; `pane_contents_history_start[-1] == '$'`; `retry_until(pane_contents_capture_visible_only_shows_prompt, 1, raises=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_start_7508de4c44 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_choose_tree_with_flags | Test Pane.choose_tree() with format, filter, sort, reverse, zoom; fixtures `session`; execute `pane.choose_tree(format_string='#{session_name}', filter_expression='#{?session_attached,1,0}', sort_order='name', reverse=True, zoom=True)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_choose_tree_with_flags_8590687499 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_chooser_smoke[choose_buffer] | Smoke test: chooser/customize methods invoke without error; case inputs `method='choose_buffer'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_choose_buffer_f5e29cc822 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_chooser_smoke[choose_client] | Smoke test: chooser/customize methods invoke without error; case inputs `method='choose_client'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_choose_client_940e1d7237 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_chooser_smoke[choose_tree] | Smoke test: chooser/customize methods invoke without error; case inputs `method='choose_tree'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_choose_tree_49e32edb8b | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_chooser_smoke[customize_mode] | Smoke test: chooser/customize methods invoke without error; case inputs `method='customize_mode'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_customize_mode_f0a891a32d | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_clear_history | Test Pane.clear_history(); fixtures `session`; execute `session.new_window(window_name='test_clearhist', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('echo line1', enter=True)`; `pane.send_keys('echo line2', enter=True)`; `pane.clear_history()`; `pane.capture_pane(start=-100)`; assert `env is not None`; `pane is not None`; `len(history) <= 30`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_clear_history_3c49111e24 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_clock_mode | Test Pane.clock_mode() enters clock mode; fixtures `session`; execute `pane.clock_mode()`; `pane.send_keys('q', enter=False)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_clock_mode_76d4a16346 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_copy_mode | Test Pane.copy_mode() enters copy mode; fixtures `session`; execute `pane.copy_mode()`; `pane.send_keys('q', enter=False)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_copy_mode_321d51fa2b | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_break_pane_basic | Test Pane.break_pane() creates a new window; fixtures `session`; execute `session.new_window(window_name='test_break')`; `pane.split(shell='sleep 1m')`; `session.refresh()`; `window.refresh()`; `new_pane.break_pane()`; assert `pane is not None`; `len(window.panes) == 2`; `len(session.windows) == initial_window_count + 1`; `len(window.panes) == 1`; `new_window.window_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_break_pane_basic_88615b93f8 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_break_pane_no_name_uses_natural_name | Pane.break_pane() without a name keeps tmux's default window name; fixtures `session`; execute `session.new_window(window_name='test_break_natural')`; `pane.split(shell='sleep 123')`; `new_pane.break_pane()`; assert `pane is not None`; `broken.window_name == 'sleep'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_break_pane_no_name_uses_natural_name_460bb1fba9 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_break_pane_with_name | Test Pane.break_pane() with window_name; fixtures `session`; execute `session.new_window(window_name='test_break_name')`; `pane.split(shell='sleep 1m')`; `new_pane.break_pane(window_name='my_broken')`; assert `pane is not None`; `new_window.window_name == 'my_broken'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_break_pane_with_name_5e3188fbf6 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_capture_pane | Verify Pane.capture_pane(); fixtures `session`; execute `session.new_window(attach=True, window_name='capture_pane', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('printf "\\n%s\\n" "Hello World !"', literal=True, suppress_history=False)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; `'$ printf "\\n%s\\n" "Hello World !"{}'.format('\n\nHello World !\n$')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane_contents == '$'`; `pane_contents == '$ printf "\\n%s\\n" "Hello World !"{}'.format('\n\nHello World !\n$')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_5c79c5858d | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_capture_pane_3_7_flags[hyperlinks] | Pane.capture_pane() -H/-L/-F on tmux 3.7+; warn-and-ignore below; case inputs `kwargs={'hyperlinks': True}`, `match='hyperlinks requires tmux 3.7'`, `test_id='hyperlinks'`; fixtures `session`; execute `session.new_window(window_name='capture_37')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.capture_pane(**kwargs)`; assert `isinstance(pane.capture_pane(**kwargs), list)`; when `not (has_gte_version('3.7'))`: execute `pane.capture_pane(**kwargs)`; expect `pytest.warns(UserWarning, match=match)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_3_7_flags_hyperlinks_459c25abda | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_capture_pane_3_7_flags[line_flags] | Pane.capture_pane() -H/-L/-F on tmux 3.7+; warn-and-ignore below; case inputs `kwargs={'line_flags': True}`, `match='line_flags requires tmux 3.7'`, `test_id='line_flags'`; fixtures `session`; execute `session.new_window(window_name='capture_37')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.capture_pane(**kwargs)`; assert `isinstance(pane.capture_pane(**kwargs), list)`; when `not (has_gte_version('3.7'))`: execute `pane.capture_pane(**kwargs)`; expect `pytest.warns(UserWarning, match=match)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_3_7_flags_line_flags_b3fe130bf7 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_capture_pane_3_7_flags[line_numbers] | Pane.capture_pane() -H/-L/-F on tmux 3.7+; warn-and-ignore below; case inputs `kwargs={'line_numbers': True}`, `match='line_numbers requires tmux 3.7'`, `test_id='line_numbers'`; fixtures `session`; execute `session.new_window(window_name='capture_37')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.capture_pane(**kwargs)`; assert `isinstance(pane.capture_pane(**kwargs), list)`; when `not (has_gte_version('3.7'))`: execute `pane.capture_pane(**kwargs)`; expect `pytest.warns(UserWarning, match=match)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_3_7_flags_line_numbers_cbeafcfc86 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_capture_pane_end | Assert Pane.capture_pane() with ``end`` param; fixtures `session`; execute `session.new_window(attach=True, window_name='capture_pane_end', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('printf "%s"', literal=True, suppress_history=False)`; `pane.capture_pane(end=0)`; `pane.capture_pane(end='-')`; `shutil.which('env')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane_contents == '$'`; `pane_contents == '$ printf "%s"\n$'`; `pane_contents == '$ printf "%s"'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_end_87c7445a38 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_capture_pane_start | Assert Pane.capture_pane() with ``start`` param; fixtures `session`; execute `session.new_window(attach=True, window_name='capture_pane_start', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('printf "%s"', literal=True, suppress_history=False)`; `pane.send_keys('clear -x', literal=True, suppress_history=False)`; `pane.capture_pane(start=-2)`; `pane.send_keys('')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane_contents == '$'`; `pane_contents == '$ printf "%s"\n$'`; `pane_contents_history_start[0] == '$ printf "%s"'`; `pane_contents_history_start[1] == '$ clear -x'`; `pane_contents_history_start[-1] == '$'`; `retry_until(pane_contents_capture_visible_only_shows_prompt, 1, raises=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_capture_pane_start_7508de4c44 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_choose_tree_with_flags | Test Pane.choose_tree() with format, filter, sort, reverse, zoom; fixtures `session`; execute `pane.choose_tree(format_string='#{session_name}', filter_expression='#{?session_attached,1,0}', sort_order='name', reverse=True, zoom=True)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_choose_tree_with_flags_8590687499 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_chooser_smoke[choose_buffer] | Smoke test: chooser/customize methods invoke without error; case inputs `method='choose_buffer'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_choose_buffer_f5e29cc822 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_chooser_smoke[choose_client] | Smoke test: chooser/customize methods invoke without error; case inputs `method='choose_client'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_choose_client_940e1d7237 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_chooser_smoke[choose_tree] | Smoke test: chooser/customize methods invoke without error; case inputs `method='choose_tree'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_choose_tree_49e32edb8b | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_chooser_smoke[customize_mode] | Smoke test: chooser/customize methods invoke without error; case inputs `method='customize_mode'`; fixtures `session`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_chooser_smoke_customize_mode_f0a891a32d | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_clear_history | Test Pane.clear_history(); fixtures `session`; execute `session.new_window(window_name='test_clearhist', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys('echo line1', enter=True)`; `pane.send_keys('echo line2', enter=True)`; `pane.clear_history()`; `pane.capture_pane(start=-100)`; assert `env is not None`; `pane is not None`; `len(history) <= 30`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_clear_history_3c49111e24 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_clock_mode | Test Pane.clock_mode() enters clock mode; fixtures `session`; execute `pane.clock_mode()`; `pane.send_keys('q', enter=False)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_clock_mode_76d4a16346 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_copy_mode | Test Pane.copy_mode() enters copy mode; fixtures `session`; execute `pane.copy_mode()`; `pane.send_keys('q', enter=False)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_copy_mode_321d51fa2b | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_copy_mode_page_down | Test Pane.copy_mode(page_down=True) on tmux 3.5+; fixtures `session`; execute `pane.copy_mode()`; `pane.copy_mode(page_down=True)`; `pane.send_keys('q', enter=False)`; assert `pane is not None`; when `not has_gte_version('3.5')`: skip via `pytest.skip('page_down requires tmux 3.5+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_copy_mode_page_down_5f89234414 | 3.5 through 3.7b | planned parity | -| tests/test_pane.py::test_copy_mode_source_pane | Test Pane.copy_mode(source_pane=...) reads another pane's history; fixtures `session`; execute `session.new_window(window_name='copy_src')`; `window.split()`; `src.send_keys('echo SOURCE_TEXT', enter=True)`; `dest.copy_mode(source_pane=str(src.pane_id))`; `dest.send_keys('q', enter=False)`; assert `src is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_copy_mode_source_pane_1abd42b965 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_message_flags[all_formats] | Test display_message() with various flag combinations; case inputs `cmd=''`, `expected_in_output='session_name'`, `kwargs={'get_text': True, 'all_formats': True}`, `min_tmux_version=None`, `test_id='all_formats'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_all_formats_a7cb1286c6 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_message_flags[format_string] | Test display_message() with various flag combinations; case inputs `cmd=''`, `expected_in_output='%'`, `kwargs={'get_text': True, 'format_string': '#{pane_id}'}`, `min_tmux_version=None`, `test_id='format_string'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_format_string_4c94bb7d53 | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_copy_mode_source_pane | Test Pane.copy_mode(source_pane=...) reads another pane's history; fixtures `session`; execute `session.new_window(window_name='copy_src')`; `window.split()`; `src.send_keys('echo SOURCE_TEXT', enter=True)`; `dest.copy_mode(source_pane=str(src.pane_id))`; `dest.send_keys('q', enter=False)`; assert `src is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_copy_mode_source_pane_1abd42b965 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_message_flags[all_formats] | Test display_message() with various flag combinations; case inputs `cmd=''`, `expected_in_output='session_name'`, `kwargs={'get_text': True, 'all_formats': True}`, `min_tmux_version=None`, `test_id='all_formats'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_all_formats_a7cb1286c6 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_message_flags[format_string] | Test display_message() with various flag combinations; case inputs `cmd=''`, `expected_in_output='%'`, `kwargs={'get_text': True, 'format_string': '#{pane_id}'}`, `min_tmux_version=None`, `test_id='format_string'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_format_string_4c94bb7d53 | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_display_message_flags[no_expand] | Test display_message() with various flag combinations; case inputs `cmd='#{pane_id}'`, `expected_in_output='#{pane_id}'`, `kwargs={'get_text': True, 'no_expand': True}`, `min_tmux_version='3.4'`, `test_id='no_expand'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_no_expand_a4d76d3352 | 3.4 through 3.7b | planned parity | -| tests/test_pane.py::test_display_message_flags[verbose] | Test display_message() with various flag combinations; case inputs `cmd=''`, `expected_in_output='session_name'`, `kwargs={'get_text': True, 'verbose': True, 'all_formats': True}`, `min_tmux_version=None`, `test_id='verbose'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_verbose_7c17dc099c | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_message_warns_on_tmux_error | Tmux stderr on ``display-message`` surfaces as a :class:`UserWarning`; fixtures `session`; execute `pane.display_message('x', get_text=True, format_string='#{pane_id}')`; expect `pytest.warns(UserWarning, match='only one of -F or argument')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_warns_on_tmux_error_752ca8c041 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_panes | Test Pane.display_panes() shows pane numbers; fixtures `control_mode`, `session`; execute `control_mode()`; `pane.display_panes()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_panes_d1be737cbf | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_close_existing | Test Pane.display_popup(close_existing=True) returns cleanly; fixtures `control_mode`, `session`; execute `control_mode()`; `pane.display_popup(close_existing=True)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_close_existing_f3dfd2c33b | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_close_on_success | Test Pane.display_popup() with close_on_success (-EE) alone; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(command=f'touch {marker}', close_on_success=True)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_close_on_success_48a77d755a | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_flags[basic] | Test Pane.display_popup() flag combinations; case inputs `kwargs={}`, `min_tmux_version=None`, `test_id='basic'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_basic_bc482c9f76 | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_display_message_flags[verbose] | Test display_message() with various flag combinations; case inputs `cmd=''`, `expected_in_output='session_name'`, `kwargs={'get_text': True, 'verbose': True, 'all_formats': True}`, `min_tmux_version=None`, `test_id='verbose'`; fixtures `session`; execute `pane.display_message(cmd, **kwargs)`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_flags_verbose_7c17dc099c | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_message_warns_on_tmux_error | Tmux stderr on ``display-message`` surfaces as a :class:`UserWarning`; fixtures `session`; execute `pane.display_message('x', get_text=True, format_string='#{pane_id}')`; expect `pytest.warns(UserWarning, match='only one of -F or argument')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_message_warns_on_tmux_error_752ca8c041 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_panes | Test Pane.display_panes() shows pane numbers; fixtures `control_mode`, `session`; execute `control_mode()`; `pane.display_panes()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_panes_d1be737cbf | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_popup_close_existing | Test Pane.display_popup(close_existing=True) returns cleanly; fixtures `control_mode`, `session`; execute `control_mode()`; `pane.display_popup(close_existing=True)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_close_existing_f3dfd2c33b | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_popup_close_on_success | Test Pane.display_popup() with close_on_success (-EE) alone; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(command=f'touch {marker}', close_on_success=True)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_close_on_success_48a77d755a | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_popup_flags[basic] | Test Pane.display_popup() flag combinations; case inputs `kwargs={}`, `min_tmux_version=None`, `test_id='basic'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_basic_bc482c9f76 | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_display_popup_flags[border_lines_v33] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'border_lines': 'single'}`, `min_tmux_version='3.3'`, `test_id='border_lines_v33'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_border_lines_v33_e860d627fb | 3.3 through 3.7b | planned parity | | tests/test_pane.py::test_display_popup_flags[border_style_v33] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'border_style': 'fg=red'}`, `min_tmux_version='3.3'`, `test_id='border_style_v33'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_border_style_v33_df76b3a3fe | 3.3 through 3.7b | planned parity | | tests/test_pane.py::test_display_popup_flags[close_on_any_key_v36] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'close_on_any_key': True}`, `min_tmux_version='3.6'`, `test_id='close_on_any_key_v36'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_close_on_any_key_v36_86b847dfd8 | 3.6 through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_flags[dimensions] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'width': 40, 'height': 10}`, `min_tmux_version=None`, `test_id='dimensions'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_dimensions_6b52563d9e | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_display_popup_flags[dimensions] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'width': 40, 'height': 10}`, `min_tmux_version=None`, `test_id='dimensions'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_dimensions_6b52563d9e | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_display_popup_flags[environment_v33] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'environment': {'FOO': 'bar'}}`, `min_tmux_version='3.3'`, `test_id='environment_v33'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_environment_v33_0e77730b00 | 3.3 through 3.7b | planned parity | | tests/test_pane.py::test_display_popup_flags[no_border_v33] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'no_border': True}`, `min_tmux_version='3.3'`, `test_id='no_border_v33'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_no_border_v33_a51011ca4d | 3.3 through 3.7b | planned parity | | tests/test_pane.py::test_display_popup_flags[no_keys_v36] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'no_keys': True}`, `min_tmux_version='3.6'`, `test_id='no_keys_v36'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_no_keys_v36_fcf004fe41 | 3.6 through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_flags[position] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'x': 'C', 'y': 'C'}`, `min_tmux_version=None`, `test_id='position'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_position_487ae89d40 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_flags[start_directory] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'start_directory': PosixPath('/tmp')}`, `min_tmux_version=None`, `test_id='start_directory'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_start_directory_077a667155 | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_display_popup_flags[position] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'x': 'C', 'y': 'C'}`, `min_tmux_version=None`, `test_id='position'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_position_487ae89d40 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_popup_flags[start_directory] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'start_directory': PosixPath('/tmp')}`, `min_tmux_version=None`, `test_id='start_directory'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_start_directory_077a667155 | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_display_popup_flags[style_v33] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'style': 'bg=blue'}`, `min_tmux_version='3.3'`, `test_id='style_v33'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_style_v33_4d78d07047 | 3.3 through 3.7b | planned parity | | tests/test_pane.py::test_display_popup_flags[title_v33] | Test Pane.display_popup() flag combinations; case inputs `kwargs={'title': 'popup_title'}`, `min_tmux_version='3.3'`, `test_id='title_v33'`; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(**call_kwargs)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_flags_title_v33_6ae6423d00 | 3.3 through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_mutual_exclusion | close_on_exit and close_on_success are mutually exclusive; fixtures `session`; execute `pane.display_popup(close_on_exit=True, close_on_success=True)`; expect `pytest.raises(ValueError, match='mutually exclusive')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_mutual_exclusion_f3463fcb69 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_display_popup_target_client | Test Pane.display_popup(target_client=...) emits ``-c <client>``; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(command=f'touch {marker}', close_on_exit=True, target_client=ctl.client_name)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_target_client_2d1fc1c6a4 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_find_window | Test Pane.find_window() opens filtered tree; fixtures `session`; execute `pane.find_window('sh')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_find_window_8ed3f18c08 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_join_pane | Test Pane.join() roundtrip with break_pane; fixtures `session`; execute `session.new_window(window_name='test_join')`; `pane.split(shell='sleep 1m')`; `window.refresh()`; `session.refresh()`; `new_pane.break_pane()`; `new_pane.join(window)`; assert `pane is not None`; `len(window.panes) == 2`; `len(window.panes) == 1`; `new_window.window_id not in window_ids`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_join_pane_5ae329eb74 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_join_pane_horizontal | Test Pane.join() with horizontal split; fixtures `session`; execute `session.new_window(window_name='test_join_h')`; `window.resize(height=40, width=80)`; `pane.split(shell='sleep 1m')`; `window.refresh()`; `new_pane.break_pane()`; `new_pane.join(window, vertical=False)`; assert `pane is not None`; `len(window.panes) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_join_pane_horizontal_fc114aa456 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_move_pane | Test Pane.move() moves pane to another window; fixtures `session`; execute `session.new_window(window_name='move_src')`; `pane.split(shell='sleep 1m')`; `session.new_window(window_name='move_dst')`; `pane_to_move.move(w2)`; `w1.refresh()`; `w2.refresh()`; assert `pane is not None`; `len(w1.panes) == 2`; `len(w1.panes) == 1`; `len(w2.panes) == initial_w2_panes + 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_move_pane_d3c9554da4 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_new_pane_error_tags_subcommand | Pane.new_pane() tags LibTmuxException with the new-pane subcommand; fixtures `session`; execute `session.new_window(window_name='new_pane_err')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(target='%99999')`; expect `pytest.raises(exc.LibTmuxException)`; assert `excinfo.value.subcommand == 'new-pane'`; `str(excinfo.value).startswith('new-pane:')`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(target='%99999')`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_error_tags_subcommand_4a743db95a | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_new_pane_floating | Pane.new_pane() creates a floating pane on tmux 3.7+ (else raises); fixtures `session`; execute `session.new_window(window_name='floating_pane')`; `window.resize(height=50, width=200)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=80, height=15, x=5, y=3, shell='sleep 30')`; assert `floating.pane_floating_flag == '1'`; `floating.pane_width == '80'`; `floating.pane_height == '15'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10)`; expect `pytest.raises(exc.LibTmuxException, match='new_pane .*requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_floating_6b77d7b2b5 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_new_pane_keep | Pane.new_pane(keep=True) sets remain-on-exit (-k) on tmux 3.7+; fixtures `session`; execute `session.new_window(window_name='floating_keep')`; `window.resize(height=50, width=200)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=80, height=15, keep=True, shell='sleep 30')`; `floating.cmd('show-options', '-p', '-v', 'remain-on-exit')`; assert `floating.pane_floating_flag == '1'`; `remain == ['key']`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, keep=True)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_keep_4b5ffc0512 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_new_pane_preserves_empty_option_values[active_border_style] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'active_border_style': ''}`, `test_id='active_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#_pane_py_test_new_pane_preserves_empty_option_values_active_border_style_8570ad6ce9 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_new_pane_preserves_empty_option_values[inactive_border_style] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'inactive_border_style': ''}`, `test_id='inactive_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#ane_py_test_new_pane_preserves_empty_option_values_inactive_border_style_6ccbab863c | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_new_pane_preserves_empty_option_values[message] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'message': ''}`, `test_id='message'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_preserves_empty_option_values_message_c68b33a3e7 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_new_pane_preserves_empty_option_values[style] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'style': ''}`, `test_id='style'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_preserves_empty_option_values_style_d25122e330 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_pane_context_manager | Test Pane context manager functionality; fixtures `session`; execute `session.new_window()`; `window.split()`; `window.refresh()`; assert `len(window.panes) == initial_pane_count + 1`; `pane in window.panes`; `len(window.panes) == initial_pane_count`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_context_manager_62ed08382e | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_dead] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_dead'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_dead_ae18d02006 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_format] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_format'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_format_ed1d8fbe23 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_in_mode] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_in_mode'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_in_mode_9596e57fe2 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_input_off] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_input_off'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_format_field_declared_and_hydrated_pane_input_off_461985887b | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_last] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_last'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_last_34a343452d | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_marked] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_marked'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_marked_605d545bd1 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_marked_set] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_marked_set'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#est_pane_py_test_pane_format_field_declared_and_hydrated_pane_marked_set_b0e3ed48e9 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_mode] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_mode'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_mode_37ec00ef51 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_path] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_path'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_path_fa345ec315 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_pipe] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_pipe'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_pipe_b422778718 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_synchronized] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_synchronized'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_pane_py_test_pane_format_field_declared_and_hydrated_pane_synchronized_65a88cc186 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_refresh_raises_when_pane_id_is_none | ``Pane.refresh()`` raises ``ValueError`` when ``pane_id`` is unset; fixtures `session`; execute `pane.refresh()`; `Pane(server=session.server)`; expect `pytest.raises(ValueError, match='pane_id')`; assert `pane.pane_id is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_refresh_raises_when_pane_id_is_none_66f817fe96 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_reset_clears_history_and_sends_reset | Pane.reset() runs both ``send-keys -R`` and ``clear-history`` (#650); fixtures `session`; execute `session.new_window(window_name='test_reset_650', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(f'echo reset_marker_{n}', enter=True)`; `pane.capture_pane(start=-100)`; `pane.reset()`; `shutil.which('env')`; assert `env is not None`; `pane is not None`; `any(('reset_marker_' in line for line in pre))`; `result is pane`; `not any(('reset_marker_' in line for line in post))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_reset_clears_history_and_sends_reset_3e29bfb3f5 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_reset_targets_non_active_pane | Pane.reset() clears the target pane, not tmux's cmdq default; fixtures `session`; execute `session.new_window(window_name='test_reset_targets', window_shell=f"{env} PS1='$ ' sh")`; `window.split(shell=f"{env} PS1='$ ' sh", attach=False)`; `window.refresh()`; `pane.cmd('display-message', '-p', '#{history_size}')`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(active_sibling.capture_pane()), 2, raises=True)`; assert `env is not None`; `active_sibling is not None`; `target.pane_id != active_sibling.pane_id`; `window.active_pane is not None`; `window.active_pane.pane_id == active_sibling.pane_id`; `target_pre > 0`; `sibling_pre > 0`; `_history_size(target) == 0`; `_history_size(active_sibling) == sibling_pre`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_reset_targets_non_active_pane_5a58caa018 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[alternate_saved_x] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='alternate_saved_x'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_scope_override_field_hydrates_alternate_saved_x_46815334e9 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[alternate_saved_y] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='alternate_saved_y'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_scope_override_field_hydrates_alternate_saved_y_3e0f3e7dc9 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[cursor_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='cursor_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_cursor_flag_2a795ab287 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[cursor_x] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='cursor_x'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_cursor_x_d4f9b7de10 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[cursor_y] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='cursor_y'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_cursor_y_873ac3adee | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[history_bytes] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='history_bytes'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_history_bytes_c312d4adc7 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[history_limit] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='history_limit'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_history_limit_487cddb770 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[history_size] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='history_size'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_history_size_777bb7d2c3 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[insert_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='insert_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_insert_flag_5037a94cc9 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[keypad_cursor_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='keypad_cursor_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_py_test_pane_scope_override_field_hydrates_keypad_cursor_flag_a006dd02a9 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[keypad_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='keypad_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_keypad_flag_3a6819feee | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_all_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_all_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_scope_override_field_hydrates_mouse_all_flag_148b334003 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_any_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_any_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_scope_override_field_hydrates_mouse_any_flag_fb70b3e8c7 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_button_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_button_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_scope_override_field_hydrates_mouse_button_flag_9afa1efe7c | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_sgr_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_sgr_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_scope_override_field_hydrates_mouse_sgr_flag_8944c5767a | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_standard_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_standard_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_scope_override_field_hydrates_mouse_standard_flag_b5d6bfaea5 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[origin_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='origin_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_origin_flag_3898d39be2 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[scroll_region_lower] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='scroll_region_lower'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_scope_override_field_hydrates_scroll_region_lower_f95cb8d620 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[scroll_region_upper] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='scroll_region_upper'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_scope_override_field_hydrates_scroll_region_upper_1a2328f65d | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_scope_override_field_hydrates[wrap_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='wrap_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_wrap_flag_668d7959ab | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_split_window_zoom | Verify splitting window with zoom; fixtures `session`; execute `session.new_window(window_name='split_without_zoom')`; `session.new_window(window_name='split_with_zoom')`; `initial_pane_without_zoom.split(zoom=False)`; `initial_pane_with_zoom.split(zoom=True)`; assert `initial_pane_without_zoom is not None`; `initial_pane_with_zoom is not None`; `pane_without_zoom.width == pane_without_zoom.window_width`; `pane_without_zoom.height is not None`; `pane_without_zoom.window_height is not None`; `pane_without_zoom.height < pane_without_zoom.window_height`; `pane_with_zoom.width == pane_with_zoom.window_width`; `pane_with_zoom.height == pane_with_zoom.window_height`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_split_window_zoom_618e4c0a8e | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_pane_synchronized_reflects_window_state | ``pane.pane_synchronized`` flips when synchronize-panes toggles; fixtures `session`; execute `window.split()`; `window.set_option('synchronize-panes', 'on')`; `pane.refresh()`; `window.set_option('synchronize-panes', 'off')`; assert `pane is not None`; `pane.pane_synchronized == '1'`; `pane.pane_synchronized == '0'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_synchronized_reflects_window_state_bddbaaffa5 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_paste_buffer | Test Pane.paste_buffer() pastes buffer content into pane; fixtures `session`; execute `session.new_window(window_name='test_paste', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `session.server.set_buffer('pasted_content', buffer_name='paste_test')`; `pane.paste_buffer(buffer_name='paste_test')`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; assert `env is not None`; `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_paste_buffer_e8a928f842 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_paste_buffer_no_vis | Pane.paste_buffer(no_vis=True) uses -S on tmux 3.7+; warn below; fixtures `session`; execute `session.new_window(window_name='paste_novis', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `session.server.set_buffer('raw_content', buffer_name='novis_test')`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None`; `pane is not None`; when `has_gte_version('3.7')`: execute `pane.paste_buffer(buffer_name='novis_test', no_vis=True)`; `pane.capture_pane()`; `retry_until(lambda: 'raw_content' in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not (has_gte_version('3.7'))`: execute `pane.paste_buffer(buffer_name='novis_test', no_vis=True)`; expect `pytest.warns(UserWarning, match='no_vis requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_paste_buffer_no_vis_a1475a57ed | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_pipe_pane | Test Pane.pipe() pipes output to a file; fixtures `session`, `tmp_path`; execute `session.new_window(window_name='test_pipe', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.pipe(f'cat >> {pipe_file}')`; `pane.send_keys('echo pipe_test_ok', enter=True)`; `pane.pipe()`; `shutil.which('env')`; assert `env is not None`; `pane is not None`; `'pipe_test_ok' in content`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pipe_pane_e8f22fe893 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_resize | Verify resizing pane; fixtures `session`; execute `session.cmd('detach-client', '-s')`; `window.split(attach=False)`; `window.split(direction=PaneDirection.Above, attach=False)`; `window.resize(height=500, width=500)`; `pane.resize(height='50')`; `window.select_layout('main-horizontal')`; assert `pane is not None`; `pane.pane_height is not None`; `pane.pane_width is not None`; `int(pane.pane_height) == 50`; `int(pane.pane_width) == 75`; `int(pane.pane_height) == 75`; `pane_height_before - pane_height_adjustment * 2 == int(pane.pane_height)`; `pane_height_before + pane_height_adjustment == int(pane.pane_height)`; `pane_height_before < pane_height_expanded`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_resize_7efb639998 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_respawn_pane_kill | Test Pane.respawn() with kill flag on active pane; fixtures `session`; execute `session.new_window(window_name='test_respawn')`; `pane.respawn(kill=True, shell='sh')`; `pane.refresh()`; assert `pane is not None`; `pane in window.panes`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_respawn_pane_kill_a42cf02479 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_select_pane_direction | Test Pane.select() with direction flags; fixtures `session`; execute `session.new_window(window_name='test_select_dir')`; `window.resize(height=40, width=80)`; `pane_top.split(direction=PaneDirection.Below)`; `pane_bottom.select()`; `pane_bottom.refresh()`; `pane_bottom.select(direction=ResizeAdjustmentDirection.Up)`; assert `pane_top is not None`; `pane_bottom.pane_active == '1'`; `pane_top.pane_active == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_direction_fb5f1d3c55 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_select_pane_disable_enable_input | Test Pane.select() with disable/enable input flags; fixtures `session`; execute `session.new_window(window_name='test_input_toggle', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.select(disable_input=True)`; `pane.send_keys('echo disabled_test', enter=False)`; `pane.select(enable_input=True)`; `pane.send_keys('echo enabled_ok', enter=True)`; assert `env is not None`; `pane is not None`; `'disabled_test' not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_disable_enable_input_b4359f56b1 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_select_pane_last | Test Pane.select() with last flag; fixtures `session`; execute `session.new_window(window_name='test_select_last')`; `pane1.split()`; `pane2.select()`; `pane2.refresh()`; `pane1.select()`; `pane1.refresh()`; assert `pane1 is not None`; `pane2.pane_active == '1'`; `pane1.pane_active == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_last_94129b39c1 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_select_pane_mark | Test Pane.select() with mark/clear_mark flags; fixtures `session`; execute `session.new_window(window_name='test_select_mark')`; `pane.select(mark=True)`; `pane.select(clear_mark=True)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_mark_2345b5e28f | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_send_keys | Verify Pane.send_keys(); fixtures `session`; execute `pane.send_keys('c-c', literal=True)`; `pane.cmd('capture-pane', '-p')`; `pane.send_keys('c-a', literal=False)`; `'\n'.join(pane.cmd('capture-pane', '-p').stdout)`; assert `pane is not None`; `'c-c' in pane_contents`; `'c-a' not in pane_contents; message='should not print to pane'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_13758b0615 | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_display_popup_mutual_exclusion | close_on_exit and close_on_success are mutually exclusive; fixtures `session`; execute `pane.display_popup(close_on_exit=True, close_on_success=True)`; expect `pytest.raises(ValueError, match='mutually exclusive')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_mutual_exclusion_f3463fcb69 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_display_popup_target_client | Test Pane.display_popup(target_client=...) emits ``-c <client>``; fixtures `control_mode`, `session`, `tmp_path`; execute `control_mode()`; `pane.display_popup(command=f'touch {marker}', close_on_exit=True, target_client=ctl.client_name)`; `retry_until(lambda: marker.exists(), 3, raises=True)`; `marker.exists()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_display_popup_target_client_2d1fc1c6a4 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_find_window | Test Pane.find_window() opens filtered tree; fixtures `session`; execute `pane.find_window('sh')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_find_window_8ed3f18c08 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_join_pane | Test Pane.join() roundtrip with break_pane; fixtures `session`; execute `session.new_window(window_name='test_join')`; `pane.split(shell='sleep 1m')`; `window.refresh()`; `session.refresh()`; `new_pane.break_pane()`; `new_pane.join(window)`; assert `pane is not None`; `len(window.panes) == 2`; `len(window.panes) == 1`; `new_window.window_id not in window_ids`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_join_pane_5ae329eb74 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_join_pane_horizontal | Test Pane.join() with horizontal split; fixtures `session`; execute `session.new_window(window_name='test_join_h')`; `window.resize(height=40, width=80)`; `pane.split(shell='sleep 1m')`; `window.refresh()`; `new_pane.break_pane()`; `new_pane.join(window, vertical=False)`; assert `pane is not None`; `len(window.panes) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_join_pane_horizontal_fc114aa456 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_move_pane | Test Pane.move() moves pane to another window; fixtures `session`; execute `session.new_window(window_name='move_src')`; `pane.split(shell='sleep 1m')`; `session.new_window(window_name='move_dst')`; `pane_to_move.move(w2)`; `w1.refresh()`; `w2.refresh()`; assert `pane is not None`; `len(w1.panes) == 2`; `len(w1.panes) == 1`; `len(w2.panes) == initial_w2_panes + 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_move_pane_d3c9554da4 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_new_pane_error_tags_subcommand | Pane.new_pane() tags LibTmuxException with the new-pane subcommand; fixtures `session`; execute `session.new_window(window_name='new_pane_err')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(target='%99999')`; expect `pytest.raises(exc.LibTmuxException)`; assert `excinfo.value.subcommand == 'new-pane'`; `str(excinfo.value).startswith('new-pane:')`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(target='%99999')`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_error_tags_subcommand_4a743db95a | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_new_pane_floating | Pane.new_pane() creates a floating pane on tmux 3.7+ (else raises); fixtures `session`; execute `session.new_window(window_name='floating_pane')`; `window.resize(height=50, width=200)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=80, height=15, x=5, y=3, shell='sleep 30')`; assert `floating.pane_floating_flag == '1'`; `floating.pane_width == '80'`; `floating.pane_height == '15'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10)`; expect `pytest.raises(exc.LibTmuxException, match='new_pane .*requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_floating_6b77d7b2b5 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_new_pane_keep | Pane.new_pane(keep=True) sets remain-on-exit (-k) on tmux 3.7+; fixtures `session`; execute `session.new_window(window_name='floating_keep')`; `window.resize(height=50, width=200)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=80, height=15, keep=True, shell='sleep 30')`; `floating.cmd('show-options', '-p', '-v', 'remain-on-exit')`; assert `floating.pane_floating_flag == '1'`; `remain == ['key']`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, keep=True)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_keep_4b5ffc0512 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_new_pane_preserves_empty_option_values[active_border_style] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'active_border_style': ''}`, `test_id='active_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#_pane_py_test_new_pane_preserves_empty_option_values_active_border_style_8570ad6ce9 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_new_pane_preserves_empty_option_values[inactive_border_style] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'inactive_border_style': ''}`, `test_id='inactive_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#ane_py_test_new_pane_preserves_empty_option_values_inactive_border_style_6ccbab863c | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_new_pane_preserves_empty_option_values[message] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'message': ''}`, `test_id='message'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_preserves_empty_option_values_message_c68b33a3e7 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_new_pane_preserves_empty_option_values[style] | Pane.new_pane() preserves empty string option values; case inputs `option_kwargs={'style': ''}`, `test_id='style'`; fixtures `session`; execute `session.new_window(window_name=f'floating_empty_{test_id}')`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.new_pane(width=40, height=10, shell='sleep 30', **option_kwargs)`; assert `floating.pane_floating_flag == '1'`; when `not (has_gte_version('3.7'))`: execute `pane.new_pane(width=40, height=10, **option_kwargs)`; expect `pytest.raises(exc.LibTmuxException, match='requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_new_pane_preserves_empty_option_values_style_d25122e330 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_pane_context_manager | Test Pane context manager functionality; fixtures `session`; execute `session.new_window()`; `window.split()`; `window.refresh()`; assert `len(window.panes) == initial_pane_count + 1`; `pane in window.panes`; `len(window.panes) == initial_pane_count`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_context_manager_62ed08382e | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_dead] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_dead'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_dead_ae18d02006 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_format] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_format'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_format_ed1d8fbe23 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_in_mode] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_in_mode'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_in_mode_9596e57fe2 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_input_off] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_input_off'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_format_field_declared_and_hydrated_pane_input_off_461985887b | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_last] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_last'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_last_34a343452d | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_marked] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_marked'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_marked_605d545bd1 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_marked_set] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_marked_set'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#est_pane_py_test_pane_format_field_declared_and_hydrated_pane_marked_set_b0e3ed48e9 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_mode] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_mode'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_mode_37ec00ef51 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_path] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_path'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_path_fa345ec315 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_pipe] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_pipe'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_format_field_declared_and_hydrated_pane_pipe_b422778718 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_format_field_declared_and_hydrated[pane_synchronized] | Tmux's pane-scope format tokens hydrate onto the typed ``Pane`` object; case inputs `field_name='pane_synchronized'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `field_name in pane.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_pane_py_test_pane_format_field_declared_and_hydrated_pane_synchronized_65a88cc186 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_refresh_raises_when_pane_id_is_none | ``Pane.refresh()`` raises ``ValueError`` when ``pane_id`` is unset; fixtures `session`; execute `pane.refresh()`; `Pane(server=session.server)`; expect `pytest.raises(ValueError, match='pane_id')`; assert `pane.pane_id is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_refresh_raises_when_pane_id_is_none_66f817fe96 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_reset_clears_history_and_sends_reset | Pane.reset() runs both ``send-keys -R`` and ``clear-history`` (#650); fixtures `session`; execute `session.new_window(window_name='test_reset_650', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(f'echo reset_marker_{n}', enter=True)`; `pane.capture_pane(start=-100)`; `pane.reset()`; `shutil.which('env')`; assert `env is not None`; `pane is not None`; `any(('reset_marker_' in line for line in pre))`; `result is pane`; `not any(('reset_marker_' in line for line in post))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_reset_clears_history_and_sends_reset_3e29bfb3f5 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_reset_targets_non_active_pane | Pane.reset() clears the target pane, not tmux's cmdq default; fixtures `session`; execute `session.new_window(window_name='test_reset_targets', window_shell=f"{env} PS1='$ ' sh")`; `window.split(shell=f"{env} PS1='$ ' sh", attach=False)`; `window.refresh()`; `pane.cmd('display-message', '-p', '#{history_size}')`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(active_sibling.capture_pane()), 2, raises=True)`; assert `env is not None`; `active_sibling is not None`; `target.pane_id != active_sibling.pane_id`; `window.active_pane is not None`; `window.active_pane.pane_id == active_sibling.pane_id`; `target_pre > 0`; `sibling_pre > 0`; `_history_size(target) == 0`; `_history_size(active_sibling) == sibling_pre`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_reset_targets_non_active_pane_5a58caa018 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[alternate_saved_x] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='alternate_saved_x'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_scope_override_field_hydrates_alternate_saved_x_46815334e9 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[alternate_saved_y] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='alternate_saved_y'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_scope_override_field_hydrates_alternate_saved_y_3e0f3e7dc9 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[cursor_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='cursor_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_cursor_flag_2a795ab287 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[cursor_x] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='cursor_x'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_cursor_x_d4f9b7de10 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[cursor_y] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='cursor_y'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_cursor_y_873ac3adee | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[history_bytes] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='history_bytes'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_history_bytes_c312d4adc7 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[history_limit] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='history_limit'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_history_limit_487cddb770 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[history_size] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='history_size'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_history_size_777bb7d2c3 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[insert_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='insert_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_insert_flag_5037a94cc9 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[keypad_cursor_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='keypad_cursor_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_py_test_pane_scope_override_field_hydrates_keypad_cursor_flag_a006dd02a9 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[keypad_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='keypad_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_keypad_flag_3a6819feee | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_all_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_all_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_scope_override_field_hydrates_mouse_all_flag_148b334003 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_any_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_any_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_scope_override_field_hydrates_mouse_any_flag_fb70b3e8c7 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_button_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_button_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_py_test_pane_scope_override_field_hydrates_mouse_button_flag_9afa1efe7c | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_sgr_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_sgr_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_pane_scope_override_field_hydrates_mouse_sgr_flag_8944c5767a | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[mouse_standard_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='mouse_standard_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_scope_override_field_hydrates_mouse_standard_flag_b5d6bfaea5 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[origin_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='origin_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_origin_flag_3898d39be2 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[scroll_region_lower] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='scroll_region_lower'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_scope_override_field_hydrates_scroll_region_lower_f95cb8d620 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[scroll_region_upper] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='scroll_region_upper'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_py_test_pane_scope_override_field_hydrates_scroll_region_upper_1a2328f65d | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_scope_override_field_hydrates[wrap_flag] | Per-token scope overrides admit each token into list-panes -F; case inputs `field_name='wrap_flag'`; fixtures `session`; execute `pane.refresh()`; assert `pane is not None`; `value is not None; message=f'{field_name} should hydrate via list-panes'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_scope_override_field_hydrates_wrap_flag_668d7959ab | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_split_window_zoom | Verify splitting window with zoom; fixtures `session`; execute `session.new_window(window_name='split_without_zoom')`; `session.new_window(window_name='split_with_zoom')`; `initial_pane_without_zoom.split(zoom=False)`; `initial_pane_with_zoom.split(zoom=True)`; assert `initial_pane_without_zoom is not None`; `initial_pane_with_zoom is not None`; `pane_without_zoom.width == pane_without_zoom.window_width`; `pane_without_zoom.height is not None`; `pane_without_zoom.window_height is not None`; `pane_without_zoom.height < pane_without_zoom.window_height`; `pane_with_zoom.width == pane_with_zoom.window_width`; `pane_with_zoom.height == pane_with_zoom.window_height`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_split_window_zoom_618e4c0a8e | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_pane_synchronized_reflects_window_state | ``pane.pane_synchronized`` flips when synchronize-panes toggles; fixtures `session`; execute `window.split()`; `window.set_option('synchronize-panes', 'on')`; `pane.refresh()`; `window.set_option('synchronize-panes', 'off')`; assert `pane is not None`; `pane.pane_synchronized == '1'`; `pane.pane_synchronized == '0'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pane_synchronized_reflects_window_state_bddbaaffa5 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_paste_buffer | Test Pane.paste_buffer() pastes buffer content into pane; fixtures `session`; execute `session.new_window(window_name='test_paste', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `session.server.set_buffer('pasted_content', buffer_name='paste_test')`; `pane.paste_buffer(buffer_name='paste_test')`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; assert `env is not None`; `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_paste_buffer_e8a928f842 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_paste_buffer_no_vis | Pane.paste_buffer(no_vis=True) uses -S on tmux 3.7+; warn below; fixtures `session`; execute `session.new_window(window_name='paste_novis', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `session.server.set_buffer('raw_content', buffer_name='novis_test')`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None`; `pane is not None`; when `has_gte_version('3.7')`: execute `pane.paste_buffer(buffer_name='novis_test', no_vis=True)`; `pane.capture_pane()`; `retry_until(lambda: 'raw_content' in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not (has_gte_version('3.7'))`: execute `pane.paste_buffer(buffer_name='novis_test', no_vis=True)`; expect `pytest.warns(UserWarning, match='no_vis requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_paste_buffer_no_vis_a1475a57ed | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_pipe_pane | Test Pane.pipe() pipes output to a file; fixtures `session`, `tmp_path`; execute `session.new_window(window_name='test_pipe', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.pipe(f'cat >> {pipe_file}')`; `pane.send_keys('echo pipe_test_ok', enter=True)`; `pane.pipe()`; `shutil.which('env')`; assert `env is not None`; `pane is not None`; `'pipe_test_ok' in content`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_pipe_pane_e8f22fe893 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_resize | Verify resizing pane; fixtures `session`; execute `session.cmd('detach-client', '-s')`; `window.split(attach=False)`; `window.split(direction=PaneDirection.Above, attach=False)`; `window.resize(height=500, width=500)`; `pane.resize(height='50')`; `window.select_layout('main-horizontal')`; assert `pane is not None`; `pane.pane_height is not None`; `pane.pane_width is not None`; `int(pane.pane_height) == 50`; `int(pane.pane_width) == 75`; `int(pane.pane_height) == 75`; `pane_height_before - pane_height_adjustment * 2 == int(pane.pane_height)`; `pane_height_before + pane_height_adjustment == int(pane.pane_height)`; `pane_height_before < pane_height_expanded`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_resize_7efb639998 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_respawn_pane_kill | Test Pane.respawn() with kill flag on active pane; fixtures `session`; execute `session.new_window(window_name='test_respawn')`; `pane.respawn(kill=True, shell='sh')`; `pane.refresh()`; assert `pane is not None`; `pane in window.panes`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_respawn_pane_kill_a42cf02479 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_select_pane_direction | Test Pane.select() with direction flags; fixtures `session`; execute `session.new_window(window_name='test_select_dir')`; `window.resize(height=40, width=80)`; `pane_top.split(direction=PaneDirection.Below)`; `pane_bottom.select()`; `pane_bottom.refresh()`; `pane_bottom.select(direction=ResizeAdjustmentDirection.Up)`; assert `pane_top is not None`; `pane_bottom.pane_active == '1'`; `pane_top.pane_active == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_direction_fb5f1d3c55 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_select_pane_disable_enable_input | Test Pane.select() with disable/enable input flags; fixtures `session`; execute `session.new_window(window_name='test_input_toggle', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.select(disable_input=True)`; `pane.send_keys('echo disabled_test', enter=False)`; `pane.select(enable_input=True)`; `pane.send_keys('echo enabled_ok', enter=True)`; assert `env is not None`; `pane is not None`; `'disabled_test' not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_disable_enable_input_b4359f56b1 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_select_pane_last | Test Pane.select() with last flag; fixtures `session`; execute `session.new_window(window_name='test_select_last')`; `pane1.split()`; `pane2.select()`; `pane2.refresh()`; `pane1.select()`; `pane1.refresh()`; assert `pane1 is not None`; `pane2.pane_active == '1'`; `pane1.pane_active == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_last_94129b39c1 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_select_pane_mark | Test Pane.select() with mark/clear_mark flags; fixtures `session`; execute `session.new_window(window_name='test_select_mark')`; `pane.select(mark=True)`; `pane.select(clear_mark=True)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_select_pane_mark_2345b5e28f | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_send_keys | Verify Pane.send_keys(); fixtures `session`; execute `pane.send_keys('c-c', literal=True)`; `pane.cmd('capture-pane', '-p')`; `pane.send_keys('c-a', literal=False)`; `'\n'.join(pane.cmd('capture-pane', '-p').stdout)`; assert `pane is not None`; `'c-c' in pane_contents`; `'c-a' not in pane_contents; message='should not print to pane'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_13758b0615 | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_send_keys_flag_only_repeat_emits_dash_N | ``send_keys(repeat=3)`` flag-only emits ``send-keys -N 3`` only; fixtures `monkeypatch`, `session`; execute `pane.send_keys(repeat=3, reset=True)`; `real_cmd(cmd_name, *args, **kw)`; assert `pane is not None`; `send_keys_calls == [('send-keys', '-R', '-N', '3')]`. | unit test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flag_only_repeat_emits_dash_N_bad6b5f577 | not tmux-bound | planned parity | -| tests/test_pane.py::test_send_keys_flag_only_requires_a_flag | ``send_keys()`` with neither ``cmd`` nor a flag raises ValueError; fixtures `session`; execute `pane.send_keys()`; expect `pytest.raises(ValueError, match='requires at least one of')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flag_only_requires_a_flag_13ca72a4e1 | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_send_keys_flag_only_requires_a_flag | ``send_keys()`` with neither ``cmd`` nor a flag raises ValueError; fixtures `session`; execute `pane.send_keys()`; expect `pytest.raises(ValueError, match='requires at least one of')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flag_only_requires_a_flag_13ca72a4e1 | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_send_keys_flag_only_reset_emits_clean_argv | ``send_keys(reset=True)`` (no positional) emits ``send-keys -R`` only; fixtures `monkeypatch`, `session`; execute `pane.send_keys(reset=True)`; `real_cmd(cmd_name, *args, **kw)`; assert `pane is not None`; `send_keys_calls == [('send-keys', '-R')]`. | unit test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flag_only_reset_emits_clean_argv_de59116e65 | not tmux-bound | planned parity | -| tests/test_pane.py::test_send_keys_flags[expand_formats] | Test send_keys() with various flag combinations; case inputs `expected_in_capture='aa'`, `key='a'`, `kwargs={'expand_formats': True, 'repeat': 2, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='expand_formats'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_expand_formats_26bfa49a92 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_send_keys_flags[hex_key_A] | Test send_keys() with various flag combinations; case inputs `expected_in_capture='A'`, `key='41'`, `kwargs={'hex_keys': True, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='hex_key_A'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_hex_key_A_c83696e0cb | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_send_keys_flags[expand_formats] | Test send_keys() with various flag combinations; case inputs `expected_in_capture='aa'`, `key='a'`, `kwargs={'expand_formats': True, 'repeat': 2, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='expand_formats'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_expand_formats_26bfa49a92 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_send_keys_flags[hex_key_A] | Test send_keys() with various flag combinations; case inputs `expected_in_capture='A'`, `key='41'`, `kwargs={'hex_keys': True, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='hex_key_A'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_hex_key_A_c83696e0cb | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_send_keys_flags[key_name_flag] | Test send_keys() with various flag combinations; case inputs `expected_in_capture=None`, `key='a'`, `kwargs={'key_name': True, 'enter': False}`, `min_tmux_version='3.4'`, `not_expected_in_capture=None`, `test_id='key_name_flag'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_key_name_flag_5a362347ab | 3.4 through 3.7b | planned parity | -| tests/test_pane.py::test_send_keys_flags[repeat_count] | Test send_keys() with various flag combinations; case inputs `expected_in_capture='aaa'`, `key='a'`, `kwargs={'repeat': 3, 'literal': True, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='repeat_count'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_repeat_count_c3148b7d12 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_send_keys_flags[reset_terminal] | Test send_keys() with various flag combinations; case inputs `expected_in_capture=None`, `key=''`, `kwargs={'reset': True, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='reset_terminal'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_reset_terminal_20ca6419bf | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_send_prefix | Test Pane.send_prefix() sends prefix key without error; fixtures `session`; execute `pane.send_prefix()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_prefix_6786722605 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_set_height | Verify Pane.set_height(); fixtures `session`; execute `session.new_window(window_name='test_set_height')`; `window.split()`; `pane1.set_height(4)`; assert `pane1 is not None`; `pane1.pane_height != pane1_height`; `pane1.pane_height is not None`; `int(pane1.pane_height) == 4`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_height_1a4e68d297 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_set_title | Test Pane.set_title() sets pane title; fixtures `session`; execute `pane.set_title('test-title')`; assert `pane is not None`; `result == pane`; `pane.pane_title == 'test-title'`; `pane.title == 'test-title'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_title_419f2ad1a8 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_set_title_special_characters | Test Pane.set_title() with spaces and unicode; fixtures `session`; execute `pane.set_title('my pane title')`; `pane.set_title('my π pane')`; assert `pane is not None`; `pane.pane_title == 'my pane title'`; `pane.pane_title == 'my π pane'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_title_special_characters_d0dffc4f6f | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_set_width | Verify Pane.set_width(); fixtures `session`; execute `session.new_window(window_name='test_set_width')`; `window.split()`; `window.select_layout('main-vertical')`; `pane1.set_width(10)`; `pane1.reset()`; assert `pane1 is not None`; `pane1.pane_width != pane1_width`; `pane1.pane_width is not None`; `int(pane1.pane_width) == 10`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_width_933585b385 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_split_3_7_flags[active_border_style] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'active_border_style': 'fg=green'}`, `test_id='active_border_style'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_active_border_style_2f0234c32f | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_3_7_flags[inactive_border_style] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'inactive_border_style': 'fg=blue'}`, `test_id='inactive_border_style'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_inactive_border_style_8111dd8d5a | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_3_7_flags[keep] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'keep': True}`, `test_id='keep'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_keep_6730641a46 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_3_7_flags[message] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'message': 'bye'}`, `test_id='message'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_message_200fa89609 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_3_7_flags[style] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'style': 'bg=red'}`, `test_id='style'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_style_b2764e49dd | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_empty | Pane.split(empty=True) creates an empty pane on tmux 3.7+; fixtures `session`; execute `session.new_window(window_name='split_empty')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(empty=True)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `not (has_gte_version('3.7'))`: execute `pane.split(empty=True)`; expect `pytest.warns(UserWarning, match='empty requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_empty_a941382146 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_pane_size | Pane.split(); fixtures `session`; execute `session.new_window(window_name='split window size')`; `window.resize(height=100, width=100)`; `pane.split(size=10)`; `pane.split(direction=PaneDirection.Right, size=10)`; `pane.split(size='10%')`; `short_pane.split(direction=PaneDirection.Right, size='10%')`; assert `pane is not None`; `short_pane.pane_height == '10'`; `short_pane.at_left`; `short_pane.at_right`; `not short_pane.at_top`; `short_pane.at_bottom`; `narrow_pane.pane_width == '10'`; `not narrow_pane.at_left`; `narrow_pane.at_right`; `narrow_pane.at_top`; `not narrow_pane.at_bottom`; `new_pane.pane_height == '8'`; `new_pane.pane_width == '10'`; `not new_pane.at_left`; `new_pane.at_right`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_pane_size_7b189df9db | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_send_keys_flags[repeat_count] | Test send_keys() with various flag combinations; case inputs `expected_in_capture='aaa'`, `key='a'`, `kwargs={'repeat': 3, 'literal': True, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='repeat_count'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_repeat_count_c3148b7d12 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_send_keys_flags[reset_terminal] | Test send_keys() with various flag combinations; case inputs `expected_in_capture=None`, `key=''`, `kwargs={'reset': True, 'enter': False}`, `min_tmux_version=None`, `not_expected_in_capture=None`, `test_id='reset_terminal'`; fixtures `session`; execute `session.new_window(window_name=f'sk_{test_id[:15]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(key, **kwargs)`; `shutil.which('env')`; `retry_until(lambda: '$' in '\n'.join(pane.capture_pane()), 2, raises=True)`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_capture is not None`: execute `pane.capture_pane()`; `retry_until(lambda: expected_in_capture in '\n'.join(pane.capture_pane()), 3, raises=True)`; `'\n'.join(pane.capture_pane())`; when `not_expected_in_capture is not None`: execute `pane.capture_pane()`; `'\n'.join(pane.capture_pane())`; assert `not_expected_in_capture not in contents`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_keys_flags_reset_terminal_20ca6419bf | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_send_prefix | Test Pane.send_prefix() sends prefix key without error; fixtures `session`; execute `pane.send_prefix()`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_send_prefix_6786722605 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_set_height | Verify Pane.set_height(); fixtures `session`; execute `session.new_window(window_name='test_set_height')`; `window.split()`; `pane1.set_height(4)`; assert `pane1 is not None`; `pane1.pane_height != pane1_height`; `pane1.pane_height is not None`; `int(pane1.pane_height) == 4`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_height_1a4e68d297 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_set_title | Test Pane.set_title() sets pane title; fixtures `session`; execute `pane.set_title('test-title')`; assert `pane is not None`; `result == pane`; `pane.pane_title == 'test-title'`; `pane.title == 'test-title'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_title_419f2ad1a8 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_set_title_special_characters | Test Pane.set_title() with spaces and unicode; fixtures `session`; execute `pane.set_title('my pane title')`; `pane.set_title('my π pane')`; assert `pane is not None`; `pane.pane_title == 'my pane title'`; `pane.pane_title == 'my π pane'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_title_special_characters_d0dffc4f6f | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_set_width | Verify Pane.set_width(); fixtures `session`; execute `session.new_window(window_name='test_set_width')`; `window.split()`; `window.select_layout('main-vertical')`; `pane1.set_width(10)`; `pane1.reset()`; assert `pane1 is not None`; `pane1.pane_width != pane1_width`; `pane1.pane_width is not None`; `int(pane1.pane_width) == 10`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_set_width_933585b385 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_split_3_7_flags[active_border_style] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'active_border_style': 'fg=green'}`, `test_id='active_border_style'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_active_border_style_2f0234c32f | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_3_7_flags[inactive_border_style] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'inactive_border_style': 'fg=blue'}`, `test_id='inactive_border_style'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_inactive_border_style_8111dd8d5a | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_3_7_flags[keep] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'keep': True}`, `test_id='keep'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_keep_6730641a46 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_3_7_flags[message] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'message': 'bye'}`, `test_id='message'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_message_200fa89609 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_3_7_flags[style] | Pane.split() tmux 3.7 styling/remain flags work on 3.7+; warn below; case inputs `kwargs={'style': 'bg=red'}`, `test_id='style'`; fixtures `session`; execute `session.new_window(window_name='split_37')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_3_7_flags_style_b2764e49dd | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_empty | Pane.split(empty=True) creates an empty pane on tmux 3.7+; fixtures `session`; execute `session.new_window(window_name='split_empty')`; `window.resize(height=40, width=80)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(empty=True)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `not (has_gte_version('3.7'))`: execute `pane.split(empty=True)`; expect `pytest.warns(UserWarning, match='empty requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_empty_a941382146 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_pane_size | Pane.split(); fixtures `session`; execute `session.new_window(window_name='split window size')`; `window.resize(height=100, width=100)`; `pane.split(size=10)`; `pane.split(direction=PaneDirection.Right, size=10)`; `pane.split(size='10%')`; `short_pane.split(direction=PaneDirection.Right, size='10%')`; assert `pane is not None`; `short_pane.pane_height == '10'`; `short_pane.at_left`; `short_pane.at_right`; `not short_pane.at_top`; `short_pane.at_bottom`; `narrow_pane.pane_width == '10'`; `not narrow_pane.at_left`; `narrow_pane.at_right`; `narrow_pane.at_top`; `not narrow_pane.at_bottom`; `new_pane.pane_height == '8'`; `new_pane.pane_width == '10'`; `not new_pane.at_left`; `new_pane.at_right`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_pane_size_7b189df9db | 3.3a through 3.7c | planned parity | | tests/test_pane.py::test_split_percentage | Test Pane.split() with percentage parameter; fixtures `session`; execute `session.new_window(window_name='test_split_pct')`; `window.resize(height=40, width=80)`; `pane.split(percentage=25)`; `new_pane.refresh()`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; `new_pane.pane_height is not None`; `int(new_pane.pane_height) <= 15`; when `not has_gte_version('3.5')`: skip via `pytest.skip('split-window -p was broken in tmux 3.4 (fixed in 3.5)')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_percentage_ed7d56084d | 3.5 through 3.7b | planned parity | -| tests/test_pane.py::test_split_percentage_size_mutual_exclusion | Test that size and percentage are mutually exclusive; fixtures `session`; execute `session.new_window(window_name='test_split_mutex')`; `pane.split(size=10, percentage=50)`; expect `pytest.raises(ValueError, match='Cannot specify both')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_percentage_size_mutual_exclusion_5c93d7dcfe | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_split_preserves_empty_option_values[active_border_style] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'active_border_style': ''}`, `test_id='active_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#est_pane_py_test_split_preserves_empty_option_values_active_border_style_8191759322 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_preserves_empty_option_values[inactive_border_style] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'inactive_border_style': ''}`, `test_id='inactive_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#t_pane_py_test_split_preserves_empty_option_values_inactive_border_style_2b3f5b8d5a | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_preserves_empty_option_values[message] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'message': ''}`, `test_id='message'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_preserves_empty_option_values_message_751487eb9d | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_preserves_empty_option_values[style] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'style': ''}`, `test_id='style'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_preserves_empty_option_values_style_96cb897ab0 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_pane.py::test_split_start_directory[empty_string] | Test Pane.split start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_empty_string_1a004c8e32 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_split_start_directory[none_value] | Test Pane.split start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_none_value_4f81075b4d | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_split_start_directory[relative_path] | Test Pane.split start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_relative_path_56f7922798 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_split_start_directory[user_path] | Test Pane.split start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_user_path_c4843ae174 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_split_start_directory_pathlib | Test Pane.split accepts pathlib.Path for start_directory; fixtures `session`, `user_path`; execute `session.new_window(window_name='test_split_pathlib')`; `pane.split(start_directory=user_path)`; `user_path.resolve()`; `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_pathlib_ebe061c9ff | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_swap_pane | Test Pane.swap() swaps two panes; fixtures `session`; execute `session.new_window(window_name='test_swap_pane')`; `window.resize(height=40, width=80)`; `pane1.split()`; `pane1.refresh()`; `pane2.refresh()`; `pane1.swap(pane2)`; assert `pane1 is not None`; `pane1.pane_index == pane2_idx`; `pane2.pane_index == pane1_idx`; `pane1.pane_id == pane1_id`; `pane2.pane_id == pane2_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_swap_pane_2a51c3efd2 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_swap_pane_invalid_args[kwargs0-target or move_up] | Pane.swap() rejects missing or conflicting arguments; case inputs `kwargs={}`, `match='target or move_up'`; fixtures `session`; execute `pane.swap(**kwargs)`; expect `pytest.raises(exc.LibTmuxException, match=match)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_swap_pane_invalid_args_kwargs0_target_or_move_up_59c9ccf38e | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_swap_pane_invalid_args[kwargs1-mutually exclusive] | Pane.swap() rejects missing or conflicting arguments; case inputs `kwargs={'move_up': True, 'move_down': True}`, `match='mutually exclusive'`; fixtures `session`; execute `pane.swap(**kwargs)`; expect `pytest.raises(exc.LibTmuxException, match=match)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_swap_pane_invalid_args_kwargs1_mutually_exclusive_0ce8e5bc8c | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_swap_pane_invalid_args[kwargs2-mutually exclusive] | Pane.swap() rejects missing or conflicting arguments; case inputs `kwargs={'move_up': True, 'target': '%0'}`, `match='mutually exclusive'`; fixtures `session`; execute `pane.swap(**kwargs)`; expect `pytest.raises(exc.LibTmuxException, match=match)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_swap_pane_invalid_args_kwargs2_mutually_exclusive_b2cf295c41 | 3.2a through 3.7b | planned parity | -| tests/test_pane.py::test_swap_pane_move_up_down | Pane.swap(move_up=True) / (move_down=True) work without a target; fixtures `session`; execute `session.new_window(window_name='test_swap_move')`; `window.resize(height=40, width=80)`; `pane1.split()`; `pane1.refresh()`; `pane2.refresh()`; `pane1.swap(move_down=True)`; assert `pane1 is not None`; `pane1.pane_index == pane2_idx`; `pane2.pane_index == pane1_idx`; `pane1.pane_index == pane1_idx`; `pane2.pane_index == pane2_idx`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_swap_pane_move_up_down_2aa558c99e | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_backward_compatible | Test that capture_pane() works without any new parameters; fixtures `session`; execute `session.active_window.split(shell='sh')`; `pane.capture_pane()`; `pane.send_keys('echo "backward compat test"', enter=True)`; `'\n'.join(pane.capture_pane())`; `retry_until(prompt_ready, 2, raises=True)`; `retry_until(output_ready, 2, raises=True)`; assert `isinstance(output, list)`; `any(('backward compat test' in line for line in output))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_backward_compatible_dadcc36ef4 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flag_smoke[alternate_screen] | Smoke-test capture_pane flags that aren't tied to output content; case inputs `kwargs={'alternate_screen': True}`, `min_tmux_version=None`; fixtures `session`; execute `pane.capture_pane(**kwargs)`; assert `pane is not None`; `isinstance(result, list)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_capture_pane_py_test_capture_pane_flag_smoke_alternate_screen_779ce81a87 | 3.2a through 3.7b | planned parity | +| tests/test_pane.py::test_split_percentage_size_mutual_exclusion | Test that size and percentage are mutually exclusive; fixtures `session`; execute `session.new_window(window_name='test_split_mutex')`; `pane.split(size=10, percentage=50)`; expect `pytest.raises(ValueError, match='Cannot specify both')`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_percentage_size_mutual_exclusion_5c93d7dcfe | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_split_preserves_empty_option_values[active_border_style] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'active_border_style': ''}`, `test_id='active_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#est_pane_py_test_split_preserves_empty_option_values_active_border_style_8191759322 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_preserves_empty_option_values[inactive_border_style] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'inactive_border_style': ''}`, `test_id='inactive_border_style'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#t_pane_py_test_split_preserves_empty_option_values_inactive_border_style_2b3f5b8d5a | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_preserves_empty_option_values[message] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'message': ''}`, `test_id='message'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_preserves_empty_option_values_message_751487eb9d | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_preserves_empty_option_values[style] | Pane.split() emits empty value flags as a separate argv entry; case inputs `option_kwargs={'style': ''}`, `test_id='style'`; fixtures `session`; execute `session.new_window(window_name=f'split_empty_{test_id}')`; `window.resize(height=40, width=120)`; assert `pane is not None`; when `has_gte_version('3.7')`: execute `pane.split(**option_kwargs)`; assert `new_pane in window.panes`; when `not (has_gte_version('3.7'))`: execute `pane.split(**option_kwargs)`; expect `pytest.warns(UserWarning, match='require tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_preserves_empty_option_values_style_96cb897ab0 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_pane.py::test_split_start_directory[empty_string] | Test Pane.split start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_empty_string_1a004c8e32 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_split_start_directory[none_value] | Test Pane.split start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_none_value_4f81075b4d | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_split_start_directory[relative_path] | Test Pane.split start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_relative_path_56f7922798 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_split_start_directory[user_path] | Test Pane.split start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_split_{test_id}')`; `pane.split(start_directory=actual_start_directory)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_user_path_c4843ae174 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_split_start_directory_pathlib | Test Pane.split accepts pathlib.Path for start_directory; fixtures `session`, `user_path`; execute `session.new_window(window_name='test_split_pathlib')`; `pane.split(start_directory=user_path)`; `user_path.resolve()`; `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `pane is not None`; `new_pane in window.panes`; `len(window.panes) == 2`; `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_split_start_directory_pathlib_ebe061c9ff | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_swap_pane | Test Pane.swap() swaps two panes; fixtures `session`; execute `session.new_window(window_name='test_swap_pane')`; `window.resize(height=40, width=80)`; `pane1.split()`; `pane1.refresh()`; `pane2.refresh()`; `pane1.swap(pane2)`; assert `pane1 is not None`; `pane1.pane_index == pane2_idx`; `pane2.pane_index == pane1_idx`; `pane1.pane_id == pane1_id`; `pane2.pane_id == pane2_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_swap_pane_2a51c3efd2 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_swap_pane_invalid_args[kwargs0-target or move_up] | Pane.swap() rejects missing or conflicting arguments; case inputs `kwargs={}`, `match='target or move_up'`; fixtures `session`; execute `pane.swap(**kwargs)`; expect `pytest.raises(exc.LibTmuxException, match=match)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_swap_pane_invalid_args_kwargs0_target_or_move_up_59c9ccf38e | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_swap_pane_invalid_args[kwargs1-mutually exclusive] | Pane.swap() rejects missing or conflicting arguments; case inputs `kwargs={'move_up': True, 'move_down': True}`, `match='mutually exclusive'`; fixtures `session`; execute `pane.swap(**kwargs)`; expect `pytest.raises(exc.LibTmuxException, match=match)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_swap_pane_invalid_args_kwargs1_mutually_exclusive_0ce8e5bc8c | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_swap_pane_invalid_args[kwargs2-mutually exclusive] | Pane.swap() rejects missing or conflicting arguments; case inputs `kwargs={'move_up': True, 'target': '%0'}`, `match='mutually exclusive'`; fixtures `session`; execute `pane.swap(**kwargs)`; expect `pytest.raises(exc.LibTmuxException, match=match)`; assert `pane is not None`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_pane_py_test_swap_pane_invalid_args_kwargs2_mutually_exclusive_b2cf295c41 | 3.3a through 3.7c | planned parity | +| tests/test_pane.py::test_swap_pane_move_up_down | Pane.swap(move_up=True) / (move_down=True) work without a target; fixtures `session`; execute `session.new_window(window_name='test_swap_move')`; `window.resize(height=40, width=80)`; `pane1.split()`; `pane1.refresh()`; `pane2.refresh()`; `pane1.swap(move_down=True)`; assert `pane1 is not None`; `pane1.pane_index == pane2_idx`; `pane2.pane_index == pane1_idx`; `pane1.pane_index == pane1_idx`; `pane2.pane_index == pane2_idx`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_py_test_swap_pane_move_up_down_2aa558c99e | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_backward_compatible | Test that capture_pane() works without any new parameters; fixtures `session`; execute `session.active_window.split(shell='sh')`; `pane.capture_pane()`; `pane.send_keys('echo "backward compat test"', enter=True)`; `'\n'.join(pane.capture_pane())`; `retry_until(prompt_ready, 2, raises=True)`; `retry_until(output_ready, 2, raises=True)`; assert `isinstance(output, list)`; `any(('backward compat test' in line for line in output))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_backward_compatible_dadcc36ef4 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flag_smoke[alternate_screen] | Smoke-test capture_pane flags that aren't tied to output content; case inputs `kwargs={'alternate_screen': True}`, `min_tmux_version=None`; fixtures `session`; execute `pane.capture_pane(**kwargs)`; assert `pane is not None`; `isinstance(result, list)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_capture_pane_py_test_capture_pane_flag_smoke_alternate_screen_779ce81a87 | 3.3a through 3.7c | planned parity | | tests/test_pane_capture_pane.py::test_capture_pane_flag_smoke[mode_screen_v36] | Smoke-test capture_pane flags that aren't tied to output content; case inputs `kwargs={'mode_screen': True}`, `min_tmux_version='3.6'`; fixtures `session`; execute `pane.capture_pane(**kwargs)`; assert `pane is not None`; `isinstance(result, list)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flag_smoke_mode_screen_v36_5627da7a9c | 3.6 through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flag_smoke[quiet] | Smoke-test capture_pane flags that aren't tied to output content; case inputs `kwargs={'quiet': True}`, `min_tmux_version=None`; fixtures `session`; execute `pane.capture_pane(**kwargs)`; assert `pane is not None`; `isinstance(result, list)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_flag_smoke_quiet_6f1c8513d6 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[all_flags_except_trim] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[34mBLUE: %s \\033[0m" "test"'`, `escape_non_printable=True`, `escape_sequences=True`, `expected_pattern='\\x1b\\[34m|BLUE'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=True`, `test_id='all_flags_except_trim'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_capture_pane_py_test_capture_pane_flags_all_flags_except_trim_922d38ac76 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[basic_capture] | Test capture_pane() with various flag combinations; case inputs `command='echo "hello world"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='hello world'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='basic_capture'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_flags_basic_capture_c13b79057e | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[basic_multiline] | Test capture_pane() with various flag combinations; case inputs `command='printf "line1\\nline2\\nline3\\n"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='line1.*line2.*line3'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='basic_multiline'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_flags_basic_multiline_faf8bbdc1a | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_non_printable_basic] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\001\\002\\003"'`, `escape_non_printable=True`, `escape_sequences=False`, `expected_pattern='\\\\00[123]'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_non_printable_basic'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_pane_capture_pane_py_test_capture_pane_flags_escape_non_printable_basic_469ed4ffd7 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_non_printable_tab] | Test capture_pane() with various flag combinations; case inputs `command='printf "a\\tb"'`, `escape_non_printable=True`, `escape_sequences=False`, `expected_pattern='a.*b'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_non_printable_tab'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_capture_pane_py_test_capture_pane_flags_escape_non_printable_tab_5898542c8c | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_bold] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[1mBOLD\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[1m'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_bold'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_bold_da0623516d | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_green] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[32mGREEN\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[32m'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_green'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_green_cad850baea | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_red] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[31mRED\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[31m'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_red'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_red_c8e93b8afa | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_with_join] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[32mGREEN TEXT\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[32m'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_with_join'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_with_join_9a8e67967f | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[join_with_preserve_trailing] | Test capture_pane() with various flag combinations; case inputs `command='printf "%s " "$(python3 -c \'print("z" * 100)\')"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='z{50,}'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=True`, `test_id='join_with_preserve_trailing'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#pane_capture_pane_py_test_capture_pane_flags_join_with_preserve_trailing_a5c4ff8bed | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[join_wrapped_long_line] | Test capture_pane() with various flag combinations; case inputs `command='printf "%s" "$(python3 -c \'print("x" * 200)\')"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='x{100,}'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='join_wrapped_long_line'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_capture_pane_py_test_capture_pane_flags_join_wrapped_long_line_c03ac2c0ae | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[join_wrapped_numbers] | Test capture_pane() with various flag combinations; case inputs `command='printf "%s" "$(seq -s "" 1 100)"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='123.*99100'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='join_wrapped_numbers'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flags_join_wrapped_numbers_72182cbd56 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[no_escape_sequences] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[31mRED\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='RED'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern='\\x1b'`, `preserve_trailing=False`, `test_id='no_escape_sequences'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_capture_pane_py_test_capture_pane_flags_no_escape_sequences_355c0c30a4 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[no_preserve_trailing] | Test capture_pane() with various flag combinations; case inputs `command='printf "text \\n"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='text'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='no_preserve_trailing'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flags_no_preserve_trailing_7bfa13ace2 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_flags[preserve_trailing_spaces] | Test capture_pane() with various flag combinations; case inputs `command='printf "text \\n"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='text '`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=True`, `test_id='preserve_trailing_spaces'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_capture_pane_py_test_capture_pane_flags_preserve_trailing_spaces_fe6fd70cf1 | 3.2a through 3.7b | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flag_smoke[quiet] | Smoke-test capture_pane flags that aren't tied to output content; case inputs `kwargs={'quiet': True}`, `min_tmux_version=None`; fixtures `session`; execute `pane.capture_pane(**kwargs)`; assert `pane is not None`; `isinstance(result, list)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_flag_smoke_quiet_6f1c8513d6 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[all_flags_except_trim] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[34mBLUE: %s \\033[0m" "test"'`, `escape_non_printable=True`, `escape_sequences=True`, `expected_pattern='\\x1b\\[34m|BLUE'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=True`, `test_id='all_flags_except_trim'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_capture_pane_py_test_capture_pane_flags_all_flags_except_trim_922d38ac76 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[basic_capture] | Test capture_pane() with various flag combinations; case inputs `command='echo "hello world"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='hello world'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='basic_capture'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_flags_basic_capture_c13b79057e | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[basic_multiline] | Test capture_pane() with various flag combinations; case inputs `command='printf "line1\\nline2\\nline3\\n"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='line1.*line2.*line3'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='basic_multiline'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_flags_basic_multiline_faf8bbdc1a | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_non_printable_basic] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\001\\002\\003"'`, `escape_non_printable=True`, `escape_sequences=False`, `expected_pattern='\\\\00[123]'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_non_printable_basic'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_pane_capture_pane_py_test_capture_pane_flags_escape_non_printable_basic_469ed4ffd7 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_non_printable_tab] | Test capture_pane() with various flag combinations; case inputs `command='printf "a\\tb"'`, `escape_non_printable=True`, `escape_sequences=False`, `expected_pattern='a.*b'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_non_printable_tab'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_capture_pane_py_test_capture_pane_flags_escape_non_printable_tab_5898542c8c | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_bold] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[1mBOLD\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[1m'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_bold'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_test_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_bold_da0623516d | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_green] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[32mGREEN\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[32m'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_green'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_green_cad850baea | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_red] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[31mRED\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[31m'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_red'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_red_c8e93b8afa | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[escape_sequences_with_join] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[32mGREEN TEXT\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=True`, `expected_pattern='\\x1b\\[32m'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='escape_sequences_with_join'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#_pane_capture_pane_py_test_capture_pane_flags_escape_sequences_with_join_9a8e67967f | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[join_with_preserve_trailing] | Test capture_pane() with various flag combinations; case inputs `command='printf "%s " "$(python3 -c \'print("z" * 100)\')"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='z{50,}'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=True`, `test_id='join_with_preserve_trailing'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#pane_capture_pane_py_test_capture_pane_flags_join_with_preserve_trailing_a5c4ff8bed | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[join_wrapped_long_line] | Test capture_pane() with various flag combinations; case inputs `command='printf "%s" "$(python3 -c \'print("x" * 200)\')"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='x{100,}'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='join_wrapped_long_line'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#test_pane_capture_pane_py_test_capture_pane_flags_join_wrapped_long_line_c03ac2c0ae | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[join_wrapped_numbers] | Test capture_pane() with various flag combinations; case inputs `command='printf "%s" "$(seq -s "" 1 100)"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='123.*99100'`, `join_wrapped=True`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='join_wrapped_numbers'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flags_join_wrapped_numbers_72182cbd56 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[no_escape_sequences] | Test capture_pane() with various flag combinations; case inputs `command='printf "\\033[31mRED\\033[0m"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='RED'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern='\\x1b'`, `preserve_trailing=False`, `test_id='no_escape_sequences'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_capture_pane_py_test_capture_pane_flags_no_escape_sequences_355c0c30a4 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[no_preserve_trailing] | Test capture_pane() with various flag combinations; case inputs `command='printf "text \\n"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='text'`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='no_preserve_trailing'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#s_test_pane_capture_pane_py_test_capture_pane_flags_no_preserve_trailing_7bfa13ace2 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_flags[preserve_trailing_spaces] | Test capture_pane() with various flag combinations; case inputs `command='printf "text \\n"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='text '`, `join_wrapped=False`, `min_tmux_version=None`, `not_expected_pattern=None`, `preserve_trailing=True`, `test_id='preserve_trailing_spaces'`, `trim_trailing=False`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#st_pane_capture_pane_py_test_capture_pane_flags_preserve_trailing_spaces_fe6fd70cf1 | 3.3a through 3.7c | planned parity | | tests/test_pane_capture_pane.py::test_capture_pane_flags[trim_trailing_basic] | Test capture_pane() with various flag combinations; case inputs `command='echo "short"'`, `escape_non_printable=False`, `escape_sequences=False`, `expected_pattern='short'`, `join_wrapped=False`, `min_tmux_version='3.4'`, `not_expected_pattern=None`, `preserve_trailing=False`, `test_id='trim_trailing_basic'`, `trim_trailing=True`; fixtures `session`; execute `session.new_window(attach=True, window_name=f'cap_{test_id[:10]}', window_shell=f"{env} PS1='$ ' sh")`; `pane.capture_pane()`; `pane.send_keys(full_command, literal=False, suppress_history=False)`; `pane.capture_pane(escape_sequences=escape_sequences, escape_non_printable=escape_non_printable, join_wrapped=join_wrapped, preserve_trailing=preserve_trailing, trim_trailing=trim_trailing)`; `shutil.which('env')`; `'\n'.join(pane.capture_pane())`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_pattern`: execute `re.search(expected_pattern, output_str, re.DOTALL)`; assert `re.search(expected_pattern, output_str, re.DOTALL); message=f"Expected pattern '{expected_pattern}' not found in output:\n{output_str}"`; when `not_expected_pattern`: execute `re.search(not_expected_pattern, output_str, re.DOTALL)`; assert `not re.search(not_expected_pattern, output_str, re.DOTALL); message=f"Unexpected pattern '{not_expected_pattern}' found in output"`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_pane_capture_pane_py_test_capture_pane_flags_trim_trailing_basic_3684833d13 | 3.4 through 3.7b | planned parity | | tests/test_pane_capture_pane.py::test_capture_pane_pending_emits_dash_P | ``capture_pane(pending=True)`` emits the tmux ``-P`` flag; fixtures `monkeypatch`, `session`; execute `pane.capture_pane(pending=True)`; `real_cmd(cmd_name, *args, **kw)`; assert `pane is not None`; `capture_calls`; `'-P' in capture_calls[0]`; `'-p' in capture_calls[0]`. | unit test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_pending_emits_dash_P_676ede056f | not tmux-bound | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_pending_returns_list | ``capture_pane(pending=True)`` returns a list (possibly empty); fixtures `session`; execute `pane.capture_pane(pending=True)`; assert `pane is not None`; `isinstance(result, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_pending_returns_list_9fdd656d0b | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_start_end_with_flags | Test that start/end parameters work with new flags; fixtures `session`; execute `session.active_window.split(shell='sh')`; `pane.capture_pane()`; `pane.send_keys('echo "line1"; echo "line2"; echo "line3"', enter=True)`; `pane.capture_pane(start=0, end='-', preserve_trailing=True)`; `'\n'.join(pane.capture_pane())`; `retry_until(prompt_ready, 2, raises=True)`; assert `isinstance(output, list)`; `len(output) > 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_start_end_with_flags_cef758a00c | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_to_buffer | Test capture_pane(to_buffer=...) writes to a tmux buffer; fixtures `session`; execute `pane.send_keys('echo "BUFFER_CAPTURE_MARKER"', enter=True)`; `pane.capture_pane()`; `pane.capture_pane(to_buffer='cap_test_buf')`; `session.server.cmd('show-buffer', '-b', 'cap_test_buf')`; `session.server.cmd('delete-buffer', '-b', 'cap_test_buf')`; `retry_until(lambda: 'BUFFER_CAPTURE_MARKER' in '\n'.join(pane.capture_pane()), 2, raises=True)`; assert `pane is not None`; `result is None`; `any(('BUFFER_CAPTURE_MARKER' in line for line in contents))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_to_buffer_357297abc8 | 3.2a through 3.7b | planned parity | -| tests/test_pane_capture_pane.py::test_capture_pane_trim_trailing_warning | Test that trim_trailing issues a warning on tmux < 3.4; fixtures `session`, `monkeypatch`; execute `session.active_window.split(shell='sh')`; `pane.capture_pane()`; `pane.capture_pane(trim_trailing=True)`; `'\n'.join(pane.capture_pane())`; `retry_until(prompt_ready, 2, raises=True)`; assert `len(w) == 1`; `'trim_trailing requires tmux 3.4+' in str(w[0].message)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_trim_trailing_warning_106c8f632e | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_plugin | Test libtmux pytest plugin; fixtures `pytester`, `monkeypatch`; execute `pytester.makefile('.ini', pytest=textwrap.dedent('\n[pytest]\naddopts=-vv\n '.strip()))`; `pytester.makeconftest(textwrap.dedent('\nimport pathlib\nimport pytest\n\n@pytest.fixture(autouse=True)\ndef setup(\n request: pytest.FixtureRequest,\n) -> None:\n pass\n '))`; `pytester.runpytest(str(first_test_filename))`; `textwrap.dedent('\n[pytest]\naddopts=-vv\n '.strip())`; `'\n[pytest]\naddopts=-vv\n '.strip()`; `textwrap.dedent('\nimport pathlib\nimport pytest\n\n@pytest.fixture(autouse=True)\ndef setup(\n request: pytest.FixtureRequest,\n) -> None:\n pass\n ')`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_plugin_ca9cfd8dd4 | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_reap_test_server_is_noop_when_socket_missing | Reaping a non-existent socket succeeds silently; execute `_libtmux_socket_dir()`; `socket_path.exists()`; `_reap_test_server(bogus_name)`; assert `not socket_path.exists()`. | JUnit extension integration test | PythonBehaviorParityTest#_test_pytest_plugin_py_test_reap_test_server_is_noop_when_socket_missing_3ad24e7f13 | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_reap_test_server_tolerates_none | ``_reap_test_server(None)`` is a no-op, not a crash; execute `_reap_test_server(None)`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_reap_test_server_tolerates_none_0b7646b9f3 | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_reap_test_server_unlinks_socket_file | ``_reap_test_server`` kills the daemon *and* unlinks the socket; execute `server.new_session(session_name='reap_probe')`; `Server(socket_name='libtmux_test_reap_unlink')`; `_libtmux_socket_dir()`; `socket_path.exists()`; `_reap_test_server('libtmux_test_reap_unlink')`; `socket_path.unlink(missing_ok=True)`; assert `socket_path.exists(); message=f'expected tmux to have created {socket_path}, but it is missing'`; `not socket_path.exists(); message=f'_reap_test_server should have unlinked {socket_path}'`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_reap_test_server_unlinks_socket_file_ca0bef2b4d | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_test_server | Test TestServer creates and cleans up server; fixtures `TestServer`; execute `TestServer()`; `server.is_alive()`; `server.new_session()`; `server.socket_name.startswith('libtmux_test')`; `server2.socket_name.startswith('libtmux_test')`; assert `server.is_alive() is False`; `server.is_alive() is True`; `len(server.sessions) == 1`; `session.session_name is not None`; `server.socket_name is not None`; `server.socket_name.startswith('libtmux_test')`; `server2.socket_name is not None`; `server2.socket_name.startswith('libtmux_test')`; `server2.socket_name != server.socket_name`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_77772185e3 | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_test_server_cleanup | Test TestServer properly cleans up after itself; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='test1')`; `server.new_session(session_name='test2')`; `server.is_alive()`; `server.kill()`; `time.sleep(0.1)`; assert `socket_name is not None`; `len(server.sessions) == 2`; `server.is_alive() is True`; `new_server.is_alive() is False`; `new_server.is_alive() is True`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_cleanup_89e16902ec | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_test_server_multiple | Test multiple TestServer instances can coexist; fixtures `TestServer`; execute `TestServer()`; `server1.new_session(session_name='test1')`; `server2.new_session(session_name='test2')`; `any((s.session_name == 'test1' for s in server1.sessions))`; `any((s.session_name == 'test2' for s in server2.sessions))`; `any((s.session_name == 'test1' for s in server2.sessions))`; assert `server1.socket_name != server2.socket_name`; `any((s.session_name == 'test1' for s in server1.sessions))`; `any((s.session_name == 'test2' for s in server2.sessions))`; `not any((s.session_name == 'test1' for s in server2.sessions))`; `not any((s.session_name == 'test2' for s in server1.sessions))`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_multiple_c049bb6ff0 | 3.2a through 3.7b | planned parity | -| tests/test_pytest_plugin.py::test_test_server_with_config | Test TestServer with config file; fixtures `TestServer`, `tmp_path`; execute `TestServer(config_file=str(config_file))`; `server.new_session()`; `session.cmd('show-options', '-g', 'status')`; `config_file.write_text('set -g status off', encoding='utf-8')`; assert `session.cmd('show-options', '-g', 'status').stdout[0] == 'status off'`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_with_config_6f5a08fc97 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[cross_session] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=2`, `select=None`, `shape='cross-session'`, `test_id='cross_session'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#on_py_test_ambiguous_point_lookup_is_catchable_and_legible_cross_session_60f6b26db9 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[grouped_session] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=2`, `select=None`, `shape='grouped'`, `test_id='grouped_session'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#_py_test_ambiguous_point_lookup_is_catchable_and_legible_grouped_session_1b1f3ea355 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[same_session_high_link_current] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=1`, `select='dup:5'`, `shape='same-session'`, `test_id='same_session_high_link_current'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#ous_point_lookup_is_catchable_and_legible_same_session_high_link_current_a3e42a3a89 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[same_session_low_link_current] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=1`, `select='dup:1'`, `shape='same-session'`, `test_id='same_session_low_link_current'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#uous_point_lookup_is_catchable_and_legible_same_session_low_link_current_59dd6a8fab | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[same_session_other_window_current] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=1`, `select='dup:2'`, `shape='same-session'`, `test_id='same_session_other_window_current'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#_point_lookup_is_catchable_and_legible_same_session_other_window_current_5cb04f11a0 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_dead_server_raises_libtmux_exception | A dead server is a different failure from a missing object; fixtures `server`, `session`; execute `server.kill()`; `Pane.from_pane_id(server=server, pane_id=pane_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_dead_server_raises_libtmux_exception_75fb5851db | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_linked_pane_resolves_like_tmux | :meth:`Pane.from_pane_id` answers with tmux's own session; fixtures `server`; execute `link_window_into_second_session(server)`; `tmux_resolves_to(server, pane.pane_id)`; `Pane.from_pane_id(server=server, pane_id=pane.pane_id)`; assert `pane is not None`; `pane.pane_id is not None`; `resolved.session_id == canonical`; `resolved.session_id == recent.session_id`; `resolved.session_id != old.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_pane_resolves_like_tmux_d48c10e1cd | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_linked_sessions_lists_each_holder_once | :attr:`Window.linked_sessions` dedupes winlinks down to sessions; fixtures `server`; execute `server.new_session(session_name='guest')`; `server.cmd('link-window', '-d', '-s', window.window_id, '-t', f'{guest.session_id}:')`; `link_window_twice_into_one_session(server)`; assert `[s.session_id for s in window.linked_sessions] == [dup.session_id]`; `holders == {dup.session_id, guest.session_id}`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_sessions_lists_each_holder_once_ffca9a885b | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_linked_sessions_returns_empty_after_server_kill | A retained window has no knowable holders after its server dies; fixtures `server`, `session`; execute `server.kill()`; assert `list(window.linked_sessions) == []`. | real-tmux integration test | PythonBehaviorParityTest#_test_resolution_py_test_linked_sessions_returns_empty_after_server_kill_68504b2cb6 | 3.2a through 3.7b | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_pending_returns_list | ``capture_pane(pending=True)`` returns a list (possibly empty); fixtures `session`; execute `pane.capture_pane(pending=True)`; assert `pane is not None`; `isinstance(result, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_pending_returns_list_9fdd656d0b | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_start_end_with_flags | Test that start/end parameters work with new flags; fixtures `session`; execute `session.active_window.split(shell='sh')`; `pane.capture_pane()`; `pane.send_keys('echo "line1"; echo "line2"; echo "line3"', enter=True)`; `pane.capture_pane(start=0, end='-', preserve_trailing=True)`; `'\n'.join(pane.capture_pane())`; `retry_until(prompt_ready, 2, raises=True)`; assert `isinstance(output, list)`; `len(output) > 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_start_end_with_flags_cef758a00c | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_to_buffer | Test capture_pane(to_buffer=...) writes to a tmux buffer; fixtures `session`; execute `pane.send_keys('echo "BUFFER_CAPTURE_MARKER"', enter=True)`; `pane.capture_pane()`; `pane.capture_pane(to_buffer='cap_test_buf')`; `session.server.cmd('show-buffer', '-b', 'cap_test_buf')`; `session.server.cmd('delete-buffer', '-b', 'cap_test_buf')`; `retry_until(lambda: 'BUFFER_CAPTURE_MARKER' in '\n'.join(pane.capture_pane()), 2, raises=True)`; assert `pane is not None`; `result is None`; `any(('BUFFER_CAPTURE_MARKER' in line for line in contents))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_to_buffer_357297abc8 | 3.3a through 3.7c | planned parity | +| tests/test_pane_capture_pane.py::test_capture_pane_trim_trailing_warning | Test that trim_trailing issues a warning on tmux < 3.4; fixtures `session`, `monkeypatch`; execute `session.active_window.split(shell='sh')`; `pane.capture_pane()`; `pane.capture_pane(trim_trailing=True)`; `'\n'.join(pane.capture_pane())`; `retry_until(prompt_ready, 2, raises=True)`; assert `len(w) == 1`; `'trim_trailing requires tmux 3.4+' in str(w[0].message)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_pane_capture_pane_py_test_capture_pane_trim_trailing_warning_106c8f632e | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_plugin | Test libtmux pytest plugin; fixtures `pytester`, `monkeypatch`; execute `pytester.makefile('.ini', pytest=textwrap.dedent('\n[pytest]\naddopts=-vv\n '.strip()))`; `pytester.makeconftest(textwrap.dedent('\nimport pathlib\nimport pytest\n\n@pytest.fixture(autouse=True)\ndef setup(\n request: pytest.FixtureRequest,\n) -> None:\n pass\n '))`; `pytester.runpytest(str(first_test_filename))`; `textwrap.dedent('\n[pytest]\naddopts=-vv\n '.strip())`; `'\n[pytest]\naddopts=-vv\n '.strip()`; `textwrap.dedent('\nimport pathlib\nimport pytest\n\n@pytest.fixture(autouse=True)\ndef setup(\n request: pytest.FixtureRequest,\n) -> None:\n pass\n ')`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_plugin_ca9cfd8dd4 | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_reap_test_server_is_noop_when_socket_missing | Reaping a non-existent socket succeeds silently; execute `_libtmux_socket_dir()`; `socket_path.exists()`; `_reap_test_server(bogus_name)`; assert `not socket_path.exists()`. | JUnit extension integration test | PythonBehaviorParityTest#_test_pytest_plugin_py_test_reap_test_server_is_noop_when_socket_missing_3ad24e7f13 | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_reap_test_server_tolerates_none | ``_reap_test_server(None)`` is a no-op, not a crash; execute `_reap_test_server(None)`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_reap_test_server_tolerates_none_0b7646b9f3 | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_reap_test_server_unlinks_socket_file | ``_reap_test_server`` kills the daemon *and* unlinks the socket; execute `server.new_session(session_name='reap_probe')`; `Server(socket_name='libtmux_test_reap_unlink')`; `_libtmux_socket_dir()`; `socket_path.exists()`; `_reap_test_server('libtmux_test_reap_unlink')`; `socket_path.unlink(missing_ok=True)`; assert `socket_path.exists(); message=f'expected tmux to have created {socket_path}, but it is missing'`; `not socket_path.exists(); message=f'_reap_test_server should have unlinked {socket_path}'`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_reap_test_server_unlinks_socket_file_ca0bef2b4d | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_test_server | Test TestServer creates and cleans up server; fixtures `TestServer`; execute `TestServer()`; `server.is_alive()`; `server.new_session()`; `server.socket_name.startswith('libtmux_test')`; `server2.socket_name.startswith('libtmux_test')`; assert `server.is_alive() is False`; `server.is_alive() is True`; `len(server.sessions) == 1`; `session.session_name is not None`; `server.socket_name is not None`; `server.socket_name.startswith('libtmux_test')`; `server2.socket_name is not None`; `server2.socket_name.startswith('libtmux_test')`; `server2.socket_name != server.socket_name`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_77772185e3 | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_test_server_cleanup | Test TestServer properly cleans up after itself; fixtures `TestServer`; execute `TestServer()`; `server.new_session(session_name='test1')`; `server.new_session(session_name='test2')`; `server.is_alive()`; `server.kill()`; `time.sleep(0.1)`; assert `socket_name is not None`; `len(server.sessions) == 2`; `server.is_alive() is True`; `new_server.is_alive() is False`; `new_server.is_alive() is True`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_cleanup_89e16902ec | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_test_server_multiple | Test multiple TestServer instances can coexist; fixtures `TestServer`; execute `TestServer()`; `server1.new_session(session_name='test1')`; `server2.new_session(session_name='test2')`; `any((s.session_name == 'test1' for s in server1.sessions))`; `any((s.session_name == 'test2' for s in server2.sessions))`; `any((s.session_name == 'test1' for s in server2.sessions))`; assert `server1.socket_name != server2.socket_name`; `any((s.session_name == 'test1' for s in server1.sessions))`; `any((s.session_name == 'test2' for s in server2.sessions))`; `not any((s.session_name == 'test1' for s in server2.sessions))`; `not any((s.session_name == 'test2' for s in server1.sessions))`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_multiple_c049bb6ff0 | 3.3a through 3.7c | planned parity | +| tests/test_pytest_plugin.py::test_test_server_with_config | Test TestServer with config file; fixtures `TestServer`, `tmp_path`; execute `TestServer(config_file=str(config_file))`; `server.new_session()`; `session.cmd('show-options', '-g', 'status')`; `config_file.write_text('set -g status off', encoding='utf-8')`; assert `session.cmd('show-options', '-g', 'status').stdout[0] == 'status off'`. | JUnit extension integration test | PythonBehaviorParityTest#tests_test_pytest_plugin_py_test_test_server_with_config_6f5a08fc97 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[cross_session] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=2`, `select=None`, `shape='cross-session'`, `test_id='cross_session'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#on_py_test_ambiguous_point_lookup_is_catchable_and_legible_cross_session_60f6b26db9 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[grouped_session] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=2`, `select=None`, `shape='grouped'`, `test_id='grouped_session'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#_py_test_ambiguous_point_lookup_is_catchable_and_legible_grouped_session_1b1f3ea355 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[same_session_high_link_current] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=1`, `select='dup:5'`, `shape='same-session'`, `test_id='same_session_high_link_current'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#ous_point_lookup_is_catchable_and_legible_same_session_high_link_current_a3e42a3a89 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[same_session_low_link_current] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=1`, `select='dup:1'`, `shape='same-session'`, `test_id='same_session_low_link_current'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#uous_point_lookup_is_catchable_and_legible_same_session_low_link_current_59dd6a8fab | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_ambiguous_point_lookup_is_catchable_and_legible[same_session_other_window_current] | An ambiguous ``get()`` explains itself, and ``except LibTmuxException`` sees it; case inputs `expected_holders=1`, `select='dup:2'`, `shape='same-session'`, `test_id='same_session_other_window_current'`; fixtures `server`; execute `build_winlink_shape(server, shape)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `isinstance(excinfo.value, exc.MultipleObjectsReturned)`; `str(excinfo.value) == f'Multiple objects returned (2): pane_id={pane.pane_id!r}'`. | real-tmux integration test | PythonBehaviorParityTest#_point_lookup_is_catchable_and_legible_same_session_other_window_current_5cb04f11a0 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_dead_server_raises_libtmux_exception | A dead server is a different failure from a missing object; fixtures `server`, `session`; execute `server.kill()`; `Pane.from_pane_id(server=server, pane_id=pane_id)`; expect `pytest.raises(exc.LibTmuxException)`; assert `pane is not None`; `pane.pane_id is not None`; `not isinstance(excinfo.value, ObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_dead_server_raises_libtmux_exception_75fb5851db | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_linked_pane_resolves_like_tmux | :meth:`Pane.from_pane_id` answers with tmux's own session; fixtures `server`; execute `link_window_into_second_session(server)`; `tmux_resolves_to(server, pane.pane_id)`; `Pane.from_pane_id(server=server, pane_id=pane.pane_id)`; assert `pane is not None`; `pane.pane_id is not None`; `resolved.session_id == canonical`; `resolved.session_id == recent.session_id`; `resolved.session_id != old.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_pane_resolves_like_tmux_d48c10e1cd | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_linked_sessions_lists_each_holder_once | :attr:`Window.linked_sessions` dedupes winlinks down to sessions; fixtures `server`; execute `server.new_session(session_name='guest')`; `server.cmd('link-window', '-d', '-s', window.window_id, '-t', f'{guest.session_id}:')`; `link_window_twice_into_one_session(server)`; assert `[s.session_id for s in window.linked_sessions] == [dup.session_id]`; `holders == {dup.session_id, guest.session_id}`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_sessions_lists_each_holder_once_ffca9a885b | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_linked_sessions_returns_empty_after_server_kill | A retained window has no knowable holders after its server dies; fixtures `server`, `session`; execute `server.kill()`; assert `list(window.linked_sessions) == []`. | real-tmux integration test | PythonBehaviorParityTest#_test_resolution_py_test_linked_sessions_returns_empty_after_server_kill_68504b2cb6 | 3.3a through 3.7c | planned parity | | tests/test_resolution.py::test_linked_sessions_returns_empty_when_session_snapshot_fails | The second listing shares the accessor's lenient error boundary; fixtures `server`, `monkeypatch`; execute `server.new_session(session_name='holder')`; `real_fetch_objs(**kwargs)`; assert `list(window.linked_sessions) == []`; when `kwargs['list_cmd'] == 'list-sessions'`: execute `exc.LibTmuxException(msg)`; raise `raise exc.LibTmuxException(msg)`. | unit test | PythonBehaviorParityTest#lution_py_test_linked_sessions_returns_empty_when_session_snapshot_fails_27d863cb7b | not tmux-bound | planned parity | | tests/test_resolution.py::test_linked_sessions_skips_holder_lost_between_snapshots | A holder disappearing between real snapshots is skipped safely; fixtures `server`, `monkeypatch`; execute `server.new_session(session_name='home')`; `server.new_session(session_name='guest')`; `server.cmd('link-window', '-d', '-s', window.window_id, '-t', f'{guest.session_id}:')`; `real_fetch_objs(**kwargs)`; assert `[holder.session_id for holder in window.linked_sessions] == [home.session_id]`; when `kwargs['list_cmd'] == 'list-windows'`: execute `guest.kill()`. | unit test | PythonBehaviorParityTest#t_resolution_py_test_linked_sessions_skips_holder_lost_between_snapshots_2a31da8767 | not tmux-bound | planned parity | | tests/test_resolution.py::test_linked_sessions_uses_two_list_calls | Resolving several holders uses two real tmux listings in total; fixtures `server`, `monkeypatch`; execute `server.new_session(session_name=name)`; `server.cmd('link-window', '-d', '-s', window.window_id, '-t', f'{guest.session_id}:')`; `link_window_twice_into_one_session(server)`; `real_fetch_objs(server=server, list_cmd='list-windows', list_extra_args=('-a',))`; `dict.fromkeys((row['session_id'] for row in rows if row.get('window_id') == window.window_id and row.get('session_id')))`; `real_fetch_objs(**kwargs)`; assert `[item.session_id for item in window.linked_sessions] == expected_session_ids`; `list_calls == ['list-windows', 'list-sessions']`. | unit test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_sessions_uses_two_list_calls_bd8e13fd14 | not tmux-bound | planned parity | -| tests/test_resolution.py::test_linked_window_resolves_like_tmux | :meth:`Window.from_window_id` answers with tmux's own session; fixtures `server`; execute `link_window_into_second_session(server)`; `tmux_resolves_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.session_id == canonical`; `resolved.session_id == recent.session_id`; `resolved.session_id != old.session_id; message="'zzz-old' sorts last by name -- resolving from a `list-windows -a` listing would answer with it"`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_window_resolves_like_tmux_4761a30783 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_missing_pane_on_live_server_raises_object_does_not_exist | A pane id that never existed is a missing object, not a broken server; fixtures `server`, `session`; execute `Pane.from_pane_id(server=server, pane_id='%99999')`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#olution_py_test_missing_pane_on_live_server_raises_object_does_not_exist_dd9758e520 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[cross_session] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=2`, `select=None`, `shape='cross-session'`, `test_id='cross_session'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#solution_py_test_multiply_linked_window_resolves_like_tmux_cross_session_d0c070463f | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[grouped_session] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=2`, `select=None`, `shape='grouped'`, `test_id='grouped_session'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#lution_py_test_multiply_linked_window_resolves_like_tmux_grouped_session_bbe7f1e42b | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[same_session_high_link_current] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=1`, `select='dup:5'`, `shape='same-session'`, `test_id='same_session_high_link_current'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#multiply_linked_window_resolves_like_tmux_same_session_high_link_current_b625449083 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[same_session_low_link_current] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=1`, `select='dup:1'`, `shape='same-session'`, `test_id='same_session_low_link_current'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#_multiply_linked_window_resolves_like_tmux_same_session_low_link_current_e4c66875a3 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[same_session_other_window_current] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=1`, `select='dup:2'`, `shape='same-session'`, `test_id='same_session_other_window_current'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#tiply_linked_window_resolves_like_tmux_same_session_other_window_current_3c8159b733 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_pane_of_multiply_linked_window_agrees_with_its_window | :meth:`Pane.from_pane_id` and :meth:`Window.from_window_id` do not disagree; fixtures `server`; execute `server.cmd('select-window', '-t', 'dup:1')`; `link_window_twice_into_one_session(server)`; `Pane.from_pane_id(server=server, pane_id=pane.pane_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; `tmux_resolves_index_to(server, pane.pane_id)`; assert `pane is not None`; `pane.pane_id is not None`; `window.window_id is not None`; `resolved_pane.window_index == resolved_window.window_index`; `resolved_pane.window_index == tmux_resolves_index_to(server, pane.pane_id)`. | real-tmux integration test | PythonBehaviorParityTest#resolution_py_test_pane_of_multiply_linked_window_agrees_with_its_window_21df30be0c | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_pane_session_follows_move_window | A :class:`Pane` held across a ``move-window`` still finds its session; fixtures `server`; execute `server.new_session(session_name='origin')`; `server.new_session(session_name='destination')`; `window.move_window(destination='99', session=destination.session_id)`; assert `pane is not None`; `pane.session.session_id == destination.session_id`; `pane.window.session_id == destination.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_pane_session_follows_move_window_eda41d9e14 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_point_lookup_by_id_is_the_way_out | The documented escape from an ambiguous scan: name the id with ``-t``; fixtures `server`; execute `link_window_into_second_session(server)`; `Pane.from_pane_id(server, pane.pane_id)`; `Window.from_window_id(server, window.window_id)`; assert `pane is not None`; `pane.pane_id is not None`; `window.window_id is not None`; `Pane.from_pane_id(server, pane.pane_id).pane_id == pane.pane_id`; `Window.from_window_id(server, window.window_id).window_id == window.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_point_lookup_by_id_is_the_way_out_50e77c5830 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_refresh_after_kill_raises_object_does_not_exist[pane] | A gone object still raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist`; case inputs `kind='pane'`, `test_id='pane'`; fixtures `session`; execute `session.new_window(window_name='doomed')`; `target.refresh()`; expect `pytest.raises(ObjectDoesNotExist)`; when `not (kind == 'window')`: execute `window.split()`; `pane.kill()`; when `kind == 'window'`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#_resolution_py_test_refresh_after_kill_raises_object_does_not_exist_pane_cf722778bb | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_refresh_after_kill_raises_object_does_not_exist[window] | A gone object still raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist`; case inputs `kind='window'`, `test_id='window'`; fixtures `session`; execute `session.new_window(window_name='doomed')`; `target.refresh()`; expect `pytest.raises(ObjectDoesNotExist)`; when `not (kind == 'window')`: execute `window.split()`; `pane.kill()`; when `kind == 'window'`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#esolution_py_test_refresh_after_kill_raises_object_does_not_exist_window_4d7db37bb1 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_same_session_double_link_answers_the_low_index | The regression in its narrowest form: tmux says 1, libtmux said 5; fixtures `server`; execute `server.cmd('select-window', '-t', 'dup:1')`; `link_window_twice_into_one_session(server)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server, window.window_id)`; assert `window.window_id is not None`; `tmux_resolves_index_to(server, window.window_id) == '1'`; `Window.from_window_id(server, window.window_id).window_index == '1'`. | real-tmux integration test | PythonBehaviorParityTest#s_test_resolution_py_test_same_session_double_link_answers_the_low_index_d2dbe2c977 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_server_collections_enumerate_winlinks[cross_session] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=2`, `select=None`, `shape='cross-session'`, `test_id='cross_session'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#t_resolution_py_test_server_collections_enumerate_winlinks_cross_session_b36c1af7f4 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_server_collections_enumerate_winlinks[grouped_session] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=2`, `select=None`, `shape='grouped'`, `test_id='grouped_session'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#resolution_py_test_server_collections_enumerate_winlinks_grouped_session_f890f4c69f | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_server_collections_enumerate_winlinks[same_session_high_link_current] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=1`, `select='dup:5'`, `shape='same-session'`, `test_id='same_session_high_link_current'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#est_server_collections_enumerate_winlinks_same_session_high_link_current_3ccca980a1 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_server_collections_enumerate_winlinks[same_session_low_link_current] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=1`, `select='dup:1'`, `shape='same-session'`, `test_id='same_session_low_link_current'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#test_server_collections_enumerate_winlinks_same_session_low_link_current_67f97dd930 | 3.2a through 3.7b | planned parity | -| tests/test_resolution.py::test_server_collections_enumerate_winlinks[same_session_other_window_current] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=1`, `select='dup:2'`, `shape='same-session'`, `test_id='same_session_other_window_current'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#_server_collections_enumerate_winlinks_same_session_other_window_current_17447cf546 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_256_colors | Assert Server respects ``colors=256``; fixtures `server`; execute `Server(colors=256)`; `myserver.cmd('list-sessions')`; assert `myserver.colors == 256`; `'-2' in proc.cmd`; `'-8' not in proc.cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_256_colors_1af3e21b1c | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_88_colors | Assert Server respects ``colors=88``; fixtures `server`; execute `Server(colors=88)`; `myserver.cmd('list-sessions')`; assert `myserver.colors == 88`; `'-8' in proc.cmd`; `'-2' not in proc.cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_88_colors_a24b676a0a | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_bind_unbind_key | Test Server.bind_key() and unbind_key() cycle; fixtures `server`; execute `server.new_session(session_name='bind_test')`; `server.bind_key('F12', 'display-message bound', key_table='root')`; `server.list_keys(key_table='root')`; `server.unbind_key('F12', key_table='root')`; `any(('F12' in line for line in keys))`; `any(('F12' in line and 'display-message' in line for line in keys))`; assert `any(('F12' in line for line in keys))`; `not any(('F12' in line and 'display-message' in line for line in keys))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_bind_unbind_key_d77633139e | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_buffer_delete | Test Server.delete_buffer(); fixtures `server`; execute `server.new_session(session_name='buf_delete')`; `server.set_buffer('to_delete', buffer_name='del_buf')`; `server.show_buffer(buffer_name='del_buf')`; `server.delete_buffer(buffer_name='del_buf')`; expect `pytest.raises(exc.LibTmuxException)`; assert `server.show_buffer(buffer_name='del_buf') == 'to_delete'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_delete_76248eed4e | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_buffer_save_append | Test Server.save_buffer() with append flag; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='buf_saveappend')`; `server.set_buffer('first_line', buffer_name='app1')`; `server.save_buffer(buf_file, buffer_name='app1')`; `server.set_buffer('second_line', buffer_name='app2')`; `server.save_buffer(buf_file, buffer_name='app2', append=True)`; `buf_file.read_text()`; assert `'first_line' in content`; `'second_line' in content`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_save_append_803c177e17 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_buffer_save_load | Test Server.save_buffer() and load_buffer() cycle; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='buf_saveload')`; `server.set_buffer('save_test_data')`; `server.save_buffer(buf_file)`; `server.load_buffer(buf_file, buffer_name='loaded_buf')`; `server.show_buffer(buffer_name='loaded_buf')`; `buf_file.read_text()`; assert `buf_file.read_text() == 'save_test_data'`; `server.show_buffer(buffer_name='loaded_buf') == 'save_test_data'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_save_load_f2d73900f7 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_buffer_set_show[set_show_append] | Test Server.set_buffer() and show_buffer() cycle; case inputs `append=True`, `buffer_name='append_test'`, `data='_second'`, `expected_content='first_second'`, `test_id='set_show_append'`; fixtures `server`; execute `server.new_session(session_name=f'buf_{test_id}')`; `server.set_buffer(data, **kwargs)`; `server.show_buffer(buffer_name=buffer_name)`; assert `result == expected_content`; when `append`: execute `server.set_buffer('first', buffer_name=buffer_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_set_show_set_show_append_123d743887 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_buffer_set_show[set_show_default] | Test Server.set_buffer() and show_buffer() cycle; case inputs `append=None`, `buffer_name=None`, `data='hello_buf'`, `expected_content='hello_buf'`, `test_id='set_show_default'`; fixtures `server`; execute `server.new_session(session_name=f'buf_{test_id}')`; `server.set_buffer(data, **kwargs)`; `server.show_buffer(buffer_name=buffer_name)`; assert `result == expected_content`; when `append`: execute `server.set_buffer('first', buffer_name=buffer_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_set_show_set_show_default_a2f68ba5f6 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_buffer_set_show[set_show_named] | Test Server.set_buffer() and show_buffer() cycle; case inputs `append=None`, `buffer_name='mybuf'`, `data='named_data'`, `expected_content='named_data'`, `test_id='set_show_named'`; fixtures `server`; execute `server.new_session(session_name=f'buf_{test_id}')`; `server.set_buffer(data, **kwargs)`; `server.show_buffer(buffer_name=buffer_name)`; assert `result == expected_content`; when `append`: execute `server.set_buffer('first', buffer_name=buffer_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_set_show_set_show_named_502e44630a | 3.2a through 3.7b | planned parity | +| tests/test_resolution.py::test_linked_window_resolves_like_tmux | :meth:`Window.from_window_id` answers with tmux's own session; fixtures `server`; execute `link_window_into_second_session(server)`; `tmux_resolves_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.session_id == canonical`; `resolved.session_id == recent.session_id`; `resolved.session_id != old.session_id; message="'zzz-old' sorts last by name -- resolving from a `list-windows -a` listing would answer with it"`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_linked_window_resolves_like_tmux_4761a30783 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_missing_pane_on_live_server_raises_object_does_not_exist | A pane id that never existed is a missing object, not a broken server; fixtures `server`, `session`; execute `Pane.from_pane_id(server=server, pane_id='%99999')`; expect `pytest.raises(exc.TmuxObjectDoesNotExist)`. | real-tmux integration test | PythonBehaviorParityTest#olution_py_test_missing_pane_on_live_server_raises_object_does_not_exist_dd9758e520 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[cross_session] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=2`, `select=None`, `shape='cross-session'`, `test_id='cross_session'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#solution_py_test_multiply_linked_window_resolves_like_tmux_cross_session_d0c070463f | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[grouped_session] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=2`, `select=None`, `shape='grouped'`, `test_id='grouped_session'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#lution_py_test_multiply_linked_window_resolves_like_tmux_grouped_session_bbe7f1e42b | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[same_session_high_link_current] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=1`, `select='dup:5'`, `shape='same-session'`, `test_id='same_session_high_link_current'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#multiply_linked_window_resolves_like_tmux_same_session_high_link_current_b625449083 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[same_session_low_link_current] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=1`, `select='dup:1'`, `shape='same-session'`, `test_id='same_session_low_link_current'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#_multiply_linked_window_resolves_like_tmux_same_session_low_link_current_e4c66875a3 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_multiply_linked_window_resolves_like_tmux[same_session_other_window_current] | A window at several winlinks resolves to the one tmux would act on; case inputs `expected_holders=1`, `select='dup:2'`, `shape='same-session'`, `test_id='same_session_other_window_current'`; fixtures `server`; execute `window.refresh()`; `build_winlink_shape(server, shape)`; `tmux_resolves_to(server, window.window_id)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `resolved.window_index == canonical_index`; `resolved.session_id == canonical_session`; `window.window_index == canonical_index`; `window.session_id == canonical_session`; `len(window.linked_sessions) == expected_holders`; when `select is not None`: execute `server.cmd('select-window', '-t', select)`. | real-tmux integration test | PythonBehaviorParityTest#tiply_linked_window_resolves_like_tmux_same_session_other_window_current_3c8159b733 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_pane_of_multiply_linked_window_agrees_with_its_window | :meth:`Pane.from_pane_id` and :meth:`Window.from_window_id` do not disagree; fixtures `server`; execute `server.cmd('select-window', '-t', 'dup:1')`; `link_window_twice_into_one_session(server)`; `Pane.from_pane_id(server=server, pane_id=pane.pane_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; `tmux_resolves_index_to(server, pane.pane_id)`; assert `pane is not None`; `pane.pane_id is not None`; `window.window_id is not None`; `resolved_pane.window_index == resolved_window.window_index`; `resolved_pane.window_index == tmux_resolves_index_to(server, pane.pane_id)`. | real-tmux integration test | PythonBehaviorParityTest#resolution_py_test_pane_of_multiply_linked_window_agrees_with_its_window_21df30be0c | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_pane_session_follows_move_window | A :class:`Pane` held across a ``move-window`` still finds its session; fixtures `server`; execute `server.new_session(session_name='origin')`; `server.new_session(session_name='destination')`; `window.move_window(destination='99', session=destination.session_id)`; assert `pane is not None`; `pane.session.session_id == destination.session_id`; `pane.window.session_id == destination.session_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_pane_session_follows_move_window_eda41d9e14 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_point_lookup_by_id_is_the_way_out | The documented escape from an ambiguous scan: name the id with ``-t``; fixtures `server`; execute `link_window_into_second_session(server)`; `Pane.from_pane_id(server, pane.pane_id)`; `Window.from_window_id(server, window.window_id)`; assert `pane is not None`; `pane.pane_id is not None`; `window.window_id is not None`; `Pane.from_pane_id(server, pane.pane_id).pane_id == pane.pane_id`; `Window.from_window_id(server, window.window_id).window_id == window.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_resolution_py_test_point_lookup_by_id_is_the_way_out_50e77c5830 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_refresh_after_kill_raises_object_does_not_exist[pane] | A gone object still raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist`; case inputs `kind='pane'`, `test_id='pane'`; fixtures `session`; execute `session.new_window(window_name='doomed')`; `target.refresh()`; expect `pytest.raises(ObjectDoesNotExist)`; when `not (kind == 'window')`: execute `window.split()`; `pane.kill()`; when `kind == 'window'`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#_resolution_py_test_refresh_after_kill_raises_object_does_not_exist_pane_cf722778bb | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_refresh_after_kill_raises_object_does_not_exist[window] | A gone object still raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist`; case inputs `kind='window'`, `test_id='window'`; fixtures `session`; execute `session.new_window(window_name='doomed')`; `target.refresh()`; expect `pytest.raises(ObjectDoesNotExist)`; when `not (kind == 'window')`: execute `window.split()`; `pane.kill()`; when `kind == 'window'`: execute `window.kill()`. | real-tmux integration test | PythonBehaviorParityTest#esolution_py_test_refresh_after_kill_raises_object_does_not_exist_window_4d7db37bb1 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_same_session_double_link_answers_the_low_index | The regression in its narrowest form: tmux says 1, libtmux said 5; fixtures `server`; execute `server.cmd('select-window', '-t', 'dup:1')`; `link_window_twice_into_one_session(server)`; `tmux_resolves_index_to(server, window.window_id)`; `Window.from_window_id(server, window.window_id)`; assert `window.window_id is not None`; `tmux_resolves_index_to(server, window.window_id) == '1'`; `Window.from_window_id(server, window.window_id).window_index == '1'`. | real-tmux integration test | PythonBehaviorParityTest#s_test_resolution_py_test_same_session_double_link_answers_the_low_index_d2dbe2c977 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_server_collections_enumerate_winlinks[cross_session] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=2`, `select=None`, `shape='cross-session'`, `test_id='cross_session'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#t_resolution_py_test_server_collections_enumerate_winlinks_cross_session_b36c1af7f4 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_server_collections_enumerate_winlinks[grouped_session] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=2`, `select=None`, `shape='grouped'`, `test_id='grouped_session'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#resolution_py_test_server_collections_enumerate_winlinks_grouped_session_f890f4c69f | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_server_collections_enumerate_winlinks[same_session_high_link_current] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=1`, `select='dup:5'`, `shape='same-session'`, `test_id='same_session_high_link_current'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#est_server_collections_enumerate_winlinks_same_session_high_link_current_3ccca980a1 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_server_collections_enumerate_winlinks[same_session_low_link_current] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=1`, `select='dup:1'`, `shape='same-session'`, `test_id='same_session_low_link_current'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#test_server_collections_enumerate_winlinks_same_session_low_link_current_67f97dd930 | 3.3a through 3.7c | planned parity | +| tests/test_resolution.py::test_server_collections_enumerate_winlinks[same_session_other_window_current] | A server-wide listing yields one row per winlink, and that is the truth; case inputs `expected_holders=1`, `select='dup:2'`, `shape='same-session'`, `test_id='same_session_other_window_current'`; fixtures `server`; execute `server.windows.filter(window_id=window.window_id)`; `server.panes.filter(pane_id=pane.pane_id)`; `build_winlink_shape(server, shape)`; assert `window.window_id is not None`; `pane is not None`; `pane.pane_id is not None`; `len(window_rows) == 2`; `len(pane_rows) == 2`; `{row.window_id for row in window_rows} == {window.window_id}`; `len(winlinks) == 2`. | real-tmux integration test | PythonBehaviorParityTest#_server_collections_enumerate_winlinks_same_session_other_window_current_17447cf546 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_256_colors | Assert Server respects ``colors=256``; fixtures `server`; execute `Server(colors=256)`; `myserver.cmd('list-sessions')`; assert `myserver.colors == 256`; `'-2' in proc.cmd`; `'-8' not in proc.cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_256_colors_1af3e21b1c | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_88_colors | Assert Server respects ``colors=88``; fixtures `server`; execute `Server(colors=88)`; `myserver.cmd('list-sessions')`; assert `myserver.colors == 88`; `'-8' in proc.cmd`; `'-2' not in proc.cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_88_colors_a24b676a0a | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_bind_unbind_key | Test Server.bind_key() and unbind_key() cycle; fixtures `server`; execute `server.new_session(session_name='bind_test')`; `server.bind_key('F12', 'display-message bound', key_table='root')`; `server.list_keys(key_table='root')`; `server.unbind_key('F12', key_table='root')`; `any(('F12' in line for line in keys))`; `any(('F12' in line and 'display-message' in line for line in keys))`; assert `any(('F12' in line for line in keys))`; `not any(('F12' in line and 'display-message' in line for line in keys))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_bind_unbind_key_d77633139e | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_buffer_delete | Test Server.delete_buffer(); fixtures `server`; execute `server.new_session(session_name='buf_delete')`; `server.set_buffer('to_delete', buffer_name='del_buf')`; `server.show_buffer(buffer_name='del_buf')`; `server.delete_buffer(buffer_name='del_buf')`; expect `pytest.raises(exc.LibTmuxException)`; assert `server.show_buffer(buffer_name='del_buf') == 'to_delete'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_delete_76248eed4e | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_buffer_save_append | Test Server.save_buffer() with append flag; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='buf_saveappend')`; `server.set_buffer('first_line', buffer_name='app1')`; `server.save_buffer(buf_file, buffer_name='app1')`; `server.set_buffer('second_line', buffer_name='app2')`; `server.save_buffer(buf_file, buffer_name='app2', append=True)`; `buf_file.read_text()`; assert `'first_line' in content`; `'second_line' in content`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_save_append_803c177e17 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_buffer_save_load | Test Server.save_buffer() and load_buffer() cycle; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='buf_saveload')`; `server.set_buffer('save_test_data')`; `server.save_buffer(buf_file)`; `server.load_buffer(buf_file, buffer_name='loaded_buf')`; `server.show_buffer(buffer_name='loaded_buf')`; `buf_file.read_text()`; assert `buf_file.read_text() == 'save_test_data'`; `server.show_buffer(buffer_name='loaded_buf') == 'save_test_data'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_save_load_f2d73900f7 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_buffer_set_show[set_show_append] | Test Server.set_buffer() and show_buffer() cycle; case inputs `append=True`, `buffer_name='append_test'`, `data='_second'`, `expected_content='first_second'`, `test_id='set_show_append'`; fixtures `server`; execute `server.new_session(session_name=f'buf_{test_id}')`; `server.set_buffer(data, **kwargs)`; `server.show_buffer(buffer_name=buffer_name)`; assert `result == expected_content`; when `append`: execute `server.set_buffer('first', buffer_name=buffer_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_set_show_set_show_append_123d743887 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_buffer_set_show[set_show_default] | Test Server.set_buffer() and show_buffer() cycle; case inputs `append=None`, `buffer_name=None`, `data='hello_buf'`, `expected_content='hello_buf'`, `test_id='set_show_default'`; fixtures `server`; execute `server.new_session(session_name=f'buf_{test_id}')`; `server.set_buffer(data, **kwargs)`; `server.show_buffer(buffer_name=buffer_name)`; assert `result == expected_content`; when `append`: execute `server.set_buffer('first', buffer_name=buffer_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_set_show_set_show_default_a2f68ba5f6 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_buffer_set_show[set_show_named] | Test Server.set_buffer() and show_buffer() cycle; case inputs `append=None`, `buffer_name='mybuf'`, `data='named_data'`, `expected_content='named_data'`, `test_id='set_show_named'`; fixtures `server`; execute `server.new_session(session_name=f'buf_{test_id}')`; `server.set_buffer(data, **kwargs)`; `server.show_buffer(buffer_name=buffer_name)`; assert `result == expected_content`; when `append`: execute `server.set_buffer('first', buffer_name=buffer_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_buffer_set_show_set_show_named_502e44630a | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_clear_prompt_history | Test Server.clear_prompt_history() clears history; fixtures `server`; execute `server.new_session(session_name='clearph_test')`; `server.clear_prompt_history()`; `server.clear_prompt_history(prompt_type='command')`; when `not has_gte_version('3.3')`: skip via `pytest.skip('clear-prompt-history added in tmux 3.3')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_clear_prompt_history_c0a285e790 | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_command_prompt[prefill_and_submit] | Test Server.command_prompt() with send-keys -K input; case inputs `expected_value='prefilled'`, `inputs='prefilled'`, `keys=['Enter']`, `min_tmux_version='3.4'`, `option_name='@cp_prefill'`, `template="set -g @cp_prefill '%1'"`, `test_id='prefill_and_submit'`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.command_prompt(template, **kwargs)`; `server.cmd('send-keys', '-K', '-c', ctl.client_name, key)`; `server.cmd('show-options', '-gv', option_name)`; assert `result.stdout[0] == expected_value`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_command_prompt_prefill_and_submit_0008ebf0d3 | 3.4 through 3.7b | planned parity | | tests/test_server.py::test_command_prompt[type_and_submit] | Test Server.command_prompt() with send-keys -K input; case inputs `expected_value='hello'`, `inputs=None`, `keys=['h', 'e', 'l', 'l', 'o', 'Enter']`, `min_tmux_version='3.4'`, `option_name='@cp_typed'`, `template="set -g @cp_typed '%1'"`, `test_id='type_and_submit'`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.command_prompt(template, **kwargs)`; `server.cmd('send-keys', '-K', '-c', ctl.client_name, key)`; `server.cmd('show-options', '-gv', option_name)`; assert `result.stdout[0] == expected_value`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_command_prompt_type_and_submit_d76de2d176 | 3.4 through 3.7b | planned parity | @@ -1246,13 +1246,13 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_server.py::test_command_prompt_extra_flags[expand_format_v33] | ``command_prompt`` exposes -F/-l/-e flags; case inputs `expected_flag='-F'`, `kwargs={'expand_format': True}`, `min_tmux_version='3.3'`; fixtures `monkeypatch`, `server`; execute `server.command_prompt("set -g @x '%1'", **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `expected_flag in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'command-prompt'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_command_prompt_extra_flags_expand_format_v33_5eab7eff82 | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_command_prompt_extra_flags[literal_v36] | ``command_prompt`` exposes -F/-l/-e flags; case inputs `expected_flag='-l'`, `kwargs={'literal': True}`, `min_tmux_version='3.6'`; fixtures `monkeypatch`, `server`; execute `server.command_prompt("set -g @x '%1'", **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `expected_flag in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'command-prompt'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_command_prompt_extra_flags_literal_v36_f428a207a5 | 3.6 through 3.7b | planned parity | | tests/test_server.py::test_command_prompt_extra_flags[no_freeze] | ``command_prompt`` exposes -F/-l/-e flags; case inputs `expected_flag='-C'`, `kwargs={'no_freeze': True}`, `min_tmux_version='3.7'`; fixtures `monkeypatch`, `server`; execute `server.command_prompt("set -g @x '%1'", **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `expected_flag in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'command-prompt'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_command_prompt_extra_flags_no_freeze_a0fbee6fd7 | 3.7 through 3.7b | planned parity | -| tests/test_server.py::test_config | ``-f`` file for tmux(1) configuration; fixtures `server`; execute `Server(config_file='test')`; assert `myserver.config_file == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_config_25243d066f | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_config | ``-f`` file for tmux(1) configuration; fixtures `server`; execute `Server(config_file='test')`; assert `myserver.config_file == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_config_25243d066f | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_confirm_before[confirm_y] | Test Server.confirm_before() with send-keys -K confirmation; case inputs `confirm_key='y'`, `custom_confirm_key=None`, `expected_value='yes'`, `min_tmux_version='3.4'`, `option_name='@cf_test_y'`, `test_id='confirm_y'`, `use_default_yes=False`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.confirm_before(f'set -g {option_name} {expected_value}', **kwargs)`; `server.cmd('send-keys', '-K', '-c', ctl.client_name, confirm_key)`; `server.cmd('show-options', '-gv', option_name)`; assert `result.stdout[0] == expected_value`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_confirm_before_confirm_y_9679d25e31 | 3.4 through 3.7b | planned parity | | tests/test_server.py::test_confirm_before[default_yes_enter] | Test Server.confirm_before() with send-keys -K confirmation; case inputs `confirm_key='Enter'`, `custom_confirm_key=None`, `expected_value='yes'`, `min_tmux_version='3.4'`, `option_name='@cf_test_enter'`, `test_id='default_yes_enter'`, `use_default_yes=True`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.confirm_before(f'set -g {option_name} {expected_value}', **kwargs)`; `server.cmd('send-keys', '-K', '-c', ctl.client_name, confirm_key)`; `server.cmd('show-options', '-gv', option_name)`; assert `result.stdout[0] == expected_value`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_confirm_before_default_yes_enter_7a009dbd49 | 3.4 through 3.7b | planned parity | -| tests/test_server.py::test_detach_all_clients_keep_client_spans_sessions | ``Server.detach_all_clients(keep_client=...)`` is server-wide; fixtures `control_mode`, `server`, `session`; execute `server.new_session(session_name='detach_all_other')`; `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_all_clients(keep_client=keep.client_name)`; `functools.partial(ControlMode, server=server, session=other_session)`; `OtherControlMode()`; assert `len(before) == 3`; `after == [keep.client_name]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_all_clients_keep_client_spans_sessions_02ead1f1a2 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_detach_all_clients_no_keep_preserves_one | ``-a`` without ``-t`` preserves tmux's most-recently-active client; fixtures `control_mode`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_all_clients()`; assert `len(before) == 2`; `len(after) == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_all_clients_no_keep_preserves_one_2d7eeafd08 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_detach_client_no_target_uses_active | ``Server.detach_client()`` without ``-t`` falls back to active client; fixtures `control_mode`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_client()`; assert `len(before) == 2`; `len(after) == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_client_no_target_uses_active_a6301769cc | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_detach_client_target_client_spans_sessions | ``Server.detach_client(target_client=...)`` resolves names server-wide; fixtures `control_mode`, `server`, `session`; execute `server.new_session(session_name='detach_other_t')`; `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_client(target_client=elsewhere.client_name)`; `functools.partial(ControlMode, server=server, session=other_session)`; `OtherControlMode()`; assert `len(before) == 2`; `elsewhere.client_name in before`; `elsewhere.client_name not in after`; `len(after) == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_client_target_client_spans_sessions_7b07f24451 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_detach_all_clients_keep_client_spans_sessions | ``Server.detach_all_clients(keep_client=...)`` is server-wide; fixtures `control_mode`, `server`, `session`; execute `server.new_session(session_name='detach_all_other')`; `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_all_clients(keep_client=keep.client_name)`; `functools.partial(ControlMode, server=server, session=other_session)`; `OtherControlMode()`; assert `len(before) == 3`; `after == [keep.client_name]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_all_clients_keep_client_spans_sessions_02ead1f1a2 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_detach_all_clients_no_keep_preserves_one | ``-a`` without ``-t`` preserves tmux's most-recently-active client; fixtures `control_mode`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_all_clients()`; assert `len(before) == 2`; `len(after) == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_all_clients_no_keep_preserves_one_2d7eeafd08 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_detach_client_no_target_uses_active | ``Server.detach_client()`` without ``-t`` falls back to active client; fixtures `control_mode`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_client()`; assert `len(before) == 2`; `len(after) == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_client_no_target_uses_active_a6301769cc | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_detach_client_target_client_spans_sessions | ``Server.detach_client(target_client=...)`` resolves names server-wide; fixtures `control_mode`, `server`, `session`; execute `server.new_session(session_name='detach_other_t')`; `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `server.detach_client(target_client=elsewhere.client_name)`; `functools.partial(ControlMode, server=server, session=other_session)`; `OtherControlMode()`; assert `len(before) == 2`; `elsewhere.client_name in before`; `elsewhere.client_name not in after`; `len(after) == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_detach_client_target_client_spans_sessions_7b07f24451 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_display_menu_flags[basic] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={}`, `min_tmux_version=None`, `test_id='basic'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_basic_c52ed1c12e | not tmux-bound | planned parity | | tests/test_server.py::test_display_menu_flags[with_border_lines_v34] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'border_lines': 'single'}`, `min_tmux_version='3.4'`, `test_id='with_border_lines_v34'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_border_lines_v34_8fb25e00bb | 3.4 through 3.7b | planned parity | | tests/test_server.py::test_display_menu_flags[with_border_style_v34] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'border_style': 'fg=red'}`, `min_tmux_version='3.4'`, `test_id='with_border_style_v34'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_border_style_v34_ef9e129011 | 3.4 through 3.7b | planned parity | @@ -1260,150 +1260,150 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_server.py::test_display_menu_flags[with_position] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'x': 'C', 'y': 'C'}`, `min_tmux_version=None`, `test_id='with_position'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_position_bec663c9e5 | not tmux-bound | planned parity | | tests/test_server.py::test_display_menu_flags[with_selected_style_v34] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'selected_style': 'bg=yellow'}`, `min_tmux_version='3.4'`, `test_id='with_selected_style_v34'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_selected_style_v34_c12d276edc | 3.4 through 3.7b | planned parity | | tests/test_server.py::test_display_menu_flags[with_starting_choice_v34] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'starting_choice': '0'}`, `min_tmux_version='3.4'`, `test_id='with_starting_choice_v34'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_starting_choice_v34_814a155a3e | 3.4 through 3.7b | planned parity | -| tests/test_server.py::test_display_menu_flags[with_stay_open] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'stay_open': True}`, `min_tmux_version='3.2'`, `test_id='with_stay_open'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_stay_open_b69bb481c4 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_display_menu_flags[with_stay_open] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'stay_open': True}`, `min_tmux_version='3.2'`, `test_id='with_stay_open'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_stay_open_b69bb481c4 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_display_menu_flags[with_style_v34] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'style': 'bg=blue'}`, `min_tmux_version='3.4'`, `test_id='with_style_v34'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_style_v34_69241b4ceb | 3.4 through 3.7b | planned parity | | tests/test_server.py::test_display_menu_flags[with_title] | Test Server.display_menu() argument construction; case inputs `items=('First', '1', 'select-pane')`, `kwargs={'title': 'menu_title'}`, `min_tmux_version=None`, `test_id='with_title'`; fixtures `monkeypatch`, `server`; execute `server.display_menu(*items, **kwargs)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `cmd == 'display-menu'`; `str(value) in flags`; `item in flags`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `cmd == 'display-menu'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_display_menu_flags_with_title_74fc7520c8 | not tmux-bound | planned parity | -| tests/test_server.py::test_getenv | Set environment then Server.show_environment(key); fixtures `server`, `session`; execute `server.set_environment('FOO', 'BAR')`; `server.getenv('FOO')`; `server.set_environment('FOO', 'DAR')`; `server.show_environment()`; assert `server.getenv('FOO') == 'BAR'`; `server.getenv('FOO') == 'DAR'`; `server.show_environment()['FOO'] == 'DAR'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_getenv_e2794ae688 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_has_session | Server.has_session() returns True if session exists; fixtures `server`, `session`; execute `server.has_session(session_name)`; `server.has_session('asdf2314324321')`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session('asdf2314324321')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_has_session_7df25e30d5 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_if_shell_false_with_else | Test Server.if_shell() with false condition and else branch; fixtures `server`; execute `server.new_session(session_name='ifshell_else')`; `server.if_shell('false', 'set -g @if_else_test yes', else_command='set -g @if_else_test no')`; `server.cmd('show-options', '-gv', '@if_else_test')`; assert `result.stdout[0] == 'no'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_if_shell_false_with_else_a42b686bae | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_if_shell_true | Test Server.if_shell() with true condition; fixtures `server`; execute `server.new_session(session_name='ifshell_test')`; `server.if_shell('true', 'set -g @if_test_true yes')`; `server.cmd('show-options', '-gv', '@if_test_true')`; assert `result.stdout[0] == 'yes'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_if_shell_true_bdf7b5be71 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_buffers | Test Server.list_buffers(); fixtures `server`; execute `server.new_session(session_name='buf_list')`; `server.set_buffer('buf_a', buffer_name='list_a')`; `server.set_buffer('buf_b', buffer_name='list_b')`; `server.list_buffers()`; assert `len(result) >= 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_buffers_3cbc39ed66 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_buffers_filter_pushes_predicate_into_tmux | ``filter=`` pushes the match into tmux's format engine (-f flag); fixtures `server`; execute `server.new_session(session_name='buf_filter')`; `server.set_buffer('keep_me', buffer_name='gap6match_alpha')`; `server.set_buffer('keep_me', buffer_name='gap6match_beta')`; `server.set_buffer('drop_me', buffer_name='gap6miss_one')`; `server.list_buffers(format_string='#{buffer_name}', filter='#{m:gap6match_*,#{buffer_name}}')`; assert `sorted(matches) == ['gap6match_alpha', 'gap6match_beta']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_buffers_filter_pushes_predicate_into_tmux_09f399a626 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_buffers_format_returns_raw_names | ``format_string`` projects raw names instead of the default template; fixtures `server`; execute `server.new_session(session_name='buf_format')`; `server.set_buffer('payload_a', buffer_name='fmt_a')`; `server.set_buffer('payload_b', buffer_name='fmt_b')`; `server.list_buffers(format_string='#{buffer_name}')`; `any(('bytes:' in line for line in names))`; assert `'fmt_a' in names`; `'fmt_b' in names`; `not any(('bytes:' in line for line in names))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_buffers_format_returns_raw_names_f953a24e1f | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_clients | Test Server.list_clients() returns list without error; fixtures `server`; execute `server.new_session(session_name='list_clients_test')`; `server.list_clients()`; assert `isinstance(result, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_clients_3bc9a653cf | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_commands | Test Server.list_commands() returns command listing; fixtures `server`; execute `server.new_session(session_name='listcmds_test')`; `server.list_commands()`; `server.list_commands(command_name='send-keys')`; assert `len(result) > 50`; `len(result) >= 1`; `'send-keys' in result[0]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_commands_1eb8b2b439 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_keys | Test Server.list_keys() returns key bindings; fixtures `server`; execute `server.new_session(session_name='listkeys_test')`; `server.list_keys()`; assert `isinstance(result, list)`; `len(result) > 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_keys_0bb88cdcfd | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_list_keys_format | Server.list_keys(format_=...) applies -F on tmux 3.7+; warns below; fixtures `server`; execute `server.new_session(session_name='listkeys_fmt')`; when `has_gte_version('3.7')`: execute `server.list_keys(format_='fmt')`; `all((line == 'fmt' for line in result))`; assert `result`; `all((line == 'fmt' for line in result))`; when `not (has_gte_version('3.7'))`: execute `server.list_keys(format_='fmt')`; expect `pytest.warns(UserWarning, match='format requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_keys_format_8502678464 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_server.py::test_lock_client | Test Server.lock_client() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.lock_client()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_lock_client_739fab5726 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_lock_server | Test Server.lock_server() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.lock_server()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_lock_server_dc3591d2c4 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session | Server.new_session creates and returns valid session; fixtures `server`; execute `server.new_session('test_new_session')`; `server.has_session('test_new_session')`; assert `mysession.session_name == 'test_new_session'`; `server.has_session('test_new_session')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_0db766e735 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_client_flags | Test Server.new_session() with client_flags flag; fixtures `server`; execute `server.new_session(session_name='flags_test', client_flags='no-output')`; assert `session.session_name == 'flags_test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_client_flags_21a8b45b06 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_environmental_variables | Server.new_session creates and returns valid session; fixtures `server`; execute `server.new_session('test_new_session', environment={'FOO': 'HI'})`; `my_session.show_environment()`; assert `my_session.show_environment()['FOO'] == 'HI'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_environmental_variables_d9c684e505 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_no_name | Server.new_session works with no name; fixtures `server`; execute `server.new_session()`; `server.has_session(first_session_name)`; `server.has_session(second_session_name)`; assert `first_session_name is not None`; `server.has_session(first_session_name)`; `expected_session_name == second_session_name`; `second_session_name is not None`; `server.has_session(second_session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_no_name_216d2c2c10 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_returns_populated_session | Server.new_session returns Session populated from -P output; fixtures `server`; execute `server.new_session(session_name='test_populated')`; assert `session.session_id is not None`; `session.session_name == 'test_populated'`; `session.window_id is not None`; `session.pane_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_returns_populated_session_5df3cdc9f3 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_shell | Verify ``Server.new_session`` creates valid session running w/ command; fixtures `server`; execute `server.new_session('test_new_session', window_command=cmd)`; `server.has_session('test_new_session')`; `pane_start_command.replace('"', '')`; assert `mysession.session_name == 'test_new_session'`; `server.has_session('test_new_session')`; `pane_start_command is not None`; `pane_start_command.replace('"', '') == cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_shell_880257086b | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_shell_env | Verify ``Server.new_session`` creates valid session running w/ command (#553); fixtures `server`; execute `server.new_session('test_new_session_env', window_command=cmd, environment=env)`; `server.has_session('test_new_session_env')`; `time.sleep(0.1)`; `pane_start_command.replace('"', '')`; assert `mysession.session_name == 'test_new_session_env'`; `server.has_session('test_new_session_env')`; `pane_start_command is not None`; `pane_start_command.replace('"', '') == cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_shell_env_8bd84ff212 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_start_directory[empty_string] | Test Server.new_session start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_empty_string_cc3a633c65 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_start_directory[none_value] | Test Server.new_session start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_none_value_cb73fa98e8 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_start_directory[relative_path] | Test Server.new_session start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_relative_path_dffb8dbd6c | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_start_directory[user_path] | Test Server.new_session start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_user_path_32ae926ce5 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_start_directory_pathlib | Test Server.new_session accepts pathlib.Path for start_directory; fixtures `server`, `user_path`; execute `server.new_session(session_name='test_pathlib_start_dir', start_directory=user_path)`; `server.has_session('test_pathlib_start_dir')`; `user_path.resolve()`; `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `session.session_name == 'test_pathlib_start_dir'`; `server.has_session('test_pathlib_start_dir')`; `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_pathlib_43f72d0bfa | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_new_session_width_height | Verify ``Server.new_session`` creates valid session running w/ dimensions; fixtures `server`; execute `server.new_session('test_new_session_width_height', window_command=cmd, x=32, y=32)`; `pane.display_message('#{window_width}', get_text=True)`; `pane.display_message('#{window_height}', get_text=True)`; assert `pane.display_message('#{window_width}', get_text=True)[0] == '32'`; `pane.display_message('#{window_height}', get_text=True)[0] == '32'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_width_height_8fb6922568 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_no_server_attached_sessions | Verify ``Server.attached_sessions`` returns empty list without tmux server; fixtures `server`; assert `server.attached_sessions == []`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_no_server_attached_sessions_ccabb719ad | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_no_server_is_alive | Verify is_alive() returns False without tmux server; fixtures `server`; execute `server.is_alive()`; assert `not server.is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_no_server_is_alive_93ab885f0e | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_no_server_sessions | Verify ``Server.sessions`` returns empty list without tmux server; fixtures `server`; assert `server.sessions == []`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_no_server_sessions_aa88e052c0 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_on_init | Verify on_init callback is called during Server initialization; fixtures `server`; execute `Server(socket_name='test_on_init', on_init=on_init)`; assert `len(called_with) == 1`; `called_with[0] is myserver`; when `myserver.is_alive()`: execute `myserver.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_on_init_03a405e770 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_raise_if_dead_does_not_raise_if_alive | Verify new_session() does not raise if tmux server is alive; fixtures `server`; execute `server.new_session()`; `server.raise_if_dead()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_raise_if_dead_does_not_raise_if_alive_6fe5a9220d | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_raise_if_dead_no_server_raises | Verify ``Server.raise_if_dead`` raises if tmux server is dead; fixtures `server`; execute `server.raise_if_dead()`; expect `pytest.raises(subprocess.CalledProcessError)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_raise_if_dead_no_server_raises_7d65b68090 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_refresh_client | Test Server.refresh_client() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.refresh_client()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_refresh_client_c7574035e0 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_getenv | Set environment then Server.show_environment(key); fixtures `server`, `session`; execute `server.set_environment('FOO', 'BAR')`; `server.getenv('FOO')`; `server.set_environment('FOO', 'DAR')`; `server.show_environment()`; assert `server.getenv('FOO') == 'BAR'`; `server.getenv('FOO') == 'DAR'`; `server.show_environment()['FOO'] == 'DAR'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_getenv_e2794ae688 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_has_session | Server.has_session() returns True if session exists; fixtures `server`, `session`; execute `server.has_session(session_name)`; `server.has_session('asdf2314324321')`; assert `session_name is not None`; `server.has_session(session_name)`; `not server.has_session('asdf2314324321')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_has_session_7df25e30d5 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_if_shell_false_with_else | Test Server.if_shell() with false condition and else branch; fixtures `server`; execute `server.new_session(session_name='ifshell_else')`; `server.if_shell('false', 'set -g @if_else_test yes', else_command='set -g @if_else_test no')`; `server.cmd('show-options', '-gv', '@if_else_test')`; assert `result.stdout[0] == 'no'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_if_shell_false_with_else_a42b686bae | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_if_shell_true | Test Server.if_shell() with true condition; fixtures `server`; execute `server.new_session(session_name='ifshell_test')`; `server.if_shell('true', 'set -g @if_test_true yes')`; `server.cmd('show-options', '-gv', '@if_test_true')`; assert `result.stdout[0] == 'yes'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_if_shell_true_bdf7b5be71 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_buffers | Test Server.list_buffers(); fixtures `server`; execute `server.new_session(session_name='buf_list')`; `server.set_buffer('buf_a', buffer_name='list_a')`; `server.set_buffer('buf_b', buffer_name='list_b')`; `server.list_buffers()`; assert `len(result) >= 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_buffers_3cbc39ed66 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_buffers_filter_pushes_predicate_into_tmux | ``filter=`` pushes the match into tmux's format engine (-f flag); fixtures `server`; execute `server.new_session(session_name='buf_filter')`; `server.set_buffer('keep_me', buffer_name='gap6match_alpha')`; `server.set_buffer('keep_me', buffer_name='gap6match_beta')`; `server.set_buffer('drop_me', buffer_name='gap6miss_one')`; `server.list_buffers(format_string='#{buffer_name}', filter='#{m:gap6match_*,#{buffer_name}}')`; assert `sorted(matches) == ['gap6match_alpha', 'gap6match_beta']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_buffers_filter_pushes_predicate_into_tmux_09f399a626 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_buffers_format_returns_raw_names | ``format_string`` projects raw names instead of the default template; fixtures `server`; execute `server.new_session(session_name='buf_format')`; `server.set_buffer('payload_a', buffer_name='fmt_a')`; `server.set_buffer('payload_b', buffer_name='fmt_b')`; `server.list_buffers(format_string='#{buffer_name}')`; `any(('bytes:' in line for line in names))`; assert `'fmt_a' in names`; `'fmt_b' in names`; `not any(('bytes:' in line for line in names))`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_buffers_format_returns_raw_names_f953a24e1f | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_clients | Test Server.list_clients() returns list without error; fixtures `server`; execute `server.new_session(session_name='list_clients_test')`; `server.list_clients()`; assert `isinstance(result, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_clients_3bc9a653cf | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_commands | Test Server.list_commands() returns command listing; fixtures `server`; execute `server.new_session(session_name='listcmds_test')`; `server.list_commands()`; `server.list_commands(command_name='send-keys')`; assert `len(result) > 50`; `len(result) >= 1`; `'send-keys' in result[0]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_commands_1eb8b2b439 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_keys | Test Server.list_keys() returns key bindings; fixtures `server`; execute `server.new_session(session_name='listkeys_test')`; `server.list_keys()`; assert `isinstance(result, list)`; `len(result) > 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_keys_0bb88cdcfd | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_list_keys_format | Server.list_keys(format_=...) applies -F on tmux 3.7+; warns below; fixtures `server`; execute `server.new_session(session_name='listkeys_fmt')`; when `has_gte_version('3.7')`: execute `server.list_keys(format_='fmt')`; `all((line == 'fmt' for line in result))`; assert `result`; `all((line == 'fmt' for line in result))`; when `not (has_gte_version('3.7'))`: execute `server.list_keys(format_='fmt')`; expect `pytest.warns(UserWarning, match='format requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_list_keys_format_8502678464 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_server.py::test_lock_client | Test Server.lock_client() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.lock_client()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_lock_client_739fab5726 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_lock_server | Test Server.lock_server() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.lock_server()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_lock_server_dc3591d2c4 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session | Server.new_session creates and returns valid session; fixtures `server`; execute `server.new_session('test_new_session')`; `server.has_session('test_new_session')`; assert `mysession.session_name == 'test_new_session'`; `server.has_session('test_new_session')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_0db766e735 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_client_flags | Test Server.new_session() with client_flags flag; fixtures `server`; execute `server.new_session(session_name='flags_test', client_flags='no-output')`; assert `session.session_name == 'flags_test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_client_flags_21a8b45b06 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_environmental_variables | Server.new_session creates and returns valid session; fixtures `server`; execute `server.new_session('test_new_session', environment={'FOO': 'HI'})`; `my_session.show_environment()`; assert `my_session.show_environment()['FOO'] == 'HI'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_environmental_variables_d9c684e505 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_no_name | Server.new_session works with no name; fixtures `server`; execute `server.new_session()`; `server.has_session(first_session_name)`; `server.has_session(second_session_name)`; assert `first_session_name is not None`; `server.has_session(first_session_name)`; `expected_session_name == second_session_name`; `second_session_name is not None`; `server.has_session(second_session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_no_name_216d2c2c10 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_returns_populated_session | Server.new_session returns Session populated from -P output; fixtures `server`; execute `server.new_session(session_name='test_populated')`; assert `session.session_id is not None`; `session.session_name == 'test_populated'`; `session.window_id is not None`; `session.pane_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_returns_populated_session_5df3cdc9f3 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_shell | Verify ``Server.new_session`` creates valid session running w/ command; fixtures `server`; execute `server.new_session('test_new_session', window_command=cmd)`; `server.has_session('test_new_session')`; `pane_start_command.replace('"', '')`; assert `mysession.session_name == 'test_new_session'`; `server.has_session('test_new_session')`; `pane_start_command is not None`; `pane_start_command.replace('"', '') == cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_shell_880257086b | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_shell_env | Verify ``Server.new_session`` creates valid session running w/ command (#553); fixtures `server`; execute `server.new_session('test_new_session_env', window_command=cmd, environment=env)`; `server.has_session('test_new_session_env')`; `time.sleep(0.1)`; `pane_start_command.replace('"', '')`; assert `mysession.session_name == 'test_new_session_env'`; `server.has_session('test_new_session_env')`; `pane_start_command is not None`; `pane_start_command.replace('"', '') == cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_shell_env_8bd84ff212 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_start_directory[empty_string] | Test Server.new_session start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_empty_string_cc3a633c65 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_start_directory[none_value] | Test Server.new_session start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_none_value_cb73fa98e8 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_start_directory[relative_path] | Test Server.new_session start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_relative_path_dffb8dbd6c | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_start_directory[user_path] | Test Server.new_session start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `server`, `monkeypatch`, `tmp_path`, `user_path`; execute `server.new_session(session_name=f'test_session_{test_id}', start_directory=actual_start_directory)`; `server.has_session(f'test_session_{test_id}')`; assert `session.session_name == f'test_session_{test_id}'`; `server.has_session(f'test_session_{test_id}')`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_user_path_32ae926ce5 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_start_directory_pathlib | Test Server.new_session accepts pathlib.Path for start_directory; fixtures `server`, `user_path`; execute `server.new_session(session_name='test_pathlib_start_dir', start_directory=user_path)`; `server.has_session('test_pathlib_start_dir')`; `user_path.resolve()`; `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `session.session_name == 'test_pathlib_start_dir'`; `server.has_session('test_pathlib_start_dir')`; `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_start_directory_pathlib_43f72d0bfa | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_new_session_width_height | Verify ``Server.new_session`` creates valid session running w/ dimensions; fixtures `server`; execute `server.new_session('test_new_session_width_height', window_command=cmd, x=32, y=32)`; `pane.display_message('#{window_width}', get_text=True)`; `pane.display_message('#{window_height}', get_text=True)`; assert `pane.display_message('#{window_width}', get_text=True)[0] == '32'`; `pane.display_message('#{window_height}', get_text=True)[0] == '32'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_new_session_width_height_8fb6922568 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_no_server_attached_sessions | Verify ``Server.attached_sessions`` returns empty list without tmux server; fixtures `server`; assert `server.attached_sessions == []`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_no_server_attached_sessions_ccabb719ad | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_no_server_is_alive | Verify is_alive() returns False without tmux server; fixtures `server`; execute `server.is_alive()`; assert `not server.is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_no_server_is_alive_93ab885f0e | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_no_server_sessions | Verify ``Server.sessions`` returns empty list without tmux server; fixtures `server`; assert `server.sessions == []`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_no_server_sessions_aa88e052c0 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_on_init | Verify on_init callback is called during Server initialization; fixtures `server`; execute `Server(socket_name='test_on_init', on_init=on_init)`; assert `len(called_with) == 1`; `called_with[0] is myserver`; when `myserver.is_alive()`: execute `myserver.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_on_init_03a405e770 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_raise_if_dead_does_not_raise_if_alive | Verify new_session() does not raise if tmux server is alive; fixtures `server`; execute `server.new_session()`; `server.raise_if_dead()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_raise_if_dead_does_not_raise_if_alive_6fe5a9220d | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_raise_if_dead_no_server_raises | Verify ``Server.raise_if_dead`` raises if tmux server is dead; fixtures `server`; execute `server.raise_if_dead()`; expect `pytest.raises(subprocess.CalledProcessError)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_raise_if_dead_no_server_raises_7d65b68090 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_refresh_client | Test Server.refresh_client() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.refresh_client()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_refresh_client_c7574035e0 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_refresh_client_request_clipboard | refresh_client(request_clipboard=True) emits -l on tmux 3.7+; fixtures `monkeypatch`, `server`; execute `server.refresh_client(request_clipboard=True)`; `real_cmd(cmd, *args, **_kw)`; assert `captured; message='Server.cmd was not invoked'`; `'-l' in flags`; when `not has_gte_version('3.7')`: skip via `pytest.skip('refresh-client -l clipboard query requires tmux 3.7+')`; when `cmd == 'refresh-client'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_refresh_client_request_clipboard_57beaa2623 | 3.7 through 3.7b | planned parity | -| tests/test_server.py::test_run_shell_args | ``args`` fill #{1}/#{2} in the command on tmux 3.7+; warn below; fixtures `server`; execute `server.new_session(session_name='run_shell_args_test')`; when `has_gte_version('3.7')`: execute `server.run_shell('echo #{1}-#{2}', args=['alpha', 'beta'])`; assert `result == ['alpha-beta']`; when `not (has_gte_version('3.7'))`: execute `server.run_shell('echo plain', args=['alpha'])`; expect `pytest.warns(UserWarning, match='args requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_args_64d490d4a8 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_server.py::test_run_shell_background | Test Server.run_shell() in background mode; fixtures `server`; execute `server.new_session(session_name='run_shell_bg_test')`; `server.run_shell('echo bg_test', background=True)`; assert `result is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_background_2301f556fa | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_run_shell_args | ``args`` fill #{1}/#{2} in the command on tmux 3.7+; warn below; fixtures `server`; execute `server.new_session(session_name='run_shell_args_test')`; when `has_gte_version('3.7')`: execute `server.run_shell('echo #{1}-#{2}', args=['alpha', 'beta'])`; assert `result == ['alpha-beta']`; when `not (has_gte_version('3.7'))`: execute `server.run_shell('echo plain', args=['alpha'])`; expect `pytest.warns(UserWarning, match='args requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_args_64d490d4a8 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_server.py::test_run_shell_background | Test Server.run_shell() in background mode; fixtures `server`; execute `server.new_session(session_name='run_shell_bg_test')`; `server.run_shell('echo bg_test', background=True)`; assert `result is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_background_2301f556fa | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_run_shell_basic | Test Server.run_shell() executes command and returns output; fixtures `server`; execute `server.new_session(session_name='run_shell_test')`; `server.run_shell('echo hello_from_run_shell')`; `any(('hello_from_run_shell' in line for line in result))`; assert `result is not None`; `any(('hello_from_run_shell' in line for line in result))`; when `not has_gte_version('3.5')`: skip via `pytest.skip('run-shell stdout passthrough requires tmux 3.5+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_basic_1089325e0a | 3.5 through 3.7b | planned parity | | tests/test_server.py::test_run_shell_cwd | ``cwd=`` sets the working directory for the shell command; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='run_shell_cwd_test')`; `server.run_shell('pwd', cwd=tmp_path)`; `any((str(tmp_path) in line for line in result))`; assert `result is not None`; `any((str(tmp_path) in line for line in result))`; when `not has_gte_version('3.5')`: skip via `pytest.skip('run-shell stdout passthrough requires tmux 3.5+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_cwd_ce2d8d6630 | 3.5 through 3.7b | planned parity | -| tests/test_server.py::test_run_shell_cwd_warns_on_old_tmux | ``cwd=`` emits a warning and skips ``-c`` on tmux <3.4; fixtures `server`, `monkeypatch`, `tmp_path`; execute `server.new_session(session_name='run_shell_cwd_warn_test')`; `server.run_shell('true', cwd=tmp_path)`; expect `pytest.warns(UserWarning, match='cwd requires tmux 3.4+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_cwd_warns_on_old_tmux_556c18c74a | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_run_shell_cwd_warns_on_old_tmux | ``cwd=`` emits a warning and skips ``-c`` on tmux <3.4; fixtures `server`, `monkeypatch`, `tmp_path`; execute `server.new_session(session_name='run_shell_cwd_warn_test')`; `server.run_shell('true', cwd=tmp_path)`; expect `pytest.warns(UserWarning, match='cwd requires tmux 3.4+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_cwd_warns_on_old_tmux_556c18c74a | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_run_shell_show_stderr | ``show_stderr=True`` captures the command's stderr into the output; fixtures `server`; execute `server.new_session(session_name='run_shell_stderr_test')`; `server.run_shell("sh -c 'echo to_stdout; echo to_stderr >&2'", show_stderr=True)`; `'\n'.join(result)`; assert `result is not None`; `'to_stdout' in joined`; `'to_stderr' in joined`; when `not has_gte_version('3.6')`: skip via `pytest.skip('run-shell -E (JOB_SHOWSTDERR) requires tmux 3.6+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_show_stderr_17f8c87e6e | 3.6 through 3.7b | planned parity | -| tests/test_server.py::test_run_shell_show_stderr_warns_on_old_tmux | ``show_stderr=True`` emits a warning and skips ``-E`` on tmux <3.6; fixtures `server`, `monkeypatch`; execute `server.new_session(session_name='run_shell_stderr_warn_test')`; `server.run_shell('true', show_stderr=True)`; expect `pytest.warns(UserWarning, match='show_stderr requires tmux 3.6+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_show_stderr_warns_on_old_tmux_9edaad1fb4 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_run_shell_show_stderr_warns_on_old_tmux | ``show_stderr=True`` emits a warning and skips ``-E`` on tmux <3.6; fixtures `server`, `monkeypatch`; execute `server.new_session(session_name='run_shell_stderr_warn_test')`; `server.run_shell('true', show_stderr=True)`; expect `pytest.warns(UserWarning, match='show_stderr requires tmux 3.6+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_run_shell_show_stderr_warns_on_old_tmux_9edaad1fb4 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_server_access_argv | ``server_access`` emits -r/-w when read_only/write are set; fixtures `monkeypatch`, `server`; execute `server.server_access(allow='alice', read_only=True)`; `server.server_access(allow='bob', write=True)`; `real_cmd(cmd, *args, **_kw)`; assert `captured[-1][1:] == ('-a', 'alice', '-r')`; `captured[-1][1:] == ('-a', 'bob', '-w')`; when `not has_gte_version('3.3')`: skip via `pytest.skip('server-access added in tmux 3.3')`; when `cmd == 'server-access'`: execute `_StubResult()`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_server_access_argv_07cf382bc4 | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_server_access_list | Test Server.server_access() list mode; fixtures `server`; execute `server.new_session(session_name='access_test')`; `server.server_access(list_access=True)`; assert `isinstance(result, list)`; when `not has_gte_version('3.3')`: skip via `pytest.skip('server-access added in tmux 3.3')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_access_list_31f46755fe | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_server_access_read_only_write_mutex | ``read_only`` and ``write`` are mutually exclusive; fixtures `server`; execute `server.server_access(allow='someuser', read_only=True, write=True)`; expect `pytest.raises(ValueError, match='mutually exclusive')`; when `not has_gte_version('3.3')`: skip via `pytest.skip('server-access added in tmux 3.3')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_access_read_only_write_mutex_d778617d24 | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_server_clients_returns_empty_on_tmux_error | ``Server.clients`` returns an empty QueryList on tmux failure; fixtures `server`, `monkeypatch`; execute `exc.LibTmuxException('simulated list-clients failure')`; assert `list(server.clients) == []`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_server_clients_returns_empty_on_tmux_error_ea525b884d | not tmux-bound | planned parity | -| tests/test_server.py::test_server_context_manager | Test Server context manager functionality; fixtures `TestServer`; execute `TestServer()`; `server.new_session()`; `server.is_alive()`; assert `server.is_alive()`; `len(server.sessions) == 1`; `session in server.sessions`; `not server.is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_context_manager_1e327821d7 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_server_context_manager | Test Server context manager functionality; fixtures `TestServer`; execute `TestServer()`; `server.new_session()`; `server.is_alive()`; assert `server.is_alive()`; `len(server.sessions) == 1`; `session in server.sessions`; `not server.is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_context_manager_1e327821d7 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_server_display_message_flags[all_formats] | Server.display_message() resolves server-scoped formats without a pane; case inputs `cmd=''`, `expected_in_output='session_name'`, `kwargs={'get_text': True, 'all_formats': True}`, `min_tmux_version=None`, `test_id='all_formats'`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message(cmd, **call_kwargs)`; `call_kwargs.setdefault('target_client', ctl.client_name)`; when `not has_gte_version('3.3')`: skip via `pytest.skip('display-message -p via control-mode client unreliable on tmux 3.2a')`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_flags_all_formats_5192eaab04 | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_server_display_message_flags[no_expand_literal] | Server.display_message() resolves server-scoped formats without a pane; case inputs `cmd='#{version}'`, `expected_in_output='#{version}'`, `kwargs={'get_text': True, 'no_expand': True}`, `min_tmux_version='3.4'`, `test_id='no_expand_literal'`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message(cmd, **call_kwargs)`; `call_kwargs.setdefault('target_client', ctl.client_name)`; when `not has_gte_version('3.3')`: skip via `pytest.skip('display-message -p via control-mode client unreliable on tmux 3.2a')`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_flags_no_expand_literal_a0dacb0756 | 3.4 through 3.7b | planned parity | | tests/test_server.py::test_server_display_message_flags[socket_path_format_string] | Server.display_message() resolves server-scoped formats without a pane; case inputs `cmd=''`, `expected_in_output='/'`, `kwargs={'get_text': True, 'format_string': '#{socket_path}'}`, `min_tmux_version=None`, `test_id='socket_path_format_string'`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message(cmd, **call_kwargs)`; `call_kwargs.setdefault('target_client', ctl.client_name)`; when `not has_gte_version('3.3')`: skip via `pytest.skip('display-message -p via control-mode client unreliable on tmux 3.2a')`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#st_server_py_test_server_display_message_flags_socket_path_format_string_0827497ab3 | 3.3 through 3.7b | planned parity | | tests/test_server.py::test_server_display_message_flags[version] | Server.display_message() resolves server-scoped formats without a pane; case inputs `cmd='#{version}'`, `expected_in_output='.'`, `kwargs={'get_text': True}`, `min_tmux_version=None`, `test_id='version'`; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message(cmd, **call_kwargs)`; `call_kwargs.setdefault('target_client', ctl.client_name)`; when `not has_gte_version('3.3')`: skip via `pytest.skip('display-message -p via control-mode client unreliable on tmux 3.2a')`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_flags_version_8f43cbd540 | 3.3 through 3.7b | planned parity | -| tests/test_server.py::test_server_display_message_no_text_returns_none | Without ``get_text=True`` the call renders to status line and returns None; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message('hi from libtmux', target_client=ctl.client_name)`; assert `result is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_no_text_returns_none_d9e3b01060 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_server_display_message_no_text_returns_none | Without ``get_text=True`` the call renders to status line and returns None; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message('hi from libtmux', target_client=ctl.client_name)`; assert `result is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_no_text_returns_none_d9e3b01060 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_server_display_message_target_client | ``target_client`` is plumbed through as ``-c``; get_text=True returns stdout; fixtures `control_mode`, `server`; execute `control_mode()`; `server.display_message('#{version}', get_text=True, target_client=ctl.client_name)`; `result[0].strip()`; assert `isinstance(result, list)`; `len(result) == 1`; `result[0].strip() != ''`; when `not has_gte_version('3.3')`: skip via `pytest.skip('display-message -p via control-mode client unreliable on tmux 3.2a')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_target_client_8b56463be4 | 3.3 through 3.7b | planned parity | -| tests/test_server.py::test_server_display_message_warns_on_tmux_error | Tmux stderr on ``display-message`` surfaces as a :class:`UserWarning`; fixtures `server`, `session`; execute `server.display_message('x', get_text=True, format_string='#{version}')`; expect `pytest.warns(UserWarning, match='only one of -F or argument')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_warns_on_tmux_error_eae195e1a6 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_server_search_panes_filter_by_id | ``Server.list_panes(filter=...)`` returns only the pane id we asked for; fixtures `server`; execute `server.new_session(session_name='gap7_pane_demo')`; `server.search_panes(filter=f'#{{m:{target.pane_id},#{{pane_id}}}}')`; `sess.active_window.split()`; assert `[p.pane_id for p in matches] == [target.pane_id]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_panes_filter_by_id_f47562f057 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_server_search_sessions_filter | ``Server.list_sessions(filter=...)`` returns only matching sessions; fixtures `server`; execute `server.new_session(session_name='gap7_keep_alpha')`; `server.new_session(session_name='gap7_keep_beta')`; `server.new_session(session_name='other_drop')`; `server.search_sessions(filter='#{m:gap7_*,#{session_name}}')`; assert `names == ['gap7_keep_alpha', 'gap7_keep_beta']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_sessions_filter_ec7be52ce7 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_server_display_message_warns_on_tmux_error | Tmux stderr on ``display-message`` surfaces as a :class:`UserWarning`; fixtures `server`, `session`; execute `server.display_message('x', get_text=True, format_string='#{version}')`; expect `pytest.warns(UserWarning, match='only one of -F or argument')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_display_message_warns_on_tmux_error_eae195e1a6 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_server_search_panes_filter_by_id | ``Server.list_panes(filter=...)`` returns only the pane id we asked for; fixtures `server`; execute `server.new_session(session_name='gap7_pane_demo')`; `server.search_panes(filter=f'#{{m:{target.pane_id},#{{pane_id}}}}')`; `sess.active_window.split()`; assert `[p.pane_id for p in matches] == [target.pane_id]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_panes_filter_by_id_f47562f057 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_server_search_sessions_filter | ``Server.list_sessions(filter=...)`` returns only matching sessions; fixtures `server`; execute `server.new_session(session_name='gap7_keep_alpha')`; `server.new_session(session_name='gap7_keep_beta')`; `server.new_session(session_name='other_drop')`; `server.search_sessions(filter='#{m:gap7_*,#{session_name}}')`; assert `names == ['gap7_keep_alpha', 'gap7_keep_beta']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_sessions_filter_ec7be52ce7 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_server_search_sessions_propagates_errors | ``Server.search_sessions`` re-raises tmux errors instead of swallowing them; fixtures `server`, `monkeypatch`; execute `server.search_sessions(filter='#{m:keep_*,#{session_name}}')`; `exc.LibTmuxException('simulated list-sessions failure')`; expect `pytest.raises(exc.LibTmuxException, match='simulated list-sessions failure')`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_sessions_propagates_errors_66a827c1ce | not tmux-bound | planned parity | -| tests/test_server.py::test_server_search_windows_filter | ``Server.list_windows(filter=...)`` returns only matching windows; fixtures `server`; execute `server.new_session(session_name='gap7_win_demo')`; `server.search_windows(filter='#{m:gap7_*,#{window_name}}')`; `sess.new_window(window_name='gap7_target')`; `sess.new_window(window_name='other_window')`; assert `'gap7_target' in names`; `'other_window' not in names`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_windows_filter_72abfdcb96 | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_server_search_windows_filter | ``Server.list_windows(filter=...)`` returns only matching windows; fixtures `server`; execute `server.new_session(session_name='gap7_win_demo')`; `server.search_windows(filter='#{m:gap7_*,#{window_name}}')`; `sess.new_window(window_name='gap7_target')`; `sess.new_window(window_name='other_window')`; assert `'gap7_target' in names`; `'other_window' not in names`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_server_search_windows_filter_72abfdcb96 | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_server_sessions_missing_socket_returns_empty | A not-yet-created tmux socket preserves the empty-list contract; fixtures `tmp_path`; execute `Server(socket_path=tmp_path / 'missing.sock')`; assert `list(missing_server.sessions) == []`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_server_sessions_missing_socket_returns_empty_8e313dc930 | not tmux-bound | planned parity | | tests/test_server.py::test_server_sessions_permission_error_returns_empty | Connection errors are absorbed into the empty-list contract too; fixtures `server`, `monkeypatch`; execute `exc.LibTmuxException('error connecting to /root/libtmux-review.sock (Permission denied)')`; assert `list(server.sessions) == []`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_server_sessions_permission_error_returns_empty_bd9199538a | not tmux-bound | planned parity | | tests/test_server.py::test_server_sessions_returns_empty_on_tmux_error | ``Server.sessions`` returns an empty QueryList on tmux failure; fixtures `server`, `monkeypatch`; execute `exc.LibTmuxException('simulated list-sessions failure')`; assert `list(server.sessions) == []`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_server_sessions_returns_empty_on_tmux_error_0bb2a2f0f0 | not tmux-bound | planned parity | -| tests/test_server.py::test_sessions_property_hydrates_cross_scope | Server.sessions hydrates active window/pane via tmux's format_defaults cascade; fixtures `server`; execute `server.new_session(session_name='hydration_cascade_sessions')`; assert `fetched is not None`; `fetched.window_id == new_session.active_window.window_id`; `active_pane is not None`; `fetched.pane_id == active_pane.pane_id`; `fetched.pane_current_command is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_sessions_property_hydrates_cross_scope_2c7b5a5326 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_show_environment | Server.show_environment() returns dict; fixtures `server`; execute `server.show_environment()`; assert `isinstance(vars_, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_environment_7ba64484f0 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_show_environment_not_set | Unset environment variable returns None; fixtures `server`; execute `server.getenv('BAR')`; assert `server.getenv('BAR') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_environment_not_set_1582655456 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_show_messages | Test Server.show_messages() returns message log; fixtures `control_mode`, `server`; execute `control_mode()`; `server.show_messages(target_client=ctl.client_name)`; assert `isinstance(result, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_messages_34918c629f | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_sessions_property_hydrates_cross_scope | Server.sessions hydrates active window/pane via tmux's format_defaults cascade; fixtures `server`; execute `server.new_session(session_name='hydration_cascade_sessions')`; assert `fetched is not None`; `fetched.window_id == new_session.active_window.window_id`; `active_pane is not None`; `fetched.pane_id == active_pane.pane_id`; `fetched.pane_current_command is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_sessions_property_hydrates_cross_scope_2c7b5a5326 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_show_environment | Server.show_environment() returns dict; fixtures `server`; execute `server.show_environment()`; assert `isinstance(vars_, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_environment_7ba64484f0 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_show_environment_not_set | Unset environment variable returns None; fixtures `server`; execute `server.getenv('BAR')`; assert `server.getenv('BAR') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_environment_not_set_1582655456 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_show_messages | Test Server.show_messages() returns message log; fixtures `control_mode`, `server`; execute `control_mode()`; `server.show_messages(target_client=ctl.client_name)`; assert `isinstance(result, list)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_messages_34918c629f | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_show_messages_terminals_jobs | Test Server.show_messages(terminals=...) and (jobs=...) work clientless; fixtures `server`; execute `server.new_session(session_name='showmsg_alt_test')`; `server.show_messages(terminals=True)`; `server.show_messages(jobs=True)`; assert `isinstance(terminals, list)`; `isinstance(jobs, list)`; when `not has_gte_version('3.6')`: skip via `pytest.skip('show-messages -T/-J without a client requires tmux 3.6+')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_messages_terminals_jobs_e75a4aff0b | 3.6 through 3.7b | planned parity | | tests/test_server.py::test_show_prompt_history | Test Server.show_prompt_history() returns history; fixtures `server`; execute `server.new_session(session_name='showph_test')`; `server.show_prompt_history()`; assert `isinstance(result, list)`; when `not has_gte_version('3.3')`: skip via `pytest.skip('show-prompt-history added in tmux 3.3')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_show_prompt_history_33bd02697d | 3.3 through 3.7b | planned parity | -| tests/test_server.py::test_socket_name | ``-L`` socket_name; fixtures `server`; execute `Server(socket_name='test')`; assert `myserver.socket_name == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_name_b0b7edd056 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_socket_name_factory | Verify socket_name_factory generates socket names; fixtures `server`; execute `Server(socket_name_factory=socket_name_factory)`; assert `myserver.socket_name == 'test_socket_0'`; `socket_names == ['test_socket_0']`; `myserver2.socket_name == 'test_socket_1'`; `socket_names == ['test_socket_0', 'test_socket_1']`; when `myserver2.is_alive()`: execute `myserver2.kill()`; when `myserver.is_alive()`: execute `myserver.kill()`; when `myserver2.is_alive()`: execute `myserver2.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_name_factory_9fecad211b | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_socket_name_precedence | Verify socket_name takes precedence over socket_name_factory; fixtures `server`; execute `Server(socket_name='explicit_name', socket_name_factory=socket_name_factory)`; `Server(socket_name_factory=socket_name_factory)`; assert `myserver.socket_name == 'explicit_name'`; `myserver2.socket_name == 'from_factory'`; when `myserver.is_alive()`: execute `myserver.kill()`; when `myserver2.is_alive()`: execute `myserver2.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_name_precedence_e229b2ee2c | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_socket_path | ``-S`` socket_path (alternative path for server socket); fixtures `server`; execute `Server(socket_path='test')`; assert `myserver.socket_path == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_path_414cdd0f5d | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_socket_name | ``-L`` socket_name; fixtures `server`; execute `Server(socket_name='test')`; assert `myserver.socket_name == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_name_b0b7edd056 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_socket_name_factory | Verify socket_name_factory generates socket names; fixtures `server`; execute `Server(socket_name_factory=socket_name_factory)`; assert `myserver.socket_name == 'test_socket_0'`; `socket_names == ['test_socket_0']`; `myserver2.socket_name == 'test_socket_1'`; `socket_names == ['test_socket_0', 'test_socket_1']`; when `myserver2.is_alive()`: execute `myserver2.kill()`; when `myserver.is_alive()`: execute `myserver.kill()`; when `myserver2.is_alive()`: execute `myserver2.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_name_factory_9fecad211b | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_socket_name_precedence | Verify socket_name takes precedence over socket_name_factory; fixtures `server`; execute `Server(socket_name='explicit_name', socket_name_factory=socket_name_factory)`; `Server(socket_name_factory=socket_name_factory)`; assert `myserver.socket_name == 'explicit_name'`; `myserver2.socket_name == 'from_factory'`; when `myserver.is_alive()`: execute `myserver.kill()`; when `myserver2.is_alive()`: execute `myserver2.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_name_precedence_e229b2ee2c | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_socket_path | ``-S`` socket_path (alternative path for server socket); fixtures `server`; execute `Server(socket_path='test')`; assert `myserver.socket_path == 'test'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_socket_path_414cdd0f5d | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_socket_path_not_derived_from_socket_name | A named socket leaves ``socket_path`` unset; execute `Server(socket_name='libtmux_test_named_socket')`; assert `myserver.socket_name == 'libtmux_test_named_socket'`; `myserver.socket_path is None`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_socket_path_not_derived_from_socket_name_6ee7453b0f | not tmux-bound | planned parity | -| tests/test_server.py::test_source_file | Test Server.source_file() sources a config file; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='source_test')`; `server.source_file(conf)`; `server.cmd('show-options', '-gv', '@source_test_opt')`; `conf.write_text('set -g @source_test_opt yes\n')`; assert `result.stdout[0] == 'yes'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_source_file_08aff0a5fe | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_source_file_quiet | Test Server.source_file() with quiet flag ignores missing files; fixtures `server`; execute `server.new_session(session_name='source_quiet')`; `server.source_file('/nonexistent/path.conf', quiet=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_source_file_quiet_920d1689dc | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_start_server | Test Server.start_server() runs without error; fixtures `server`; execute `server.new_session(session_name='startsvr_test')`; `server.start_server()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_start_server_aca8dbe99f | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_suspend_client | Test Server.suspend_client() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.suspend_client()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_suspend_client_bca6a43c1e | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_tmux_bin_custom_path | Custom tmux_bin path is used for commands; fixtures `caplog`; execute `shutil.which('tmux')`; `Server(socket_name='test_tmux_bin', tmux_bin=tmux_path)`; `s.cmd('list-sessions')`; `any((tmux_path in r.tmux_cmd for r in running_records))`; assert `tmux_path is not None`; `s.tmux_bin == tmux_path`; `any((tmux_path in r.tmux_cmd for r in running_records))`; when `s.is_alive()`: execute `s.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_tmux_bin_custom_path_cfdd5e61a4 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_tmux_bin_default | Default tmux_bin is None, falls back to shutil.which; fixtures `server`; assert `server.tmux_bin is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_tmux_bin_default_0871ffd23c | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_source_file | Test Server.source_file() sources a config file; fixtures `server`, `tmp_path`; execute `server.new_session(session_name='source_test')`; `server.source_file(conf)`; `server.cmd('show-options', '-gv', '@source_test_opt')`; `conf.write_text('set -g @source_test_opt yes\n')`; assert `result.stdout[0] == 'yes'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_source_file_08aff0a5fe | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_source_file_quiet | Test Server.source_file() with quiet flag ignores missing files; fixtures `server`; execute `server.new_session(session_name='source_quiet')`; `server.source_file('/nonexistent/path.conf', quiet=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_source_file_quiet_920d1689dc | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_start_server | Test Server.start_server() runs without error; fixtures `server`; execute `server.new_session(session_name='startsvr_test')`; `server.start_server()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_start_server_aca8dbe99f | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_suspend_client | Test Server.suspend_client() runs without error; fixtures `control_mode`, `server`; execute `control_mode()`; `server.suspend_client()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_suspend_client_bca6a43c1e | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_tmux_bin_custom_path | Custom tmux_bin path is used for commands; fixtures `caplog`; execute `shutil.which('tmux')`; `Server(socket_name='test_tmux_bin', tmux_bin=tmux_path)`; `s.cmd('list-sessions')`; `any((tmux_path in r.tmux_cmd for r in running_records))`; assert `tmux_path is not None`; `s.tmux_bin == tmux_path`; `any((tmux_path in r.tmux_cmd for r in running_records))`; when `s.is_alive()`: execute `s.kill()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_tmux_bin_custom_path_cfdd5e61a4 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_tmux_bin_default | Default tmux_bin is None, falls back to shutil.which; fixtures `server`; assert `server.tmux_bin is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_tmux_bin_default_0871ffd23c | 3.3a through 3.7c | planned parity | | tests/test_server.py::test_tmux_bin_invalid_path | Invalid tmux_bin raises TmuxCommandNotFound; execute `Server(tmux_bin='/nonexistent/tmux')`; `s.cmd('list-sessions')`; expect `pytest.raises(exc.TmuxCommandNotFound)`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_tmux_bin_invalid_path_65283949c8 | not tmux-bound | planned parity | | tests/test_server.py::test_tmux_bin_invalid_path_raise_if_dead | Invalid tmux_bin raises TmuxCommandNotFound in raise_if_dead(); execute `Server(tmux_bin='/nonexistent/tmux')`; `s.raise_if_dead()`; expect `pytest.raises(exc.TmuxCommandNotFound)`. | unit test | PythonBehaviorParityTest#tests_test_server_py_test_tmux_bin_invalid_path_raise_if_dead_5cc8c0e5e7 | not tmux-bound | planned parity | -| tests/test_server.py::test_wait_for_set_flag | Test Server.wait_for() with set_flag; fixtures `server`; execute `server.new_session(session_name='wait_test')`; `server.wait_for('test_channel_set', set_flag=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_wait_for_set_flag_094065e1a9 | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_windows_property_hydrates_active_pane | Server.windows hydrates each window's active pane via the cascade; fixtures `server`, `session`; execute `session.new_window(window_name='hydration_cascade_windows')`; assert `fetched is not None`; `active_pane is not None`; `fetched.pane_id == active_pane.pane_id`; `fetched.pane_current_command is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_windows_property_hydrates_active_pane_14771ea36b | 3.2a through 3.7b | planned parity | -| tests/test_server.py::test_with_server_is_alive | Verify is_alive() returns True when tmux server is alive; fixtures `server`; execute `server.new_session()`; `server.is_alive()`; assert `server.is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_with_server_is_alive_b7b068c8dd | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_active_pane | Session.active_pane returns Pane; fixtures `session`; assert `isinstance(session.active_pane, Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_active_pane_43afb8c4be | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_active_window | Session.active_window returns Window; fixtures `session`; assert `isinstance(session.active_window, Window)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_active_window_fae471f129 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_cmd_inserts_session_id | Verify Session.cmd() inserts session_id; fixtures `session`; execute `session.cmd('not-a-command', last_arg)`; assert `'-t' in cmd.cmd`; `current_session_id in cmd.cmd`; `cmd.cmd[-1] == last_arg`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_cmd_inserts_session_id_7f4de558f3 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_detach_client_no_target_detaches_all_session_clients | Test Session.detach_client() without a target detaches all session clients; fixtures `control_mode`, `session`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `session.detach_client()`; assert `len(before) == 2`; `len(after) == 0`. | real-tmux integration test | PythonBehaviorParityTest#est_session_py_test_detach_client_no_target_detaches_all_session_clients_b3e4b9869c | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_empty_session_option_returns_None | Verify Session._show_option returns None for unset option; fixtures `session`; execute `session._show_option('default-shell')`; assert `session._show_option('default-shell') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_empty_session_option_returns_None_19c68e0e4c | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_has_session | Server.has_session returns True if has session_name exists; fixtures `server`, `session`; execute `server.has_session(TEST_SESSION_NAME)`; `server.has_session(TEST_SESSION_NAME[:-2])`; `server.has_session(TEST_SESSION_NAME[:-2], exact=False)`; `server.has_session('asdf2314324321')`; assert `TEST_SESSION_NAME is not None`; `server.has_session(TEST_SESSION_NAME)`; `not server.has_session(TEST_SESSION_NAME[:-2])`; `server.has_session(TEST_SESSION_NAME[:-2], exact=False)`; `not server.has_session('asdf2314324321')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_has_session_2fd59585c3 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_kill_session_group | Session.kill(group=True) uses -g on tmux 3.7+; warn-and-ignore below; fixtures `server`; execute `server.new_session(session_name='kill_group_base')`; when `has_gte_version('3.7')`: execute `session.kill(group=True)`; `server.has_session('kill_group_base')`; assert `not server.has_session('kill_group_base')`; when `not (has_gte_version('3.7'))`: execute `session.kill(group=True)`; expect `pytest.warns(UserWarning, match='group requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_kill_session_group_70d2bafe79 | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_session.py::test_last_window | Test Session.last_window() selects previous window; fixtures `session`; execute `session.new_window(window_name='last_a', attach=True)`; `session.new_window(window_name='last_b', attach=True)`; `session.refresh()`; `session.last_window()`; assert `session.active_window.window_id == w2.window_id`; `result.window_id == w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_last_window_57ed77cbf1 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_lock_session | Test Session.lock_session() runs without error; fixtures `session`; execute `session.lock_session()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_lock_session_1cc8396c56 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_session | Server.new_session creates new session; fixtures `server`; execute `server.new_session(session_name=new_session_name, detach=True)`; assert `isinstance(new_session, Session)`; `new_session.session_name == new_session_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_session_e2de3569ae | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_kill_existing | Test Session.new_window() with kill_existing flag; fixtures `session`; execute `session.new_window(window_name='kill_orig', window_index='5')`; `session.new_window(window_name='kill_replace', window_index='5', kill_existing=True)`; `session.refresh()`; assert `w1.window_name == 'kill_orig'`; `w1.window_index == '5'`; `w2.window_name == 'kill_replace'`; `w2.window_index == '5'`; `'kill_orig' not in names`; `'kill_replace' in names`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_kill_existing_bd39f40040 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_select_existing | Test Session.new_window() with select_existing flag — new name; fixtures `session`; execute `session.new_window(window_name='selexist_new', select_existing=True)`; assert `w.window_name == 'selexist_new'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_select_existing_d6c7048fc6 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_start_directory[empty_string] | Test Session.new_window start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_empty_string_53afd32113 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_start_directory[none_value] | Test Session.new_window start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_none_value_61ebed7357 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_start_directory[relative_path] | Test Session.new_window start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_relative_path_df9ac332cd | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_start_directory[user_path] | Test Session.new_window start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_user_path_e9b3c36747 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_start_directory_pathlib | Test Session.new_window accepts pathlib.Path for start_directory; fixtures `session`, `user_path`; execute `session.new_window(window_name='test_pathlib_start_dir', start_directory=user_path)`; `user_path.resolve()`; `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `window.window_name == 'test_pathlib_start_dir'`; `window in session.windows`; `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_pathlib_ebf77c18a8 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_with_environment[multiple_env_vars] | Verify new window with environment vars; case inputs `environment={'ENV_VAR_1': 'window_1', 'ENV_VAR_2': 'window_2'}`, `test_id='multiple_env_vars'`; fixtures `session`; execute `session.new_window(attach=True, window_name='window_with_environment', window_shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_with_environment_multiple_env_vars_cc4d25b3ea | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_new_window_with_environment[single_env_var] | Verify new window with environment vars; case inputs `environment={'ENV_VAR': 'window'}`, `test_id='single_env_var'`; fixtures `session`; execute `session.new_window(attach=True, window_name='window_with_environment', window_shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_with_environment_single_env_var_2f715b3eb0 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_next_window | Test Session.next_window() selects next window; fixtures `session`; execute `session.new_window(window_name='next_a', attach=True)`; `session.new_window(window_name='next_b', attach=False)`; `session.refresh()`; `session.next_window()`; assert `session.active_window.window_id == w1.window_id`; `result.window_id != w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_next_window_4309c4baa6 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_periods_raise_bad_session_name[colon_in_name] | Verify session names with periods raise BadSessionName; case inputs `raises=True`, `session_name='hey:its a colon'`, `test_id='colon_in_name'`; fixtures `server`, `session`; when `raises`: execute `session.rename_session(new_name)`; `server.new_session(session_name)`; `server.has_session(session_name)`; `server.switch_client(new_name)`; `server.attach_session(new_name)`; expect `pytest.raises(exc.BadSessionName)`; when `not (raises)`: execute `server.new_session(session_name)`; `server.has_session(session_name)`; `session.rename_session(new_name)`; `server.switch_client(new_name)`; expect `pytest.raises(exc.LibTmuxException)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_periods_raise_bad_session_name_colon_in_name_aca7571512 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_periods_raise_bad_session_name[period_in_name] | Verify session names with periods raise BadSessionName; case inputs `raises=True`, `session_name='hey.period'`, `test_id='period_in_name'`; fixtures `server`, `session`; when `raises`: execute `session.rename_session(new_name)`; `server.new_session(session_name)`; `server.has_session(session_name)`; `server.switch_client(new_name)`; `server.attach_session(new_name)`; expect `pytest.raises(exc.BadSessionName)`; when `not (raises)`: execute `server.new_session(session_name)`; `server.has_session(session_name)`; `session.rename_session(new_name)`; `server.switch_client(new_name)`; expect `pytest.raises(exc.LibTmuxException)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_periods_raise_bad_session_name_period_in_name_4aca936d92 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_periods_raise_bad_session_name[valid_name] | Verify session names with periods raise BadSessionName; case inputs `raises=False`, `session_name='hey moo'`, `test_id='valid_name'`; fixtures `server`, `session`; when `raises`: execute `session.rename_session(new_name)`; `server.new_session(session_name)`; `server.has_session(session_name)`; `server.switch_client(new_name)`; `server.attach_session(new_name)`; expect `pytest.raises(exc.BadSessionName)`; when `not (raises)`: execute `server.new_session(session_name)`; `server.has_session(session_name)`; `session.rename_session(new_name)`; `server.switch_client(new_name)`; expect `pytest.raises(exc.LibTmuxException)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_periods_raise_bad_session_name_valid_name_58f1b62bac | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_previous_window | Test Session.previous_window() selects previous window; fixtures `session`; execute `session.new_window(window_name='prev_a', attach=True)`; `session.new_window(window_name='prev_b', attach=True)`; `session.refresh()`; `session.previous_window()`; assert `session.active_window.window_id == w2.window_id`; `result.window_id == w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_previous_window_b24c28acd6 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_remove_environment | Remove environment variable; fixtures `session`; execute `session.getenv('BAM')`; `session.set_environment('BAM', 'OK')`; `session.remove_environment('BAM')`; assert `session.getenv('BAM') is None`; `session.getenv('BAM') == 'OK'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_remove_environment_3dc5ca0644 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_select_window | Session.select_window moves window; fixtures `session`; execute `session.new_window(window_name='test_window')`; `session.select_window(window_base_index)`; `session.select_window(window_base_index + 1)`; assert `window_idx is not None`; `window_count >= 2`; `len(session.windows) == window_count`; `isinstance(selected_window1, Window)`; `selected_window1.id == active_window1.id`; `isinstance(selected_window2, Window)`; `selected_window2.id == active_window2.id`; `selected_window1.id != selected_window2.id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_select_window_3c2530cb01 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_select_window_returns_Window | Session.select_window returns Window object; fixtures `session`; execute `session.select_window(window_base_index)`; assert `len(session.windows) == window_count`; `window_idx is not None`; `isinstance(window, Window)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_select_window_returns_Window_cfccc7771f | 3.2a through 3.7b | planned parity | +| tests/test_server.py::test_wait_for_set_flag | Test Server.wait_for() with set_flag; fixtures `server`; execute `server.new_session(session_name='wait_test')`; `server.wait_for('test_channel_set', set_flag=True)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_wait_for_set_flag_094065e1a9 | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_windows_property_hydrates_active_pane | Server.windows hydrates each window's active pane via the cascade; fixtures `server`, `session`; execute `session.new_window(window_name='hydration_cascade_windows')`; assert `fetched is not None`; `active_pane is not None`; `fetched.pane_id == active_pane.pane_id`; `fetched.pane_current_command is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_windows_property_hydrates_active_pane_14771ea36b | 3.3a through 3.7c | planned parity | +| tests/test_server.py::test_with_server_is_alive | Verify is_alive() returns True when tmux server is alive; fixtures `server`; execute `server.new_session()`; `server.is_alive()`; assert `server.is_alive()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_server_py_test_with_server_is_alive_b7b068c8dd | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_active_pane | Session.active_pane returns Pane; fixtures `session`; assert `isinstance(session.active_pane, Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_active_pane_43afb8c4be | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_active_window | Session.active_window returns Window; fixtures `session`; assert `isinstance(session.active_window, Window)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_active_window_fae471f129 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_cmd_inserts_session_id | Verify Session.cmd() inserts session_id; fixtures `session`; execute `session.cmd('not-a-command', last_arg)`; assert `'-t' in cmd.cmd`; `current_session_id in cmd.cmd`; `cmd.cmd[-1] == last_arg`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_cmd_inserts_session_id_7f4de558f3 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_detach_client_no_target_detaches_all_session_clients | Test Session.detach_client() without a target detaches all session clients; fixtures `control_mode`, `session`, `server`; execute `control_mode()`; `server.cmd('list-clients', '-F', '#{client_name}')`; `session.detach_client()`; assert `len(before) == 2`; `len(after) == 0`. | real-tmux integration test | PythonBehaviorParityTest#est_session_py_test_detach_client_no_target_detaches_all_session_clients_b3e4b9869c | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_empty_session_option_returns_None | Verify Session._show_option returns None for unset option; fixtures `session`; execute `session._show_option('default-shell')`; assert `session._show_option('default-shell') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_empty_session_option_returns_None_19c68e0e4c | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_has_session | Server.has_session returns True if has session_name exists; fixtures `server`, `session`; execute `server.has_session(TEST_SESSION_NAME)`; `server.has_session(TEST_SESSION_NAME[:-2])`; `server.has_session(TEST_SESSION_NAME[:-2], exact=False)`; `server.has_session('asdf2314324321')`; assert `TEST_SESSION_NAME is not None`; `server.has_session(TEST_SESSION_NAME)`; `not server.has_session(TEST_SESSION_NAME[:-2])`; `server.has_session(TEST_SESSION_NAME[:-2], exact=False)`; `not server.has_session('asdf2314324321')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_has_session_2fd59585c3 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_kill_session_group | Session.kill(group=True) uses -g on tmux 3.7+; warn-and-ignore below; fixtures `server`; execute `server.new_session(session_name='kill_group_base')`; when `has_gte_version('3.7')`: execute `session.kill(group=True)`; `server.has_session('kill_group_base')`; assert `not server.has_session('kill_group_base')`; when `not (has_gte_version('3.7'))`: execute `session.kill(group=True)`; expect `pytest.warns(UserWarning, match='group requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_kill_session_group_70d2bafe79 | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_session.py::test_last_window | Test Session.last_window() selects previous window; fixtures `session`; execute `session.new_window(window_name='last_a', attach=True)`; `session.new_window(window_name='last_b', attach=True)`; `session.refresh()`; `session.last_window()`; assert `session.active_window.window_id == w2.window_id`; `result.window_id == w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_last_window_57ed77cbf1 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_lock_session | Test Session.lock_session() runs without error; fixtures `session`; execute `session.lock_session()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_lock_session_1cc8396c56 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_session | Server.new_session creates new session; fixtures `server`; execute `server.new_session(session_name=new_session_name, detach=True)`; assert `isinstance(new_session, Session)`; `new_session.session_name == new_session_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_session_e2de3569ae | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_kill_existing | Test Session.new_window() with kill_existing flag; fixtures `session`; execute `session.new_window(window_name='kill_orig', window_index='5')`; `session.new_window(window_name='kill_replace', window_index='5', kill_existing=True)`; `session.refresh()`; assert `w1.window_name == 'kill_orig'`; `w1.window_index == '5'`; `w2.window_name == 'kill_replace'`; `w2.window_index == '5'`; `'kill_orig' not in names`; `'kill_replace' in names`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_kill_existing_bd39f40040 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_select_existing | Test Session.new_window() with select_existing flag — new name; fixtures `session`; execute `session.new_window(window_name='selexist_new', select_existing=True)`; assert `w.window_name == 'selexist_new'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_select_existing_d6c7048fc6 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_start_directory[empty_string] | Test Session.new_window start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_empty_string_53afd32113 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_start_directory[none_value] | Test Session.new_window start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_none_value_61ebed7357 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_start_directory[relative_path] | Test Session.new_window start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_relative_path_df9ac332cd | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_start_directory[user_path] | Test Session.new_window start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_{test_id}', start_directory=actual_start_directory)`; assert `window.window_name == f'test_window_{test_id}'`; `window in session.windows`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_user_path_e9b3c36747 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_start_directory_pathlib | Test Session.new_window accepts pathlib.Path for start_directory; fixtures `session`, `user_path`; execute `session.new_window(window_name='test_pathlib_start_dir', start_directory=user_path)`; `user_path.resolve()`; `active_pane.refresh()`; `pathlib.Path(active_pane.pane_current_path).resolve()`; `pathlib.Path(active_pane.pane_current_path)`; assert `window.window_name == 'test_pathlib_start_dir'`; `window in session.windows`; `active_pane is not None`; `active_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_start_directory_pathlib_ebf77c18a8 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_with_environment[multiple_env_vars] | Verify new window with environment vars; case inputs `environment={'ENV_VAR_1': 'window_1', 'ENV_VAR_2': 'window_2'}`, `test_id='multiple_env_vars'`; fixtures `session`; execute `session.new_window(attach=True, window_name='window_with_environment', window_shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_with_environment_multiple_env_vars_cc4d25b3ea | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_new_window_with_environment[single_env_var] | Verify new window with environment vars; case inputs `environment={'ENV_VAR': 'window'}`, `test_id='single_env_var'`; fixtures `session`; execute `session.new_window(attach=True, window_name='window_with_environment', window_shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_new_window_with_environment_single_env_var_2f715b3eb0 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_next_window | Test Session.next_window() selects next window; fixtures `session`; execute `session.new_window(window_name='next_a', attach=True)`; `session.new_window(window_name='next_b', attach=False)`; `session.refresh()`; `session.next_window()`; assert `session.active_window.window_id == w1.window_id`; `result.window_id != w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_next_window_4309c4baa6 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_periods_raise_bad_session_name[colon_in_name] | Verify session names with periods raise BadSessionName; case inputs `raises=True`, `session_name='hey:its a colon'`, `test_id='colon_in_name'`; fixtures `server`, `session`; when `raises`: execute `session.rename_session(new_name)`; `server.new_session(session_name)`; `server.has_session(session_name)`; `server.switch_client(new_name)`; `server.attach_session(new_name)`; expect `pytest.raises(exc.BadSessionName)`; when `not (raises)`: execute `server.new_session(session_name)`; `server.has_session(session_name)`; `session.rename_session(new_name)`; `server.switch_client(new_name)`; expect `pytest.raises(exc.LibTmuxException)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_periods_raise_bad_session_name_colon_in_name_aca7571512 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_periods_raise_bad_session_name[period_in_name] | Verify session names with periods raise BadSessionName; case inputs `raises=True`, `session_name='hey.period'`, `test_id='period_in_name'`; fixtures `server`, `session`; when `raises`: execute `session.rename_session(new_name)`; `server.new_session(session_name)`; `server.has_session(session_name)`; `server.switch_client(new_name)`; `server.attach_session(new_name)`; expect `pytest.raises(exc.BadSessionName)`; when `not (raises)`: execute `server.new_session(session_name)`; `server.has_session(session_name)`; `session.rename_session(new_name)`; `server.switch_client(new_name)`; expect `pytest.raises(exc.LibTmuxException)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_periods_raise_bad_session_name_period_in_name_4aca936d92 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_periods_raise_bad_session_name[valid_name] | Verify session names with periods raise BadSessionName; case inputs `raises=False`, `session_name='hey moo'`, `test_id='valid_name'`; fixtures `server`, `session`; when `raises`: execute `session.rename_session(new_name)`; `server.new_session(session_name)`; `server.has_session(session_name)`; `server.switch_client(new_name)`; `server.attach_session(new_name)`; expect `pytest.raises(exc.BadSessionName)`; when `not (raises)`: execute `server.new_session(session_name)`; `server.has_session(session_name)`; `session.rename_session(new_name)`; `server.switch_client(new_name)`; expect `pytest.raises(exc.LibTmuxException)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_periods_raise_bad_session_name_valid_name_58f1b62bac | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_previous_window | Test Session.previous_window() selects previous window; fixtures `session`; execute `session.new_window(window_name='prev_a', attach=True)`; `session.new_window(window_name='prev_b', attach=True)`; `session.refresh()`; `session.previous_window()`; assert `session.active_window.window_id == w2.window_id`; `result.window_id == w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_previous_window_b24c28acd6 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_remove_environment | Remove environment variable; fixtures `session`; execute `session.getenv('BAM')`; `session.set_environment('BAM', 'OK')`; `session.remove_environment('BAM')`; assert `session.getenv('BAM') is None`; `session.getenv('BAM') == 'OK'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_remove_environment_3dc5ca0644 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_select_window | Session.select_window moves window; fixtures `session`; execute `session.new_window(window_name='test_window')`; `session.select_window(window_base_index)`; `session.select_window(window_base_index + 1)`; assert `window_idx is not None`; `window_count >= 2`; `len(session.windows) == window_count`; `isinstance(selected_window1, Window)`; `selected_window1.id == active_window1.id`; `isinstance(selected_window2, Window)`; `selected_window2.id == active_window2.id`; `selected_window1.id != selected_window2.id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_select_window_3c2530cb01 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_select_window_returns_Window | Session.select_window returns Window object; fixtures `session`; execute `session.select_window(window_base_index)`; assert `len(session.windows) == window_count`; `window_idx is not None`; `isinstance(window, Window)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_select_window_returns_Window_cfccc7771f | 3.3a through 3.7c | planned parity | | tests/test_session.py::test_session_attach_does_not_fail_if_session_killed_during_attach[session_killed_during_attach_should_not_raise] | Regression test: Session.attach() should not fail if session is killed; case inputs `raises=False`, `test_id='session_killed_during_attach_should_not_raise'`; fixtures `server`, `monkeypatch`; execute `server.new_session(detach=True)`; `original_cmd(cmd_name, *args, **kwargs)`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `test_session.attach()`; when `cmd_name == 'attach-session'`: execute `test_session.kill()`; `MockTmuxCmd()`. | unit test | PythonBehaviorParityTest#ssion_killed_during_attach_session_killed_during_attach_should_not_raise_5a5ea1348c | not tmux-bound | planned parity | -| tests/test_session.py::test_session_context_manager | Test Session context manager functionality; fixtures `server`; execute `server.new_session()`; `session.new_window()`; `server.has_session(session_name)`; assert `len(session.windows) >= 2`; `window in session.windows`; `session_name is not None`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_context_manager_7305aa9ad8 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_format] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_format'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ession_py_test_session_format_field_declared_and_hydrated_session_format_6959099e5b | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_group_attached_list] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_group_attached_list'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_session_format_field_declared_and_hydrated_session_group_attached_list_a8533e10cb | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_group_many_attached] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_group_many_attached'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_session_format_field_declared_and_hydrated_session_group_many_attached_586ff0ac5a | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_grouped] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_grouped'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ssion_py_test_session_format_field_declared_and_hydrated_session_grouped_dd7ad36766 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_many_attached] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_many_attached'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#py_test_session_format_field_declared_and_hydrated_session_many_attached_221190fa18 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_marked] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_marked'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ession_py_test_session_format_field_declared_and_hydrated_session_marked_f2b182bbe3 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_last_window_exception_tags_subcommand | Wrapper raises tag LibTmuxException with the originating tmux subcommand; fixtures `session`; execute `session.last_window()`; expect `pytest.raises(exc.LibTmuxException)`; assert `excinfo.value.subcommand == 'last-window'`; `str(excinfo.value).startswith('last-window:')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_last_window_exception_tags_subcommand_720092e2a6 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_new_window_with_direction | Verify new window with direction; fixtures `session`; execute `window.refresh()`; `session.new_window(window_name='Example')`; `session.new_window(window_name='Window before', direction=WindowDirection.Before)`; `session.new_window(window_name='Window after', direction=WindowDirection.After)`; `window_initial.refresh()`; `window_after.refresh()`; assert `window_initial.window_index == '2'`; `window_before.window_index == '1'`; `window_initial.window_index == '3'`; `window_after.window_index == '3'`; `window_initial.window_index == '4'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_new_window_with_direction_290e43ec8f | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_refresh_raises_when_session_id_is_none | ``Session.refresh()`` raises ``ValueError`` when ``session_id`` is unset; fixtures `server`; execute `session.refresh()`; `Session(server=server)`; expect `pytest.raises(ValueError, match='session_id')`; assert `session.session_id is None`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_session_py_test_session_refresh_raises_when_session_id_is_none_02badcb26e | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_rename | Session.rename_session renames session; fixtures `session`; execute `session.rename_session(test_name)`; `session.rename_session(TEST_SESSION_NAME)`; assert `session_name is not None`; `session_name == test_name`; `session_name == TEST_SESSION_NAME`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_rename_b25190e247 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_scope_override_field_hydrates[active_window_index] | Session-scope overrides admit each token into list-sessions -F; case inputs `field_name='active_window_index'`; fixtures `session`; execute `session.refresh()`; assert `value is not None; message=f'{field_name} should hydrate via list-sessions'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ession_py_test_session_scope_override_field_hydrates_active_window_index_a5f79a80e3 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_scope_override_field_hydrates[last_window_index] | Session-scope overrides admit each token into list-sessions -F; case inputs `field_name='last_window_index'`; fixtures `session`; execute `session.refresh()`; assert `value is not None; message=f'{field_name} should hydrate via list-sessions'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#_session_py_test_session_scope_override_field_hydrates_last_window_index_aec50e17d6 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_search_panes_filter_by_id | ``Session.search_panes(filter=...)`` returns only matching pane ids; fixtures `session`; execute `window.split()`; `session.search_panes(filter=f'#{{m:{target.pane_id},#{{pane_id}}}}')`; assert `[p.pane_id for p in matches] == [target.pane_id]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_search_panes_filter_by_id_b253910268 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_session_search_windows_filter | ``Session.search_windows(filter=...)`` filters via tmux's native -f flag; fixtures `session`; execute `session.new_window(window_name='gap7s_keep')`; `session.new_window(window_name='other_drop')`; `session.search_windows(filter='#{m:gap7s_*,#{window_name}}')`; assert `names == ['gap7s_keep']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_search_windows_filter_7b3007135d | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_set_option_ambiguous | Session.set_option raises AmbiguousOption for invalid option; fixtures `session`; execute `session.set_option('default-', 43)`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_option_ambiguous_06e0278764 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_set_option_invalid | Session.set_option raises InvalidOption for invalid option; fixtures `session`; execute `session.set_option('afewewfew', 43)`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_option_invalid_a60ed811d7 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_set_show_environment_single | Set environment then Session.show_environment(key); fixtures `session`; execute `session.set_environment('FOO', 'BAR')`; `session.getenv('FOO')`; `session.set_environment('FOO', 'DAR')`; `session.show_environment()`; assert `session.getenv('FOO') == 'BAR'`; `session.getenv('FOO') == 'DAR'`; `session.show_environment()['FOO'] == 'DAR'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_show_environment_single_04a3f86366 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_set_show_option | Set option then Session._show_option(key); fixtures `session`; execute `session.set_option('history-limit', 20)`; `session._show_option('history-limit')`; `session.set_option('history-limit', 40)`; assert `session._show_option('history-limit') == 20`; `session._show_option('history-limit') == 40`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_show_option_bd853b248e | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_set_show_options_single | Set option then Session._show_options(key); fixtures `session`; execute `session.set_option('history-limit', 20)`; `session._show_option('history-limit')`; `session.set_option('history-limit', 40)`; `session._show_options()`; assert `session._show_option('history-limit') == 20`; `session._show_option('history-limit') == 40`; `session._show_options()['history-limit'] == 40`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_show_options_single_d5eb9d218d | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_show_environment | Session.show_environment() returns dict; fixtures `session`; execute `session.show_environment()`; assert `isinstance(vars_, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_environment_9f76bdb7e3 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_show_environment_not_set | Not set environment variable returns None; fixtures `session`; execute `session.getenv('BAR')`; assert `session.getenv('BAR') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_environment_not_set_2bcea8ec05 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_show_option_ambiguous | Session._show_option raises AmbiguousOption for ambiguous option; fixtures `session`; execute `session._show_option('default-')`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_option_ambiguous_90d740f1ff | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_show_option_unknown | Session.show_option raises InvalidOption for invalid option; fixtures `session`; execute `session.show_option('moooz')`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_option_unknown_41d85dc0df | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_show_options | Session._show_options() returns dict; fixtures `session`; execute `session._show_options()`; assert `isinstance(options, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_options_dd3d5288e1 | 3.2a through 3.7b | planned parity | -| tests/test_session.py::test_unset_environment | Unset environment variable; fixtures `session`; execute `session.getenv('BAM')`; `session.set_environment('BAM', 'OK')`; `session.unset_environment('BAM')`; assert `session.getenv('BAM') is None`; `session.getenv('BAM') == 'OK'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_unset_environment_5178b103fc | 3.2a through 3.7b | planned parity | -| tests/test_tmuxobject.py::test_filter | Test self.filter() retrieves child object; fixtures `server`; execute `server.new_session('test')`; `window.split()`; `server.windows.filter(window_id='$' + next(namer))`; `session.windows.filter(window_id='@' + next(namer))`; `window.panes.filter(pane_id='%' + next(namer))`; assert `session_id is not None`; `get_session_by_id == session`; `isinstance(get_session_by_id, Session)`; `len(server.windows.filter(window_id='$' + next(namer))) == 0`; `window_id is not None`; `get_window_by_id == window`; `isinstance(get_window_by_id, Window)`; `len(session.windows.filter(window_id='@' + next(namer))) == 0`; `pane_id is not None`; `get_pane_by_id == pane`; `isinstance(get_pane_by_id, Pane)`; `len(window.panes.filter(pane_id='%' + next(namer))) == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_filter_3a7e9e1ce9 | 3.2a through 3.7b | planned parity | -| tests/test_tmuxobject.py::test_find_where | Test that find_where() retrieves single matching object; fixtures `server`, `session`; execute `server.sessions.filter(session_id=session_id)`; assert `session_id is not None`; `server.sessions.get(session_id=session_id) == session_`; `isinstance(server.sessions.filter(session_id=session_id)[0], Session)`; `window_id is not None`; `session_.windows.get(window_id=window_id) == window`; `isinstance(session_.windows.get(window_id=window_id), Window)`; `pane_id is not None`; `window.panes.get(pane_id=pane_id) == pane`; `isinstance(window.panes.get(pane_id=pane_id), Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_find_where_e854af52ee | 3.2a through 3.7b | planned parity | -| tests/test_tmuxobject.py::test_find_where_None | .find_where returns None if no results found; fixtures `server`, `session`; execute `server.sessions.filter(session_name=nonexistent_session)`; assert `len(server.sessions.filter(session_name=nonexistent_session)) == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_find_where_None_c347302743 | 3.2a through 3.7b | planned parity | -| tests/test_tmuxobject.py::test_find_where_multiple_infos | .find_where returns objects with multiple attributes; fixtures `server`, `session`; assert `session_id is not None`; `session_name is not None`; `find_where == session_`; `isinstance(find_where, Session)`; `window_id is not None`; `window_index is not None`; `find_window_where == window`; `isinstance(find_window_where, Window)`; `pane_id is not None`; `pane_tty is not None`; `find_pane_where == pane`; `isinstance(find_pane_where, Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_find_where_multiple_infos_3f54521fc2 | 3.2a through 3.7b | planned parity | -| tests/test_tmuxobject.py::test_where | Test self.where() returns matching objects; fixtures `server`, `session`; execute `window.split()`; `server.sessions.filter(session_id=session_id, session_name=session_name)`; `window.panes.filter(pane_id=pane_id, pane_tty=pane_tty)`; `session_.windows.filter(window_id=window_id, window_index=window_index)`; assert `session_id is not None`; `session_name is not None`; `len(server_sessions) == 1`; `isinstance(server_sessions, list)`; `server_sessions[0] == session_`; `isinstance(server_sessions[0], Session)`; `window_id is not None`; `window_index is not None`; `len(session_windows) == 1`; `isinstance(session_windows, list)`; `session_windows[0] == window`; `isinstance(session_windows[0], Window)`; `pane_id is not None`; `pane_tty is not None`; `len(window_panes) == 1`; `isinstance(window_panes, list)`; `window_panes[0] == pane`; `isinstance(window_panes[0], Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_where_e9a9019393 | 3.2a through 3.7b | planned parity | +| tests/test_session.py::test_session_context_manager | Test Session context manager functionality; fixtures `server`; execute `server.new_session()`; `session.new_window()`; `server.has_session(session_name)`; assert `len(session.windows) >= 2`; `window in session.windows`; `session_name is not None`; `not server.has_session(session_name)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_context_manager_7305aa9ad8 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_format] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_format'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ession_py_test_session_format_field_declared_and_hydrated_session_format_6959099e5b | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_group_attached_list] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_group_attached_list'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_session_format_field_declared_and_hydrated_session_group_attached_list_a8533e10cb | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_group_many_attached] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_group_many_attached'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_session_format_field_declared_and_hydrated_session_group_many_attached_586ff0ac5a | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_grouped] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_grouped'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ssion_py_test_session_format_field_declared_and_hydrated_session_grouped_dd7ad36766 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_many_attached] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_many_attached'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#py_test_session_format_field_declared_and_hydrated_session_many_attached_221190fa18 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_format_field_declared_and_hydrated[session_marked] | Tmux's session-scope format tokens hydrate onto the typed ``Session``; case inputs `field_name='session_marked'`; fixtures `session`; execute `session.refresh()`; assert `field_name in session.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ession_py_test_session_format_field_declared_and_hydrated_session_marked_f2b182bbe3 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_last_window_exception_tags_subcommand | Wrapper raises tag LibTmuxException with the originating tmux subcommand; fixtures `session`; execute `session.last_window()`; expect `pytest.raises(exc.LibTmuxException)`; assert `excinfo.value.subcommand == 'last-window'`; `str(excinfo.value).startswith('last-window:')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_last_window_exception_tags_subcommand_720092e2a6 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_new_window_with_direction | Verify new window with direction; fixtures `session`; execute `window.refresh()`; `session.new_window(window_name='Example')`; `session.new_window(window_name='Window before', direction=WindowDirection.Before)`; `session.new_window(window_name='Window after', direction=WindowDirection.After)`; `window_initial.refresh()`; `window_after.refresh()`; assert `window_initial.window_index == '2'`; `window_before.window_index == '1'`; `window_initial.window_index == '3'`; `window_after.window_index == '3'`; `window_initial.window_index == '4'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_new_window_with_direction_290e43ec8f | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_refresh_raises_when_session_id_is_none | ``Session.refresh()`` raises ``ValueError`` when ``session_id`` is unset; fixtures `server`; execute `session.refresh()`; `Session(server=server)`; expect `pytest.raises(ValueError, match='session_id')`; assert `session.session_id is None`. | real-tmux integration test | PythonBehaviorParityTest#ests_test_session_py_test_session_refresh_raises_when_session_id_is_none_02badcb26e | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_rename | Session.rename_session renames session; fixtures `session`; execute `session.rename_session(test_name)`; `session.rename_session(TEST_SESSION_NAME)`; assert `session_name is not None`; `session_name == test_name`; `session_name == TEST_SESSION_NAME`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_rename_b25190e247 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_scope_override_field_hydrates[active_window_index] | Session-scope overrides admit each token into list-sessions -F; case inputs `field_name='active_window_index'`; fixtures `session`; execute `session.refresh()`; assert `value is not None; message=f'{field_name} should hydrate via list-sessions'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ession_py_test_session_scope_override_field_hydrates_active_window_index_a5f79a80e3 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_scope_override_field_hydrates[last_window_index] | Session-scope overrides admit each token into list-sessions -F; case inputs `field_name='last_window_index'`; fixtures `session`; execute `session.refresh()`; assert `value is not None; message=f'{field_name} should hydrate via list-sessions'`; `isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#_session_py_test_session_scope_override_field_hydrates_last_window_index_aec50e17d6 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_search_panes_filter_by_id | ``Session.search_panes(filter=...)`` returns only matching pane ids; fixtures `session`; execute `window.split()`; `session.search_panes(filter=f'#{{m:{target.pane_id},#{{pane_id}}}}')`; assert `[p.pane_id for p in matches] == [target.pane_id]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_search_panes_filter_by_id_b253910268 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_session_search_windows_filter | ``Session.search_windows(filter=...)`` filters via tmux's native -f flag; fixtures `session`; execute `session.new_window(window_name='gap7s_keep')`; `session.new_window(window_name='other_drop')`; `session.search_windows(filter='#{m:gap7s_*,#{window_name}}')`; assert `names == ['gap7s_keep']`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_session_search_windows_filter_7b3007135d | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_set_option_ambiguous | Session.set_option raises AmbiguousOption for invalid option; fixtures `session`; execute `session.set_option('default-', 43)`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_option_ambiguous_06e0278764 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_set_option_invalid | Session.set_option raises InvalidOption for invalid option; fixtures `session`; execute `session.set_option('afewewfew', 43)`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_option_invalid_a60ed811d7 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_set_show_environment_single | Set environment then Session.show_environment(key); fixtures `session`; execute `session.set_environment('FOO', 'BAR')`; `session.getenv('FOO')`; `session.set_environment('FOO', 'DAR')`; `session.show_environment()`; assert `session.getenv('FOO') == 'BAR'`; `session.getenv('FOO') == 'DAR'`; `session.show_environment()['FOO'] == 'DAR'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_show_environment_single_04a3f86366 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_set_show_option | Set option then Session._show_option(key); fixtures `session`; execute `session.set_option('history-limit', 20)`; `session._show_option('history-limit')`; `session.set_option('history-limit', 40)`; assert `session._show_option('history-limit') == 20`; `session._show_option('history-limit') == 40`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_show_option_bd853b248e | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_set_show_options_single | Set option then Session._show_options(key); fixtures `session`; execute `session.set_option('history-limit', 20)`; `session._show_option('history-limit')`; `session.set_option('history-limit', 40)`; `session._show_options()`; assert `session._show_option('history-limit') == 20`; `session._show_option('history-limit') == 40`; `session._show_options()['history-limit'] == 40`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_set_show_options_single_d5eb9d218d | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_show_environment | Session.show_environment() returns dict; fixtures `session`; execute `session.show_environment()`; assert `isinstance(vars_, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_environment_9f76bdb7e3 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_show_environment_not_set | Not set environment variable returns None; fixtures `session`; execute `session.getenv('BAR')`; assert `session.getenv('BAR') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_environment_not_set_2bcea8ec05 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_show_option_ambiguous | Session._show_option raises AmbiguousOption for ambiguous option; fixtures `session`; execute `session._show_option('default-')`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_option_ambiguous_90d740f1ff | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_show_option_unknown | Session.show_option raises InvalidOption for invalid option; fixtures `session`; execute `session.show_option('moooz')`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_option_unknown_41d85dc0df | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_show_options | Session._show_options() returns dict; fixtures `session`; execute `session._show_options()`; assert `isinstance(options, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_show_options_dd3d5288e1 | 3.3a through 3.7c | planned parity | +| tests/test_session.py::test_unset_environment | Unset environment variable; fixtures `session`; execute `session.getenv('BAM')`; `session.set_environment('BAM', 'OK')`; `session.unset_environment('BAM')`; assert `session.getenv('BAM') is None`; `session.getenv('BAM') == 'OK'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_session_py_test_unset_environment_5178b103fc | 3.3a through 3.7c | planned parity | +| tests/test_tmuxobject.py::test_filter | Test self.filter() retrieves child object; fixtures `server`; execute `server.new_session('test')`; `window.split()`; `server.windows.filter(window_id='$' + next(namer))`; `session.windows.filter(window_id='@' + next(namer))`; `window.panes.filter(pane_id='%' + next(namer))`; assert `session_id is not None`; `get_session_by_id == session`; `isinstance(get_session_by_id, Session)`; `len(server.windows.filter(window_id='$' + next(namer))) == 0`; `window_id is not None`; `get_window_by_id == window`; `isinstance(get_window_by_id, Window)`; `len(session.windows.filter(window_id='@' + next(namer))) == 0`; `pane_id is not None`; `get_pane_by_id == pane`; `isinstance(get_pane_by_id, Pane)`; `len(window.panes.filter(pane_id='%' + next(namer))) == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_filter_3a7e9e1ce9 | 3.3a through 3.7c | planned parity | +| tests/test_tmuxobject.py::test_find_where | Test that find_where() retrieves single matching object; fixtures `server`, `session`; execute `server.sessions.filter(session_id=session_id)`; assert `session_id is not None`; `server.sessions.get(session_id=session_id) == session_`; `isinstance(server.sessions.filter(session_id=session_id)[0], Session)`; `window_id is not None`; `session_.windows.get(window_id=window_id) == window`; `isinstance(session_.windows.get(window_id=window_id), Window)`; `pane_id is not None`; `window.panes.get(pane_id=pane_id) == pane`; `isinstance(window.panes.get(pane_id=pane_id), Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_find_where_e854af52ee | 3.3a through 3.7c | planned parity | +| tests/test_tmuxobject.py::test_find_where_None | .find_where returns None if no results found; fixtures `server`, `session`; execute `server.sessions.filter(session_name=nonexistent_session)`; assert `len(server.sessions.filter(session_name=nonexistent_session)) == 0`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_find_where_None_c347302743 | 3.3a through 3.7c | planned parity | +| tests/test_tmuxobject.py::test_find_where_multiple_infos | .find_where returns objects with multiple attributes; fixtures `server`, `session`; assert `session_id is not None`; `session_name is not None`; `find_where == session_`; `isinstance(find_where, Session)`; `window_id is not None`; `window_index is not None`; `find_window_where == window`; `isinstance(find_window_where, Window)`; `pane_id is not None`; `pane_tty is not None`; `find_pane_where == pane`; `isinstance(find_pane_where, Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_find_where_multiple_infos_3f54521fc2 | 3.3a through 3.7c | planned parity | +| tests/test_tmuxobject.py::test_where | Test self.where() returns matching objects; fixtures `server`, `session`; execute `window.split()`; `server.sessions.filter(session_id=session_id, session_name=session_name)`; `window.panes.filter(pane_id=pane_id, pane_tty=pane_tty)`; `session_.windows.filter(window_id=window_id, window_index=window_index)`; assert `session_id is not None`; `session_name is not None`; `len(server_sessions) == 1`; `isinstance(server_sessions, list)`; `server_sessions[0] == session_`; `isinstance(server_sessions[0], Session)`; `window_id is not None`; `window_index is not None`; `len(session_windows) == 1`; `isinstance(session_windows, list)`; `session_windows[0] == window`; `isinstance(session_windows[0], Window)`; `pane_id is not None`; `pane_tty is not None`; `len(window_panes) == 1`; `isinstance(window_panes, list)`; `window_panes[0] == pane`; `isinstance(window_panes[0], Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_tmuxobject_py_test_where_e9a9019393 | 3.3a through 3.7c | planned parity | | tests/test_version.py::test_version[beta_version] | Assert LooseVersion constructor against various version strings; case inputs `test_id='beta_version'`, `version='1.0.0b'`; execute `LooseVersion(version)`; assert `LooseVersion(version)`. | unit test | PythonBehaviorParityTest#tests_test_version_py_test_version_beta_version_f692c9af3d | not tmux-bound | planned parity | | tests/test_version.py::test_version[beta_with_number] | Assert LooseVersion constructor against various version strings; case inputs `test_id='beta_with_number'`, `version='1.0.0b1'`; execute `LooseVersion(version)`; assert `LooseVersion(version)`. | unit test | PythonBehaviorParityTest#tests_test_version_py_test_version_beta_with_number_3c86e150d1 | not tmux-bound | planned parity | | tests/test_version.py::test_version[beta_with_os] | Assert LooseVersion constructor against various version strings; case inputs `test_id='beta_with_os'`, `version='1.0.0b-openbsd'`; execute `LooseVersion(version)`; assert `LooseVersion(version)`. | unit test | PythonBehaviorParityTest#tests_test_version_py_test_version_beta_with_os_d06ed0daf6 | not tmux-bound | planned parity | @@ -1421,84 +1421,84 @@ under [`tests/`](https://github.com/tmux-python/libtmux/blob/c4a980b/tests) and | tests/test_version.py::test_version_compare[less_than_equal_raises] | Assert version comparisons; case inputs `a='1'`, `b='1'`, `op=<built-in function lt>`, `raises=<class 'AssertionError'>`, `test_id='less_than_equal_raises'`; execute `op(LooseVersion(a), LooseVersion(b))`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `LooseVersion(a)`; `LooseVersion(b)`; assert `op(LooseVersion(a), LooseVersion(b))`. | unit test | PythonBehaviorParityTest#tests_test_version_py_test_version_compare_less_than_equal_raises_79bfce1a72 | not tmux-bound | planned parity | | tests/test_version.py::test_version_compare[less_than_openbsd] | Assert version comparisons; case inputs `a='1'`, `b='1.0.0-openbsd'`, `op=<built-in function lt>`, `raises=False`, `test_id='less_than_openbsd'`; execute `op(LooseVersion(a), LooseVersion(b))`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `LooseVersion(a)`; `LooseVersion(b)`; assert `op(LooseVersion(a), LooseVersion(b))`. | unit test | PythonBehaviorParityTest#tests_test_version_py_test_version_compare_less_than_openbsd_3e1b42f2d1 | not tmux-bound | planned parity | | tests/test_version.py::test_version_compare[less_than_patch] | Assert version comparisons; case inputs `a='1'`, `b='1.0.0p1'`, `op=<built-in function lt>`, `raises=False`, `test_id='less_than_patch'`; execute `op(LooseVersion(a), LooseVersion(b))`; `t.cast('type[Exception]', raises)`; `t.cast('RaisesExc', does_not_raise())`; `does_not_raise()`; `LooseVersion(a)`; `LooseVersion(b)`; assert `op(LooseVersion(a), LooseVersion(b))`. | unit test | PythonBehaviorParityTest#tests_test_version_py_test_version_compare_less_than_patch_3976fa2757 | not tmux-bound | planned parity | -| tests/test_window.py::test_active_pane | Window.active_window returns active Pane; fixtures `session`; assert `isinstance(window.active_pane, Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_active_pane_2a044ab40a | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_deprecated_window_methods_emit_warning[set_window_option] | Verify deprecated Window methods emit DeprecationWarning (0.50.0); case inputs `test_case=DeprecatedMethodTestCase(test_id='set_window_option', method_name='set_window_option', args=('main-pane-height', 20), kwargs={}, expected_error_match='Window\\.set_window_option\\(\\) is deprecated')`; fixtures `session`; execute `session.new_window(window_name='test_deprecation')`; `method(*test_case.args, **test_case.kwargs)`; expect `pytest.warns(DeprecationWarning, match=test_case.expected_error_match)`. | real-tmux integration test | PythonBehaviorParityTest#_window_py_test_deprecated_window_methods_emit_warning_set_window_option_a7a3be4c0b | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_deprecated_window_methods_emit_warning[show_window_option] | Verify deprecated Window methods emit DeprecationWarning (0.50.0); case inputs `test_case=DeprecatedMethodTestCase(test_id='show_window_option', method_name='show_window_option', args=('main-pane-height',), kwargs={}, expected_error_match='Window\\.show_window_option\\(\\) is deprecated')`; fixtures `session`; execute `session.new_window(window_name='test_deprecation')`; `method(*test_case.args, **test_case.kwargs)`; expect `pytest.warns(DeprecationWarning, match=test_case.expected_error_match)`. | real-tmux integration test | PythonBehaviorParityTest#window_py_test_deprecated_window_methods_emit_warning_show_window_option_6e07ddb7b6 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_deprecated_window_methods_emit_warning[show_window_options] | Verify deprecated Window methods emit DeprecationWarning (0.50.0); case inputs `test_case=DeprecatedMethodTestCase(test_id='show_window_options', method_name='show_window_options', args=(), kwargs={}, expected_error_match='Window\\.show_window_options\\(\\) is deprecated')`; fixtures `session`; execute `session.new_window(window_name='test_deprecation')`; `method(*test_case.args, **test_case.kwargs)`; expect `pytest.warns(DeprecationWarning, match=test_case.expected_error_match)`. | real-tmux integration test | PythonBehaviorParityTest#indow_py_test_deprecated_window_methods_emit_warning_show_window_options_bb60c6c4a9 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_empty_window_name | New windows can be created with empty string for window name; fixtures `session`; execute `session.set_option('automatic-rename', 'off')`; `session.new_window(window_name="''", attach=True)`; `session.cmd('list-windows', '-F', '#{window_name}', '-f', '#{==:#{session_name},' + session.session_name + '}')`; assert `window == session.active_window`; `window.window_name == "''"`; `session.session_name is not None`; `"''" in cmd.stdout`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_empty_window_name_fe50634976 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_empty_window_option_returns_None | Verify unset window option returns None; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_option('alternate-screen')`; assert `window.show_option('alternate-screen') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_empty_window_option_returns_None_72aa5bb02d | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_fresh_window_data | Verify window data is fresh; fixtures `session`; execute `window.split()`; `session.new_window(window_name='second')`; `session.new_window(window_name='hey')`; `session.select_window('1')`; `active_window._show_option('pane-base-index', global_=True)`; `pane_to_select.select()`; assert `active_window is not None`; `pane_base_idx is not None`; `len(session.windows) == 1`; `len(session.active_window.panes) == 1`; `session.session_id != '@0'`; `current_windows == 1`; `isinstance(session.server, Server)`; `isinstance(w, Window)`; `isinstance(window, Window)`; `pane_to_select is not None`; `active_pane is not None`; `pane_to_select_2 is not None`; `current_windows == len(session.windows)`; `window_to_kill is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_fresh_window_data_c86c1a0f96 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_kill_window | Test window.kill() kills window; fixtures `session`; execute `session.new_window()`; `w.kill()`; `w.refresh()`; expect `pytest.raises(ObjectDoesNotExist)`; assert `w.window_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_kill_window_d8eb2eff37 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_last_pane | Test Window.last_pane() selects the previously active pane; fixtures `session`; execute `session.new_window(window_name='test_last_pane')`; `window.last_pane()`; `pane1.split()`; `pane2.select()`; `pane1.select()`; `pane2.refresh()`; assert `pane1 is not None`; `result is not None`; `pane2.pane_active == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_last_pane_d5eb4916f7 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_link_unlink_window | Test Window.link() and Window.unlink(); fixtures `server`, `session`; execute `server.new_session(session_name='link_target')`; `session.new_window(window_name='link_test')`; `w.link(s2, detach=True)`; `s2.refresh()`; `linked_windows[0].unlink()`; assert `'link_test' in s2_window_names`; `len(linked_windows) > 0`; `'link_test' not in s2_window_names`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_link_unlink_window_32d7f06beb | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window | Window.move_window results in changed index; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.move_window(new_index)`; assert `window.window_index is not None`; `window.window_index == new_index`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_7001ddfe96 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_kill_target | Test Window.move_window() with kill_target flag; fixtures `session`; execute `session.new_window(window_name='move_w1')`; `session.new_window(window_name='move_w2')`; `session.refresh()`; `extra_windows[0].move_window(destination=w2_index, kill_target=True)`; assert `w2.window_index is not None`; `len(extra_windows) == 1`; `len(session.windows) == initial_count - 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_kill_target_b7e089fe88 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_no_select | Test Window.move_window() with no_select flag; fixtures `session`; execute `session.new_window(window_name='nosel_w1', attach=True)`; `session.new_window(window_name='nosel_w2', attach=False)`; `session.refresh()`; `w2.move_window(destination='99', no_select=True)`; assert `session.active_window.window_id == w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_no_select_a6ab22d3d4 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_relative_returns_fresh_window[after-0] | Window.move_window() returns fresh state for relative moves; case inputs `destination_offset=0`, `flag_name='after'`; fixtures `session`; execute `session.new_window(window_name='move_middle')`; `session.new_window(window_name='move_relative')`; `Window.from_window_id(server=session.server, window_id=moving_window.window_id)`; assert `destination_window.window_index is not None`; `moving_window.window_id is not None`; `moving_window.window_index == fresh_window.window_index`; `moving_window.session_id == fresh_window.session_id`; when `flag_name == 'after'`: execute `moving_window.move_window(destination, after=True)`; when `not (flag_name == 'after')`: execute `moving_window.move_window(destination, before=True)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_window_py_test_move_window_relative_returns_fresh_window_after_0_de00174c42 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_relative_returns_fresh_window[before-1] | Window.move_window() returns fresh state for relative moves; case inputs `destination_offset=1`, `flag_name='before'`; fixtures `session`; execute `session.new_window(window_name='move_middle')`; `session.new_window(window_name='move_relative')`; `Window.from_window_id(server=session.server, window_id=moving_window.window_id)`; assert `destination_window.window_index is not None`; `moving_window.window_id is not None`; `moving_window.window_index == fresh_window.window_index`; `moving_window.session_id == fresh_window.session_id`; when `flag_name == 'after'`: execute `moving_window.move_window(destination, after=True)`; when `not (flag_name == 'after')`: execute `moving_window.move_window(destination, before=True)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_window_py_test_move_window_relative_returns_fresh_window_before_1_151f2f516d | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_renumber | Test Window.move_window() with renumber flag; fixtures `session`; execute `session.new_window(window_name='ren_w1')`; `session.new_window(window_name='ren_w2')`; `session.new_window(window_name='ren_w3')`; `session.refresh()`; `w2.kill()`; `w3.move_window(renumber=True)`; assert `indices[i + 1] - indices[i] == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_renumber_d5796a5b32 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_to_other_session | Window.move_window to other session; fixtures `server`, `session`; execute `session.new_window(window_name='test_window')`; `server.new_session('test_move_window')`; `window.move_window(session=new_session.session_id)`; assert `window_id is not None`; `new_session.windows.get(window_id=window_id) == window`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_to_other_session_92d3c24001 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_move_window_to_other_session_with_destination | Window.move_window() returns fresh state for cross-session moves; fixtures `server`, `session`; execute `session.new_window(window_name='move_cross_session')`; `server.new_session('test_move_window_destination')`; `window.move_window(destination=destination, session=new_session.session_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `fresh_window.session_id == new_session.session_id`; `fresh_window.window_index == destination`; `window.session_id == fresh_window.session_id`; `window.window_index == fresh_window.window_index`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_to_other_session_with_destination_e7f86afe1d | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_new_pane | Window.new_pane() creates a floating pane on tmux 3.7+ (else raises); fixtures `session`; execute `session.new_window(window_name='win_floating')`; `window.resize(height=50, width=200)`; when `has_gte_version('3.7')`: execute `window.new_pane(width=60, height=12, shell='sleep 30')`; assert `floating.pane_floating_flag == '1'`; `floating in window.panes`; when `not (has_gte_version('3.7'))`: execute `window.new_pane(width=40, height=10)`; expect `pytest.raises(exc.LibTmuxException, match='new_pane .*requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_pane_3580a3317d | 3.2a through 3.7b; conditional boundaries: 3.7 | planned parity | -| tests/test_window.py::test_new_window_name_colon_period_accepted[colon] | Window names with ':' and '.' are accepted verbatim; case inputs `test_id='colon'`, `window_name='project:frontend'`; fixtures `session`; execute `session.new_window(window_name=window_name)`; `window.kill()`; assert `window.window_name == window_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_window_name_colon_period_accepted_colon_007435d489 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_new_window_name_colon_period_accepted[period] | Window names with ':' and '.' are accepted verbatim; case inputs `test_id='period'`, `window_name='app-v1.0'`; fixtures `session`; execute `session.new_window(window_name=window_name)`; `window.kill()`; assert `window.window_name == window_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_window_name_colon_period_accepted_period_6ef9d54ed8 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_new_window_with_direction | Verify new window with direction; fixtures `session`; execute `window.refresh()`; `session.new_window(window_name='Example')`; `window_initial.new_window(window_name='Window before', direction=WindowDirection.Before)`; `window_initial.refresh()`; `window_initial.new_window(window_name='Window after', direction=WindowDirection.After)`; `window_after.refresh()`; assert `window_initial.window_index == '2'`; `window_before.window_index == '2'`; `window_initial.window_index == '3'`; `window_after.window_index == '4'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_window_with_direction_c2a47c1ee5 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_newest_pane_data | Test window.panes has fresh data; fixtures `session`; execute `session.new_window(window_name='test', attach=True)`; `window.split(attach=True)`; assert `isinstance(window, Window)`; `len(window.panes) == 1`; `len(window.panes) == 2`; `len(window.panes) == 3`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_newest_pane_data_79e2ce3983 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_next_layout | Test Window.next_layout() cycles to the next layout; fixtures `session`; execute `session.new_window(window_name='test_next_layout')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout('even-horizontal')`; `window.refresh()`; `window.next_layout()`; assert `pane is not None`; `layout_before != layout_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_next_layout_b7b4774637 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_previous_layout | Test Window.previous_layout() cycles back; fixtures `session`; execute `session.new_window(window_name='test_prev_layout')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout('even-horizontal')`; `window.refresh()`; `window.next_layout()`; assert `pane is not None`; `layout_before == layout_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_previous_layout_dde68474fa | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_resize | Verify resizing window; fixtures `session`; execute `session.cmd('detach-client', '-s')`; `window.resize(height=10)`; `window.resize(width=10)`; `window.resize(adjustment_direction=ResizeAdjustmentDirection.Down, adjustment=window_height_adjustment * 2)`; `window.resize(adjustment_direction=ResizeAdjustmentDirection.Up, adjustment=window_height_adjustment)`; `window.resize(height=50)`; assert `window.window_height is not None`; `window.window_width is not None`; `int(window.window_height) == 10`; `int(window.window_width) == 10`; `window_height_before + window_height_adjustment * 2 == int(window.window_height)`; `window_height_before - window_height_adjustment == int(window.window_height)`; `window_height_before > window_height_shrunk`; `window`; `window_height_before < window_height_expanded`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_resize_c4965fd743 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_respawn_window | Test Window.respawn() with kill flag; fixtures `session`; execute `session.new_window(window_name='test_respawn_w')`; `window.respawn(kill=True, shell='sh')`; `window.refresh()`; `session.refresh()`; assert `window.window_id in [w.window_id for w in session.windows]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_respawn_window_b5eafa65d6 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_rotate_window | Test Window.rotate() rotates pane positions; fixtures `session`; execute `session.new_window(window_name='test_rotate')`; `window.resize(height=40, width=80)`; `window.rotate()`; `pane1.split()`; `pane2.split()`; `pane1.refresh()`; assert `pane1 is not None`; `idx_before != idx_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_rotate_window_6eca5ac10b | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_select_layout_accepts_no_arg | Tmux allows select-layout with no arguments, so let's allow it here; fixtures `server`, `session`; execute `session.new_window(window_name='test_window')`; `window.select_layout()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_accepts_no_arg_0af9999b14 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_select_layout_mutual_exclusion | Test that layout string and flags are mutually exclusive; fixtures `session`; execute `session.new_window(window_name='test_layout_mutex')`; `window.select_layout('tiled', spread=True)`; expect `pytest.raises(ValueError, match='Cannot specify both')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_mutual_exclusion_884674c936 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_select_layout_next_previous | Test Window.select_layout() with next/previous flags; fixtures `session`; execute `session.new_window(window_name='test_layout_cycle')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout('even-horizontal')`; `window.refresh()`; `window.select_layout(next_layout=True)`; assert `pane is not None`; `layout_before != layout_after_next`; `layout_after_prev == layout_before`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_next_previous_a1947ac64f | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_select_layout_spread | Test Window.select_layout() with spread flag; fixtures `session`; execute `session.new_window(window_name='test_layout_spread')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout(spread=True)`; assert `pane is not None`; `len(window.panes) == 3`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_spread_df22951796 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_select_window | Test Window.select_window(); fixtures `session`; execute `session.new_window(window_name='testing 3')`; `session.select_window(str(window_base_index))`; `session.select_window('testing 3')`; assert `window_count == 1`; `session.active_window.window_index is not None`; `window.window_index is not None`; `int(window_base_index) + 1 == int(window.window_index)`; `window_base_index == int(session.active_window.window_index)`; `int(window_base_index) + 1 == int(session.active_window.window_index)`; `len(session.windows) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_window_7403165fda | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_set_and_show_window_options | Window.set_option() then Window._show_options(key); fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('main-pane-height', 20)`; `window._show_option('main-pane-height')`; `window.set_option('main-pane-height', 40)`; `window._show_options(scope=OptionScope.Session)`; `window.set_option('pane-border-format', ' #P ')`; expect `pytest.raises(KeyError)`; assert `window._show_option('main-pane-height') == 20`; `window._show_option('main-pane-height') == 40`; `window._show_options(scope=OptionScope.Session)['main-pane-height'] == 40`; `window._show_option('pane-border-format') == ' #P '`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_and_show_window_options_227ba527da | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_set_window_and_show_window_options | Window.set_option() then Window.show_option(key); fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('main-pane-height', 20)`; `window.show_option('main-pane-height')`; `window.set_option('main-pane-height', 40)`; `window.show_options()`; `window.set_option('pane-border-format', ' #P ')`; assert `window.show_option('main-pane-height') == 20`; `window.show_option('main-pane-height') == 40`; `window.show_options()['main-pane-height'] == 40`; `window.show_option('pane-border-format') == ' #P '`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_window_and_show_window_options_d96249517a | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_set_window_option_ambiguous | set_option raises AmbiguousOption for ambiguous option; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('clock-mode', 12)`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_window_option_ambiguous_1c63d8c847 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_set_window_option_invalid | Window.set_option raises InvalidOption for invalid option key; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('afewewfew', 43)`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_window_option_invalid_dc1098935a | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_show_window_option | Set option then Window.show_option(key); fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('main-pane-height', 20)`; `window.show_option('main-pane-height')`; `window.set_option('main-pane-height', 40)`; assert `window.show_option('main-pane-height') == 20`; `window.show_option('main-pane-height') == 40`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_option_81f670d32c | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_show_window_option_ambiguous | show_option raises AmbiguousOption for ambiguous option; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_option('clock-mode')`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_option_ambiguous_7e784bd71f | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_show_window_option_unknown | Window.show_option raises InvalidOption for bad option key; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_option('moooz')`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_option_unknown_b9f31bc4f9 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_show_window_options | Window.show_options() returns dict; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_options()`; `window._show_options()`; `window._show_options(scope=OptionScope.Pane)`; `window._show_options(scope=OptionScope.Pane, global_=True)`; `window._show_options(scope=OptionScope.Window)`; assert `isinstance(options, dict)`; `isinstance(options_2, dict)`; `isinstance(pane_options, dict)`; `isinstance(pane_options_global, dict)`; `isinstance(window_options, dict)`; `isinstance(window_options_global, dict)`; `isinstance(server_options, dict)`; `isinstance(server_options_global, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_options_13b17290c2 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split | Window.split() splits window, returns new Pane, vertical; fixtures `session`; execute `session.new_window(window_name=window_name, attach=True)`; `window.split()`; `float(first_pane.pane_height)`; `float(window.window_width)`; assert `len(window.panes) == 2`; `isinstance(pane, Pane)`; `window.window_width is not None`; `first_pane.pane_height is not None`; `float(first_pane.pane_height) <= (float(window.window_width) + 1) / 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_ed84764758 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_horizontal | Window.split() splits window, returns new Pane, horizontal; fixtures `session`; execute `session.new_window(window_name=window_name, attach=True)`; `window.split(direction=PaneDirection.Right)`; `float(first_pane.pane_width)`; `float(window.window_width)`; assert `len(window.panes) == 2`; `isinstance(pane, Pane)`; `first_pane.pane_width is not None`; `window.window_width is not None`; `float(first_pane.pane_width) <= (float(window.window_width) + 1) / 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_horizontal_7407298f62 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_shell | Window.split() splits window, returns new Pane, vertical; fixtures `session`; execute `session.new_window(window_name=window_name, attach=True)`; `window.split(shell=cmd)`; `float(first_pane.pane_height)`; `float(window.window_width)`; `pane_start_command.replace('"', '')`; assert `len(window.panes) == 2`; `isinstance(pane, Pane)`; `first_pane.pane_height is not None`; `window.window_width is not None`; `float(first_pane.pane_height) <= (float(window.window_width) + 1) / 2`; `pane_start_command.replace('"', '') == cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_shell_4321a69614 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_size | Window.split() respects size; fixtures `session`; execute `session.new_window(window_name='split window size')`; `window.resize(height=100, width=100)`; `window.split(size=10)`; `window.split(direction=PaneDirection.Right, size=10)`; `window.split(size='10%')`; `window.split(direction=PaneDirection.Right, size='10%')`; assert `pane.pane_height == '10'`; `pane.pane_width == '10'`; `pane.pane_height == '8'`; `pane.pane_width == '8'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_size_9a457fc89d | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_start_directory[empty_string] | Test Window.split start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_empty_string_aa5d5e39ca | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_start_directory[none_value] | Test Window.split start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_none_value_b9fffb1db2 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_start_directory[relative_path] | Test Window.split start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_relative_path_0cc2e5ad5e | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_start_directory[user_path] | Test Window.split start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_user_path_84ef1bf3db | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_start_directory_pathlib | Test Window.split accepts pathlib.Path for start_directory; fixtures `session`, `user_path`; execute `session.new_window(window_name='test_window_split_pathlib')`; `window.split(start_directory=user_path)`; `user_path.resolve()`; `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_pathlib_dcdba93572 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_window_zoom | Verify splitting window with zoom; fixtures `session`; execute `session.new_window(window_name='split_without_zoom')`; `session.new_window(window_name='split_with_zoom')`; `window_without_zoom.split(zoom=False)`; `window_with_zoom.split(zoom=True)`; assert `pane_without_zoom.width == pane_without_zoom.window_width`; `pane_without_zoom.height is not None`; `pane_without_zoom.window_height is not None`; `pane_without_zoom.height < pane_without_zoom.window_height`; `pane_with_zoom.width == pane_with_zoom.window_width`; `pane_with_zoom.height == pane_with_zoom.window_height`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_window_zoom_0aea52f199 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_with_environment[multiple_env_vars] | Verify splitting window with environment variables; case inputs `environment={'ENV_VAR_1': 'pane_1', 'ENV_VAR_2': 'pane_2'}`, `test_id='multiple_env_vars'`; fixtures `session`; execute `session.new_window(window_name='split_with_environment')`; `window.split(shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; `time.sleep(0.05)`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_with_environment_multiple_env_vars_2a95b86945 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_split_with_environment[single_env_var] | Verify splitting window with environment variables; case inputs `environment={'ENV_VAR': 'pane'}`, `test_id='single_env_var'`; fixtures `session`; execute `session.new_window(window_name='split_with_environment')`; `window.split(shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; `time.sleep(0.05)`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_with_environment_single_env_var_60d98c3334 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_swap_window | Test Window.swap() swaps two windows; fixtures `session`; execute `session.new_window(window_name='swap_w1')`; `session.new_window(window_name='swap_w2')`; `w1.swap(w2)`; `w1.refresh()`; `w2.refresh()`; assert `w1.window_index == w2_idx`; `w2.window_index == w1_idx`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_swap_window_5c8e1d1d27 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_context_manager | Test Window context manager functionality; fixtures `session`; execute `session.new_window()`; `window.split()`; assert `window in session.windows`; `pane in window.panes`; `len(window.panes) == 2`; `window not in session.windows`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_context_manager_a437730957 | 3.2a through 3.7b | planned parity | +| tests/test_window.py::test_active_pane | Window.active_window returns active Pane; fixtures `session`; assert `isinstance(window.active_pane, Pane)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_active_pane_2a044ab40a | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_deprecated_window_methods_emit_warning[set_window_option] | Verify deprecated Window methods emit DeprecationWarning (0.50.0); case inputs `test_case=DeprecatedMethodTestCase(test_id='set_window_option', method_name='set_window_option', args=('main-pane-height', 20), kwargs={}, expected_error_match='Window\\.set_window_option\\(\\) is deprecated')`; fixtures `session`; execute `session.new_window(window_name='test_deprecation')`; `method(*test_case.args, **test_case.kwargs)`; expect `pytest.warns(DeprecationWarning, match=test_case.expected_error_match)`. | real-tmux integration test | PythonBehaviorParityTest#_window_py_test_deprecated_window_methods_emit_warning_set_window_option_a7a3be4c0b | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_deprecated_window_methods_emit_warning[show_window_option] | Verify deprecated Window methods emit DeprecationWarning (0.50.0); case inputs `test_case=DeprecatedMethodTestCase(test_id='show_window_option', method_name='show_window_option', args=('main-pane-height',), kwargs={}, expected_error_match='Window\\.show_window_option\\(\\) is deprecated')`; fixtures `session`; execute `session.new_window(window_name='test_deprecation')`; `method(*test_case.args, **test_case.kwargs)`; expect `pytest.warns(DeprecationWarning, match=test_case.expected_error_match)`. | real-tmux integration test | PythonBehaviorParityTest#window_py_test_deprecated_window_methods_emit_warning_show_window_option_6e07ddb7b6 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_deprecated_window_methods_emit_warning[show_window_options] | Verify deprecated Window methods emit DeprecationWarning (0.50.0); case inputs `test_case=DeprecatedMethodTestCase(test_id='show_window_options', method_name='show_window_options', args=(), kwargs={}, expected_error_match='Window\\.show_window_options\\(\\) is deprecated')`; fixtures `session`; execute `session.new_window(window_name='test_deprecation')`; `method(*test_case.args, **test_case.kwargs)`; expect `pytest.warns(DeprecationWarning, match=test_case.expected_error_match)`. | real-tmux integration test | PythonBehaviorParityTest#indow_py_test_deprecated_window_methods_emit_warning_show_window_options_bb60c6c4a9 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_empty_window_name | New windows can be created with empty string for window name; fixtures `session`; execute `session.set_option('automatic-rename', 'off')`; `session.new_window(window_name="''", attach=True)`; `session.cmd('list-windows', '-F', '#{window_name}', '-f', '#{==:#{session_name},' + session.session_name + '}')`; assert `window == session.active_window`; `window.window_name == "''"`; `session.session_name is not None`; `"''" in cmd.stdout`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_empty_window_name_fe50634976 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_empty_window_option_returns_None | Verify unset window option returns None; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_option('alternate-screen')`; assert `window.show_option('alternate-screen') is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_empty_window_option_returns_None_72aa5bb02d | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_fresh_window_data | Verify window data is fresh; fixtures `session`; execute `window.split()`; `session.new_window(window_name='second')`; `session.new_window(window_name='hey')`; `session.select_window('1')`; `active_window._show_option('pane-base-index', global_=True)`; `pane_to_select.select()`; assert `active_window is not None`; `pane_base_idx is not None`; `len(session.windows) == 1`; `len(session.active_window.panes) == 1`; `session.session_id != '@0'`; `current_windows == 1`; `isinstance(session.server, Server)`; `isinstance(w, Window)`; `isinstance(window, Window)`; `pane_to_select is not None`; `active_pane is not None`; `pane_to_select_2 is not None`; `current_windows == len(session.windows)`; `window_to_kill is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_fresh_window_data_c86c1a0f96 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_kill_window | Test window.kill() kills window; fixtures `session`; execute `session.new_window()`; `w.kill()`; `w.refresh()`; expect `pytest.raises(ObjectDoesNotExist)`; assert `w.window_id is not None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_kill_window_d8eb2eff37 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_last_pane | Test Window.last_pane() selects the previously active pane; fixtures `session`; execute `session.new_window(window_name='test_last_pane')`; `window.last_pane()`; `pane1.split()`; `pane2.select()`; `pane1.select()`; `pane2.refresh()`; assert `pane1 is not None`; `result is not None`; `pane2.pane_active == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_last_pane_d5eb4916f7 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_link_unlink_window | Test Window.link() and Window.unlink(); fixtures `server`, `session`; execute `server.new_session(session_name='link_target')`; `session.new_window(window_name='link_test')`; `w.link(s2, detach=True)`; `s2.refresh()`; `linked_windows[0].unlink()`; assert `'link_test' in s2_window_names`; `len(linked_windows) > 0`; `'link_test' not in s2_window_names`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_link_unlink_window_32d7f06beb | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window | Window.move_window results in changed index; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.move_window(new_index)`; assert `window.window_index is not None`; `window.window_index == new_index`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_7001ddfe96 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_kill_target | Test Window.move_window() with kill_target flag; fixtures `session`; execute `session.new_window(window_name='move_w1')`; `session.new_window(window_name='move_w2')`; `session.refresh()`; `extra_windows[0].move_window(destination=w2_index, kill_target=True)`; assert `w2.window_index is not None`; `len(extra_windows) == 1`; `len(session.windows) == initial_count - 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_kill_target_b7e089fe88 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_no_select | Test Window.move_window() with no_select flag; fixtures `session`; execute `session.new_window(window_name='nosel_w1', attach=True)`; `session.new_window(window_name='nosel_w2', attach=False)`; `session.refresh()`; `w2.move_window(destination='99', no_select=True)`; assert `session.active_window.window_id == w1.window_id`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_no_select_a6ab22d3d4 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_relative_returns_fresh_window[after-0] | Window.move_window() returns fresh state for relative moves; case inputs `destination_offset=0`, `flag_name='after'`; fixtures `session`; execute `session.new_window(window_name='move_middle')`; `session.new_window(window_name='move_relative')`; `Window.from_window_id(server=session.server, window_id=moving_window.window_id)`; assert `destination_window.window_index is not None`; `moving_window.window_id is not None`; `moving_window.window_index == fresh_window.window_index`; `moving_window.session_id == fresh_window.session_id`; when `flag_name == 'after'`: execute `moving_window.move_window(destination, after=True)`; when `not (flag_name == 'after')`: execute `moving_window.move_window(destination, before=True)`. | real-tmux integration test | PythonBehaviorParityTest#ts_test_window_py_test_move_window_relative_returns_fresh_window_after_0_de00174c42 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_relative_returns_fresh_window[before-1] | Window.move_window() returns fresh state for relative moves; case inputs `destination_offset=1`, `flag_name='before'`; fixtures `session`; execute `session.new_window(window_name='move_middle')`; `session.new_window(window_name='move_relative')`; `Window.from_window_id(server=session.server, window_id=moving_window.window_id)`; assert `destination_window.window_index is not None`; `moving_window.window_id is not None`; `moving_window.window_index == fresh_window.window_index`; `moving_window.session_id == fresh_window.session_id`; when `flag_name == 'after'`: execute `moving_window.move_window(destination, after=True)`; when `not (flag_name == 'after')`: execute `moving_window.move_window(destination, before=True)`. | real-tmux integration test | PythonBehaviorParityTest#s_test_window_py_test_move_window_relative_returns_fresh_window_before_1_151f2f516d | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_renumber | Test Window.move_window() with renumber flag; fixtures `session`; execute `session.new_window(window_name='ren_w1')`; `session.new_window(window_name='ren_w2')`; `session.new_window(window_name='ren_w3')`; `session.refresh()`; `w2.kill()`; `w3.move_window(renumber=True)`; assert `indices[i + 1] - indices[i] == 1`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_renumber_d5796a5b32 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_to_other_session | Window.move_window to other session; fixtures `server`, `session`; execute `session.new_window(window_name='test_window')`; `server.new_session('test_move_window')`; `window.move_window(session=new_session.session_id)`; assert `window_id is not None`; `new_session.windows.get(window_id=window_id) == window`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_to_other_session_92d3c24001 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_move_window_to_other_session_with_destination | Window.move_window() returns fresh state for cross-session moves; fixtures `server`, `session`; execute `session.new_window(window_name='move_cross_session')`; `server.new_session('test_move_window_destination')`; `window.move_window(destination=destination, session=new_session.session_id)`; `Window.from_window_id(server=server, window_id=window.window_id)`; assert `window.window_id is not None`; `fresh_window.session_id == new_session.session_id`; `fresh_window.window_index == destination`; `window.session_id == fresh_window.session_id`; `window.window_index == fresh_window.window_index`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_move_window_to_other_session_with_destination_e7f86afe1d | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_new_pane | Window.new_pane() creates a floating pane on tmux 3.7+ (else raises); fixtures `session`; execute `session.new_window(window_name='win_floating')`; `window.resize(height=50, width=200)`; when `has_gte_version('3.7')`: execute `window.new_pane(width=60, height=12, shell='sleep 30')`; assert `floating.pane_floating_flag == '1'`; `floating in window.panes`; when `not (has_gte_version('3.7'))`: execute `window.new_pane(width=40, height=10)`; expect `pytest.raises(exc.LibTmuxException, match='new_pane .*requires tmux 3.7')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_pane_3580a3317d | 3.3a through 3.7c; conditional boundaries: 3.7 | planned parity | +| tests/test_window.py::test_new_window_name_colon_period_accepted[colon] | Window names with ':' and '.' are accepted verbatim; case inputs `test_id='colon'`, `window_name='project:frontend'`; fixtures `session`; execute `session.new_window(window_name=window_name)`; `window.kill()`; assert `window.window_name == window_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_window_name_colon_period_accepted_colon_007435d489 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_new_window_name_colon_period_accepted[period] | Window names with ':' and '.' are accepted verbatim; case inputs `test_id='period'`, `window_name='app-v1.0'`; fixtures `session`; execute `session.new_window(window_name=window_name)`; `window.kill()`; assert `window.window_name == window_name`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_window_name_colon_period_accepted_period_6ef9d54ed8 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_new_window_with_direction | Verify new window with direction; fixtures `session`; execute `window.refresh()`; `session.new_window(window_name='Example')`; `window_initial.new_window(window_name='Window before', direction=WindowDirection.Before)`; `window_initial.refresh()`; `window_initial.new_window(window_name='Window after', direction=WindowDirection.After)`; `window_after.refresh()`; assert `window_initial.window_index == '2'`; `window_before.window_index == '2'`; `window_initial.window_index == '3'`; `window_after.window_index == '4'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_new_window_with_direction_c2a47c1ee5 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_newest_pane_data | Test window.panes has fresh data; fixtures `session`; execute `session.new_window(window_name='test', attach=True)`; `window.split(attach=True)`; assert `isinstance(window, Window)`; `len(window.panes) == 1`; `len(window.panes) == 2`; `len(window.panes) == 3`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_newest_pane_data_79e2ce3983 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_next_layout | Test Window.next_layout() cycles to the next layout; fixtures `session`; execute `session.new_window(window_name='test_next_layout')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout('even-horizontal')`; `window.refresh()`; `window.next_layout()`; assert `pane is not None`; `layout_before != layout_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_next_layout_b7b4774637 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_previous_layout | Test Window.previous_layout() cycles back; fixtures `session`; execute `session.new_window(window_name='test_prev_layout')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout('even-horizontal')`; `window.refresh()`; `window.next_layout()`; assert `pane is not None`; `layout_before == layout_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_previous_layout_dde68474fa | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_resize | Verify resizing window; fixtures `session`; execute `session.cmd('detach-client', '-s')`; `window.resize(height=10)`; `window.resize(width=10)`; `window.resize(adjustment_direction=ResizeAdjustmentDirection.Down, adjustment=window_height_adjustment * 2)`; `window.resize(adjustment_direction=ResizeAdjustmentDirection.Up, adjustment=window_height_adjustment)`; `window.resize(height=50)`; assert `window.window_height is not None`; `window.window_width is not None`; `int(window.window_height) == 10`; `int(window.window_width) == 10`; `window_height_before + window_height_adjustment * 2 == int(window.window_height)`; `window_height_before - window_height_adjustment == int(window.window_height)`; `window_height_before > window_height_shrunk`; `window`; `window_height_before < window_height_expanded`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_resize_c4965fd743 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_respawn_window | Test Window.respawn() with kill flag; fixtures `session`; execute `session.new_window(window_name='test_respawn_w')`; `window.respawn(kill=True, shell='sh')`; `window.refresh()`; `session.refresh()`; assert `window.window_id in [w.window_id for w in session.windows]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_respawn_window_b5eafa65d6 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_rotate_window | Test Window.rotate() rotates pane positions; fixtures `session`; execute `session.new_window(window_name='test_rotate')`; `window.resize(height=40, width=80)`; `window.rotate()`; `pane1.split()`; `pane2.split()`; `pane1.refresh()`; assert `pane1 is not None`; `idx_before != idx_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_rotate_window_6eca5ac10b | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_select_layout_accepts_no_arg | Tmux allows select-layout with no arguments, so let's allow it here; fixtures `server`, `session`; execute `session.new_window(window_name='test_window')`; `window.select_layout()`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_accepts_no_arg_0af9999b14 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_select_layout_mutual_exclusion | Test that layout string and flags are mutually exclusive; fixtures `session`; execute `session.new_window(window_name='test_layout_mutex')`; `window.select_layout('tiled', spread=True)`; expect `pytest.raises(ValueError, match='Cannot specify both')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_mutual_exclusion_884674c936 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_select_layout_next_previous | Test Window.select_layout() with next/previous flags; fixtures `session`; execute `session.new_window(window_name='test_layout_cycle')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout('even-horizontal')`; `window.refresh()`; `window.select_layout(next_layout=True)`; assert `pane is not None`; `layout_before != layout_after_next`; `layout_after_prev == layout_before`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_next_previous_a1947ac64f | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_select_layout_spread | Test Window.select_layout() with spread flag; fixtures `session`; execute `session.new_window(window_name='test_layout_spread')`; `window.resize(height=40, width=80)`; `pane.split()`; `window.select_layout(spread=True)`; assert `pane is not None`; `len(window.panes) == 3`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_layout_spread_df22951796 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_select_window | Test Window.select_window(); fixtures `session`; execute `session.new_window(window_name='testing 3')`; `session.select_window(str(window_base_index))`; `session.select_window('testing 3')`; assert `window_count == 1`; `session.active_window.window_index is not None`; `window.window_index is not None`; `int(window_base_index) + 1 == int(window.window_index)`; `window_base_index == int(session.active_window.window_index)`; `int(window_base_index) + 1 == int(session.active_window.window_index)`; `len(session.windows) == 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_select_window_7403165fda | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_set_and_show_window_options | Window.set_option() then Window._show_options(key); fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('main-pane-height', 20)`; `window._show_option('main-pane-height')`; `window.set_option('main-pane-height', 40)`; `window._show_options(scope=OptionScope.Session)`; `window.set_option('pane-border-format', ' #P ')`; expect `pytest.raises(KeyError)`; assert `window._show_option('main-pane-height') == 20`; `window._show_option('main-pane-height') == 40`; `window._show_options(scope=OptionScope.Session)['main-pane-height'] == 40`; `window._show_option('pane-border-format') == ' #P '`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_and_show_window_options_227ba527da | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_set_window_and_show_window_options | Window.set_option() then Window.show_option(key); fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('main-pane-height', 20)`; `window.show_option('main-pane-height')`; `window.set_option('main-pane-height', 40)`; `window.show_options()`; `window.set_option('pane-border-format', ' #P ')`; assert `window.show_option('main-pane-height') == 20`; `window.show_option('main-pane-height') == 40`; `window.show_options()['main-pane-height'] == 40`; `window.show_option('pane-border-format') == ' #P '`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_window_and_show_window_options_d96249517a | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_set_window_option_ambiguous | set_option raises AmbiguousOption for ambiguous option; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('clock-mode', 12)`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_window_option_ambiguous_1c63d8c847 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_set_window_option_invalid | Window.set_option raises InvalidOption for invalid option key; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('afewewfew', 43)`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_set_window_option_invalid_dc1098935a | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_show_window_option | Set option then Window.show_option(key); fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.set_option('main-pane-height', 20)`; `window.show_option('main-pane-height')`; `window.set_option('main-pane-height', 40)`; assert `window.show_option('main-pane-height') == 20`; `window.show_option('main-pane-height') == 40`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_option_81f670d32c | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_show_window_option_ambiguous | show_option raises AmbiguousOption for ambiguous option; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_option('clock-mode')`; expect `pytest.raises(exc.AmbiguousOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_option_ambiguous_7e784bd71f | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_show_window_option_unknown | Window.show_option raises InvalidOption for bad option key; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_option('moooz')`; expect `pytest.raises(exc.InvalidOption)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_option_unknown_b9f31bc4f9 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_show_window_options | Window.show_options() returns dict; fixtures `session`; execute `session.new_window(window_name='test_window')`; `window.show_options()`; `window._show_options()`; `window._show_options(scope=OptionScope.Pane)`; `window._show_options(scope=OptionScope.Pane, global_=True)`; `window._show_options(scope=OptionScope.Window)`; assert `isinstance(options, dict)`; `isinstance(options_2, dict)`; `isinstance(pane_options, dict)`; `isinstance(pane_options_global, dict)`; `isinstance(window_options, dict)`; `isinstance(window_options_global, dict)`; `isinstance(server_options, dict)`; `isinstance(server_options_global, dict)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_show_window_options_13b17290c2 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split | Window.split() splits window, returns new Pane, vertical; fixtures `session`; execute `session.new_window(window_name=window_name, attach=True)`; `window.split()`; `float(first_pane.pane_height)`; `float(window.window_width)`; assert `len(window.panes) == 2`; `isinstance(pane, Pane)`; `window.window_width is not None`; `first_pane.pane_height is not None`; `float(first_pane.pane_height) <= (float(window.window_width) + 1) / 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_ed84764758 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_horizontal | Window.split() splits window, returns new Pane, horizontal; fixtures `session`; execute `session.new_window(window_name=window_name, attach=True)`; `window.split(direction=PaneDirection.Right)`; `float(first_pane.pane_width)`; `float(window.window_width)`; assert `len(window.panes) == 2`; `isinstance(pane, Pane)`; `first_pane.pane_width is not None`; `window.window_width is not None`; `float(first_pane.pane_width) <= (float(window.window_width) + 1) / 2`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_horizontal_7407298f62 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_shell | Window.split() splits window, returns new Pane, vertical; fixtures `session`; execute `session.new_window(window_name=window_name, attach=True)`; `window.split(shell=cmd)`; `float(first_pane.pane_height)`; `float(window.window_width)`; `pane_start_command.replace('"', '')`; assert `len(window.panes) == 2`; `isinstance(pane, Pane)`; `first_pane.pane_height is not None`; `window.window_width is not None`; `float(first_pane.pane_height) <= (float(window.window_width) + 1) / 2`; `pane_start_command.replace('"', '') == cmd`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_shell_4321a69614 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_size | Window.split() respects size; fixtures `session`; execute `session.new_window(window_name='split window size')`; `window.resize(height=100, width=100)`; `window.split(size=10)`; `window.split(direction=PaneDirection.Right, size=10)`; `window.split(size='10%')`; `window.split(direction=PaneDirection.Right, size='10%')`; assert `pane.pane_height == '10'`; `pane.pane_width == '10'`; `pane.pane_height == '8'`; `pane.pane_width == '8'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_size_9a457fc89d | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_start_directory[empty_string] | Test Window.split start_directory parameter handling; case inputs `description='Empty string should not add -c flag'`, `start_directory=''`, `test_id='empty_string'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_empty_string_aa5d5e39ca | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_start_directory[none_value] | Test Window.split start_directory parameter handling; case inputs `description='None should not add -c flag'`, `start_directory=None`, `test_id='none_value'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_none_value_b9fffb1db2 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_start_directory[relative_path] | Test Window.split start_directory parameter handling; case inputs `description='Relative path should add -c flag'`, `start_directory='./relative/path'`, `test_id='relative_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_relative_path_0cc2e5ad5e | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_start_directory[user_path] | Test Window.split start_directory parameter handling; case inputs `description='User path should add -c flag'`, `start_directory='{user_path}'`, `test_id='user_path'`; fixtures `session`, `monkeypatch`, `tmp_path`, `user_path`; execute `session.new_window(window_name=f'test_window_split_{test_id}')`; `window.split(start_directory=actual_start_directory)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; when `start_directory and str(start_directory) not in {'', 'None'} and not (f'{user_path}' in str(start_directory)) and str(start_directory).startswith('./')`: execute `temp_dir.mkdir(parents=True, exist_ok=True)`; `temp_dir.resolve()`; when `expected_path`: execute `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_user_path_84ef1bf3db | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_start_directory_pathlib | Test Window.split accepts pathlib.Path for start_directory; fixtures `session`, `user_path`; execute `session.new_window(window_name='test_window_split_pathlib')`; `window.split(start_directory=user_path)`; `user_path.resolve()`; `new_pane.refresh()`; `pathlib.Path(new_pane.pane_current_path).resolve()`; `pathlib.Path(new_pane.pane_current_path)`; assert `new_pane in window.panes`; `len(window.panes) == 2`; `new_pane.pane_current_path is not None`; `actual_path == expected_path`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_start_directory_pathlib_dcdba93572 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_window_zoom | Verify splitting window with zoom; fixtures `session`; execute `session.new_window(window_name='split_without_zoom')`; `session.new_window(window_name='split_with_zoom')`; `window_without_zoom.split(zoom=False)`; `window_with_zoom.split(zoom=True)`; assert `pane_without_zoom.width == pane_without_zoom.window_width`; `pane_without_zoom.height is not None`; `pane_without_zoom.window_height is not None`; `pane_without_zoom.height < pane_without_zoom.window_height`; `pane_with_zoom.width == pane_with_zoom.window_width`; `pane_with_zoom.height == pane_with_zoom.window_height`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_window_zoom_0aea52f199 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_with_environment[multiple_env_vars] | Verify splitting window with environment variables; case inputs `environment={'ENV_VAR_1': 'pane_1', 'ENV_VAR_2': 'pane_2'}`, `test_id='multiple_env_vars'`; fixtures `session`; execute `session.new_window(window_name='split_with_environment')`; `window.split(shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; `time.sleep(0.05)`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_with_environment_multiple_env_vars_2a95b86945 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_split_with_environment[single_env_var] | Verify splitting window with environment variables; case inputs `environment={'ENV_VAR': 'pane'}`, `test_id='single_env_var'`; fixtures `session`; execute `session.new_window(window_name='split_with_environment')`; `window.split(shell=f"{env} PS1='$ ' sh", environment=environment)`; `pane.send_keys(f'echo ${k}')`; `pane.capture_pane()`; `shutil.which('env')`; `time.sleep(0.05)`; assert `env is not None; message='Cannot find usable `env` in PATH.'`; `pane is not None`; `pane.capture_pane()[-2] == v`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_split_with_environment_single_env_var_60d98c3334 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_swap_window | Test Window.swap() swaps two windows; fixtures `session`; execute `session.new_window(window_name='swap_w1')`; `session.new_window(window_name='swap_w2')`; `w1.swap(w2)`; `w1.refresh()`; `w2.refresh()`; assert `w1.window_index == w2_idx`; `w2.window_index == w1_idx`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_swap_window_5c8e1d1d27 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_context_manager | Test Window context manager functionality; fixtures `session`; execute `session.new_window()`; `window.split()`; assert `window in session.windows`; `pane in window.panes`; `len(window.panes) == 2`; `window not in session.windows`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_context_manager_a437730957 | 3.3a through 3.7c | planned parity | | tests/test_window.py::test_window_display_message_flags[no_expand_literal] | Window.display_message() resolves window-scoped formats; case inputs `cmd='#{window_id}'`, `expected_in_output='#{window_id}'`, `kwargs={'get_text': True, 'no_expand': True}`, `min_tmux_version='3.4'`, `test_id='no_expand_literal'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_flags_no_expand_literal_a801096c41 | 3.4 through 3.7b | planned parity | -| tests/test_window.py::test_window_display_message_flags[window_id] | Window.display_message() resolves window-scoped formats; case inputs `cmd='#{window_id}'`, `expected_in_output='@'`, `kwargs={'get_text': True}`, `min_tmux_version=None`, `test_id='window_id'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_flags_window_id_5a05df26f2 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_display_message_flags[window_index_via_format_string] | Window.display_message() resolves window-scoped formats; case inputs `cmd=''`, `expected_in_output='1'`, `kwargs={'get_text': True, 'format_string': '#{window_index}'}`, `min_tmux_version=None`, `test_id='window_index_via_format_string'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#ndow_py_test_window_display_message_flags_window_index_via_format_string_8f708c0dda | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_display_message_flags[zoomed_flag_default_zero] | Window.display_message() resolves window-scoped formats; case inputs `cmd='#{window_zoomed_flag}'`, `expected_in_output='0'`, `kwargs={'get_text': True}`, `min_tmux_version=None`, `test_id='zoomed_flag_default_zero'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#est_window_py_test_window_display_message_flags_zoomed_flag_default_zero_264423d0a5 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_display_message_no_text_returns_none | Without ``get_text=True`` the call renders to status line and returns None; fixtures `session`; execute `window.display_message('hi from libtmux')`; assert `result is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_no_text_returns_none_4beae0a2b4 | 3.2a through 3.7b | planned parity | +| tests/test_window.py::test_window_display_message_flags[window_id] | Window.display_message() resolves window-scoped formats; case inputs `cmd='#{window_id}'`, `expected_in_output='@'`, `kwargs={'get_text': True}`, `min_tmux_version=None`, `test_id='window_id'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_flags_window_id_5a05df26f2 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_display_message_flags[window_index_via_format_string] | Window.display_message() resolves window-scoped formats; case inputs `cmd=''`, `expected_in_output='1'`, `kwargs={'get_text': True, 'format_string': '#{window_index}'}`, `min_tmux_version=None`, `test_id='window_index_via_format_string'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#ndow_py_test_window_display_message_flags_window_index_via_format_string_8f708c0dda | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_display_message_flags[zoomed_flag_default_zero] | Window.display_message() resolves window-scoped formats; case inputs `cmd='#{window_zoomed_flag}'`, `expected_in_output='0'`, `kwargs={'get_text': True}`, `min_tmux_version=None`, `test_id='zoomed_flag_default_zero'`; fixtures `session`; execute `window.display_message(cmd, **kwargs)`; when `min_tmux_version and (not has_gte_version(min_tmux_version))`: skip via `pytest.skip(f'Requires tmux {min_tmux_version}+')`; when `expected_in_output is not None`: execute `'\n'.join(result)`; assert `result is not None`; `expected_in_output in output`. | real-tmux integration test | PythonBehaviorParityTest#est_window_py_test_window_display_message_flags_zoomed_flag_default_zero_264423d0a5 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_display_message_no_text_returns_none | Without ``get_text=True`` the call renders to status line and returns None; fixtures `session`; execute `window.display_message('hi from libtmux')`; assert `result is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_no_text_returns_none_4beae0a2b4 | 3.3a through 3.7c | planned parity | | tests/test_window.py::test_window_display_message_target_client | ``target_client`` is plumbed through as ``-c``; fixtures `control_mode`, `session`; execute `control_mode()`; `window.display_message('#{window_id}', get_text=True, target_client=ctl.client_name)`; `result[0].startswith('@')`; assert `isinstance(result, list)`; `len(result) == 1`; `result[0].startswith('@')`; when `not has_gte_version('3.3')`: skip via `pytest.skip('display-message -p via control-mode client unreliable on tmux 3.2a')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_target_client_9f23f6ef06 | 3.3 through 3.7b | planned parity | -| tests/test_window.py::test_window_display_message_warns_on_tmux_error | Tmux stderr on ``display-message`` surfaces as a :class:`UserWarning`; fixtures `session`; execute `window.display_message('x', get_text=True, format_string='#{window_id}')`; expect `pytest.warns(UserWarning, match='only one of -F or argument')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_warns_on_tmux_error_9c99c2acac | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_flags_field_returns_string | ``window_flags`` summarizes window state and reads as a string; fixtures `session`; execute `window.refresh()`; assert `isinstance(window.window_flags, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_flags_field_returns_string_3ef969de3e | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_active_clients_list] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_active_clients_list'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#est_window_format_field_declared_and_hydrated_window_active_clients_list_94961a7521 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_active_sessions_list] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_active_sessions_list'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#st_window_format_field_declared_and_hydrated_window_active_sessions_list_1eaa504749 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_activity_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_activity_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#w_py_test_window_format_field_declared_and_hydrated_window_activity_flag_6f9534f5b6 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_bell_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_bell_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#indow_py_test_window_format_field_declared_and_hydrated_window_bell_flag_b530d90b78 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_bigger] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_bigger'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_window_py_test_window_format_field_declared_and_hydrated_window_bigger_b2db8ac5ed | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_end_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_end_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#window_py_test_window_format_field_declared_and_hydrated_window_end_flag_2e1a1a7868 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_flags] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_flags'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#st_window_py_test_window_format_field_declared_and_hydrated_window_flags_58713f2850 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_format] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_format'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_window_py_test_window_format_field_declared_and_hydrated_window_format_2ddf445c63 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_last_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_last_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#indow_py_test_window_format_field_declared_and_hydrated_window_last_flag_dc04b5fc3c | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_silence_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_silence_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ow_py_test_window_format_field_declared_and_hydrated_window_silence_flag_8d06c7bef7 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_start_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_start_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ndow_py_test_window_format_field_declared_and_hydrated_window_start_flag_8a06735207 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_visible_layout] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_visible_layout'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#_py_test_window_format_field_declared_and_hydrated_window_visible_layout_a78ac844e1 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_refresh_raises_when_window_id_is_none | ``Window.refresh()`` raises ``ValueError`` when ``window_id`` is unset; fixtures `session`; execute `window.refresh()`; `Window(server=session.server)`; expect `pytest.raises(ValueError, match='window_id')`; assert `window.window_id is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_refresh_raises_when_window_id_is_none_45ce538830 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_rename[rename_with_escapes] | Test Window.rename_window(); case inputs `test_id='rename_with_escapes'`, `window_name_after='hello \\\\ wazzup 0'`, `window_name_before='test'`, `window_name_input='hello \\ wazzup 0'`; fixtures `session`; execute `session.set_option('automatic-rename', 'off', scope=None)`; `session.new_window(window_name=window_name_before, attach=True)`; `window.rename_window(window_name_input)`; assert `window == session.active_window`; `window.window_name == window_name_before`; `window.window_name == window_name_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_rename_rename_with_escapes_69f5d0ecce | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_rename[rename_with_spaces] | Test Window.rename_window(); case inputs `test_id='rename_with_spaces'`, `window_name_after='ha ha ha fjewlkjflwef'`, `window_name_before='test'`, `window_name_input='ha ha ha fjewlkjflwef'`; fixtures `session`; execute `session.set_option('automatic-rename', 'off', scope=None)`; `session.new_window(window_name=window_name_before, attach=True)`; `window.rename_window(window_name_input)`; assert `window == session.active_window`; `window.window_name == window_name_before`; `window.window_name == window_name_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_rename_rename_with_spaces_c12af2abc5 | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_search_panes_filter_by_id | ``Window.search_panes(filter=...)`` returns only the matching pane id; fixtures `session`; execute `window.split()`; `window.search_panes(filter=f'#{{m:{target.pane_id},#{{pane_id}}}}')`; assert `[p.pane_id for p in matches] == [target.pane_id]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_search_panes_filter_by_id_4dd56628de | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_search_panes_no_filter_equivalent_to_property | ``search_panes()`` with no filter matches the existing ``panes`` property; fixtures `session`; execute `window.split()`; `window.search_panes()`; assert `from_property == from_method`. | real-tmux integration test | PythonBehaviorParityTest#test_window_py_test_window_search_panes_no_filter_equivalent_to_property_e52428dc6e | 3.2a through 3.7b | planned parity | -| tests/test_window.py::test_window_zoomed_flag_field_toggle | ``window.window_zoomed_flag`` reflects tmux's zoom state across refresh; fixtures `session`; execute `window.split()`; `window.refresh()`; `pane.resize(zoom=True)`; assert `window.window_zoomed_flag == '0'`; `pane is not None`; `window.window_zoomed_flag == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_zoomed_flag_field_toggle_d1301f3b2f | 3.2a through 3.7b | planned parity | +| tests/test_window.py::test_window_display_message_warns_on_tmux_error | Tmux stderr on ``display-message`` surfaces as a :class:`UserWarning`; fixtures `session`; execute `window.display_message('x', get_text=True, format_string='#{window_id}')`; expect `pytest.warns(UserWarning, match='only one of -F or argument')`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_display_message_warns_on_tmux_error_9c99c2acac | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_flags_field_returns_string | ``window_flags`` summarizes window state and reads as a string; fixtures `session`; execute `window.refresh()`; assert `isinstance(window.window_flags, str)`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_flags_field_returns_string_3ef969de3e | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_active_clients_list] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_active_clients_list'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#est_window_format_field_declared_and_hydrated_window_active_clients_list_94961a7521 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_active_sessions_list] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_active_sessions_list'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#st_window_format_field_declared_and_hydrated_window_active_sessions_list_1eaa504749 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_activity_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_activity_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#w_py_test_window_format_field_declared_and_hydrated_window_activity_flag_6f9534f5b6 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_bell_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_bell_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#indow_py_test_window_format_field_declared_and_hydrated_window_bell_flag_b530d90b78 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_bigger] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_bigger'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_window_py_test_window_format_field_declared_and_hydrated_window_bigger_b2db8ac5ed | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_end_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_end_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#window_py_test_window_format_field_declared_and_hydrated_window_end_flag_2e1a1a7868 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_flags] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_flags'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#st_window_py_test_window_format_field_declared_and_hydrated_window_flags_58713f2850 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_format] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_format'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#t_window_py_test_window_format_field_declared_and_hydrated_window_format_2ddf445c63 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_last_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_last_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#indow_py_test_window_format_field_declared_and_hydrated_window_last_flag_dc04b5fc3c | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_silence_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_silence_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ow_py_test_window_format_field_declared_and_hydrated_window_silence_flag_8d06c7bef7 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_start_flag] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_start_flag'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#ndow_py_test_window_format_field_declared_and_hydrated_window_start_flag_8a06735207 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_format_field_declared_and_hydrated[window_visible_layout] | Tmux's window-scope format tokens hydrate onto the typed ``Window``; case inputs `field_name='window_visible_layout'`; fixtures `session`; execute `window.refresh()`; assert `field_name in window.__dataclass_fields__`; `value is None or isinstance(value, str)`. | real-tmux integration test | PythonBehaviorParityTest#_py_test_window_format_field_declared_and_hydrated_window_visible_layout_a78ac844e1 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_refresh_raises_when_window_id_is_none | ``Window.refresh()`` raises ``ValueError`` when ``window_id`` is unset; fixtures `session`; execute `window.refresh()`; `Window(server=session.server)`; expect `pytest.raises(ValueError, match='window_id')`; assert `window.window_id is None`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_refresh_raises_when_window_id_is_none_45ce538830 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_rename[rename_with_escapes] | Test Window.rename_window(); case inputs `test_id='rename_with_escapes'`, `window_name_after='hello \\\\ wazzup 0'`, `window_name_before='test'`, `window_name_input='hello \\ wazzup 0'`; fixtures `session`; execute `session.set_option('automatic-rename', 'off', scope=None)`; `session.new_window(window_name=window_name_before, attach=True)`; `window.rename_window(window_name_input)`; assert `window == session.active_window`; `window.window_name == window_name_before`; `window.window_name == window_name_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_rename_rename_with_escapes_69f5d0ecce | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_rename[rename_with_spaces] | Test Window.rename_window(); case inputs `test_id='rename_with_spaces'`, `window_name_after='ha ha ha fjewlkjflwef'`, `window_name_before='test'`, `window_name_input='ha ha ha fjewlkjflwef'`; fixtures `session`; execute `session.set_option('automatic-rename', 'off', scope=None)`; `session.new_window(window_name=window_name_before, attach=True)`; `window.rename_window(window_name_input)`; assert `window == session.active_window`; `window.window_name == window_name_before`; `window.window_name == window_name_after`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_rename_rename_with_spaces_c12af2abc5 | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_search_panes_filter_by_id | ``Window.search_panes(filter=...)`` returns only the matching pane id; fixtures `session`; execute `window.split()`; `window.search_panes(filter=f'#{{m:{target.pane_id},#{{pane_id}}}}')`; assert `[p.pane_id for p in matches] == [target.pane_id]`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_search_panes_filter_by_id_4dd56628de | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_search_panes_no_filter_equivalent_to_property | ``search_panes()`` with no filter matches the existing ``panes`` property; fixtures `session`; execute `window.split()`; `window.search_panes()`; assert `from_property == from_method`. | real-tmux integration test | PythonBehaviorParityTest#test_window_py_test_window_search_panes_no_filter_equivalent_to_property_e52428dc6e | 3.3a through 3.7c | planned parity | +| tests/test_window.py::test_window_zoomed_flag_field_toggle | ``window.window_zoomed_flag`` reflects tmux's zoom state across refresh; fixtures `session`; execute `window.split()`; `window.refresh()`; `pane.resize(zoom=True)`; assert `window.window_zoomed_flag == '0'`; `pane is not None`; `window.window_zoomed_flag == '1'`. | real-tmux integration test | PythonBehaviorParityTest#tests_test_window_py_test_window_zoomed_flag_field_toggle_d1301f3b2f | 3.3a through 3.7c | planned parity | diff --git a/examples/api/README.md b/examples/api/README.md index 6eb46ca6d..d7e2e3458 100644 --- a/examples/api/README.md +++ b/examples/api/README.md @@ -34,7 +34,7 @@ their effects through `IOApp`; constructing an `IO` alone does not run it. ## Minimal consumer -Use JDK 25, Git, and tmux 3.2a through 3.7c on Linux. The source checkout +Use JDK 25, Git, and tmux 3.3a through 3.7c on Linux. The source checkout supplies the Gradle wrapper and library dependencies. Save the chosen variant's `settings.gradle.kts` and `build.gradle.kts`, the shared `gradle.properties` and `run.sh`, and one complete program under `src/main/java`, `src/main/kotlin`, diff --git a/integration-tests/src/test/java/io/github/libtmux/it/CaptureIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/CaptureIntegrationTest.java index 4376f5a9f..1b41ab40b 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/CaptureIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/CaptureIntegrationTest.java @@ -22,8 +22,8 @@ * ready the moment the pane exists, and keys sent before it is are lost. Every count below therefore * describes tmux and not a race. * - *

Sizes are asserted in relative terms because tmux 3.2a ignores {@code -x}/{@code -y} on a - * detached session, so a pane there is the default size and not the one asked for. + *

Sizes are asserted in relative terms, so a count does not depend on the pane's exact + * dimensions. */ @ExtendWith(TmuxExtension.class) final class CaptureIntegrationTest { diff --git a/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java index 05c8ff917..64c393f27 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/ControlWatchIntegrationTest.java @@ -65,7 +65,7 @@ void aRenameIsAnnouncedWithTheNameItWasGiven(Server server) throws Exception { /** * A message another client aims at this one arrives as a notification, " : " and all. A name - * cannot carry that separator across releases: 3.2a to 3.6 store {@code :} as {@code _} and 3.7 + * cannot carry that separator across releases: 3.3a to 3.6 store {@code :} as {@code _} and 3.7 * refuses it, so the message is the real-tmux proof that only a subscription is split there. */ @Test diff --git a/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java index e7704515c..961d71cdf 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/CreationIntegrationTest.java @@ -154,8 +154,8 @@ void aWindowCommandStartsInTheRequestedDirectory(Server server, @TempDir Path di /** * A relative directory starts every kind of process where this JVM's working directory says. The - * daemon is started from another directory, because tmux 3.2a resolves a relative {@code -c} - * against the server's own, and a daemon started from here would agree with it by accident. + * daemon is started from another directory, because a daemon started from here would agree with this + * process about a relative {@code -c} by accident. */ @Test void aRelativeStartDirectoryResolvesAgainstThisProcess(@TempDir Path directory) throws Exception { diff --git a/integration-tests/src/test/java/io/github/libtmux/it/FailedReadIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/FailedReadIntegrationTest.java index b41266f8f..ca7c836a2 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/FailedReadIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/FailedReadIntegrationTest.java @@ -145,7 +145,7 @@ void aMissingSessionOnALiveServerIsStillFalse(Server server) { /** * A name tmux has no option for, rather than an unset {@code @user} one: tmux accepts any - * {@code @} name as a user option, and 3.2a answers an unset one with an empty value where 3.7 + * {@code @} name as a user option, and releases differ on an unset one: an empty value where 3.7 * calls it invalid. Only a name that is not an option at all means the same thing on every * supported release. */ diff --git a/integration-tests/src/test/java/io/github/libtmux/it/HookIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/HookIntegrationTest.java index 669a5bbb0..13e508fea 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/HookIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/HookIntegrationTest.java @@ -16,7 +16,7 @@ /** * Binding commands to tmux events, at each scope that holds them. * - *

{@code set-hook} declares the same flags from 3.2a to 3.7b, so nothing here is version-gated. + *

{@code set-hook} declares the same flags across the supported range, so nothing here is version-gated. * What is easy to get wrong is the scope: a hook set where it does not live is accepted and dropped. */ @ExtendWith(TmuxExtension.class) diff --git a/integration-tests/src/test/java/io/github/libtmux/it/LeadingDashIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/LeadingDashIntegrationTest.java index b37e1a057..7bc0bf07c 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/LeadingDashIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/LeadingDashIntegrationTest.java @@ -144,9 +144,9 @@ void aShellCommandCanBeginWithADash(Server server) { */ @Test void theRawCommandEscapeHatchDoesNotInsertTheTerminator(Server server) { - // tmux's own wording for a rejected flag changed across the supported range — 3.2a says - // "unknown option -- Z" and 3.7 "unknown flag -Z" — so this asserts that it refused, which - // is the fact, rather than how it said so. + // tmux's own wording for a rejected flag differs between releases — "unknown option -- Z", + // "unknown flag -Z" — so this asserts that it refused, which is the fact, rather than how + // it said so. assertFalse( server.cmd("run-shell", UNKNOWN).succeeded(), "cmd is the way past the typed API, so it does not quietly rewrite the argv"); diff --git a/integration-tests/src/test/java/io/github/libtmux/it/OptionWritesIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/OptionWritesIntegrationTest.java index d12fb2add..2b36a3926 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/OptionWritesIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/OptionWritesIntegrationTest.java @@ -15,7 +15,7 @@ /** * Writing options in the ways tmux offers beyond replacing a value outright. * - *

{@code set-option} and {@code show-options} declare byte-identical flags from 3.2a to 3.7b, so + *

{@code set-option} and {@code show-options} declare byte-identical flags across the supported range, so * nothing here is version-gated. What varies between releases is how many options exist, which is * why no count below is asserted exactly. */ diff --git a/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java index 8501cb53c..746716550 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/PaneProcessIntegrationTest.java @@ -22,7 +22,7 @@ * Restarting what a pane runs, watching what it prints, and asking tmux things a snapshot does not * carry. * - *

None of these commands changed a flag between 3.2a and 3.7b, so there is no version rule here — + *

None of these commands changed a flag across the supported range, so there is no version rule here — * only behaviour, checked on whichever release the lane runs. */ @ExtendWith(TmuxExtension.class) diff --git a/integration-tests/src/test/java/io/github/libtmux/it/PaneWaitFidelityIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/PaneWaitFidelityIntegrationTest.java index 764bd89ed..da5d1d2fa 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/PaneWaitFidelityIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/PaneWaitFidelityIntegrationTest.java @@ -32,9 +32,8 @@ final class PaneWaitFidelityIntegrationTest { /** - * Sized by resizing the window rather than the session: a detached session cannot be given a - * size before tmux 3.3, and {@code resize-pane} does not move a lone pane in a detached session - * at all — measured on 3.2a, where the width stayed 80 — while {@code resize-window} does. + * Sized by resizing the window rather than the session: {@code resize-pane} does not move a lone pane in a detached session at all, + * while {@code resize-window} does. */ private static Pane shell(Server server, String name, int columns) throws InterruptedException { Window window = server.newSession(session -> session.named(name).running("env", "PS1=$ ", "ENV=", "/bin/sh")) diff --git a/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java index 8f546e02e..80955eebb 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/ServerControlIntegrationTest.java @@ -22,9 +22,7 @@ * Deciding inside tmux, locking, and reading what the server has been told. * *

{@code if-shell}, {@code lock-server} and {@code show-messages} declare the same flags on every - * supported release. The prompt-history commands do not exist at all before 3.3, which is a floor rather - * than a flag, so both branches assert: 3.2a takes the refusing one and every later release the - * working one. + * supported release. The prompt-history commands exist on every supported release. */ @ExtendWith(TmuxExtension.class) final class ServerControlIntegrationTest { diff --git a/integration-tests/src/test/java/io/github/libtmux/it/ServerScriptingIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/ServerScriptingIntegrationTest.java index 9f40903b5..e46ac6f25 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/ServerScriptingIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/ServerScriptingIntegrationTest.java @@ -23,19 +23,17 @@ /** * Asking the server to run things and to say what it knows. * - *

{@code run-shell} reports its command's output on 3.2a, loses it from 3.3 through 3.4, and - * reports it again from 3.5. That is a hole in the middle of the range rather than a floor, so both - * branches assert here and the version arithmetic is spelled out rather than assumed. + *

{@code run-shell} loses its command's output on 3.3a and 3.4 and reports it again from 3.5. + * That is a hole at the start of the range rather than a floor, so both branches assert here and + * the version arithmetic is spelled out rather than assumed. */ @ExtendWith(TmuxExtension.class) final class ServerScriptingIntegrationTest { - private static final TmuxVersion LOST = new TmuxVersion(3, 3, ""); private static final TmuxVersion FOUND = new TmuxVersion(3, 5, ""); private static boolean losesShellOutput(Server server) { - TmuxVersion running = server.version(); - return running.atLeast(LOST) && !running.atLeast(FOUND); + return !server.version().atLeast(FOUND); } // -------------------------------------------------------------------------------- expanding @@ -134,17 +132,17 @@ void readingWhatTheCommandPrintedWorksOrRefuses(Server server) { } /** - * A refusal must not be a silent one: the releases that lose the output are exactly 3.3, 3.3a - * and 3.4, and every other lane has to take the capturing path. + * A refusal must not be a silent one: the releases that lose the output are exactly 3.3a and + * 3.4, and every other lane has to take the capturing path. */ @Test - void exactlyTheThreeBrokenReleasesRefuse(Server server) { + void exactlyTheTwoBrokenReleasesRefuse(Server server) { String lane = System.getProperty("libtmux.tmux.expected"); if (lane == null) { return; // not a matrix lane; the ordinary suite runs whichever tmux is on PATH } - boolean expectedToRefuse = List.of("3.3", "3.3a", "3.4").contains(lane); + boolean expectedToRefuse = List.of("3.3a", "3.4").contains(lane); assertEquals(expectedToRefuse, losesShellOutput(server), "lane " + lane + " disagrees with the version rule"); } diff --git a/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java index 8f95b0c86..381d06e07 100644 --- a/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java +++ b/integration-tests/src/test/java/io/github/libtmux/it/TmuxVersionIntegrationTest.java @@ -23,7 +23,9 @@ final class TmuxVersionIntegrationTest { void theServerReportsItsOwnVersion(Server server) { TmuxVersion version = server.version(); - assertTrue(version.atLeast(new TmuxVersion(3, 3, "a")), "this library supports 3.3a and later: " + version); + assertTrue( + version.atLeast(TmuxVersion.MINIMUM), + "this library supports " + TmuxVersion.MINIMUM + " and later: " + version); } /** diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ReadingTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ReadingTest.java index 4ab0eb70e..ca48d36c5 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ReadingTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ReadingTest.java @@ -118,10 +118,9 @@ void onlyTheLinesAddedSinceTheCursorComeBack(Server server) { * screens together. * *

The history is dropped through tmux rather than by running {@code clear} in the pane. What - * the shell's own clear does to the scrollback is not the same across the supported range — on - * 3.2a the cleared lines are still in history, so a reader that found them there and reported - * continuity was right — and this is here to pin what this server promises, not what a terminal - * happens to do with an escape sequence. + * the shell's own clear does to the scrollback is not the same across releases, and this is + * here to pin what this server promises, not what a terminal happens to do with an escape + * sequence. */ @Test void aPaneWhoseHistoryIsGoneIsReportedAsADiscontinuity(Server server) { diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ToolsAgainstTmuxTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ToolsAgainstTmuxTest.java index ca5319350..7c567cb5e 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ToolsAgainstTmuxTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/ToolsAgainstTmuxTest.java @@ -550,7 +550,6 @@ void selectLayoutAcceptsAUniquePresetPrefix(Server server) { * it, this call reached an uncaught {@code ArrayIndexOutOfBoundsException}, which the answer * dispatcher in {@code TmuxMcpServer} does not catch, so it left the tool boundary as a * transport-level failure instead of an {@code isError} result the model can read and act on. - * tmux 3.2a exits without saying anything, so there the reason names the missing directory. */ @Test void createSessionUnderAMissingSocketDirectoryReportsTmuxsOwnReason(@TempDir Path directory) throws IOException { @@ -559,14 +558,11 @@ void createSessionUnderAMissingSocketDirectoryReportsTmuxsOwnReason(@TempDir Pat .build(); try (Server broken = Server.open(missingDirectory)) { - String reported = broken.run(List.of("-V")).stdout().get(0); - boolean speaks = TmuxVersion.parse(reported.substring(reported.indexOf(' ') + 1)) - .atLeast(new TmuxVersion(3, 3, "")); LibTmuxException failure = assertThrows( LibTmuxException.class, () -> Operations.createSession(TestCalls.on(broken, "session_name", "x"))); assertTrue( - String.valueOf(failure.getMessage()).contains(speaks ? "error creating" : "does not exist"), + String.valueOf(failure.getMessage()).contains("error creating"), "the reason, not a generic message: " + failure.getMessage()); } } diff --git a/libtmux-scala/README.md b/libtmux-scala/README.md index 9fce462fb..3f76ee5ee 100644 --- a/libtmux-scala/README.md +++ b/libtmux-scala/README.md @@ -89,7 +89,7 @@ the daemon running. ## Support and status - **Scala 3.9 and JDK 25 or newer.** -- **tmux 3.2a through 3.7c**, which the Java library supports and every tmux +- **tmux 3.3a through 3.7c**, which the Java library supports and every tmux lane in CI runs these suites against. [Compatibility](../docs/guide/scala/compatibility.md) has the detail. - **Alpha.** Pin an exact version rather than a range. diff --git a/libtmux-scala/src/test/scala/io/github/libtmux/scaladsl/SnapshotSuite.scala b/libtmux-scala/src/test/scala/io/github/libtmux/scaladsl/SnapshotSuite.scala index 0c143d22b..efcfd71aa 100644 --- a/libtmux-scala/src/test/scala/io/github/libtmux/scaladsl/SnapshotSuite.scala +++ b/libtmux-scala/src/test/scala/io/github/libtmux/scaladsl/SnapshotSuite.scala @@ -66,7 +66,7 @@ final class SnapshotSuite extends FunSuite { ServerSnapshot.of( Instant.EPOCH, 123L, - new TmuxVersion(3, 2, "a"), + new TmuxVersion(3, 3, "a"), JList.of( new SessionState(session, "one", false, 2), new SessionState(other, "two", false, 1) diff --git a/libtmux-workspace-cli/README.md b/libtmux-workspace-cli/README.md index 24a3aac2f..c60f90894 100644 --- a/libtmux-workspace-cli/README.md +++ b/libtmux-workspace-cli/README.md @@ -217,7 +217,7 @@ Then the agent can: ## Support and status -- **JDK 25 or newer**, and **tmux 3.2a or newer**. +- **JDK 25 or newer**, and **tmux 3.3a or newer**. - **Linux, including WSL.** CI runs on Ubuntu. A macOS job runs the same checks and reports without gating a merge. - **Alpha.** Commands, flags and output records change without notice. Pin an diff --git a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java index c30eac01b..6558a4b81 100644 --- a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java +++ b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java @@ -490,7 +490,6 @@ void loadSizesAColdSessionFromColumnsAndLines() throws Exception { Path socket = directory.resolve("size-socket"); Files.writeString(source, "session_name: sized\nwindows:\n - window_name: a\n - window_name: b\n"); try (Server server = server(socket)) { - assumeSizesDetachedSessions(server); try { assertFalse(server.isAlive(), "the daemon must still be cold before this load"); Result result = invoke( @@ -528,7 +527,6 @@ void loadSizesASessionAt80x24WithoutColumnsOrLines() throws Exception { assertTrue(wrapper.toFile().setExecutable(true)); Files.writeString(source, "session_name: default-size\nwindows: [{}]\n"); try (Server server = server(socket)) { - assumeSizesDetachedSessions(server); try { Result result = invoke( java.util.Map.of("LIBTMUX_TEST_TMUX", wrapper.toString()), @@ -1171,9 +1169,8 @@ void layoutCorpusPreservesAnIndependentSession() throws Exception { var panes = keeper.windows().getFirst().panes().stream() .map(io.github.libtmux.Pane::id) .toList(); - String version = server.version().atLeast(io.github.libtmux.TmuxVersion.parse("3.5")) - ? "3.7c" - : server.version().atLeast(io.github.libtmux.TmuxVersion.parse("3.3")) ? "3.3a" : "3.2a"; + String version = + server.version().atLeast(io.github.libtmux.TmuxVersion.parse("3.5")) ? "3.7c" : "3.3a"; for (var item : cases) { String id = item.path("id").asText(); var workspace = mapper.createObjectNode().put("session_name", "layout-" + id); @@ -2805,14 +2802,6 @@ void ndjsonPaneAndWindowEventsCarryInputIndexSessionIdAndOrdinal() throws Except } } - /** tmux 3.2a accepts -x/-y for a detached session and ignores them, so the CLI sends no size there. */ - private static void assumeSizesDetachedSessions(Server server) { - String client = server.cmd("-V").stdout().getFirst().replace("tmux ", ""); - org.junit.jupiter.api.Assumptions.assumeTrue( - io.github.libtmux.TmuxVersion.parse(client).atLeast(io.github.libtmux.TmuxVersion.parse("3.3a")), - "detached session sizing needs tmux 3.3a"); - } - /** * Waits for a pane to run {@code command}. tmux reports a pane before its process has become the * command it was given, so a read straight after creating one can still see what is about to exec. diff --git a/libtmux-workspace-cli/src/test/resources/layout-preflight.json b/libtmux-workspace-cli/src/test/resources/layout-preflight.json index 70ae59b85..0d67e29da 100644 --- a/libtmux-workspace-cli/src/test/resources/layout-preflight.json +++ b/libtmux-workspace-cli/src/test/resources/layout-preflight.json @@ -4,7 +4,6 @@ "layout": "even-horizontal", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -14,7 +13,6 @@ "layout": "even-vertical", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -24,7 +22,6 @@ "layout": "main-horizontal", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -34,7 +31,6 @@ "layout": "main-vertical", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -44,7 +40,6 @@ "layout": "tiled", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -54,7 +49,6 @@ "layout": "main-horizontal-mirrored", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": true } @@ -64,7 +58,6 @@ "layout": "main-vertical-mirrored", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": true } @@ -74,7 +67,6 @@ "layout": "t", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -84,7 +76,6 @@ "layout": "even-h", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -94,7 +85,6 @@ "layout": "main-h", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": false } @@ -104,7 +94,6 @@ "layout": "invalid-layout", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -114,7 +103,6 @@ "layout": "f", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -124,7 +112,6 @@ "layout": "dead,", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -134,7 +121,6 @@ "layout": "not-a-layout", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -144,7 +130,6 @@ "layout": "b25d,80x24,0,0,0", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -154,7 +139,6 @@ "layout": "c85e,80x24,0,0", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -164,7 +148,6 @@ "layout": "89f5,80x24,0,0{39x24,0,0,0,40x24,40,0,1}", "pane_count": 2, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -174,7 +157,6 @@ "layout": "203f,80x24,0,0{39x24,0,0,40x24,40,0}", "pane_count": 2, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -184,7 +166,6 @@ "layout": "ac5c,80x24,0,0{39x24,0,0,0,40x24,40,0[40x11,40,0,1,40x12,40,12,2]}", "pane_count": 3, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -195,7 +176,6 @@ "layout": "8a08,1x1,0,0{39x24,0,0,0,40x24,40,0,1}", "pane_count": 2, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -205,7 +185,6 @@ "layout": "12f1,80x24,0,0{39x24,0,0,0,40x24,40,0,1", "pane_count": 2, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -215,7 +194,6 @@ "layout": "89d5,80x24,0,0{39x24,0,0,0,40x24,40,0,1]", "pane_count": 2, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -225,7 +203,6 @@ "layout": "32d2,80x24,0,0{}", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -235,7 +212,6 @@ "layout": "c4c8,garbage", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -245,7 +221,6 @@ "layout": "b25d,80x24,0,0,0", "pane_count": 2, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -255,7 +230,6 @@ "layout": "79f5,80x24,0,0{39x23,0,0,0,40x24,40,0,1}", "pane_count": 2, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -265,7 +239,6 @@ "layout": "ffff,80x24,0,0,0", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -275,7 +248,6 @@ "layout": "e", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -285,7 +257,6 @@ "layout": "main-", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -295,7 +266,6 @@ "layout": "main-v", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": false } @@ -305,7 +275,6 @@ "layout": "even-vertical-extra", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -315,7 +284,6 @@ "layout": "ac5c,80x24,0,0{39x24,0,0,0,40x24,40,0[40x11,40,0,1,40x12,40,12,2]}", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -325,7 +293,6 @@ "layout": "ac5c,80x24,0,0{39x24,0,0,0,40x24,40,0[40x11,40,0,1,40x12,40,12,2]}", "pane_count": 2, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } @@ -335,7 +302,6 @@ "layout": "ae7c,80x24,0,0{39x24,0,0,0,40x24,40,0[39x11,40,0,1,40x12,40,12,2]}", "pane_count": 3, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -345,7 +311,6 @@ "layout": "b33f,80x24,0,0{39x24,0,0,0,1x1,40,0[40x11,40,0,1,40x12,40,12,2]}", "pane_count": 3, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -355,7 +320,6 @@ "layout": "5bf2,80x24,0,0,0junk", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -365,7 +329,6 @@ "layout": "925f,80x24,0;0,0", "pane_count": 1, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -375,7 +338,6 @@ "layout": "4a17,80x24,0,0{39x24,0,0,0,40x24,40,0[]}", "pane_count": 2, "expected_valid": { - "3.2a": false, "3.3a": false, "3.7c": false } @@ -386,7 +348,6 @@ "layout": "79f5,80x24,0,0{39x23,0,0,0,40x24,40,0,1}", "pane_count": 1, "expected_valid": { - "3.2a": true, "3.3a": true, "3.7c": true } diff --git a/libtmux/README.md b/libtmux/README.md index 32ea7dcc5..1a71866c8 100644 --- a/libtmux/README.md +++ b/libtmux/README.md @@ -36,7 +36,7 @@ API reference: [libtmux.org][reference] for the current trunk, ## Quick start -Add it through the BOM. It needs JDK 25 and a tmux between 3.2a and 3.7c: +Add it through the BOM. It needs JDK 25 and a tmux between 3.3a and 3.7c: ```kotlin @@ -441,7 +441,7 @@ rename.getAnnotation(io.github.libtmux.catalog.Operation.class).value(); // ## Support and status -- **JDK 25 or newer**, and **tmux 3.2a through 3.7c**. +- **JDK 25 or newer**, and **tmux 3.3a through 3.7c**. - **Linux, including WSL.** CI runs on Ubuntu. A macOS job runs the same checks and reports without gating a merge. - **Alpha.** The API will change without notice. diff --git a/libtmux/src/main/java/io/github/libtmux/Environment.java b/libtmux/src/main/java/io/github/libtmux/Environment.java index e66a5c55f..3e1b8075e 100644 --- a/libtmux/src/main/java/io/github/libtmux/Environment.java +++ b/libtmux/src/main/java/io/github/libtmux/Environment.java @@ -47,9 +47,9 @@ public final class Environment { * from one read of a value tmux had stored perfectly. * *

{@code -s} is unambiguous instead: a value is quoted and its {@code $}, backtick, {@code "} - * and {@code \} are escaped, so the only unescaped quote is the one that ends it. Present since - * 3.2a, where the flag and the two format strings are already what they are now. One release - * writes it differently — see {@link #repaired}. + * and {@code \} are escaped, so the only unescaped quote is the one that ends it. Present on + * every supported release, with the flag and the two format strings the same throughout. One + * release writes it differently — see {@link #repaired}. */ private static final List SHELL_FORM = List.of("-s"); diff --git a/libtmux/src/main/java/io/github/libtmux/Layout.java b/libtmux/src/main/java/io/github/libtmux/Layout.java index ccbde4667..86bc8550a 100644 --- a/libtmux/src/main/java/io/github/libtmux/Layout.java +++ b/libtmux/src/main/java/io/github/libtmux/Layout.java @@ -55,22 +55,27 @@ public static Optional byTmuxName(String name) { } /** - * The first supported release that has this layout. + * The first supported release that has this layout, or empty when every supported release does. * *

A switch rather than a field, so that adding a layout cannot compile until someone says * which release brought it — and so the enum holds nothing but its name. */ - TmuxVersion since() { + Optional since() { return switch (this) { - case MAIN_HORIZONTAL_MIRRORED, MAIN_VERTICAL_MIRRORED -> new TmuxVersion(3, 5, ""); - case EVEN_HORIZONTAL, EVEN_VERTICAL, MAIN_HORIZONTAL, MAIN_VERTICAL, TILED -> new TmuxVersion(3, 2, "a"); + case MAIN_HORIZONTAL_MIRRORED, MAIN_VERTICAL_MIRRORED -> Optional.of(new TmuxVersion(3, 5, "")); + case EVEN_HORIZONTAL, EVEN_VERTICAL, MAIN_HORIZONTAL, MAIN_VERTICAL, TILED -> Optional.empty(); }; } + /** Whether the running tmux has this layout. */ + boolean availableOn(TmuxVersion running) { + return since().map(running::atLeast).orElse(true); + } + /** Refuses this layout when the running tmux predates it. */ public void requireSupported(TmuxVersion running) { - if (!running.atLeast(since())) { - throw new UnsupportedFeatureException("the " + this + " layout", since(), running); + if (!availableOn(running)) { + throw new UnsupportedFeatureException("the " + this + " layout", since().orElseThrow(), running); } } diff --git a/libtmux/src/main/java/io/github/libtmux/Layouts.java b/libtmux/src/main/java/io/github/libtmux/Layouts.java index 55c12b36f..448c7f20d 100644 --- a/libtmux/src/main/java/io/github/libtmux/Layouts.java +++ b/libtmux/src/main/java/io/github/libtmux/Layouts.java @@ -168,7 +168,7 @@ public static String require(String layout, Server server, int paneCount) { } private static Optional named(String value, TmuxVersion version) { - return builtIn(value, version).filter(layout -> version.atLeast(layout.since())); + return builtIn(value, version).filter(layout -> layout.availableOn(version)); } /** @@ -182,8 +182,10 @@ private static IllegalArgumentException ambiguous(String layout, TmuxVersion run return new IllegalArgumentException("not a unique layout for tmux " + running + ": " + layout); } String names = matches.stream().map(Layout::tmuxName).collect(java.util.stream.Collectors.joining(", ")); - TmuxVersion introduced = - matches.stream().map(Layout::since).max(TmuxVersion::compareTo).orElseThrow(); + TmuxVersion introduced = matches.stream() + .map(candidate -> candidate.since().orElse(TmuxVersion.MINIMUM)) + .max(TmuxVersion::compareTo) + .orElseThrow(); return new IllegalArgumentException("not a unique layout for tmux " + running + ": " + layout + "; matches " + names + ", ambiguous since tmux " + introduced + "; use the full name"); } @@ -198,7 +200,7 @@ private static TmuxVersion version(Server server) { private static List supported(TmuxVersion running) { return Arrays.stream(Layout.values()) - .filter(candidate -> running.atLeast(candidate.since())) + .filter(candidate -> candidate.availableOn(running)) .toList(); } diff --git a/libtmux/src/main/java/io/github/libtmux/OptionKey.java b/libtmux/src/main/java/io/github/libtmux/OptionKey.java index 09c87f3ac..e2e9f108b 100644 --- a/libtmux/src/main/java/io/github/libtmux/OptionKey.java +++ b/libtmux/src/main/java/io/github/libtmux/OptionKey.java @@ -20,7 +20,7 @@ * on {@link Options} remain for anything else. * *

The constants are the ones whose type is the same on every supported release, checked against - * tmux's own option table from 3.2a to 3.7c. {@code status} is not among the flags for that reason: + * tmux's own option table across the supported range. {@code status} is not among the flags for that reason: * tmux reads it as a choice of {@code off}, {@code on} and a line count. * * @param what the value reads as diff --git a/libtmux/src/main/java/io/github/libtmux/Options.java b/libtmux/src/main/java/io/github/libtmux/Options.java index b1cc0e67f..4738ace2c 100644 --- a/libtmux/src/main/java/io/github/libtmux/Options.java +++ b/libtmux/src/main/java/io/github/libtmux/Options.java @@ -145,7 +145,7 @@ public Map all() { * *

A listed value is escaped with {@code vis(3)} and wrapped in whichever quotes that release * chose, and which characters it reaches changed inside the supported range — {@code a$b} prints - * as {@code "a\$b"} on 3.2a and {@code "a\\$b"} on 3.4. {@code -v} output is ambiguous on 3.4 + * as {@code "a\$b"} on 3.3a and {@code "a\\$b"} on 3.4. {@code -v} output is ambiguous on 3.4 * and 3.5, which print a carriage return and a literal {@code \r} alike, so those two read the * listing's quoted spelling; elsewhere {@code -v} prints the value itself, as {@link #get} reads it. */ diff --git a/libtmux/src/main/java/io/github/libtmux/PaneCommand.java b/libtmux/src/main/java/io/github/libtmux/PaneCommand.java index 9d08cb45c..28ec662a2 100644 --- a/libtmux/src/main/java/io/github/libtmux/PaneCommand.java +++ b/libtmux/src/main/java/io/github/libtmux/PaneCommand.java @@ -154,10 +154,10 @@ private String ahead(String line) { * The framed rows, with anything the marker shared its row with kept as the last of them. * *

Each row loses its trailing space, because tmux's does not mean anything. Asking it to - * rejoin wrapped rows also asks it to keep trailing spaces, and it pads the rows out to do so on - * 3.2a though not on 3.7c — measured — so one command answered {@code built} on one release and - * {@code built} followed by fifteen spaces on another. {@code -T} would ask tmux for less but - * arrived in 3.4, below the range this supports. A terminal cannot tell a printed trailing space + * rejoin wrapped rows also asks it to keep trailing spaces, and a release can pad the rows out + * to do so, as 3.7c does not — measured — so one command could answer {@code built} on one + * release and {@code built} followed by fifteen spaces on another. {@code -T} would ask tmux for + * less but arrived in 3.4, so the oldest supported release lacks it. A terminal cannot tell a printed trailing space * from a cell nothing ever wrote, so dropping both is the only answer that is the same * everywhere. */ diff --git a/libtmux/src/main/java/io/github/libtmux/SessionCreation.java b/libtmux/src/main/java/io/github/libtmux/SessionCreation.java index b6074b1a5..4958fff2b 100644 --- a/libtmux/src/main/java/io/github/libtmux/SessionCreation.java +++ b/libtmux/src/main/java/io/github/libtmux/SessionCreation.java @@ -17,9 +17,9 @@ private SessionCreation() {} * binary when it did not, rather than letting an empty list reach {@code SessionId} and throw * an unchecked collection exception with neither in it. * - *

tmux 3.2a says nothing at all when it cannot create its socket: the server queues the - * error and tells the client to exit at once, and the client leaves before the error reaches - * it. So a silent exit checks the one cause this client can see for itself. + *

A tmux that exits without a word when it cannot create its socket, because the server queues + * the error and tells the client to exit before the error reaches it, leaves nothing to read. So + * a silent exit checks the one cause this client can see for itself. */ static String failureMessage(ServerConfig config, CommandResult result) { if (!result.stderr().isEmpty()) { diff --git a/libtmux/src/main/java/io/github/libtmux/SessionLookup.java b/libtmux/src/main/java/io/github/libtmux/SessionLookup.java index cdbf49cfe..db5f87040 100644 --- a/libtmux/src/main/java/io/github/libtmux/SessionLookup.java +++ b/libtmux/src/main/java/io/github/libtmux/SessionLookup.java @@ -20,7 +20,8 @@ static Optional named(Server server, String name) { RowFormat format = RowFormat.of("session_id", "session_name", "version"); CommandResult result = server.cmd("list-sessions", "-F", format.template()); if (!result.succeeded()) { - // A live server with no sessions lists nothing and exits 0, on 3.2a and 3.7d alike, so + // A live server with no sessions lists nothing and exits 0 on every supported + // release, so // any failure is not an answer: no daemon, a socket this user cannot open, or a binary // that is not tmux. failed() tells the first apart. throw server.failed("list-sessions", result); diff --git a/libtmux/src/main/java/io/github/libtmux/Shell.java b/libtmux/src/main/java/io/github/libtmux/Shell.java index 93a5f30ad..0e6f560bb 100644 --- a/libtmux/src/main/java/io/github/libtmux/Shell.java +++ b/libtmux/src/main/java/io/github/libtmux/Shell.java @@ -16,11 +16,10 @@ public final class Shell { /** - * tmux lost {@code run-shell}'s output in 3.3a and found it again in 3.5. The command exists on - * every release, so {@code list-commands} cannot tell the gap from the working releases. + * tmux lost {@code run-shell}'s output in 3.3a and found it again in 3.5; 3.3a is the first + * supported release, so everything below this one is the gap. The command exists on every + * release, so {@code list-commands} cannot tell the gap from the working releases. */ - private static final TmuxVersion OUTPUT_LOST = new TmuxVersion(3, 3, ""); - private static final TmuxVersion OUTPUT_FOUND = new TmuxVersion(3, 5, ""); private final Server server; @@ -47,7 +46,7 @@ public void run(String command) { public List capturing(String command) { Objects.requireNonNull(command, "command"); TmuxVersion running = server.version(); - if (running.atLeast(OUTPUT_LOST) && !running.atLeast(OUTPUT_FOUND)) { + if (!running.atLeast(OUTPUT_FOUND)) { throw new UnsupportedFeatureException( "reading what run-shell printed is broken between tmux 3.3a and 3.4, and this server runs " + running); diff --git a/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java b/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java index 1a5d1d76b..7ecfcb932 100644 --- a/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java +++ b/libtmux/src/main/java/io/github/libtmux/SnapshotCapture.java @@ -314,7 +314,7 @@ private Optional kept( * *

The filter loops each listed row's own session, so every listing keeps whole sessions, and a * window linked into two sessions brings both. tmux evaluates this loop inside {@code -f} the same - * way on every release from 3.2a; see {@code docs/decisions/0005-pushdown-lowering-is-exact-or-refused.md}. + * way on every supported release; see {@code docs/decisions/0005-pushdown-lowering-is-exact-or-refused.md}. */ Optional sessionsHolding(PaneId id) { ServerProcess process = process() diff --git a/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java b/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java index a7aa716d8..0282e3035 100644 --- a/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java +++ b/libtmux/src/main/java/io/github/libtmux/TmuxFormats.java @@ -76,8 +76,8 @@ static java.util.List printed(java.util.List lines, TmuxVersion * The names to look for when a caller names a session: the one this release stores for it, then * the name exactly as given, which is what {@link Session#name} already reports. * - *

tmux doubles each backslash; 3.2a through 3.4 put a backslash before a {@code $} that a - * letter, {@code _}, or { follows; 3.2a through 3.6 store {@code .} and {@code :} + *

tmux doubles each backslash; 3.3a through 3.4 put a backslash before a {@code $} that a + * letter, {@code _}, or { follows; 3.3a through 3.6 store {@code .} and {@code :} * as {@code _} and a control character as a C escape or three octal digits. 3.7 and later refuse * control characters. The stored form is an escaping, so it names at most one session; only a * name that is itself some other name's stored form is ambiguous, and the stored form wins. diff --git a/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java b/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java index 29e7ebfcf..50d634874 100644 --- a/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java +++ b/libtmux/src/main/java/io/github/libtmux/TmuxVersion.java @@ -8,7 +8,7 @@ * A released tmux version, ordered the way tmux releases are. * *

tmux numbers a patch release with a letter rather than a third number: 3.7a follows 3.7, and - * 3.2a follows 3.2. Comparing the text alone gets that wrong in both directions, and a feature gate + * 3.3a follows 3.3. Comparing the text alone gets that wrong in both directions, and a feature gate * that gets it wrong reads a format the running tmux does not have. * *

Between releases, tmux reports itself as {@code next-M.m}: a development build tracking toward diff --git a/libtmux/src/main/java/io/github/libtmux/Window.java b/libtmux/src/main/java/io/github/libtmux/Window.java index 29b2bed7e..204e8ca35 100644 --- a/libtmux/src/main/java/io/github/libtmux/Window.java +++ b/libtmux/src/main/java/io/github/libtmux/Window.java @@ -349,7 +349,7 @@ public void previousLayout() { * pane's id. A classic string — every release before 3.8, or an older reader against a newer * server — restores the geometry but names no pane, so which process lands in which cell can * differ from where it started; whether it does depends on whether the pane list still happens - * to match the order the layout was saved in. Confirmed by hand on 3.2a and 3.7c. + * to match the order the layout was saved in. Confirmed by hand on 3.7c. * * @throws IllegalArgumentException if the string is not a layout tmux wrote * @throws UnsupportedFeatureException if it is JSON-shaped but this server predates JSON diff --git a/libtmux/src/main/java/io/github/libtmux/WindowSpec.java b/libtmux/src/main/java/io/github/libtmux/WindowSpec.java index cf54c0c04..7e435f7ff 100644 --- a/libtmux/src/main/java/io/github/libtmux/WindowSpec.java +++ b/libtmux/src/main/java/io/github/libtmux/WindowSpec.java @@ -207,7 +207,7 @@ public Builder running(String... argv) { /** * Starts the window in this directory. A relative one resolves against this process's working - * directory, on every release: tmux 3.2a would resolve it against the server's. + * directory, on every release. */ public Builder in(Path directory) { this.directory = Objects.requireNonNull(directory, "directory"); diff --git a/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java b/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java index 1569da9f6..0213fafec 100644 --- a/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java +++ b/libtmux/src/main/java/io/github/libtmux/control/ControlClient.java @@ -298,7 +298,7 @@ private void finishAttach(SessionId session) { *

Without this, this client's own {@code %layout-change} carries the classic string even on a * server new enough to write JSON elsewhere: the same window's layout then disagrees depending * on which kind of client read it — the mismatch a {@code watch}ed layout format or a parsed - * notification would otherwise hit. Measured harmless back to 3.2a: {@code refresh-client -f + * notification would otherwise hit. Measured harmless on every supported release: {@code refresh-client -f * new-layouts} completes with no error on every supported release, just with nothing to change * before 3.8, so this is sent unconditionally rather than gated on a version. */ @@ -465,7 +465,7 @@ public ControlReply watch(String name, String target, String format) { /** * tmux(1) documents {@code refresh-client -B name:what:format}'s {@code what} as empty, * {@code %N}, {@code %*}, {@code @N} or {@code @*} only - a session id or an arbitrary word was - * never a spelling the manual promises, even though 3.2a and 3.7c happen to accept one leniently + * never a spelling the manual promises, even though some releases happen to accept one leniently * (confirmed against the matrix). On master the same non-empty, non-{@code %}/{@code @} target * is accepted by the parser but delivers nothing, silently. The empty string is the one * spelling confirmed to mean "the attached session" and to actually fire on every tested release, diff --git a/libtmux/src/main/java/io/github/libtmux/control/Notification.java b/libtmux/src/main/java/io/github/libtmux/control/Notification.java index 18b50a4cd..69061bfb9 100644 --- a/libtmux/src/main/java/io/github/libtmux/control/Notification.java +++ b/libtmux/src/main/java/io/github/libtmux/control/Notification.java @@ -85,11 +85,11 @@ record SubscriptionChanged( /** * {@code %pause}: tmux stopped sending a pane's output to this client, which turned flow control * on with {@code refresh-client -f pause-after=N} and fell N seconds behind. {@code refresh-client - * -A '%1:continue'} resumes it. tmux 3.2 and later. + * -A '%1:continue'} resumes it. */ record Pause(PaneId pane) implements Notification {} - /** {@code %continue}: a paused pane's output flows to this client again. tmux 3.2 and later. */ + /** {@code %continue}: a paused pane's output flows to this client again. */ record Continue(PaneId pane) implements Notification {} /** {@code %message}: text another client showed on this one with {@code display-message -c}. tmux 3.4 and later. */ diff --git a/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java b/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java index add164723..472520466 100644 --- a/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java +++ b/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java @@ -13,12 +13,12 @@ /** * What the window and session specs lower to, and what they refuse. * - *

Neither command changed its flags between 3.2a and 3.7b, so the version rules here are about + *

Neither command changed its flags between 3.3a and 3.7b, so the version rules here are about * behaviour tmux accepts and then ignores rather than about flags it rejects. */ final class CreationSpecTest { - private static final TmuxVersion V32A = new TmuxVersion(3, 2, "a"); + private static final TmuxVersion V33A = TmuxVersion.MINIMUM; private static final TmuxVersion V37B = new TmuxVersion(3, 7, "b"); private static final String FORMAT = "#{window_id}"; @@ -93,9 +93,9 @@ void aWindowStartDirectoryIsPassedToTmux() { } /** - * tmux 3.2a resolves a relative {@code -c} against the server's working directory, falling back - * to home; 3.3 onwards resolves it against the requesting client's, which is this process's. - * Every spawn site sends it resolved, so every release starts the process where 3.3 does. + * tmux resolves a relative {@code -c} against the requesting client's working directory, which + * is this process's. Every spawn site sends it resolved anyway, so the start directory never + * depends on where the server was started. */ @Test void aRelativeDirectoryReachesTmuxResolvedAgainstThisProcess() { @@ -104,7 +104,7 @@ void aRelativeDirectoryReachesTmuxResolvedAgainstThisProcess() { List session = SessionSpec.builder().in(relative).build().argv(FORMAT); List window = WindowSpec.builder().in(relative).build().argv("$1", FORMAT); - List split = SplitSpec.builder().in(relative).build().argv("%1", FORMAT, V32A); + List split = SplitSpec.builder().in(relative).build().argv("%1", FORMAT, V33A); List respawn = Pane.respawnArgv(new PaneId("%1"), relative); assertEquals(resolved, session.get(session.indexOf("-c") + 1)); diff --git a/libtmux/src/test/java/io/github/libtmux/PaneCommandTest.java b/libtmux/src/test/java/io/github/libtmux/PaneCommandTest.java index 8177dfa30..079c68956 100644 --- a/libtmux/src/test/java/io/github/libtmux/PaneCommandTest.java +++ b/libtmux/src/test/java/io/github/libtmux/PaneCommandTest.java @@ -115,9 +115,9 @@ void theTrapIsArmedForInterruptAndTerminateAsWellAsExit() { } /** - * Asking tmux to rejoin wrapped rows also asks it to keep trailing spaces, and it pads the rows - * out to the pane to do so on 3.2a where 3.7c does not. The same command answered {@code built} - * on one and {@code built} followed by fifteen spaces on the other. + * Asking tmux to rejoin wrapped rows also asks it to keep trailing spaces, and a release can pad + * the rows out to the pane to do so, as 3.7c does not. The same command could answer {@code + * built} on one and {@code built} followed by fifteen spaces on the other. */ @Test void tmuxsOwnPaddingIsNotTheCommandsOutput() { diff --git a/libtmux/src/test/java/io/github/libtmux/ServerTest.java b/libtmux/src/test/java/io/github/libtmux/ServerTest.java index 9d2a70a29..5063fdb04 100644 --- a/libtmux/src/test/java/io/github/libtmux/ServerTest.java +++ b/libtmux/src/test/java/io/github/libtmux/ServerTest.java @@ -817,7 +817,7 @@ void promptHistoryUsesTheSupportedCommandDirectly(@TempDir Path directory) throw /** * An older client cannot talk to a newer daemon at all - confirmed against the matrix, - * a 3.2a client against a 3.7c daemon on the same socket fails outright, {@code server exited + * a 3.3a client against a 3.7c daemon on the same socket fails outright, {@code server exited * unexpectedly}. tmux reports that identically to "no daemon ever started here", so {@code * versionForCreation} used to read it as "no daemon" and quietly substitute the configured * binary's own {@code -V}, inverting {@link Server#version()}'s documented guarantee that it @@ -989,9 +989,10 @@ void aSocketUnderAMissingDirectoryFailsSessionCreationWithTmuxsOwnReason(@TempDi } /** - * tmux 3.2a exits 0 and prints nothing when it cannot create its socket: the server queues the - * error and tells the client to exit in the same breath, and the client leaves first. 3.3 and - * later print {@code error creating ...}. So when tmux says nothing, the directory is checked here. + * A tmux that exits 0 and prints nothing when it cannot create its socket, because the server + * queues the error and tells the client to exit in the same breath, leaves nothing to read. tmux + * prints {@code error creating ...} itself, so only when it says nothing is the directory checked + * here. */ @Test void aSilentExitUnderAMissingSocketDirectoryNamesTheDirectory(@TempDir Path directory) { diff --git a/libtmux/src/test/java/io/github/libtmux/SplitSpecTest.java b/libtmux/src/test/java/io/github/libtmux/SplitSpecTest.java index b2bcc6f2c..f4eaded9b 100644 --- a/libtmux/src/test/java/io/github/libtmux/SplitSpecTest.java +++ b/libtmux/src/test/java/io/github/libtmux/SplitSpecTest.java @@ -190,9 +190,9 @@ void theThreeSevenOptionsAreRefusedBeforeTmuxIsAsked() { void theRefusalNamesTheFeatureAndBothVersions() { UnsupportedFeatureException refused = assertThrows( UnsupportedFeatureException.class, - () -> argv(SplitSpec.builder().empty().build(), new TmuxVersion(3, 2, "a"))); + () -> argv(SplitSpec.builder().empty().build(), new TmuxVersion(3, 3, "a"))); - assertEquals("an empty pane requires tmux 3.7, but this server runs 3.2a", refused.getMessage()); + assertEquals("an empty pane requires tmux 3.7, but this server runs 3.3a", refused.getMessage()); } @Test @@ -205,7 +205,7 @@ void aSpecUsingNoThreeSevenOptionsIsFineOnEveryRelease() { .zoomed() .build(); - assertFalse(argv(everyday, new TmuxVersion(3, 2, "a")).isEmpty()); + assertFalse(argv(everyday, TmuxVersion.MINIMUM).isEmpty()); } /** A 3.7 patch release still has what 3.7 had; letter ordering is the trap this guards. */ diff --git a/libtmux/src/test/java/io/github/libtmux/TmuxEnvironmentTest.java b/libtmux/src/test/java/io/github/libtmux/TmuxEnvironmentTest.java index a73ddb47d..5d6b8900f 100644 --- a/libtmux/src/test/java/io/github/libtmux/TmuxEnvironmentTest.java +++ b/libtmux/src/test/java/io/github/libtmux/TmuxEnvironmentTest.java @@ -18,7 +18,7 @@ */ final class TmuxEnvironmentTest { - /** The exact shape observed on every lane from 3.2a to 3.7b. */ + /** The exact shape observed on every lane from 3.3a to 3.7b. */ private static Map inside() { return Map.of("TMUX", "/tmp/libtmux-abc/s,2821815,0", "TMUX_PANE", "%0"); } diff --git a/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java b/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java index 94d2258fb..b83aa8ea0 100644 --- a/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java +++ b/libtmux/src/test/java/io/github/libtmux/control/ControlClientTest.java @@ -118,8 +118,8 @@ void everyArgumentReachesTmuxExactlyAsGiven() { /** * tmux(1) documents {@code refresh-client -B name:what:format}'s {@code what} as empty, * {@code %N}, {@code %*}, {@code @N} or {@code @*} only. A session id ({@code $0}) or an - * arbitrary word were never spellings the manual promises for "the attached session" - 3.2a and - * 3.7c happen to accept them leniently, but on master the identical call is accepted and fires + * arbitrary word were never spellings the manual promises for "the attached session" - some releases + * happen to accept them leniently, but on master the identical call is accepted and fires * nothing, silently. {@code watch} now sends the one spelling confirmed to work everywhere - * {@code ""} - for anything that does not itself name a pane or window. */ diff --git a/libtmux/src/test/java/io/github/libtmux/exception/ExceptionTreeTest.java b/libtmux/src/test/java/io/github/libtmux/exception/ExceptionTreeTest.java index 7e654675e..cd1a986ae 100644 --- a/libtmux/src/test/java/io/github/libtmux/exception/ExceptionTreeTest.java +++ b/libtmux/src/test/java/io/github/libtmux/exception/ExceptionTreeTest.java @@ -36,7 +36,7 @@ private static String nextStep(LibTmuxException failure) { @Test void everyFailureNamesWhatTheCallerDoesNext() { - TmuxVersion old = TmuxVersion.parse("3.2a"); + TmuxVersion old = TmuxVersion.parse("3.3a"); assertAll( () -> assertEquals("re-resolve gone", nextStep(new TargetGoneException("gone"))), () -> assertEquals("start a server down", nextStep(new ServerUnavailableException("down"))), From 87a19fa548f39467f7974867d29dd9333fba3624 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Oct 2026 22:08:31 -0500 Subject: [PATCH 05/11] TypingTest(fix): Join wrapped capture rows why: A delivered marker can cross terminal rows and disappear from a literal substring assertion even though both intended panes received it. what: - Join soft-wrapped rows in the shared typing capture helper - Reproduce wrapping with ready cat consumers at a fixed pane width - Retain effective-recipient and disabled-pane assertions --- .../io/github/libtmux/mcp/TypingTest.java | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java index b09d8330f..4263a3763 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java @@ -627,6 +627,45 @@ void synchronizedKeysReachOnlyTheEffectiveOnCohort(Server server) throws Excepti assertFalse(captureOf(server, disabled.id().value()).contains(marker)); } + @Test + void typingCapturePreservesSoftWrappedInput(Server server) throws Exception { + Pane source = server.panes().getFirst(); + source.window().resizeTo(new io.github.libtmux.Dimensions(80, 24)); + String prefix = "P".repeat(70); + String consumer = "stty raw -echo; printf '%s' '" + prefix + "'; exec cat"; + source.respawn("sh", "-c", consumer); + Pane peer = + source.split(SplitSpec.builder().running("sh", "-c", consumer).build()); + Pane disabled = + source.split(SplitSpec.builder().running("sh", "-c", consumer).build()); + assertTrue(await(() -> List.of(source, peer, disabled).stream() + .allMatch(pane -> "cat".equals(pane.expand("#{pane_current_command}")) + && captureOf(server, pane.id().value()).contains(prefix)))); + source.window().synchronizePanes(); + disabled.options().set("synchronize-panes", "off"); + String marker = "batch-first-marker"; + + Typing.Sent sent = sendKeys(server, source.id().value(), marker); + + assertEquals(sorted(source.id().value(), peer.id().value()), sent.resolvedPaneIds()); + assertTrue( + await(() -> String.join( + "\n", + server.cmd( + "capture-pane", + "-p", + "-t", + source.id().value()) + .stdout()) + .contains("batch-firs\nt-marker")), + "the fixture did not soft-wrap its input"); + assertTrue( + captureOf(server, source.id().value()).contains(marker), + captureOf(server, source.id().value())); + assertTrue(await(() -> captureOf(server, peer.id().value()).contains(marker))); + assertFalse(captureOf(server, disabled.id().value()).contains(marker)); + } + @Test void sourceOffIgnoresATruePeer(Server server) throws Exception { var source = server.panes().getFirst(); @@ -983,7 +1022,8 @@ public void close() {} } static String captureOf(Server server, String pane) { - return String.join("\n", server.cmd("capture-pane", "-p", "-t", pane).stdout()); + return String.join( + "\n", server.cmd("capture-pane", "-p", "-J", "-t", pane).stdout()); } private static List sorted(String... paneIds) { From bca503f63173e3b5b2a57fd276059b2c982e0a59 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Oct 2026 22:08:31 -0500 Subject: [PATCH 06/11] tmux(docs): Record soft-wrapped capture rows why: Delivered-text assertions depend on native behaviour that differs from a simple substring assumption. what: - Record soft-wrap capture results on native 3.3a and 3.7c. --- docs/internals/tmux-behaviour.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/internals/tmux-behaviour.md b/docs/internals/tmux-behaviour.md index 62e98a35e..b6ba6f826 100644 --- a/docs/internals/tmux-behaviour.md +++ b/docs/internals/tmux-behaviour.md @@ -14,6 +14,14 @@ Copies and symlinks named `opaque client` and `opaque-link` report accepts that exact basename or the canonical `tmux` prefix, followed by a complete version token. Daemon reports remain bare version tokens. +## Soft-wrapped capture rows + +On tmux 3.3a and 3.7c, `capture-pane -p` inserts a newline where terminal +output wraps across screen rows. With a 70-character prefix in an 80-column +pane, `batch-first-marker` is captured as `batch-firs\nt-marker` despite +arriving intact. `capture-pane -p -J` joins those rows without joining hard +line breaks; typing tests use it when checking delivered text. + ## break-pane on tmux 3.7 `break-pane` gets automatic window naming wrong in two opposite, silent ways, From 9a7cca788baaef2b93c1ff7b219b9bbed02f055a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Oct 2026 22:42:36 -0500 Subject: [PATCH 07/11] MCP(test): Synchronize pane output fixtures why: A wrapped completion marker or output arriving before a wait's entry capture made JDK checks fail for unrelated fixture timing. what: - Join soft-wrapped completion capture and force that regression. - Release shell output after the real wait entry capture. - Preserve cold and warm echo assertions without a fixed delay. --- .../libtmux/mcp/RunningCommandsTest.java | 16 ++- .../libtmux/mcp/WaitingForTextTest.java | 115 +++++++++--------- 2 files changed, 74 insertions(+), 57 deletions(-) diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java index 259654a25..937e533e6 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java @@ -590,6 +590,19 @@ void completionSignalWithoutAValidStatusRetainsThePane( } } + @Test + void completionCaptureKeepsSoftWrappedMarkers(Server server) throws Exception { + Pane pane = server.panes().getFirst(); + pane.window().resizeTo(new io.github.libtmux.Dimensions(20, 24)); + String marker = "lt" + "a".repeat(32) + "-e:00"; + pane.respawn("sh", "-c", "printf '%s' " + Shell.quote(marker) + "; exec cat"); + assertTrue(await(() -> "cat".equals(pane.expand("#{pane_current_command}")))); + + assertTrue( + capture(server, pane.id().value()).contains(marker), + capture(server, pane.id().value())); + } + @Test void cancelledRunRetainsItsPaneUntilTheStatusMarker(Server server, @TempDir Path temporary) throws Exception { String pane = server.panes().getFirst().id().value(); @@ -978,7 +991,8 @@ private static Optional nonce(CommandRequest request) { } private static String capture(Server server, String paneId) { - return String.join("\n", server.cmd("capture-pane", "-p", "-t", paneId).stdout()); + return String.join( + "\n", server.cmd("capture-pane", "-p", "-J", "-t", paneId).stdout()); } private static void assertRefusedBeforeRunWork( diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java index ef08d66d7..b66010cdc 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java @@ -8,7 +8,12 @@ import io.github.libtmux.Pane; import io.github.libtmux.Server; import io.github.libtmux.junit5.TmuxExtension; +import io.github.libtmux.transport.CommandRequest; +import io.github.libtmux.transport.CommandResult; +import io.github.libtmux.transport.ProcessTransport; +import io.github.libtmux.transport.TmuxTransport; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,10 +44,8 @@ void outputTheTerminalWrappedForDisplayIsStillMatched(Server server) { // prompt happens to leave the cursor. String marker = "wrapped-" + "x".repeat(120) + "-marker"; - send(server, pane, "(sleep 1; echo " + marker + ") &"); - - WaitingForText.Waited waited = WaitingForText.waitFor( - TestCalls.on(server, "pane_id", pane, "patterns", List.of(marker), "timeout", 20)); + WaitingForText.Waited waited = + afterEntry(server, pane, "echo " + marker, "patterns", List.of(marker), "timeout", 20); assertEquals("MATCHED", waited.outcome(), "output the wait saw: " + waited.output()); } @@ -50,10 +53,8 @@ void outputTheTerminalWrappedForDisplayIsStillMatched(Server server) { @Test void textThatArrivesIsMatchedAndTheWaitEndsAtOnce(Server server) { String pane = server.panes().get(0).id().value(); - send(server, pane, "(sleep 1; echo the-server-is-ready) &"); - - WaitingForText.Waited waited = WaitingForText.waitFor( - TestCalls.on(server, "pane_id", pane, "patterns", List.of("the-server-is-ready"), "timeout", 20)); + WaitingForText.Waited waited = afterEntry( + server, pane, "echo the-server-is-ready", "patterns", List.of("the-server-is-ready"), "timeout", 20); assertEquals("MATCHED", waited.outcome(), "output the wait saw: " + waited.output()); assertEquals("the-server-is-ready", waited.matched()); @@ -122,9 +123,9 @@ void sendThenWaitDoesNotMatchTheEchoedCommandLine(Server server) { Pane pane = server.panes().get(0); String marker = "echo-marker-cold"; - typeAndSubmit(server, pane, "sleep 1; echo " + marker); - - assertMatchedTheOutputNotTheEcho(server, pane, marker); + assertMatchedTheOutputNotTheEcho( + afterEntry(server, pane.id().value(), "echo " + marker, "patterns", List.of(marker), "timeout", 20), + marker); } /** As above, against a shell whose prompt has already settled rather than a freshly split one. */ @@ -141,28 +142,17 @@ void sendThenWaitDoesNotMatchTheEchoedCommandLineOnAWarmShellEither(Server serve .outcome(), "the warm-up run never finished, so it still owns the pane"); - typeAndSubmit(server, pane, "sleep 1; echo " + marker); - - assertMatchedTheOutputNotTheEcho(server, pane, marker); + assertMatchedTheOutputNotTheEcho( + afterEntry(server, pane.id().value(), "echo " + marker, "patterns", List.of(marker), "timeout", 20), + marker); } - /** - * Types a line and submits it. The line sleeps before printing so its output lands after the - * wait takes its starting cursor; output already on screen by then does not count, and a fast - * shell would otherwise print it first. - */ private static void typeAndSubmit(Server server, Pane pane, String line) { Typing.sendKeys(TestCalls.on(server, "pane_id", pane.id().value(), "keys", List.of(line), "literal", true)); Typing.sendKeys(TestCalls.on(server, "pane_id", pane.id().value(), "keys", List.of("Enter"), "literal", false)); } - private static void assertMatchedTheOutputNotTheEcho(Server server, Pane pane, String marker) { - // Generous on purpose: what is pinned here is which line matched, not how soon. The - // command sleeps a second before printing, and a five-second budget left barely four for a - // loaded machine to get there - it lost one lane of the matrix that way. - WaitingForText.Waited waited = WaitingForText.waitFor( - TestCalls.on(server, "pane_id", pane.id().value(), "patterns", List.of(marker), "timeout", 20)); - + private static void assertMatchedTheOutputNotTheEcho(WaitingForText.Waited waited, String marker) { assertEquals("MATCHED", waited.outcome(), "the output must eventually appear"); String matchedLine = String.valueOf(waited.matchedLine()); assertTrue( @@ -177,18 +167,16 @@ private static void assertMatchedTheOutputNotTheEcho(Server server, Pane pane, S @Test void aStopPatternEndsTheWaitOnFailureRatherThanRunningToTheDeadline(Server server) { String pane = server.panes().get(0).id().value(); - send(server, pane, "(sleep 1; echo 'error: it did not build') &"); - - WaitingForText.Waited waited = WaitingForText.waitFor(TestCalls.on( + WaitingForText.Waited waited = afterEntry( server, - "pane_id", pane, + "echo 'error: it did not build'", "patterns", List.of("listening on"), "stop", List.of("error:"), "timeout", - 25)); + 25); assertEquals("STOPPED", waited.outcome()); assertEquals("error:", waited.matched()); @@ -199,9 +187,7 @@ void aStopPatternEndsTheWaitOnFailureRatherThanRunningToTheDeadline(Server serve @Test void withNoPatternsAnyNewOutputEndsTheWait(Server server) { String pane = server.panes().get(0).id().value(); - send(server, pane, "(sleep 1; echo anything-at-all) &"); - - WaitingForText.Waited waited = WaitingForText.waitFor(TestCalls.on(server, "pane_id", pane, "timeout", 20)); + WaitingForText.Waited waited = afterEntry(server, pane, "echo anything-at-all", "timeout", 20); assertEquals("MATCHED", waited.outcome()); assertTrue(String.valueOf(waited.note()).contains("any new output"), String.valueOf(waited.note())); @@ -224,10 +210,8 @@ void aWaitThatFindsNothingSaysHowToCarryOnWithoutRereading(Server server) { @Test void aPatternIsPlainTextUnlessAskedToBeAnExpression(Server server) { String pane = server.panes().get(0).id().value(); - send(server, pane, "(sleep 1; echo '[FAILED] one test') &"); - - WaitingForText.Waited waited = WaitingForText.waitFor( - TestCalls.on(server, "pane_id", pane, "patterns", List.of("[FAILED]"), "timeout", 20)); + WaitingForText.Waited waited = + afterEntry(server, pane, "echo '[FAILED] one test'", "patterns", List.of("[FAILED]"), "timeout", 20); assertEquals("MATCHED", waited.outcome()); } @@ -263,10 +247,8 @@ void aCursorFromAnotherPaneIsRejectedBeforeWaiting(Server server) { @Test void aSinglePatternSentWithoutAListIsStillUnderstood(Server server) { String pane = server.panes().get(0).id().value(); - send(server, pane, "(sleep 1; echo lone-pattern-seen) &"); - - WaitingForText.Waited waited = WaitingForText.waitFor( - TestCalls.on(server, "pane_id", pane, "patterns", "lone-pattern-seen", "timeout", 20)); + WaitingForText.Waited waited = + afterEntry(server, pane, "echo lone-pattern-seen", "patterns", "lone-pattern-seen", "timeout", 20); assertEquals("MATCHED", waited.outcome(), "output the wait saw: " + waited.output()); } @@ -298,19 +280,40 @@ private static boolean onScreen(Server server, String pane, String text) { return false; } - /** - * Starts a command in the background and returns at once, without a tracked run to learn when - * it finishes - {@code wait_for_text} is for exactly that case, output nobody here is watching - * for through a command result. - * - *

Goes through {@link Typing#sendKeys}, the same as {@link #typeAndSubmit}, rather than a raw - * {@code send-keys}: every command here is an {@code echo } whose own typed-and-submitted - * text contains the marker too, on screen the instant it is submitted - before the backgrounded - * process prints anything. Only {@link io.github.libtmux.TypedText} can tell that line apart from real output, and - * only a call that goes through it gets recorded there. - */ - private static void send(Server server, String pane, String command) { - Typing.sendKeys(TestCalls.on(server, "pane_id", pane, "keys", List.of(command), "literal", true)); - Typing.sendKeys(TestCalls.on(server, "pane_id", pane, "keys", List.of("Enter"), "literal", false)); + /** Output is released only after the native entry capture has finished. */ + private WaitingForText.Waited afterEntry(Server server, String pane, String command, Object... arguments) { + AtomicBoolean waiting = new AtomicBoolean(); + AtomicBoolean released = new AtomicBoolean(); + try (ProcessTransport processes = new ProcessTransport()) { + TmuxTransport gated = new TmuxTransport() { + @Override + public CommandResult execute(CommandRequest request) { + CommandResult result = processes.execute(request); + boolean capture = request.commands().stream() + .flatMap(List::stream) + .anyMatch( + argument -> argument.equals("capture-pane") || argument.contains("'capture-pane'")); + if (waiting.get() && capture && result.succeeded() && released.compareAndSet(false, true)) { + assertTrue(server.cmd("send-keys", "-t", pane, "Enter").succeeded()); + } + return result; + } + + @Override + public void close() {} + }; + try (Server measured = Server.using(server.config(), gated)) { + Pane target = Targets.pane(measured, pane); + typeAndSubmit(measured, target, "read libtmux_gate; " + command); + Object[] parameters = new Object[arguments.length + 2]; + parameters[0] = "pane_id"; + parameters[1] = pane; + System.arraycopy(arguments, 0, parameters, 2, arguments.length); + waiting.set(true); + WaitingForText.Waited waited = WaitingForText.waitFor(TestCalls.on(measured, parameters)); + assertTrue(released.get(), "the wait did not take its entry capture"); + return waited; + } + } } } From 7d9466f630e320789e55569fffe94d75d413add3 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Oct 2026 04:05:28 -0500 Subject: [PATCH 08/11] MCP(test): Let the shell settle before typing why: JDK 25/27 and the 3.5 and master lanes failed four WaitingForTextTest cases with PRESENT_AT_ENTRY. Keys sent to a shell that is still starting are echoed by the terminal, and the shell draws its prompt straight after that echo on the same row, so the echo is glued to the prompt and no longer stands apart as a typed line. what: - Restore the trunk fixtures of WaitingForTextTest. - Wait for the pane to show something and hold still for half a second before typeAndSubmit and send write to it. Verified with 20 busy loops on a 10-core host, bash with a long prompt, tmux 3.5 lane. The trunk fixtures fail sendThenWaitDoesNotMatchTheEchoedCommandLine (PRESENT_AT_ENTRY); with the wait, RunningCommandsTest, TypingTest and WaitingForTextTest pass under the same load. Trunk's libtmux-mcp:test is restored FROM-CACHE in CI, so its own run never exercised these cases. --- .../libtmux/mcp/WaitingForTextTest.java | 143 +++++++++++------- 1 file changed, 86 insertions(+), 57 deletions(-) diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java index b66010cdc..2f370ef1c 100644 --- a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java @@ -8,12 +8,7 @@ import io.github.libtmux.Pane; import io.github.libtmux.Server; import io.github.libtmux.junit5.TmuxExtension; -import io.github.libtmux.transport.CommandRequest; -import io.github.libtmux.transport.CommandResult; -import io.github.libtmux.transport.ProcessTransport; -import io.github.libtmux.transport.TmuxTransport; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -44,8 +39,10 @@ void outputTheTerminalWrappedForDisplayIsStillMatched(Server server) { // prompt happens to leave the cursor. String marker = "wrapped-" + "x".repeat(120) + "-marker"; - WaitingForText.Waited waited = - afterEntry(server, pane, "echo " + marker, "patterns", List.of(marker), "timeout", 20); + send(server, pane, "(sleep 1; echo " + marker + ") &"); + + WaitingForText.Waited waited = WaitingForText.waitFor( + TestCalls.on(server, "pane_id", pane, "patterns", List.of(marker), "timeout", 20)); assertEquals("MATCHED", waited.outcome(), "output the wait saw: " + waited.output()); } @@ -53,8 +50,10 @@ void outputTheTerminalWrappedForDisplayIsStillMatched(Server server) { @Test void textThatArrivesIsMatchedAndTheWaitEndsAtOnce(Server server) { String pane = server.panes().get(0).id().value(); - WaitingForText.Waited waited = afterEntry( - server, pane, "echo the-server-is-ready", "patterns", List.of("the-server-is-ready"), "timeout", 20); + send(server, pane, "(sleep 1; echo the-server-is-ready) &"); + + WaitingForText.Waited waited = WaitingForText.waitFor( + TestCalls.on(server, "pane_id", pane, "patterns", List.of("the-server-is-ready"), "timeout", 20)); assertEquals("MATCHED", waited.outcome(), "output the wait saw: " + waited.output()); assertEquals("the-server-is-ready", waited.matched()); @@ -123,9 +122,9 @@ void sendThenWaitDoesNotMatchTheEchoedCommandLine(Server server) { Pane pane = server.panes().get(0); String marker = "echo-marker-cold"; - assertMatchedTheOutputNotTheEcho( - afterEntry(server, pane.id().value(), "echo " + marker, "patterns", List.of(marker), "timeout", 20), - marker); + typeAndSubmit(server, pane, "sleep 1; echo " + marker); + + assertMatchedTheOutputNotTheEcho(server, pane, marker); } /** As above, against a shell whose prompt has already settled rather than a freshly split one. */ @@ -142,17 +141,29 @@ void sendThenWaitDoesNotMatchTheEchoedCommandLineOnAWarmShellEither(Server serve .outcome(), "the warm-up run never finished, so it still owns the pane"); - assertMatchedTheOutputNotTheEcho( - afterEntry(server, pane.id().value(), "echo " + marker, "patterns", List.of(marker), "timeout", 20), - marker); + typeAndSubmit(server, pane, "sleep 1; echo " + marker); + + assertMatchedTheOutputNotTheEcho(server, pane, marker); } + /** + * Types a line and submits it. The line sleeps before printing so its output lands after the + * wait takes its starting cursor; output already on screen by then does not count, and a fast + * shell would otherwise print it first. + */ private static void typeAndSubmit(Server server, Pane pane, String line) { + awaitQuietShell(server, pane.id().value()); Typing.sendKeys(TestCalls.on(server, "pane_id", pane.id().value(), "keys", List.of(line), "literal", true)); Typing.sendKeys(TestCalls.on(server, "pane_id", pane.id().value(), "keys", List.of("Enter"), "literal", false)); } - private static void assertMatchedTheOutputNotTheEcho(WaitingForText.Waited waited, String marker) { + private static void assertMatchedTheOutputNotTheEcho(Server server, Pane pane, String marker) { + // Generous on purpose: what is pinned here is which line matched, not how soon. The + // command sleeps a second before printing, and a five-second budget left barely four for a + // loaded machine to get there - it lost one lane of the matrix that way. + WaitingForText.Waited waited = WaitingForText.waitFor( + TestCalls.on(server, "pane_id", pane.id().value(), "patterns", List.of(marker), "timeout", 20)); + assertEquals("MATCHED", waited.outcome(), "the output must eventually appear"); String matchedLine = String.valueOf(waited.matchedLine()); assertTrue( @@ -167,16 +178,18 @@ private static void assertMatchedTheOutputNotTheEcho(WaitingForText.Waited waite @Test void aStopPatternEndsTheWaitOnFailureRatherThanRunningToTheDeadline(Server server) { String pane = server.panes().get(0).id().value(); - WaitingForText.Waited waited = afterEntry( + send(server, pane, "(sleep 1; echo 'error: it did not build') &"); + + WaitingForText.Waited waited = WaitingForText.waitFor(TestCalls.on( server, + "pane_id", pane, - "echo 'error: it did not build'", "patterns", List.of("listening on"), "stop", List.of("error:"), "timeout", - 25); + 25)); assertEquals("STOPPED", waited.outcome()); assertEquals("error:", waited.matched()); @@ -187,7 +200,9 @@ void aStopPatternEndsTheWaitOnFailureRatherThanRunningToTheDeadline(Server serve @Test void withNoPatternsAnyNewOutputEndsTheWait(Server server) { String pane = server.panes().get(0).id().value(); - WaitingForText.Waited waited = afterEntry(server, pane, "echo anything-at-all", "timeout", 20); + send(server, pane, "(sleep 1; echo anything-at-all) &"); + + WaitingForText.Waited waited = WaitingForText.waitFor(TestCalls.on(server, "pane_id", pane, "timeout", 20)); assertEquals("MATCHED", waited.outcome()); assertTrue(String.valueOf(waited.note()).contains("any new output"), String.valueOf(waited.note())); @@ -210,8 +225,10 @@ void aWaitThatFindsNothingSaysHowToCarryOnWithoutRereading(Server server) { @Test void aPatternIsPlainTextUnlessAskedToBeAnExpression(Server server) { String pane = server.panes().get(0).id().value(); - WaitingForText.Waited waited = - afterEntry(server, pane, "echo '[FAILED] one test'", "patterns", List.of("[FAILED]"), "timeout", 20); + send(server, pane, "(sleep 1; echo '[FAILED] one test') &"); + + WaitingForText.Waited waited = WaitingForText.waitFor( + TestCalls.on(server, "pane_id", pane, "patterns", List.of("[FAILED]"), "timeout", 20)); assertEquals("MATCHED", waited.outcome()); } @@ -247,8 +264,10 @@ void aCursorFromAnotherPaneIsRejectedBeforeWaiting(Server server) { @Test void aSinglePatternSentWithoutAListIsStillUnderstood(Server server) { String pane = server.panes().get(0).id().value(); - WaitingForText.Waited waited = - afterEntry(server, pane, "echo lone-pattern-seen", "patterns", "lone-pattern-seen", "timeout", 20); + send(server, pane, "(sleep 1; echo lone-pattern-seen) &"); + + WaitingForText.Waited waited = WaitingForText.waitFor( + TestCalls.on(server, "pane_id", pane, "patterns", "lone-pattern-seen", "timeout", 20)); assertEquals("MATCHED", waited.outcome(), "output the wait saw: " + waited.output()); } @@ -280,40 +299,50 @@ private static boolean onScreen(Server server, String pane, String text) { return false; } - /** Output is released only after the native entry capture has finished. */ - private WaitingForText.Waited afterEntry(Server server, String pane, String command, Object... arguments) { - AtomicBoolean waiting = new AtomicBoolean(); - AtomicBoolean released = new AtomicBoolean(); - try (ProcessTransport processes = new ProcessTransport()) { - TmuxTransport gated = new TmuxTransport() { - @Override - public CommandResult execute(CommandRequest request) { - CommandResult result = processes.execute(request); - boolean capture = request.commands().stream() - .flatMap(List::stream) - .anyMatch( - argument -> argument.equals("capture-pane") || argument.contains("'capture-pane'")); - if (waiting.get() && capture && result.succeeded() && released.compareAndSet(false, true)) { - assertTrue(server.cmd("send-keys", "-t", pane, "Enter").succeeded()); - } - return result; - } - - @Override - public void close() {} - }; - try (Server measured = Server.using(server.config(), gated)) { - Pane target = Targets.pane(measured, pane); - typeAndSubmit(measured, target, "read libtmux_gate; " + command); - Object[] parameters = new Object[arguments.length + 2]; - parameters[0] = "pane_id"; - parameters[1] = pane; - System.arraycopy(arguments, 0, parameters, 2, arguments.length); - waiting.set(true); - WaitingForText.Waited waited = WaitingForText.waitFor(TestCalls.on(measured, parameters)); - assertTrue(released.get(), "the wait did not take its entry capture"); - return waited; + /** + * Starts a command in the background and returns at once, without a tracked run to learn when + * it finishes - {@code wait_for_text} is for exactly that case, output nobody here is watching + * for through a command result. + * + *

Goes through {@link Typing#sendKeys}, the same as {@link #typeAndSubmit}, rather than a raw + * {@code send-keys}: every command here is an {@code echo } whose own typed-and-submitted + * text contains the marker too, on screen the instant it is submitted - before the backgrounded + * process prints anything. Only {@link io.github.libtmux.TypedText} can tell that line apart from real output, and + * only a call that goes through it gets recorded there. + */ + private static void send(Server server, String pane, String command) { + awaitQuietShell(server, pane); + Typing.sendKeys(TestCalls.on(server, "pane_id", pane, "keys", List.of(command), "literal", true)); + Typing.sendKeys(TestCalls.on(server, "pane_id", pane, "keys", List.of("Enter"), "literal", false)); + } + + /** + * Returns once the shell has drawn its prompt and the pane has stopped changing. + * + *

Keys sent to a shell that is still starting are echoed by the terminal, not the shell, and + * the shell then draws its prompt straight after that echo on the same row. The echo is then + * glued to the prompt and no longer stands apart as a typed line, which is a different case from + * the one these tests pin. A slow runner reaches it by sending the keys a moment too early. + */ + private static void awaitQuietShell(Server server, String pane) { + long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(30); + String last = null; + int steady = 0; + while (System.nanoTime() < deadline) { + String now = String.join( + "\n", server.cmd("capture-pane", "-p", "-t", pane).stdout()); + steady = now.equals(last) && !now.isBlank() ? steady + 1 : 0; + if (steady >= 5) { + return; + } + last = now; + try { + Thread.sleep(100); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new AssertionError("interrupted waiting for the shell to settle", interrupted); } } + throw new AssertionError("the shell never settled: " + last); } } From cc486d81044b00a5b9c2e947266307c114823baa Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Oct 2026 08:57:36 -0500 Subject: [PATCH 09/11] CI(test): Refuse real binaries below 3.3a in CI why: Refusal below the floor was tested only against FakeTmux, so a change in how `run-shell -C` fails on 3.2a or 3.3 would have passed CI. A 3.0a or 3.1c daemon, which a newer client can still reach, was not run at all, and the 3.8 release candidates had no lane that could build: upstream removed their tags and published no tarball. A rejection lane whose binary resolved to a supported release would have passed too, by taking the other branch of the check, and a lane whose test class went missing would have passed by running nothing. what: - VersionCheckLaneIntegrationTest starts the lane's own binary on a private socket, makes a sentinel session, option and buffer with that binary's client, then drives the library: below 3.3a every call (raw, batch, typed, lenient list, buffers, kill) raises UnsupportedFeatureException and the sentinels are unchanged; on a supported release the same calls work. - The test reads libtmux.tmux.expected and requires it to equal the parsed -V (master must report a development build), and reads libtmux.tmux.rejection to require supported == !rejectionLane. The client lane's libtmux.tmux.client is held to libtmux.tmux.client.expected the same way. - The workflow step that prints each lane's version fails when the binary is not the release the lane names, client included. - `rejectionLanes` (3.0a, 3.1c, 3.2a, 3.3) run only the classes a module lists in `libtmuxRejectionTests`, and belong to testTmuxMatrix; the workflow builds them as "must be refused" lanes. A module that lists none skips its lane, and the lane of one that lists classes fails unless every one produced a result, since Gradle fails only when no filter matches at all. - Every rejection lane puts a 3.3a client in front of the old daemon (`libtmux.tmux.client`). The 3.0a and 3.1c lanes require the refusal within 10 s on the plain, fenced and control paths; with the old binary as client, only its rejection of -N is reached. - Every rejection lane replaces a known 3.3a daemon with the lane's own on the same socket and requires the refusal, within the bound and with the sentinel untouched, through cmd, a fenced batch, a control attach and Server.control. OldDaemonToolCallTest does so through an MCP tool call, and OldDaemonControlSuite through the Cats Control. - `3.8` and `3.8-rc3` join the preview lanes beside 3.8-rc2. The candidates build from the commit their tag named (rc2 17d32ae4, rc3 331611b6). Verified on the real binaries: the lane classes pass on 3.0a, 3.1c, 3.2a, 3.3 and 3.3a; with the floor not enforced the class fails on 3.2a and 3.3 ("Expected UnsupportedFeatureException to be thrown, but nothing was thrown"). With the lane guards removed, a 3.2a lane that resolves to a 3.7c binary passes; with them it fails. Restoring read-to-EOF fails the 3.1c lane at its bound. The suite passes on 3.8-rc2 and 3.8-rc3. --- .github/CONTRIBUTING.md | 14 + .github/workflows/tmux-matrix.yml | 83 +++++- .../kotlin/libtmux.tmux-matrix.gradle.kts | 42 ++- integration-tests/build.gradle.kts | 7 + .../it/VersionCheckLaneIntegrationTest.java | 282 ++++++++++++++++++ .../scaladsl/OldDaemonControlSuite.scala | 78 +++++ libtmux-mcp/build.gradle.kts | 3 + .../libtmux/mcp/OldDaemonToolCallTest.java | 198 ++++++++++++ 8 files changed, 698 insertions(+), 9 deletions(-) create mode 100644 integration-tests/src/test/java/io/github/libtmux/it/VersionCheckLaneIntegrationTest.java create mode 100644 integration-tests/src/test/scala/io/github/libtmux/scaladsl/OldDaemonControlSuite.scala create mode 100644 libtmux-mcp/src/test/java/io/github/libtmux/mcp/OldDaemonToolCallTest.java diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 83193b74a..8f138a576 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -237,6 +237,20 @@ whole real-tmux suite runs against every one of those releases, and each lane checks it really ran the tmux it is named after. Moving the range means moving `workflows/tmux-matrix.yml` and the README together. +Releases below the floor have rejection lanes that run only the version check +against the real binary. Each lane also puts a 3.3a client in front of the old +daemon through `libtmux.tmux.client`, because a client as old as 3.0a or 3.1c +only ever rejects `-N`, and the daemon's own answers reach the library no other +way. The lane replaces a daemon the library already knows with the old one on +the same socket. Each lane passes the release it names (`libtmux.tmux.expected`) +and whether it is a rejection lane (`libtmux.tmux.rejection`), and the version +check fails when the binary reports another release or the wrong side of the +floor; the workflow step that prints each version fails the same way. A module +lists the classes its rejection lanes run in `libtmuxRejectionTests`, and the +lane fails unless every one of them produced a result. The 3.8 release +candidates are preview lanes built from the commit their tag named, since +upstream removed the tags. + **The API carries no compatibility guarantee.** This project is alpha: releases carry an `-alpha` prerelease tag, any release may change or remove exported identifiers without a deprecation period, and only the newest release is diff --git a/.github/workflows/tmux-matrix.yml b/.github/workflows/tmux-matrix.yml index 2f8584014..ea900ce5d 100644 --- a/.github/workflows/tmux-matrix.yml +++ b/.github/workflows/tmux-matrix.yml @@ -27,7 +27,7 @@ jobs: lanes: # Preview lanes carry their own label: this is the only place a reader sees which entries the # supported range does not include, since the README and the matrix property list only the rest. - name: tmux ${{ matrix.tmux }}${{ matrix.experimental && ' (preview, not supported)' || '' }} + name: tmux ${{ matrix.tmux }}${{ matrix.experimental && ' (preview, not supported)' || '' }}${{ matrix.rejection && ' (must be refused)' || '' }} runs-on: ubuntu-latest timeout-minutes: ${{ matrix.tmux == 'master' && 25 || 15 }} continue-on-error: ${{ matrix.experimental == true }} @@ -43,8 +43,29 @@ jobs: # candidate for a release not yet cut, and master is upstream's development branch, not a # version. Both may fail and neither moves the README's claimed range. include: + # Below the floor, matching the plugin's `rejectionLanes`: the real binary must refuse + # every call and leave a sentinel session, option and buffer untouched. Each also runs + # as the daemon behind a 3.3a client, the only way a 3.0a or 3.1c answer reaches the library. + - tmux: '3.0a' + rejection: true + client: '3.3a' + - tmux: '3.1c' + rejection: true + client: '3.3a' + - tmux: '3.2a' + rejection: true + client: '3.3a' + - tmux: '3.3' + rejection: true + client: '3.3a' + - tmux: '3.8' + experimental: true - tmux: '3.8-rc2' experimental: true + commit: 17d32ae40d28f1548d4d75b32161ffdc69305482 + - tmux: '3.8-rc3' + experimental: true + commit: 331611b632ae3b9306d57dc44f21ca05f567b878 - tmux: master experimental: true steps: @@ -69,7 +90,7 @@ jobs: # master has no release tarball to build from a tag, so it is checked out and autoreconf'd # instead. autoconf and automake are needed only for that. - name: Build dependencies for a git checkout - if: matrix.tmux == 'master' + if: matrix.tmux == 'master' || matrix.commit run: sudo apt-get install --no-install-recommends -y autoconf automake # Keyed by version alone: a released tarball does not change, so a hit is the same binary the @@ -84,7 +105,7 @@ jobs: key: tmux-${{ matrix.tmux }}-${{ runner.os }}-${{ runner.arch }} - name: Build tmux ${{ matrix.tmux }} - if: matrix.tmux != 'master' && steps.tmux-cache.outputs.cache-hit != 'true' + if: matrix.tmux != 'master' && !matrix.commit && steps.tmux-cache.outputs.cache-hit != 'true' env: LANE: ${{ matrix.tmux }} run: | @@ -98,6 +119,48 @@ jobs: make -j"$(nproc)" make install + # The 3.8 release candidates have no tarball and their tags are gone upstream, so each is + # fetched by the commit it was tagged at. + - name: Build tmux ${{ matrix.tmux }} from its commit + if: matrix.commit && steps.tmux-cache.outputs.cache-hit != 'true' + env: + LANE: ${{ matrix.tmux }} + COMMIT: ${{ matrix.commit }} + run: | + set -eux + git init "$HOME/tmux-src-$LANE" + cd "$HOME/tmux-src-$LANE" + git fetch --depth 1 https://github.com/tmux/tmux.git "$COMMIT" + git checkout --detach FETCH_HEAD + sh autogen.sh + ./configure --prefix="$HOME/tmux-builds/$LANE" + make -j"$(nproc)" + make install + + # The daemon of a 3.0a or 3.1c lane is reached by this client. + - name: Restore tmux ${{ matrix.client }} for the client + if: matrix.client + id: client-cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ~/tmux-builds/${{ matrix.client }} + key: tmux-${{ matrix.client }}-${{ runner.os }}-${{ runner.arch }} + + - name: Build tmux ${{ matrix.client }} for the client + if: matrix.client && steps.client-cache.outputs.cache-hit != 'true' + env: + CLIENT: ${{ matrix.client }} + run: | + set -eux + work="$(mktemp -d)" + curl -fsSL -o "$work/tmux.tar.gz" \ + "https://github.com/tmux/tmux/releases/download/$CLIENT/tmux-$CLIENT.tar.gz" + tar -xzf "$work/tmux.tar.gz" -C "$work" + cd "$work/tmux-$CLIENT" + ./configure --prefix="$HOME/tmux-builds/$CLIENT" + make -j"$(nproc)" + make install + # No release, no tag even: master is a moving branch, so it is built the way a contributor # without a dist tarball would, generating the files a tarball normally ships pre-built. - name: Build tmux master from a git checkout @@ -114,8 +177,20 @@ jobs: - name: The lane runs the tmux it is named after env: LANE: ${{ matrix.tmux }} + CLIENT: ${{ matrix.client }} run: | - "$HOME/tmux-builds/$LANE/bin/tmux" -V + check() { + reported="$("$HOME/tmux-builds/$1/bin/tmux" -V)" + echo "$1: $reported" + if [ "$1" = master ]; then + case "$reported" in "tmux next-"*) ;; *) echo "::error::$1 reports $reported"; exit 1 ;; esac + elif [ "${reported#* }" != "$1" ]; then + echo "::error::the $1 build reports $reported" + exit 1 + fi + } + check "$LANE" + if [ -n "$CLIENT" ]; then check "$CLIENT"; fi - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6 with: diff --git a/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts b/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts index 707fc6e39..112a89127 100644 --- a/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts +++ b/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts @@ -14,11 +14,18 @@ val lanes = listOf("3.3a", "3.4", "3.5", "3.6", "3.7", "3.7a", "3.7b", "3.7c") // release that makes it real rather than after. Allowed to fail, and left out of testTmuxMatrix and // the README's claimed range on purpose — 3.8-rc2 is a candidate for a release not yet cut, and master // is upstream's development branch, not a version at all. -val previewLanes = listOf("3.8-rc2", "master") +val previewLanes = listOf("3.8", "3.8-rc2", "3.8-rc3", "master") + +// Below the floor: these run only the version check cases against the real binary, which must refuse +// every call and leave a sentinel session, option and buffer as it was. They are part of the matrix +// because a refusal that a fake alone vouches for is not one. Each is also a daemon reached by a +// client of the first supported release: a client as old as 3.0a or 3.1c only ever rejects `-N`. +val rejectionLanes = listOf("3.0a", "3.1c", "3.2a", "3.3") +val clientLane = "3.3a" val matrix = providers.gradleProperty("libtmuxMatrix") -fun registerLane(lane: String) = +fun registerLane(lane: String, rejection: Boolean = false) = tasks.register("test-tmux-$lane") { group = "verification" description = "Runs the real-tmux tests against tmux $lane." @@ -30,6 +37,22 @@ fun registerLane(lane: String) = // ordinary fork asserts nothing and fails saying so. useJUnitPlatform { excludeTags("fixture", "carrier", "benchmark", "locale") } + // By class, not by tag: the Scala suites run under another engine that ignores tags. + if (rejection) { + // A module names the classes its rejection lanes run in `libtmuxRejectionTests`. One that + // names none has nothing to refuse with, so its lane is skipped. One that does fails the + // lane unless every one of them produced a result, because Gradle itself fails only when + // no filter matches at all, and a lane that ran a test less proves less. + val classes = (project.findProperty("libtmuxRejectionTests") as? List<*>).orEmpty().map { it.toString() } + classes.forEach { filter.includeTestsMatching(it) } + onlyIf("the module names no rejection test class") { classes.isNotEmpty() } + doLast { + val results = reports.junitXml.outputLocation.get().asFile + val missing = classes.filter { !File(results, "TEST-$it.xml").exists() } + check(missing.isEmpty()) { "the rejection lane ran no test of $missing" } + } + } + // Resolved lazily: the task is registered whether or not a matrix exists, so asking for // it without one fails loudly instead of the lane silently not existing. val binary = matrix.map { "$it/$lane/bin/tmux" } @@ -37,6 +60,14 @@ fun registerLane(lane: String) = // Declared so the suite can check it got the tmux this lane is named after. Without it a // lane that ignored the binary would run against whatever is on PATH and still be green. systemProperty("libtmux.tmux.expected", lane) + // Whether this lane's binary is below the floor, so the version check suite can require the + // outcome the lane was built for instead of accepting whichever branch the binary takes. + systemProperty("libtmux.tmux.rejection", rejection.toString()) + if (rejection) { + // The binary above is the daemon here; the library's own client is a newer build. + systemProperty("libtmux.tmux.client", matrix.map { "$it/$clientLane/bin/tmux" }.getOrElse("tmux-matrix-not-configured")) + systemProperty("libtmux.tmux.client.expected", clientLane) + } val configured = matrix.isPresent val configuredBinary = binary.getOrElse("tmux-matrix-not-configured") // A bare `tmux` is this lane's build too. Code that names only a socket, as a reader's @@ -55,11 +86,12 @@ fun registerLane(lane: String) = } } -val laneTasks = lanes.map(::registerLane) -previewLanes.forEach(::registerLane) +val laneTasks = lanes.map { registerLane(it) } +previewLanes.forEach { registerLane(it) } +val rejectionTasks = rejectionLanes.map { registerLane(it, rejection = true) } rootProject.tasks.maybeCreate("testTmuxMatrix").apply { group = "verification" description = "Runs every real-tmux test against every supported tmux release." - dependsOn(laneTasks) + dependsOn(laneTasks, rejectionTasks) } diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index ccf734e2e..f1c589897 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -12,6 +12,13 @@ plugins { `java-test-fixtures` } +// The classes the rejection lanes of the tmux matrix run in this module. +extra["libtmuxRejectionTests"] = + listOf( + "io.github.libtmux.it.VersionCheckLaneIntegrationTest", + "io.github.libtmux.scaladsl.OldDaemonControlSuite", + ) + dependencies { testImplementation(project(":libtmux")) testImplementation(project(":libtmux-jackson")) diff --git a/integration-tests/src/test/java/io/github/libtmux/it/VersionCheckLaneIntegrationTest.java b/integration-tests/src/test/java/io/github/libtmux/it/VersionCheckLaneIntegrationTest.java new file mode 100644 index 000000000..fe69d6a66 --- /dev/null +++ b/integration-tests/src/test/java/io/github/libtmux/it/VersionCheckLaneIntegrationTest.java @@ -0,0 +1,282 @@ +package io.github.libtmux.it; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import io.github.libtmux.Server; +import io.github.libtmux.ServerConfig; +import io.github.libtmux.ServerEndpoint; +import io.github.libtmux.Session; +import io.github.libtmux.SessionId; +import io.github.libtmux.TmuxVersion; +import io.github.libtmux.control.ControlClient; +import io.github.libtmux.exception.UnsupportedFeatureException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * The version check against the real binary of the lane. On a release below the floor every library call is + * refused and a sentinel session, option and buffer made with the binary's own client stays as it + * was; on a supported release the same calls work. The rejection lanes run only this class, so + * nothing there is judged by a fake. The 3.0a and 3.1c lanes also put a newer client in front of the + * old daemon, which is the only way their own answers reach the library. + */ +@Tag("version-check") +final class VersionCheckLaneIntegrationTest { + + private static final TmuxVersion FLOOR = TmuxVersion.MINIMUM; + + @Test + void theBinaryOfTheLaneIsCheckedOrRefusedBeforeAnyEffect() throws Exception { + String binary = System.getProperty("libtmux.tmux", "tmux"); + Path root = Files.createTempDirectory(Path.of("/tmp/libtmux-java-test"), "version-check-"); + Path socket = root.resolve("s"); + TmuxVersion running = versionOf(binary, socket); + boolean supported = running.compareTo(FLOOR) >= 0; + requireLaneBinary(System.getProperty("libtmux.tmux.expected"), running, "libtmux.tmux"); + String rejection = System.getProperty("libtmux.tmux.rejection"); + if (rejection != null) { + assertEquals( + !Boolean.parseBoolean(rejection), + supported, + "the lane " + (Boolean.parseBoolean(rejection) ? "must be refused" : "must be supported") + + " but its binary reports " + running); + } + try { + raw(binary, socket, "new-session", "-d", "-s", "sentinel"); + raw(binary, socket, "set-option", "-g", "@sentinel", "kept"); + raw(binary, socket, "set-buffer", "-b", "sentinel", "keep"); + + ServerConfig config = ServerConfig.builder() + .binary(binary) + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .build(); + try (Server server = Server.open(config)) { + if (supported) { + server.requireCompatibleTmux(); + assertTrue(server.cmd("set-option", "-g", "@sentinel", "changed") + .succeeded()); + server.newSession("added"); + assertEquals(List.of("changed"), raw(binary, socket, "show-options", "-gqv", "@sentinel")); + assertEquals( + List.of("added", "sentinel"), + raw(binary, socket, "list-sessions", "-F", "#{session_name}").stream() + .sorted() + .toList()); + return; + } + assertThrows(UnsupportedFeatureException.class, server::requireCompatibleTmux); + assertThrows( + UnsupportedFeatureException.class, + () -> server.cmd("set-option", "-g", "@sentinel", "changed")); + assertThrows( + UnsupportedFeatureException.class, + () -> server.batch() + .add("set-option", "-g", "@sentinel", "changed") + .run()); + assertThrows(UnsupportedFeatureException.class, () -> server.newSession("intruder")); + assertThrows(UnsupportedFeatureException.class, server::sessions); + assertThrows( + UnsupportedFeatureException.class, + () -> server.buffers().delete("sentinel")); + assertThrows(UnsupportedFeatureException.class, server::killServer); + } + assertEquals(List.of("kept"), raw(binary, socket, "show-options", "-gqv", "@sentinel")); + assertEquals(List.of("keep"), raw(binary, socket, "show-buffer", "-b", "sentinel")); + assertEquals(List.of("sentinel"), raw(binary, socket, "list-sessions", "-F", "#{session_name}")); + } finally { + try { + raw(binary, socket, "kill-server"); + } catch (IOException | AssertionError gone) { + // The server may already be gone. + } + Files.deleteIfExists(socket); + Files.deleteIfExists(root); + } + } + + /** + * A daemon from before 3.2 reached by a 3.3a-or-newer client: 3.1c rejects the probe with a usage + * line and 3.0a refuses the client, and both leave the client's standard output open after it + * exits. The refusal has to come back long before the command timeout and change nothing. + */ + @Test + void anOlderDaemonBehindANewerClientIsRefusedPromptly() throws Exception { + String lane = System.getProperty("libtmux.tmux.expected", ""); + assumeTrue(lane.equals("3.0a") || lane.equals("3.1c"), "only the pre-3.2 lanes have such a daemon"); + String client = System.getProperty("libtmux.tmux.client"); + assertNotNull(client, "lane " + lane + " needs libtmux.tmux.client, a tmux of 3.3a or newer"); + String daemon = System.getProperty("libtmux.tmux", "tmux"); + Path root = Files.createTempDirectory(Path.of("/tmp/libtmux-java-test"), "old-daemon-"); + Path socket = root.resolve("s"); + TmuxVersion clientVersion = versionOf(client, socket); + assertTrue(clientVersion.compareTo(FLOOR) >= 0, "the client of lane " + lane + " reports " + clientVersion); + requireLaneBinary(System.getProperty("libtmux.tmux.client.expected"), clientVersion, "libtmux.tmux.client"); + try { + raw(daemon, socket, "new-session", "-d", "-s", "sentinel"); + raw(daemon, socket, "set-option", "-g", "@sentinel", "kept"); + ServerConfig config = ServerConfig.builder() + .binary(client) + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .defaultTimeout(Duration.ofSeconds(60)) + .build(); + try (Server server = Server.open(config)) { + Duration bound = Duration.ofSeconds(10); + UnsupportedFeatureException refused = assertTimeoutPreemptively( + bound, + () -> assertThrows( + UnsupportedFeatureException.class, + () -> server.cmd("set-option", "-g", "@sentinel", "changed"))); + String message = String.valueOf(refused.getMessage()); + assertTrue(message.contains("older than 3.2"), message); + assertTrue(message.contains("restart any running server"), message); + assertTimeoutPreemptively( + bound, + () -> assertThrows(UnsupportedFeatureException.class, () -> server.newSession("intruder"))); + assertTimeoutPreemptively( + bound, () -> assertThrows(UnsupportedFeatureException.class, server::requireCompatibleTmux)); + // The fenced path, and a control attach, twice: a second attach in one JVM is where a + // pipe the old daemon keeps open used to hold the call until its timeout. + assertTimeoutPreemptively( + bound, + () -> assertThrows( + UnsupportedFeatureException.class, + () -> server.batch() + .add("set-option", "-g", "@sentinel", "changed") + .run())); + for (int attempt = 0; attempt < 2; attempt++) { + assertTimeoutPreemptively( + bound, + () -> assertThrows( + UnsupportedFeatureException.class, + () -> ControlClient.attachUnfenced( + config, new SessionId("$0"), Duration.ofSeconds(8)))); + } + } + assertEquals(List.of("kept"), raw(daemon, socket, "show-options", "-gqv", "@sentinel")); + assertEquals(List.of("sentinel"), raw(daemon, socket, "list-sessions", "-F", "#{session_name}")); + } finally { + try { + raw(daemon, socket, "kill-server"); + } catch (IOException | AssertionError gone) { + // The server may already be gone. + } + Files.deleteIfExists(socket); + Files.deleteIfExists(root); + } + } + + /** + * A daemon the library already knows as 3.3a is replaced on the same socket by the lane's own + * binary, which is below the floor. The cached version must not be trusted: the next call is refused as unsupported, + * promptly and before any effect, on the plain, the fenced and the control paths. + */ + @Test + void aKnownDaemonReplacedByAnOlderOneIsRefusedWithoutAnEffect() throws Exception { + String lane = System.getProperty("libtmux.tmux.expected", ""); + assumeTrue( + "true".equals(System.getProperty("libtmux.tmux.rejection")), "only a rejection lane has an old daemon"); + String client = System.getProperty("libtmux.tmux.client"); + assertNotNull(client, "lane " + lane + " needs libtmux.tmux.client, a tmux of 3.3a or newer"); + String daemon = System.getProperty("libtmux.tmux", "tmux"); + Duration bound = Duration.ofSeconds(10); + for (String path : List.of("cmd", "batch", "control", "server.control")) { + Path root = Files.createTempDirectory(Path.of("/tmp/libtmux-java-test"), "replaced-daemon-"); + Path socket = root.resolve("s"); + ServerConfig config = ServerConfig.builder() + .binary(client) + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .defaultTimeout(Duration.ofSeconds(60)) + .build(); + try (Server server = Server.open(config)) { + raw(client, socket, "new-session", "-d", "-s", "known"); + List known = server.sessions(); + assertEquals(1, known.size(), "the 3.3a daemon is known before it is replaced"); + raw(client, socket, "kill-server"); + for (int wait = 0; wait < 100 && Files.exists(socket); wait++) { + TimeUnit.MILLISECONDS.sleep(50); + } + raw(daemon, socket, "new-session", "-d", "-s", "sentinel"); + raw(daemon, socket, "set-option", "-g", "@sentinel", "kept"); + + UnsupportedFeatureException refused = assertTimeoutPreemptively( + bound, + () -> assertThrows(UnsupportedFeatureException.class, () -> { + if (path.equals("cmd")) { + server.cmd("set-option", "-g", "@sentinel", "changed"); + } else if (path.equals("batch")) { + server.batch() + .add("set-option", "-g", "@sentinel", "changed") + .run(); + } else if (path.equals("control")) { + ControlClient.attachUnfenced(config, new SessionId("$0"), Duration.ofSeconds(8)); + } else { + server.control(known.getFirst(), Duration.ofSeconds(8)); + } + })); + assertTrue(refused.belowFloor(), path + ": " + refused.getMessage()); + assertEquals(List.of("kept"), raw(daemon, socket, "show-options", "-gqv", "@sentinel"), path); + assertEquals(List.of("sentinel"), raw(daemon, socket, "list-sessions", "-F", "#{session_name}"), path); + } finally { + try { + raw(daemon, socket, "kill-server"); + } catch (IOException | AssertionError gone) { + // The server may already be gone. + } + Files.deleteIfExists(socket); + Files.deleteIfExists(root); + } + } + } + + /** + * The release a lane binary reports, which must be the one its lane names: a lane that resolved + * another build would otherwise pass by taking the other branch of the check. {@code master} is + * the one lane that is not a release, and reports a development build. + */ + private static void requireLaneBinary(String expected, TmuxVersion running, String property) { + if (expected == null) { + return; + } + if (expected.equals("master")) { + assertTrue(running.development(), property + " is the master lane but reports " + running); + } else { + assertEquals(TmuxVersion.parse(expected), running, property + " is not the build its lane names"); + } + } + + private static TmuxVersion versionOf(String binary, Path socket) throws Exception { + return TmuxVersion.parse(raw(binary, socket, "-V").getFirst().replaceFirst("^\\S+ ", "")); + } + + /** The binary's own client, outside the library. */ + private static List raw(String binary, Path socket, String... arguments) throws Exception { + List command = new ArrayList<>(List.of(binary)); + if (!arguments[0].equals("-V")) { + command.addAll(List.of("-S", socket.toString(), "-f", "/dev/null")); + } + command.addAll(List.of(arguments)); + Process process = new ProcessBuilder(command).redirectErrorStream(true).start(); + String output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + assertTrue(process.waitFor(30, TimeUnit.SECONDS), command.toString()); + if (process.exitValue() != 0) { + throw new AssertionError(command + " exited " + process.exitValue() + ": " + output); + } + return output.isEmpty() ? List.of() : List.of(output.stripTrailing().split("\n")); + } +} diff --git a/integration-tests/src/test/scala/io/github/libtmux/scaladsl/OldDaemonControlSuite.scala b/integration-tests/src/test/scala/io/github/libtmux/scaladsl/OldDaemonControlSuite.scala new file mode 100644 index 000000000..ee430dc9f --- /dev/null +++ b/integration-tests/src/test/scala/io/github/libtmux/scaladsl/OldDaemonControlSuite.scala @@ -0,0 +1,78 @@ +package io.github.libtmux.scaladsl + +import _root_.cats.effect.IO +import _root_.cats.effect.unsafe.implicits.global +import io.github.libtmux.{ServerConfig, ServerEndpoint, SessionId} +import io.github.libtmux.exception.UnsupportedFeatureException +import io.github.libtmux.scaladsl.cats.Control +import java.nio.file.{Files, Path} +import java.time.Duration +import munit.FunSuite +import scala.concurrent.duration._ + +/** Attaching a Cats control client to a real daemon below the floor is refused + * as unsupported, promptly and twice over: a 3.0 or 3.1 daemon keeps the + * probe's standard output open, which used to hold the second attach until its + * timeout. Runs in the rejection lanes of the tmux matrix and is skipped + * elsewhere. + */ +final class OldDaemonControlSuite extends FunSuite { + + private def raw(binary: String, socket: Path, args: String*): Unit = { + val command = List(binary, "-S", socket.toString, "-f", "/dev/null") ++ args + val process = new ProcessBuilder(command*).redirectErrorStream(true).start() + process.getInputStream.readAllBytes() + assert(process.waitFor(30, java.util.concurrent.TimeUnit.SECONDS), command) + assertEquals(process.exitValue(), 0, command.toString) + } + + test( + "Control.attachUnfenced on a daemon below the floor is refused as unsupported" + ) { + val lane = sys.props.getOrElse("libtmux.tmux.expected", "") + assume( + sys.props.get("libtmux.tmux.rejection").contains("true"), + "only a rejection lane has an old daemon" + ) + val client = sys.props.getOrElse("libtmux.tmux.client", "") + assert(client.nonEmpty, s"lane $lane needs libtmux.tmux.client") + val daemon = sys.props.getOrElse("libtmux.tmux", "tmux") + val root = Files.createTempDirectory( + Path.of("/tmp/libtmux-java-test"), + "cats-old-daemon-" + ) + val socket = root.resolve("s") + try { + raw(daemon, socket, "new-session", "-d", "-s", "sentinel") + val config = ServerConfig + .builder() + .binary(client) + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .defaultTimeout(Duration.ofSeconds(60)) + .build() + for (_ <- 1 to 2) { + val attempt = Control + .attachUnfenced[IO]( + config, + new SessionId("$0"), + Duration.ofSeconds(8) + ) + .use(_ => IO.unit) + .attempt + .timeout(10.seconds) + .unsafeRunSync() + attempt match { + case Left(refused: UnsupportedFeatureException) => + assert(refused.belowFloor(), refused.getMessage) + case other => fail(s"expected the version refusal, got $other") + } + } + } finally { + try raw(daemon, socket, "kill-server") + catch { case _: Throwable => () } + Files.deleteIfExists(socket) + Files.deleteIfExists(root) + } + } +} diff --git a/libtmux-mcp/build.gradle.kts b/libtmux-mcp/build.gradle.kts index c6bed14e6..0595d008f 100644 --- a/libtmux-mcp/build.gradle.kts +++ b/libtmux-mcp/build.gradle.kts @@ -4,6 +4,9 @@ plugins { application } +// The classes the rejection lanes of the tmux matrix run in this module. +extra["libtmuxRejectionTests"] = listOf("io.github.libtmux.mcp.OldDaemonToolCallTest") + // An MCP client launches this as a subprocess and speaks JSON-RPC over its standard streams, so it // needs a real launcher rather than a library jar. installDist writes one with its own lib // directory; no stdout but the protocol may reach the client. diff --git a/libtmux-mcp/src/test/java/io/github/libtmux/mcp/OldDaemonToolCallTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/OldDaemonToolCallTest.java new file mode 100644 index 000000000..78d420a0a --- /dev/null +++ b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/OldDaemonToolCallTest.java @@ -0,0 +1,198 @@ +package io.github.libtmux.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import io.github.libtmux.Server; +import io.github.libtmux.ServerConfig; +import io.github.libtmux.ServerEndpoint; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.ProtocolVersions; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +/** + * A tool call answered by a real daemon below the floor that has replaced a 3.3a one on the same + * socket. The version the server learned must not be trusted: the call is refused with the remedy, + * promptly, and the old daemon's sessions are left alone. The rejection lanes of the tmux matrix + * run this class beside the library's own; elsewhere it is skipped. + */ +final class OldDaemonToolCallTest { + + private static final Duration BOUND = Duration.ofSeconds(15); + + @Test + void aToolCallAfterAnOlderDaemonReplacesAKnownOneIsRefusedWithTheRemedy() throws Exception { + String lane = System.getProperty("libtmux.tmux.expected", ""); + assumeTrue( + "true".equals(System.getProperty("libtmux.tmux.rejection")), "only a rejection lane has an old daemon"); + String client = System.getProperty("libtmux.tmux.client"); + assertNotNull(client, "lane " + lane + " needs libtmux.tmux.client, a tmux of 3.3a or newer"); + String daemon = System.getProperty("libtmux.tmux", "tmux"); + Path root = Files.createTempDirectory(Path.of("/tmp/libtmux-java-test"), "mcp-old-daemon-"); + Path socket = root.resolve("s"); + ServerConfig config = ServerConfig.builder() + .binary(client) + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .defaultTimeout(Duration.ofSeconds(60)) + .build(); + // The profile was read at startup, while the daemon still passed. + ToolSurface surface = ToolSurface.resolve( + Map.of(ToolSurface.TOOLSETS_ENV, "", ToolSurface.TOOLS_ENV, "list_sessions,create_session"), + new SocketProfile( + "path:" + socket, + "operator-current", + "existing", + "unknown", + socket.toString(), + "tmux -N -S '" + socket + "' attach", + false)); + Wire output = new Wire(); + try (Server server = Server.open(config); + PipedInputStream input = new PipedInputStream(); + PipedOutputStream stdin = new PipedOutputStream(input)) { + raw(client, socket, "new-session", "-d", "-s", "known"); + McpSyncServer mcp = TmuxMcpServer.overStdio(server, input, output, surface, () -> {}); + try { + send(stdin, initialize()); + assertTrue(output.await(1, 5), "initialization did not answer"); + send(stdin, "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}"); + + JsonNode known = call(stdin, output, 2, "list_sessions"); + assertEquals(false, known.path("isError").asBoolean(), known.toString()); + + raw(client, socket, "kill-server"); + for (int wait = 0; wait < 100 && Files.exists(socket); wait++) { + TimeUnit.MILLISECONDS.sleep(50); + } + raw(daemon, socket, "new-session", "-d", "-s", "sentinel"); + + for (int id = 3; id <= 4; id++) { + String tool = id == 3 ? "list_sessions" : "create_session"; + int call = id; + JsonNode refused = assertTimeoutPreemptively(BOUND, () -> call(stdin, output, call, tool)); + assertEquals(true, refused.path("isError").asBoolean(), refused.toString()); + assertEquals( + "UNSUPPORTED_FEATURE", + refused.path("_meta").path("error_code").asText(), + refused.toString()); + String message = refused.path("content").get(0).path("text").asText(); + assertTrue(message.contains("requires tmux 3.3a or newer"), message); + assertTrue(message.contains("restart any running server"), message); + } + } finally { + mcp.close(); + } + assertEquals(List.of("sentinel"), raw(daemon, socket, "list-sessions", "-F", "#{session_name}")); + } finally { + try { + raw(daemon, socket, "kill-server"); + } catch (IOException | AssertionError gone) { + // The server may already be gone. + } + Files.deleteIfExists(socket); + Files.deleteIfExists(root); + } + } + + private static JsonNode call(PipedOutputStream stdin, Wire output, int id, String tool) throws Exception { + send( + stdin, + Answers.JSON.writeValueAsString(Map.of( + "jsonrpc", + "2.0", + "id", + "call-" + id, + "method", + "tools/call", + "params", + Map.of( + "name", + tool, + "arguments", + tool.equals("create_session") ? Map.of("session_name", "x") : Map.of())))); + assertTrue(output.await(id, 60), tool + " did not answer"); + String line = output.lines().stream() + .filter(l -> l.contains("\"call-" + id + "\"")) + .findFirst() + .orElseThrow(() -> new AssertionError("no response to " + tool)); + return Answers.JSON.readTree(line).path("result"); + } + + private static void send(PipedOutputStream stdin, String line) throws IOException { + stdin.write((line + "\n").getBytes(StandardCharsets.UTF_8)); + stdin.flush(); + } + + private static String initialize() { + return "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"" + + ProtocolVersions.MCP_2025_11_25 + + "\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1\"}}}"; + } + + /** The binary's own client, outside the library. */ + private static List raw(String binary, Path socket, String... arguments) throws Exception { + List command = new ArrayList<>(List.of(binary, "-S", socket.toString(), "-f", "/dev/null")); + command.addAll(List.of(arguments)); + Process process = new ProcessBuilder(command).redirectErrorStream(true).start(); + String output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + assertTrue(process.waitFor(30, TimeUnit.SECONDS), command.toString()); + if (process.exitValue() != 0) { + throw new AssertionError(command + " exited " + process.exitValue() + ": " + output); + } + return output.isEmpty() ? List.of() : List.of(output.stripTrailing().split("\n")); + } + + /** Collects protocol lines and lets a test wait for the nth. */ + private static final class Wire extends OutputStream { + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + private int lines; + + @Override + public synchronized void write(int value) { + bytes.write(value); + if (value == '\n') { + lines++; + notifyAll(); + } + } + + @Override + public synchronized void write(byte[] values, int offset, int length) { + for (int index = offset; index < offset + length; index++) { + write(values[index]); + } + } + + synchronized List lines() { + return bytes.toString(StandardCharsets.UTF_8).lines().toList(); + } + + synchronized boolean await(int expected, long seconds) throws InterruptedException { + long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(seconds); + long remaining = end - System.nanoTime(); + while (lines < expected && remaining > 0) { + TimeUnit.NANOSECONDS.timedWait(this, remaining); + remaining = end - System.nanoTime(); + } + return lines >= expected; + } + } +} From de7f4eff5c77812902b3e1008666330e16e35d39 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Oct 2026 22:35:03 -0500 Subject: [PATCH 10/11] Workspace(fix): Start no daemon on attach handoff why: The terminal handoff spawns tmux itself, outside the version check, so a daemon that exited after the load could be started again by a client the check never saw. what: - Build the attach-session argv in attachCommand and put -N after the endpoint. - Run the handoff through an injectable Handoff, and assert the argv that attach spawns, with TMUX and TMUX_PANE removed. Verified: spawning the argv without -N from attach fails ExecutionTest.attachSpawnsTheHandoffWithoutStartingADaemon. --- .../libtmux/workspace/cli/Execution.java | 44 +++++++++++++----- .../libtmux/workspace/cli/ExecutionTest.java | 46 +++++++++++++++++++ 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java index 140decbc8..48b2df948 100644 --- a/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java +++ b/libtmux-workspace-cli/src/main/java/io/github/libtmux/workspace/cli/Execution.java @@ -757,7 +757,7 @@ private static boolean sameDaemon(Main.Context context, Server selected) { * Where an attached load ends up: outside tmux ({@code insideTmux} false), switched via a known * client, or switched blind when the invoking pane cannot be identified. */ - private record AttachTarget(boolean insideTmux, Optional client) {} + record AttachTarget(boolean insideTmux, Optional client) {} /** * Decided before anything is built: every way the invoking context can fail to carry this load @@ -806,8 +806,36 @@ private static char promptAnswer(Main.Context context, String prompt, char fallb : Character.toLowerCase(line.strip().charAt(0)); } + /** The terminal handoff; {@code -N} keeps it from starting a daemon the version check never saw. */ + static List attachCommand(List endpoint, String session) { + var argv = new ArrayList<>(endpoint); + argv.add(1, "-N"); + argv.addAll(List.of("attach-session", "-t", session)); + return List.copyOf(argv); + } + + /** Runs the terminal handoff to completion and returns its exit status. */ + @FunctionalInterface + interface Handoff { + int run(ProcessBuilder builder) throws IOException, InterruptedException; + } + + private static int spawn(ProcessBuilder builder) throws IOException, InterruptedException { + Process child = builder.start(); + try { + return child.waitFor(); + } finally { + if (child.isAlive()) child.destroyForcibly(); + } + } + private static void attach(Server server, Session session, Main.Context context, AttachTarget target) throws IOException, InterruptedException { + attach(server, session, context, target, Execution::spawn); + } + + static void attach(Server server, Session session, Main.Context context, AttachTarget target, Handoff handoff) + throws IOException, InterruptedException { if (target.insideTmux()) { authenticate(context, server); if (target.client().isPresent()) { @@ -834,10 +862,7 @@ private static void attach(Server server, Session session, Main.Context context, } return; } - var argv = new ArrayList<>(server.config().endpointCommand()); - argv.add("attach-session"); - argv.add("-t"); - argv.add(session.id().value()); + var argv = attachCommand(server.config().endpointCommand(), session.id().value()); // This process's own descriptors, not a fresh open of the tty by path: a client whose stdio // is reopened attaches and reads input but draws nothing. ProcessBuilder builder = new ProcessBuilder(argv) @@ -848,13 +873,8 @@ private static void attach(Server server, Session session, Main.Context context, builder.environment().putAll(context.environment()); builder.environment().remove("TMUX"); builder.environment().remove("TMUX_PANE"); - Process child = builder.start(); - try { - int status = child.waitFor(); - if (status != 0) throw new Main.Failure(Machine.Code.TMUX_FAILED, status, "tmux attachment failed"); - } finally { - if (child.isAlive()) child.destroyForcibly(); - } + int status = handoff.run(builder); + if (status != 0) throw new Main.Failure(Machine.Code.TMUX_FAILED, status, "tmux attachment failed"); } static List existingSessions(Server server) { diff --git a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java index 6558a4b81..3e1568a00 100644 --- a/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java +++ b/libtmux-workspace-cli/src/test/java/io/github/libtmux/workspace/cli/ExecutionTest.java @@ -28,6 +28,52 @@ import org.junit.jupiter.params.provider.ValueSource; final class ExecutionTest { + + @Test + void theTerminalHandoffNeverStartsADaemon() { + assertEquals( + java.util.List.of("tmux", "-N", "-S", "/tmp/s", "attach-session", "-t", "$1"), + Execution.attachCommand(java.util.List.of("tmux", "-S", "/tmp/s"), "$1")); + } + + /** The argv {@code attach} spawns, not the helper that builds one: removing -N there must fail. */ + @Test + void attachSpawnsTheHandoffWithoutStartingADaemon() throws Exception { + Path socket = directory.resolve("handoff-socket"); + try (Server server = server(socket)) { + try { + Session session = server.newSession("handoff"); + java.util.List> spawned = new java.util.ArrayList<>(); + Main.Context context = new Main.Context( + java.util.Map.of("TMUX", "inherited", "TMUX_PANE", "%1", "PATH", "/usr/bin"), + directory, + InputStream.nullInputStream(), + new ByteArrayOutputStream(), + new ByteArrayOutputStream()); + + Execution.attach( + server, + session, + context, + new Execution.AttachTarget(false, java.util.Optional.empty()), + builder -> { + spawned.add(builder.command()); + assertFalse(builder.environment().containsKey("TMUX")); + assertFalse(builder.environment().containsKey("TMUX_PANE")); + return 0; + }); + + var expected = new java.util.ArrayList<>(server.config().endpointCommand()); + expected.add(1, "-N"); + expected.addAll( + java.util.List.of("attach-session", "-t", session.id().value())); + assertEquals(java.util.List.of(expected), spawned); + } finally { + if (server.isAlive()) server.killServer(); + } + } + } + @TempDir Path directory; From 7c0f8f7016c43b6cccc81f4ba865742d109d2b9c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Oct 2026 08:57:55 -0500 Subject: [PATCH 11/11] Changelog(docs): Record the tmux 3.3a floor why: The raised daemon minimum changes which installations and Python backends can run JVM products, and callers of TmuxVersion and CommandRequest see new behaviour. what: - Record the 3.3a requirement and its refusals, TmuxVersion.MINIMUM and belowFloor(), CommandRequest.discardStdout, the MCP floor and startup refusal, tmux_unsupported, the attach handoff, the strict version order and the rc2 layout gate under Unreleased, each citing #69. - Add the migration notes: upgrade the daemon as well as the client, the refusal for a daemon older than 3.2, belowFloor(), the Python backend requirement, and the version order and strict parse. --- CHANGELOG.md | 41 ++++++++++++++++++++++++++++++++ MIGRATION.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4473d990..aab0ead3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,47 @@ production. ## Unreleased +### Added + +- **`TmuxVersion.MINIMUM` and `UnsupportedFeatureException.belowFloor()`.** + `MINIMUM` is the oldest supported tmux, 3.3a. `belowFloor()` is true when the + exception reports a tmux older than that and false when one optional feature + is missing from a supported tmux, so a handler that skips optional features + can still surface a floor refusal. (#69) +- **`CommandRequest` can discard standard output.** `discardStdout` sends it to + the null device for a caller that reads only the exit status and standard + error; the version probe uses it, because tmux 3.0 and 3.1 daemons hold the + client's standard output open after it exits. The four-argument constructor + is unchanged. (#69) +- **The MCP server states the tmux floor.** Its instructions and + `tmux://capabilities` (`minimumTmux`) name 3.3a. Against an older tmux, + startup prints one `libtmux-mcp:` line and exits non-zero without a stack + trace. (#69) +- **`tmux-workspace` reports a tmux older than 3.3a as `tmux_unsupported`.** + The code appears in JSON output beside the message, which says to upgrade + tmux or use the previous release. (#69) + +### Changed + +- **tmux 3.3a is the minimum across the JVM modules and products.** Upgrade the + running daemon as well as the client. Commands refuse older daemons with + `UnsupportedFeatureException` before their requested effects, and the message + says to upgrade tmux and restart any running server, or use the previous + libtmux release. This includes 3.0 and 3.1, which cannot name their version. A + version that names no release, such as `master`, fails with + `MalformedResponseException`. The Python bridge also requires compatible + Python libtmux. See [migration notes](MIGRATION.md). (#69) +- **The workspace CLI's attached handoff starts no daemon.** `workspace load` + passes `-N` to the `attach-session` it runs. (#69) +- **`TmuxVersion` orders release candidates below their release and parses + strictly.** `3.8-rc < 3.8`, `compareTo` agrees with `equals`, and text such + as `3.3a garbage`, `3.3a-master`, `next-3.9a` or `3.8-rc0` is rejected. + `atLeast` follows the same order, so a candidate or `next-` build no longer + meets a gate on the release it names. (#69) +- **JSON layouts are gated on tmux 3.8-rc2.** The release candidates already + write them, so the gate names the earliest build measured to; `3.8-rc` and + `next-3.8` are still refused. (#69) + ### Documented - **Every README opens with a quick start and is sectioned by task, with the diff --git a/MIGRATION.md b/MIGRATION.md index ca869ec63..5715978b7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -8,6 +8,73 @@ API changes that require updates to calling code are recorded here. See A breaking type is named on its own `api-break:` line. Mentioning the type in the prose is not that line. +### tmux 3.3a is the minimum + +Upgrade the tmux daemon to 3.3a or newer. Installing a newer client alone does +not upgrade a daemon that is already running. Finish or save its sessions, +stop that daemon, then start the new tmux binary on the same endpoint. + +Commands through Java, Kotlin and Scala, including MCP tools and native +workspace loading, refuse an older daemon with `UnsupportedFeatureException` +before sending the requested operation. Raw commands and batches follow the +same rule. The message says to upgrade tmux and restart any running server, or +to use the previous libtmux release. A daemon older than 3.2 cannot answer the +version probe and is refused the same way. + +`UnsupportedFeatureException.belowFloor()` is true for this refusal and false +when one optional feature is missing from a supported tmux. A handler that +skips optional features should test it first, so that a tmux below the floor +is surfaced and not skipped. + +Standalone `cmd("-V")` and `cmd("-h")` remain available for executable +diagnostics; their results do not authorize a daemon operation. A version +token that names no release, such as `master`, fails with +`MalformedResponseException`. A candidate for 3.3a does not satisfy the +released 3.3a minimum. + +Ordinary command requests carry an atomic version guard. Control-client +startup checks the floor through its process carrier before launching `-C`; +it does not pin the endpoint between that check and attachment. These checks +require native built-in command semantics. A tmux configuration that replaces +commands such as `if-shell` or `run-shell` through `command-alias` is outside +this qualification. + +The first session on an unused endpoint checks the configured binary before +starting a daemon. Reads leave an unused endpoint empty; cleanup of an absent +daemon remains harmless. Optional features retain their own version +requirements. + +The MCP server names the floor in its instructions and in +`tmux://capabilities` as `minimumTmux`. Against an older tmux it prints one +`libtmux-mcp:` line and exits non-zero at startup, and a tool call answered by +an older daemon mid-session returns the `UNSUPPORTED_FEATURE` error code. The +workspace CLI reports the refusal with the machine code `tmux_unsupported`. + +Python extensions and the Python shell use `Server.requireCompatibleTmux()` +before starting Python. This validates the selected daemon or executable +without starting a daemon; later Java operations still run their own version +check. The bridge also requires Python libtmux whose declared +`TMUX_MIN_VERSION` is 3.3a or newer and whose own dispatcher enforces that +floor on each command. An older installed backend is refused before builders, +plugins or shell code run, and upgrading tmuxp alone does not supply a +command-level version check. + +### Version ordering and parsing + +`TmuxVersion.compareTo` now orders `next-X.Y < X.Y-rc < X.Y-rcN < X.Y < X.Ya`, +so it agrees with `equals`; a candidate no longer compares equal to its +release. `atLeast` follows that order: `3.8-rc` and `next-3.8` no longer meet a +gate on `3.8`, which the 0.0.1-alpha.14 notes below describe the other way +round. Gate a behaviour that a candidate already ships on the earliest build +measured to have it, such as `new TmuxVersion(3, 8, "", false, "rc2")` for JSON +layouts and an empty dead-pane pid. Use `sameRelease` for a gate on one +release's own defect. + +`TmuxVersion.parse` accepts only what tmux has reported (`3.3a`, `3.8-rc2`, +`next-3.9`), with numbers of up to four digits, and throws +`IllegalArgumentException` for anything else, such as `3.3a garbage`, +`next-3.9a` or `3.8-rc0`. + ## 0.0.1-alpha.16 ### `CommandResult.stdout()` keeps carriage returns