diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 79b5d0acf..8f138a576 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -232,11 +232,25 @@ 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. +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/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/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..ea900ce5d 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: @@ -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 }} @@ -38,13 +38,34 @@ 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. 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: @@ -132,6 +207,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/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 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..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 @@ -8,17 +8,24 @@ // 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 // 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/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/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/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 c91cf936b..52cc7ea4f 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 @@ -351,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 c0a362058..b6ba6f826 100644 --- a/docs/internals/tmux-behaviour.md +++ b/docs/internals/tmux-behaviour.md @@ -1,11 +1,27 @@ # 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. +## 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. + +## 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, @@ -25,7 +41,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 | @@ -44,19 +60,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 @@ -82,14 +95,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/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/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/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/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/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..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 @@ -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..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 @@ -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 @@ -160,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 { @@ -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/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/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/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/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/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 f91aead07..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) @@ -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/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/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..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 @@ -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; @@ -23,15 +22,11 @@ * 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 { - 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 +238,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/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/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..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, 2, "a")), "this library supports 3.2a and later: " + version); + assertTrue( + version.atLeast(TmuxVersion.MINIMUM), + "this library supports " + TmuxVersion.MINIMUM + " 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/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/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/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-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/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/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/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 6a2429e15..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 @@ -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 @@ -1156,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, @@ -1206,6 +1213,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/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 f41aa04a9..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")); @@ -278,7 +352,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 +378,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 +399,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 +476,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 +497,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/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; + } + } +} 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/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/RunningCommandsTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/RunningCommandsTest.java index 194a9b18a..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 @@ -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")); } @@ -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(); @@ -621,7 +634,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 +982,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) @@ -975,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/TmuxMcpServerTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TmuxMcpServerTest.java index 81df0b5ef..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; /** @@ -49,6 +51,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()); } @@ -248,12 +253,92 @@ 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(); 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/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-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/TypingTest.java index 2dabf92da..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 @@ -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 @@ -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(); @@ -954,7 +993,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); @@ -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) { 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-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java b/libtmux-mcp/src/test/java/io/github/libtmux/mcp/WaitingForTextTest.java index ef08d66d7..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 @@ -152,6 +152,7 @@ void sendThenWaitDoesNotMatchTheEchoedCommandLineOnAWarmShellEither(Server serve * 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)); } @@ -310,7 +311,38 @@ private static boolean onScreen(Server server, String pane, String text) { * 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); + } } 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-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/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 f587195a5..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 @@ -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( @@ -587,29 +592,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) @@ -769,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 @@ -818,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()) { @@ -846,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) @@ -860,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/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 4ecde6d62..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; @@ -72,6 +118,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 { @@ -381,7 +536,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( @@ -419,7 +573,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()), @@ -495,9 +648,13 @@ 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}. */ - @Test - void loadSkipsSizingOnATmuxOlderThan33a() 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 +669,27 @@ void loadSkipsSizingOnATmuxOlderThan33a() 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"); - assertEquals(0, result.code(), result.err()); - assertFalse(Files.readString(trace).lines().anyMatch("-x"::equals), Files.readString(trace)); + 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 { if (server.isAlive()) server.killServer(); } @@ -1049,9 +1215,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); @@ -1128,7 +1293,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()); @@ -2683,14 +2848,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/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 { 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-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/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/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/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 226639e6c..448c7f20d 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() {} @@ -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,15 +182,17 @@ 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"); } 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); } @@ -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/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/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/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..4958fff2b 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 { @@ -22,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()) { @@ -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/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/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/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 53dc30f69..7ecfcb932 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); @@ -334,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 024c2cb7d..0282e3035 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; } @@ -74,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 d46175302..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 @@ -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/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/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..0213fafec 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 { @@ -290,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. */ @@ -457,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/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..472520466 100644 --- a/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java +++ b/libtmux/src/test/java/io/github/libtmux/CreationSpecTest.java @@ -6,23 +6,19 @@ 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; /** * 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 V33 = new TmuxVersion(3, 3, ""); - private static final TmuxVersion V33A = new TmuxVersion(3, 3, "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}"; @@ -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 @@ -97,18 +93,18 @@ 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() { 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 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)); @@ -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/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/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..5063fdb04 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,36 +807,17 @@ 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()); - } } /** * 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 @@ -991,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) { @@ -1014,19 +1013,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 +1083,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 +1132,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 +1147,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 +1178,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 +1229,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/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/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..b83aa8ea0 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") @@ -88,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. */ @@ -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/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"))), 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; }