Developing and Testing¶
The tests live in tests/, written with pytest. They run against a real tmux
server on a separate socket ($ tmux -L test_case), so they never disturb your
own sessions.
Install the latest code from git¶
Get the source¶
Check out the code from GitHub:
$ git clone [email protected]:tmux-python/tmuxp.git
$ cd tmuxp
Bootstrap¶
The easiest way to set up a dev environment is with uv, which manages the virtualenv and Python dependencies for you. (See uv’s documentation to install uv itself.)
Create the virtualenv and install everything locked in uv.lock:
$ uv sync --all-extras --dev
To refresh those packages later:
$ uv sync --all-extras --dev --upgrade
Then prefix any Python command with uv run:
$ uv run [command]
That’s it — you’re ready to code.
Advanced: manual virtualenv¶
Prefer to manage the virtualenv yourself? Create one:
$ virtualenv .venv
Activate it in your current shell:
$ source .venv/bin/activate
Install tmuxp in editable mode, so your edits take effect immediately:
$ pip install -e .
With a uv-managed project, add the checkout as an editable dev dependency instead:
$ uv add --dev --editable .
Prefer a one-off, pipx-style run while you hack? Call tmuxp through uvx:
$ uvx tmuxp
Test runner¶
pytest runs the tests. Inside the virtualenv, the tmuxp command and a
project-local python are already on your PATH.
Rerun on file change¶
Watch files and re-run tests on every save, via pytest-watcher:
$ just start
Manual¶
$ uv run py.test
Or:
$ just test
pytest options¶
Pass extra arguments through PYTEST_ADDOPTS. See the pytest usage docs for
everything it accepts.
Verbose:
$ env PYTEST_ADDOPTS="--verbose" just start
Pick a file:
$ env PYTEST_ADDOPTS="tests/workspace/test_builder.py" just start
Drop into a single test and stop on the first error:
$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py::test_automatic_rename_option" \
just start
Drop into pdb on the first error:
$ env PYTEST_ADDOPTS="-x -s --pdb" just start
With ipython installed:
$ env PYTEST_ADDOPTS="--pdbcls=IPython.terminal.debugger:TerminalPdb" just start
Manual invocation¶
Test a single file:
$ py.test tests/test_config.py
A single test inside it:
$ py.test tests/test_config.py::test_export_json
Several at once, space-separated:
$ py.test tests/test_{window,pane}.py tests/test_config.py::test_export_json
Visual testing¶
You can watch the suite build sessions in real time by keeping a client open in a second terminal.
Terminal 1 — start a server on the test socket:
$ tmux -L test_case
Terminal 2 — from the tmuxp checkout (and your virtualenv, if you use one), run the builder tests:
$ py.test tests/workspace/test_builder.py
Terminal 1 flickers as sessions build before your eyes — the building tmuxp normally hides from users.
Testing options¶
Set RETRY_TIMEOUT_SECONDS if certain workspace-builder tests are stubborn on
your machine, e.g. RETRY_TIMEOUT_SECONDS=10 py.test. CI runs the same suite:
name: tests
on: [push, pull_request]
jobs:
build:
# Don't run twice for internal PRs from our own repo
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.14']
tmux-version: ['3.2a', '3.3a', '3.4', '3.5', '3.6', '3.7', 'master']
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Test runtime dependencies
run: |
uv run --no-dev -p python${{ matrix.python-version }} -- python -c '
from tmuxp import _internal, cli, workspace, exc, log, plugin, shell, types, util, __version__
from tmuxp._internal import config_reader, types
from tmuxp.cli import convert, debug_info, edit, freeze, import_config, load, ls, shell, utils
from tmuxp.workspace import builder, constants, finders, freezer, importers, loader, validation
from libtmux import __version__ as __libtmux_version__
print("tmuxp version:", __version__)
print("libtmux version:", __libtmux_version__)
'
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Setup tmux build cache for tmux ${{ matrix.tmux-version }}
id: tmux-build-cache
uses: actions/cache@v5
with:
path: ~/tmux-builds/tmux-${{ matrix.tmux-version }}
key: tmux-${{ matrix.tmux-version }}
- name: Build tmux ${{ matrix.tmux-version }}
if: steps.tmux-build-cache.outputs.cache-hit != 'true'
run: |
sudo apt install libevent-dev libncurses5-dev libtinfo-dev libutempter-dev bison
mkdir ~/tmux-builds
mkdir ~/tmux-src
git clone https://github.com/tmux/tmux.git ~/tmux-src/tmux-${{ matrix.tmux-version }}
cd ~/tmux-src/tmux-${{ matrix.tmux-version }}
git checkout ${{ matrix.tmux-version }}
sh autogen.sh
./configure --prefix=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }} && make && make install
export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH
cd ~
tmux -V
- name: Lint with ruff check .
run: uv run ruff check .
- name: Format with ruff
run: uv run ruff format . --check
- name: Lint with mypy
run: uv run mypy .
- name: Print python versions
run: |
python -V
uv run python -V
- name: Test with pytest
continue-on-error: ${{ matrix.tmux-version == 'master' }}
run: |
sudo apt install libevent-2.1-7
export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH
ls $HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin
tmux -V
uv run py.test --cov=./ --cov-report=xml --verbose
- uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
release:
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
permissions:
id-token: write
attestations: write
strategy:
matrix:
python-version: ['3.14']
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Build package
run: uv build
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
attestations: true
skip-existing: true
Documentation¶
Rebuild the docs whenever a source file changes:
$ just watch-docs
tmuxp developer config¶
After you Install the latest code from git, load the project’s own workspace from the checkout root:
$ tmuxp load .
This loads the .tmuxp.yaml at the project root:
session_name: tmuxp
start_directory: ./ # load session relative to config location (project root).
shell_command_before:
- uv virtualenv --quiet > /dev/null 2>&1 && clear
windows:
- window_name: tmuxp
focus: True
layout: main-horizontal
options:
main-pane-height: 67%
panes:
- focus: true
- pane
- just watch-mypy
- just watch-test
- window_name: docs
layout: main-horizontal
options:
main-pane-height: 67%
start_directory: docs/
panes:
- focus: true
- pane
- pane
- just start
Formatting¶
Linting¶
The project uses ruff for linting, import sorting, and formatting.
Lint:
$ just ruff
Autofix what ruff can:
$ uv run ruff check . --fix --show-fixes
Formatting¶
ruff format handles formatting:
$ just ruff-format
Type checking¶
mypy does static type checking:
$ just mypy
Re-check on change:
$ just watch-mypy
Continuous integration¶
tmuxp uses GitHub Actions for continuous integration. To see the tmux and
Python versions under test, read .github/workflows/tests.yml. Builds run on
master and on pull requests, and are visible on the build site.