From c2cb01d205beda6a9e90e7817e8f51f15ddb0be8 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 05:16:57 -0500 Subject: [PATCH 01/11] ci(coverage): Update coverage configuration This is based on pytest's See also: https://github.com/pytest-dev/pytest/blob/7.1.x/.coveragerc --- .coveragerc | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.coveragerc b/.coveragerc index 9ed88302e..7c74c56a0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,15 +1,20 @@ [run] +parallel = 1 +branch = 1 + omit = - test/* - */_vendor/* - */_* - pkg/* - */log.py + docs/conf.py + */_compat.py [report] +skip_covered = True +show_missing = True exclude_lines = - pragma: no cover - def __repr__ - raise NotImplementedError - if __name__ == .__main__.: - def parse_args + \#\s*pragma: no cover + ^\s*raise NotImplementedError\b + ^\s*return NotImplemented\b + ^\s*assert False(,|$) + ^\s*assert_never\( + + ^\s*if TYPE_CHECKING: + ^\s*@overload( |$) From 6a12794b4659acc99480d4eab979c200dc46e9e6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 9 Sep 2022 06:58:49 -0500 Subject: [PATCH 02/11] build(deps): pytest_plugin.py infrastructure --- libtmux/{conftest.py => pytest_plugin.py} | 0 pyproject.toml | 3 +++ tests/conftest.py | 1 - 3 files changed, 3 insertions(+), 1 deletion(-) rename libtmux/{conftest.py => pytest_plugin.py} (100%) diff --git a/libtmux/conftest.py b/libtmux/pytest_plugin.py similarity index 100% rename from libtmux/conftest.py rename to libtmux/pytest_plugin.py diff --git a/pyproject.toml b/pyproject.toml index a4d41744f..069a9bab9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,9 @@ coverage = ["codecov", "coverage", "pytest-cov"] format = ["black", "isort"] lint = ["flake8", "flake8-bugbear", "flake8-comprehensions", "mypy"] +[tool.poetry.plugins.pytest11] +libtmux = "libtmux.pytest_plugin" + [tool.mypy] strict = true diff --git a/tests/conftest.py b/tests/conftest.py index 535e57934..e69de29bb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +0,0 @@ -from libtmux.conftest import * # noqa F40 From 247962f8e3a96846a26fc17315d22416461ad255 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 9 Sep 2022 07:01:43 -0500 Subject: [PATCH 03/11] tests: Change prefix to libtmux_ --- libtmux/pytest_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtmux/pytest_plugin.py b/libtmux/pytest_plugin.py index 280d58253..9e045f13e 100644 --- a/libtmux/pytest_plugin.py +++ b/libtmux/pytest_plugin.py @@ -51,7 +51,7 @@ def clear_env(monkeypatch: MonkeyPatch) -> None: def server(request: SubRequest, monkeypatch: MonkeyPatch) -> Server: t = Server() - t.socket_name = "tmuxp_test%s" % next(namer) + t.socket_name = "libtmux_test%s" % next(namer) def fin() -> None: t.kill_server() From 07302485e11c4ff869f7d42625556096adcfc676 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 05:32:36 -0500 Subject: [PATCH 04/11] ci(pytest): Fix coverage due to pytest plugin change Moving from conftest.py to pytest_plugin.py (which comes via a setuptools entry point) requires a workaround to prevent loss of coverage from real libtmux tests relying on our pytest plugin. See also: - https://pytest-cov.readthedocs.io/en/latest/plugins.html - https://github.com/pytest-dev/pytest/issues/935#issuecomment-245107960 --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e5443194d..64b3ba367 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -68,7 +68,11 @@ jobs: export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH ls $HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin tmux -V - poetry run py.test --cov=./ --cov-report=xml + poetry run py.test --cov=./ --cov-append --cov-report=xml + env: + COV_CORE_SOURCE: . + COV_CORE_CONFIG: .coveragerc + COV_CORE_DATAFILE: .coverage.eager - uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} From 2aa5408c5eb9ecfa8e59de930d53eb073c997d4b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 06:33:35 -0500 Subject: [PATCH 05/11] pytest plugin: Add default tmux configuration --- libtmux/pytest_plugin.py | 50 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/libtmux/pytest_plugin.py b/libtmux/pytest_plugin.py index 9e045f13e..49373c1bb 100644 --- a/libtmux/pytest_plugin.py +++ b/libtmux/pytest_plugin.py @@ -1,5 +1,7 @@ +import getpass import logging import os +import pathlib import shutil import typing as t @@ -17,6 +19,47 @@ from libtmux.session import Session logger = logging.getLogger(__name__) +USING_ZSH = "zsh" in os.getenv("SHELL", "") + + +@pytest.fixture(autouse=True, scope="session") +def home_path(tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path: + return tmp_path_factory.mktemp("home") + + +@pytest.fixture(autouse=True, scope="session") +def user_path(home_path: pathlib.Path) -> pathlib.Path: + p = home_path / getpass.getuser() + p.mkdir() + return p + + +@pytest.mark.skipif(USING_ZSH, reason="Using ZSH") +@pytest.fixture(autouse=USING_ZSH, scope="session") +def zshrc(user_path: pathlib.Path) -> pathlib.Path: + """This quiets ZSH default message. + + Needs a startup file .zshenv, .zprofile, .zshrc, .zlogin. + """ + p = user_path / ".zshrc" + p.touch() + return p + + +@pytest.fixture(scope="function") +def config_file(user_path: pathlib.Path) -> pathlib.Path: + """Set default tmux configuration (base indexes for windows, panes) + + We need this for tests to work across tmux versions in our CI matrix. + """ + c = user_path / ".tmux.conf" + c.write_text( + """ +set -g base-index 1 + """, + encoding="utf-8", + ) + return c @pytest.fixture(autouse=True) @@ -48,9 +91,10 @@ def clear_env(monkeypatch: MonkeyPatch) -> None: @pytest.fixture(scope="function") -def server(request: SubRequest, monkeypatch: MonkeyPatch) -> Server: - - t = Server() +def server( + request: SubRequest, monkeypatch: MonkeyPatch, config_file: pathlib.Path +) -> Server: + t = Server(config_file=str(config_file.absolute())) t.socket_name = "libtmux_test%s" % next(namer) def fin() -> None: From ffa8483a88d4fd9617697b3b7978e5666f34fc63 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 06:20:25 -0500 Subject: [PATCH 06/11] docs: Base indexes This makes the docs look more realistic, but we need to make it work across tmux versions and python versions put into the CI matrix. --- README.md | 26 +++++++++++++------------- docs/quickstart.md | 28 ++++++++++++++-------------- docs/reference/properties.md | 10 +++++----- docs/topics/traversal.md | 16 ++++++++-------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 9c751f3dd..b3ec76ad6 100644 --- a/README.md +++ b/README.md @@ -70,23 +70,23 @@ List sessions: ```python >>> server.list_sessions() -[Session($... libtmux_...), Session($... ...)] +[Session($1 ...), Session($0 ...)] ``` Find session: ```python ->>> server.get_by_id('$0') -Session($... ...) +>>> server.get_by_id('$1') +Session($1 ...) ``` Find session by dict lookup: ```python >>> server.sessions[0].rename_session('foo') -Session($... foo) +Session($1 foo) >>> server.find_where({ "session_name": "foo" }) -Session($... foo) +Session($1 foo) ``` Control your session: @@ -103,7 +103,7 @@ Create new window in the background (don't switch to it): ```python >>> session.new_window(attach=False, window_name="ha in the bg") -Window(@... ...:ha in the bg, Session($... libtmux_...)) +Window(@2 2:ha in the bg, Session($1 ...)) ``` Close window: @@ -118,14 +118,14 @@ Grab remaining tmux window: ```python >>> window = session.attached_window >>> window.split_window(attach=False) -Pane(%... Window(@... ...:..., Session($... libtmux_...))) +Pane(%2 Window(@1 1:... Session($1 ...))) ``` Rename window: ```python >>> window.rename_window('libtmuxower') -Window(@... ...:libtmuxower, Session($... ...)) +Window(@1 1:libtmuxower, Session($1 ...)) ``` Split window (create a new pane): @@ -134,13 +134,13 @@ Split window (create a new pane): >>> pane = window.split_window() >>> pane = window.split_window(attach=False) >>> pane.select_pane() -Pane(%... Window(@... ...:..., Session($... libtmux_...))) +Pane(%3 Window(@1 1:..., Session($1 ...))) >>> window = session.new_window(attach=False, window_name="test") >>> window -Window(@... ...:test, Session($...)) +Window(@2 2:test, Session($1 ...)) >>> pane = window.split_window(attach=False) >>> pane -Pane(%... Window(@... ...:..., Session($... libtmux_...))) +Pane(%5 Window(@2 2:test, Session($1 ...))) ``` Type inside the pane (send key strokes): @@ -175,9 +175,9 @@ Traverse and navigate: ```python >>> pane.window -Window(@... ...:..., Session($... ...)) +Window(@1 1:..., Session($1 ...)) >>> pane.window.session -Session($... ...) +Session($1 ...) ``` # Python support diff --git a/docs/quickstart.md b/docs/quickstart.md index f2cef0b99..8d8b19350 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -139,7 +139,7 @@ We can list sessions with {meth}`Server.list_sessions`: ```python >>> server.list_sessions() -[Session($... ...), Session($... ...)] +[Session($1 ...), Session($0 ...)] ``` This returns a list of {class}`Session` objects you can grab. We can @@ -147,7 +147,7 @@ find our current session with: ```python >>> server.list_sessions()[0] -Session($... ...) +Session($1 ...) ``` However, this isn't guaranteed, libtmux works against current tmux information, the @@ -162,7 +162,7 @@ tmux sessions use the `$[0-9]` convention as a way to identify sessions. ```python >>> server.get_by_id('$1') -Session($... ...) +Session($1 ...) ``` You may `session = server.get_by_id('$')` to use the session object. @@ -172,10 +172,10 @@ You may `session = server.get_by_id('$')` to use the session object. ```python # Just for setting up the example: >>> server.sessions[0].rename_session('foo') -Session($... foo) +Session($1 foo) >>> server.find_where({ "session_name": "foo" }) -Session($... foo) +Session($1 foo) ``` With `find_where`, pass in a dict and return the first object found. In @@ -188,11 +188,11 @@ So you may now use: ```python # Prepping the example: >>> server.sessions[0].rename_session('foo') -Session($... foo) +Session($1 foo) >>> session = server.find_where({ "session_name": "foo" }) >>> session -Session($... foo) +Session($1 foo) ``` to give us a `session` object to play with. @@ -206,7 +206,7 @@ Let's make a {meth}`Session.new_window`, in the background: ```python >>> session.new_window(attach=False, window_name="ha in the bg") -Window(@... ...:ha in the bg, Session($... ...)) +Window(@2 ...:ha in the bg, Session($1 ...)) ``` So a few things: @@ -245,7 +245,7 @@ should have history, so navigate up with the arrow key. ```python >>> session.new_window(attach=False, window_name="ha in the bg") -Window(@... ...:ha in the bg, Session($... ...)) +Window(@2 ...:ha in the bg, Session($1 ...)) ``` Try to kill the window by the matching id `@[0-9999]`. @@ -253,7 +253,7 @@ Try to kill the window by the matching id `@[0-9999]`. ```python # Setup >>> session.new_window(attach=False, window_name="ha in the bg") -Window(@... ...:ha in the bg, Session($... ...)) +Window(@1 ...:ha in the bg, Session($1 ...)) >>> session.kill_window('ha in the bg') ``` @@ -264,7 +264,7 @@ object: ```python >>> window = session.new_window(attach=False, window_name="check this out") >>> window -Window(@... ...:check this out, Session($... ...)) +Window(@2 2:check this out, Session($1 ...)) ``` And kill: @@ -291,7 +291,7 @@ Let's create a pane, {meth}`Window.split_window`: ```python >>> window.split_window(attach=False) -Pane(%... Window(@... ...:..., Session($... ...))) +Pane(%2 Window(@1 ...:..., Session($1 ...))) ``` Powered up. Let's have a break down: @@ -304,7 +304,7 @@ Also, since you are aware of this power, let's commemorate the experience: ```python >>> window.rename_window('libtmuxower') -Window(@... ...:..., Session($... ...)) +Window(@1 ...:..., Session($1 ...)) ``` You should have noticed {meth}`Window.rename_window` renamed the window. @@ -329,7 +329,7 @@ can also use the `.select_*` available on the object, in this case the pane has ```python >>> pane.select_pane() -Pane(%... Window(@... ...:..., Session($... ...))) +Pane(%1 Window(@1 ...:..., Session($1 ...))) ``` ```{eval-rst} diff --git a/docs/reference/properties.md b/docs/reference/properties.md index a2061fa12..085361f3f 100644 --- a/docs/reference/properties.md +++ b/docs/reference/properties.md @@ -43,7 +43,7 @@ Get the {class}`~libtmux.Session` object: ```python >>> session = server.sessions[0] >>> session -Session($... libtmux_...) +Session($1 libtmux_...) ``` Quick access to basic attributes: @@ -53,7 +53,7 @@ Quick access to basic attributes: 'libtmux_...' >>> session.id -'$...' +'$1' ``` To see all attributes for a session: @@ -79,7 +79,7 @@ The same concepts apply for {class}`~libtmux.Window`: >>> window = session.attached_window >>> window -Window(@... ...:..., Session($... ...)) +Window(@1 ...:..., Session($1 ...)) ``` Basics: @@ -89,7 +89,7 @@ Basics: '...' >>> window.id -'@...' +'@1' >>> window.height '...' @@ -120,7 +120,7 @@ Get the {class}`~libtmux.Pane`: >>> pane = window.attached_pane >>> pane -Pane(%... Window(@... ...:..., Session($... libtmux_...))) +Pane(%1 Window(@1 ...:..., Session($1 libtmux_...))) ``` Basics: diff --git a/docs/topics/traversal.md b/docs/topics/traversal.md index a03b515c0..02b537306 100644 --- a/docs/topics/traversal.md +++ b/docs/topics/traversal.md @@ -42,14 +42,14 @@ Get first session {class}`~libtmux.Session` to `session`: ```python >>> session = server.sessions[0] >>> session -Session($... ...) +Session($1 ...) ``` Get a list of sessions: ```python >>> server.sessions -[Session($... ...), Session($... ...)] +[Session($1 ...), Session($0 ...)] ``` Iterate through sessions in a server: @@ -57,29 +57,29 @@ Iterate through sessions in a server: ```python >>> for sess in server.sessions: ... print(sess) -Session($... ...) -Session($... ...) +Session($1 ...) +Session($0 ...) ``` Grab a {class}`~libtmux.Window` from a session: ```python >>> session.windows[0] -Window(@... ...:..., Session($... ...)) +Window(@1 ...:..., Session($1 ...)) ``` Grab the currently focused window from session: ```python >>> session.attached_window -Window(@... ...:..., Session($... ...)) +Window(@1 ...:..., Session($1 ...)) ``` Grab the currently focused {class}`Pane` from session: ```python >>> session.attached_pane -Pane(%... Window(@... ...:..., Session($... ...))) +Pane(%1 Window(@1 ...:..., Session($1 ...))) ``` Assign the attached {class}`~libtmux.Pane` to `p`: @@ -93,7 +93,7 @@ Access the window/server of a pane: ```python >>> p = session.attached_pane >>> p.window -Window(@... ...:..., Session($... ...)) +Window(@1 ...:..., Session($1 ...)) >>> p.server From 057bdec4e621fdea311f77803100b6ec39cf092c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 06:52:05 -0500 Subject: [PATCH 07/11] docs(libtmux modules): Base index output improvements --- libtmux/pane.py | 4 ++-- libtmux/server.py | 6 +++--- libtmux/window.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libtmux/pane.py b/libtmux/pane.py index 0ac259f99..469a0d9b3 100644 --- a/libtmux/pane.py +++ b/libtmux/pane.py @@ -39,13 +39,13 @@ class Pane(TmuxMappingObject): Examples -------- >>> pane - Pane(%1 Window(@1 ...:..., Session($1 ...))) + Pane(%1 Window(@1 1:..., Session($1 ...))) >>> pane in window.panes True >>> pane.window - Window(@1 ...:..., Session($1 ...)) + Window(@1 1:..., Session($1 ...)) >>> pane.session Session($1 ...) diff --git a/libtmux/server.py b/libtmux/server.py index e0e7c35c7..a049f3b9e 100644 --- a/libtmux/server.py +++ b/libtmux/server.py @@ -56,13 +56,13 @@ class Server(TmuxRelationalObject["Session", "SessionDict"], EnvironmentMixin): [Session($1 ...)] >>> server.sessions[0].windows - [Window(@1 ...:..., Session($1 ...)] + [Window(@1 1:..., Session($1 ...)] >>> server.sessions[0].attached_window - Window(@1 ...:..., Session($1 ...) + Window(@1 1:..., Session($1 ...) >>> server.sessions[0].attached_pane - Pane(%1 Window(@1 ...:..., Session($1 ...))) + Pane(%1 Window(@1 1:..., Session($1 ...))) References ---------- diff --git a/libtmux/window.py b/libtmux/window.py index d38b8298c..434dca17c 100644 --- a/libtmux/window.py +++ b/libtmux/window.py @@ -44,7 +44,7 @@ class Window(TmuxMappingObject, TmuxRelationalObject["Pane", "PaneDict"]): >>> window = session.new_window('My project') >>> window - Window(@... ...:My project, Session($... ...)) + Window(@2 2:My project, Session($... ...)) Windows have panes: @@ -329,10 +329,10 @@ def rename_window(self, new_name: str) -> "Window": >>> window = session.attached_window >>> window.rename_window('My project') - Window(@1 ...:My project, Session($1 ...)) + Window(@1 1:My project, Session($1 ...)) >>> window.rename_window('New name') - Window(@1 ...:New name, Session($1 ...)) + Window(@1 1:New name, Session($1 ...)) """ import shlex From 71176d8a1aec7c27764d8ed49b306763ca9ec62e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 07:28:49 -0500 Subject: [PATCH 08/11] docs(prettier): Line wrapping --- .prettierrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index de753c537..2d553cc6c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "printWidth": 100 + "printWidth": 100, + "proseWrap": 'always' } From 14cde4dd84bc87f8c75a0ca6770293c3763766bc Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 07:28:37 -0500 Subject: [PATCH 09/11] docs(conf): Add pytest to intersphinx_mapping --- docs/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 1bbb683df..94c7f7fd6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -160,7 +160,10 @@ ) ] -intersphinx_mapping = {"http://docs.python.org/": None} +intersphinx_mapping = { + "": ("https://docs.python.org/", None), + "pytest": ("https://docs.pytest.org/en/stable/", None), +} def linkcode_resolve( From 17837f72d9f4558c03120d2baa0aafbf53e324b3 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 07:39:09 -0500 Subject: [PATCH 10/11] refactor!(pytest_plugin): Remove all autouse from default usage This prevents plugins from being invoked simply by having libtmux installed. We don't want that - it would interrupt systems. Explicit is better than implicit. Move autouse for libtmux itself to the appropriate places. --- docs/conftest.py | 1 + libtmux/conftest.py | 44 +++++++++++++++++++++++++++ libtmux/pytest_plugin.py | 65 +++++++++++++++++++++------------------- tests/conftest.py | 1 + 4 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 docs/conftest.py create mode 100644 libtmux/conftest.py diff --git a/docs/conftest.py b/docs/conftest.py new file mode 100644 index 000000000..62571f546 --- /dev/null +++ b/docs/conftest.py @@ -0,0 +1 @@ +from libtmux.conftest import * # NOQA: F4 diff --git a/libtmux/conftest.py b/libtmux/conftest.py new file mode 100644 index 000000000..c99992127 --- /dev/null +++ b/libtmux/conftest.py @@ -0,0 +1,44 @@ +import pathlib +import shutil +import typing as t + +import pytest + +from _pytest.doctest import DoctestItem + +from libtmux.pytest_plugin import USING_ZSH + +if t.TYPE_CHECKING: + from libtmux.session import Session + + +@pytest.fixture(autouse=True) +def add_doctest_fixtures( + request: pytest.FixtureRequest, + doctest_namespace: t.Dict[str, t.Any], +) -> None: + if isinstance(request._pyfuncitem, DoctestItem) and shutil.which("tmux"): + request.getfixturevalue("set_home") + doctest_namespace["server"] = request.getfixturevalue("server") + session: "Session" = request.getfixturevalue("session") + doctest_namespace["session"] = session + doctest_namespace["window"] = session.attached_window + doctest_namespace["pane"] = session.attached_pane + + +@pytest.fixture(autouse=True, scope="function") +def set_home( + monkeypatch: pytest.MonkeyPatch, + user_path: pathlib.Path, +) -> None: + monkeypatch.setenv("HOME", str(user_path)) + + +@pytest.fixture(autouse=True, scope="session") +@pytest.mark.usefixtures("clear_env") +def setup( + request: pytest.FixtureRequest, + config_file: pathlib.Path, +) -> None: + if USING_ZSH: + request.getfixturevalue("zshrc") diff --git a/libtmux/pytest_plugin.py b/libtmux/pytest_plugin.py index 49373c1bb..863e9e44f 100644 --- a/libtmux/pytest_plugin.py +++ b/libtmux/pytest_plugin.py @@ -2,15 +2,10 @@ import logging import os import pathlib -import shutil import typing as t import pytest -from _pytest.doctest import DoctestItem -from _pytest.fixtures import SubRequest -from _pytest.monkeypatch import MonkeyPatch - from libtmux import exc from libtmux.server import Server from libtmux.test import TEST_SESSION_PREFIX, get_test_session_name, namer @@ -22,20 +17,33 @@ USING_ZSH = "zsh" in os.getenv("SHELL", "") -@pytest.fixture(autouse=True, scope="session") +@pytest.fixture(scope="session") def home_path(tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path: + """Temporary `/home/` path.""" return tmp_path_factory.mktemp("home") -@pytest.fixture(autouse=True, scope="session") -def user_path(home_path: pathlib.Path) -> pathlib.Path: - p = home_path / getpass.getuser() +@pytest.fixture(scope="session") +def home_user_name() -> str: + """Default username to set for :func:`user_path` fixture.""" + return getpass.getuser() + + +@pytest.fixture(scope="session") +def user_path(home_path: pathlib.Path, home_user_name: str) -> pathlib.Path: + """Default temporary user directory. + + Used by: :func:`config_file`, :func:`zshrc` + + Note: You will need to set the home directory, see :ref:`set_home`. + """ + p = home_path / home_user_name p.mkdir() return p @pytest.mark.skipif(USING_ZSH, reason="Using ZSH") -@pytest.fixture(autouse=USING_ZSH, scope="session") +@pytest.fixture(scope="session") def zshrc(user_path: pathlib.Path) -> pathlib.Path: """This quiets ZSH default message. @@ -46,11 +54,15 @@ def zshrc(user_path: pathlib.Path) -> pathlib.Path: return p -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def config_file(user_path: pathlib.Path) -> pathlib.Path: - """Set default tmux configuration (base indexes for windows, panes) + """Default `.tmux.conf` configuration. + + - ``base-index -g 1`` + + These guarantee pane and windows targets can be reliably referenced and asserted. - We need this for tests to work across tmux versions in our CI matrix. + Note: You will need to set the home directory, see :ref:`set_home`. """ c = user_path / ".tmux.conf" c.write_text( @@ -62,8 +74,8 @@ def config_file(user_path: pathlib.Path) -> pathlib.Path: return c -@pytest.fixture(autouse=True) -def clear_env(monkeypatch: MonkeyPatch) -> None: +@pytest.fixture +def clear_env(monkeypatch: pytest.MonkeyPatch) -> None: """Clear out any unnecessary environment variables that could interrupt tests. tmux show-environment tests were being interrupted due to a lot of crazy env vars. @@ -92,9 +104,12 @@ def clear_env(monkeypatch: MonkeyPatch) -> None: @pytest.fixture(scope="function") def server( - request: SubRequest, monkeypatch: MonkeyPatch, config_file: pathlib.Path + request: pytest.FixtureRequest, + monkeypatch: pytest.MonkeyPatch, + config_file: pathlib.Path, ) -> Server: - t = Server(config_file=str(config_file.absolute())) + """Returns a new, temporary :class:`libtmux.Server`""" + t = Server() t.socket_name = "libtmux_test%s" % next(namer) def fin() -> None: @@ -106,7 +121,8 @@ def fin() -> None: @pytest.fixture(scope="function") -def session(request: SubRequest, server: Server) -> "Session": +def session(request: pytest.FixtureRequest, server: Server) -> "Session": + """Returns a new, temporary :class:`libtmux.Session`""" session_name = "tmuxp" if not server.has_session(session_name): @@ -146,16 +162,3 @@ def session(request: SubRequest, server: Server) -> "Session": assert TEST_SESSION_NAME != "tmuxp" return session - - -@pytest.fixture(autouse=True) -def add_doctest_fixtures( - request: SubRequest, - doctest_namespace: t.Dict[str, t.Any], -) -> None: - if isinstance(request._pyfuncitem, DoctestItem) and shutil.which("tmux"): - doctest_namespace["server"] = request.getfixturevalue("server") - session: "Session" = request.getfixturevalue("session") - doctest_namespace["session"] = session - doctest_namespace["window"] = session.attached_window - doctest_namespace["pane"] = session.attached_pane diff --git a/tests/conftest.py b/tests/conftest.py index e69de29bb..62571f546 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1 @@ +from libtmux.conftest import * # NOQA: F4 From 87b13bfde265cddb853c07db84c832a1c72dd4f1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 10 Sep 2022 07:29:01 -0500 Subject: [PATCH 11/11] docs(pytest-plugin): Add pytest plugin page --- docs/developing.md | 8 ++++ docs/index.md | 1 + docs/pytest-plugin.md | 98 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 docs/pytest-plugin.md diff --git a/docs/developing.md b/docs/developing.md index 244b55b44..2ace7f008 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -23,6 +23,14 @@ Makefile commands prefixed with `watch_` will watch files and rerun. Helpers: `make test` Rerun tests on file change: `make watch_test` (requires [entr(1)]) +### Pytest plugin + +:::{seealso} + +See {ref}`pytest_plugin`. + +::: + ## Documentation Default preview server: http://localhost:8023 diff --git a/docs/index.md b/docs/index.md index bf7af19ca..ed92e5ade 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,6 +28,7 @@ api :hidden: developing +pytest-plugin internals/index history glossary diff --git a/docs/pytest-plugin.md b/docs/pytest-plugin.md new file mode 100644 index 000000000..8f546f2e3 --- /dev/null +++ b/docs/pytest-plugin.md @@ -0,0 +1,98 @@ +(pytest_plugin)= + +# `pytest` plugin + +Testing tmux with libtmux + +```{seealso} Using libtmux? + +Do you want more flexbility? Correctness? Power? Defaults changed? [Connect with us] on the tracker, we want to know +your case, we won't stabilize APIs until we're sure everything is by the book. + +[connect with us]: https://github.com/tmux-python/libtmux/discussions + +``` + +```{module} libtmux.pytest_plugin + +``` + +## Usage + +Install `libtmux` via the python package manager of your choosing, e.g. + +```console +$ pip install libtmux +``` + +The pytest plugin will automatically be detected via pytest, and the fixtures will be added. + +## Fixtures + +`pytest-tmux` works through providing {ref}`pytest fixtures ` - so read up on +those! + +The plugin's fixtures guarantee a fresh, headless `tmux(1)` server, session, window, or pane is +passed into your test. + +(recommended-fixtures)= + +## Recommended fixtures + +These are fixtures are automatically used when the plugin is enabled and `pytest` is ran. + +- Creating temporary, test directories for: + - `/home/` ({func}`home_path`) + - `/home/${user}` ({func}`user_path`) +- Default `.tmux.conf` configuration with these settings ({func}`config_file`): + + - `base-index -g 1` + + These are set to ensure panes and windows can be reliably referenced and asserted. + +## Setting a tmux configuration + +If you would like :func:`session` fixture to automatically use a configuration, you have a few +options: + +- Pass a `config_file` into :class:`libtmux.server.Server` +- Set the `HOME` directory to a local or temporary pytest path with a configurat configuration file + +You could also read the code and override :func:`server`'s fixture in your own doctest. doctest. + +(set_home)= + +### Setting a temporary home directory + +```python +import pathlib +import pytest + +@pytest.fixture(autouse=True, scope="function") +def set_home( + monkeypatch: pytest.MonkeyPatch, + user_path: pathlib.Path, +): + monkeypatch.setenv("HOME", str(user_path)) +``` + +## See examples + +View libtmux's own [tests/](https://github.com/tmux-python/libtmux/tree/master/tests) as well as +tmuxp's [tests/](https://github.com/tmux-python/tmuxp/tree/master/tests). + +libtmux's tests `autouse` the {ref}`recommended-fixtures` above to ensure stable, assertions and +object lookups in the test grid. + +## API reference + +```{eval-rst} +.. autoapimodule:: libtmux.pytest_plugin + :members: + :inherited-members: + :private-members: + :show-inheritance: + :member-order: bysource + :exclude-members: Server, TEST_SESSION_PREFIX, get_test_session_name, + namer, logger +```