Skip to content

Commit b34ed38

Browse files
authored
feat!(pytest plugin): Add a pytest plugin for libtmux (#411)
Add a pytest plugin for libtmux. This requires doing some things to fix coverage (which changes when a conrtest.py is moved to a pytest plugin, since code touched by the pytest plugins isn't incorporated by default). Also code that normally would run automatically via autouse=True was set to not be autoused.
2 parents 35e6dfa + 87b13bf commit b34ed38

19 files changed

+372
-157
lines changed

Diff for: .coveragerc

+15-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
[run]
2+
parallel = 1
3+
branch = 1
4+
25
omit =
3-
test/*
4-
*/_vendor/*
5-
*/_*
6-
pkg/*
7-
*/log.py
6+
docs/conf.py
7+
*/_compat.py
88

99
[report]
10+
skip_covered = True
11+
show_missing = True
1012
exclude_lines =
11-
pragma: no cover
12-
def __repr__
13-
raise NotImplementedError
14-
if __name__ == .__main__.:
15-
def parse_args
13+
\#\s*pragma: no cover
14+
^\s*raise NotImplementedError\b
15+
^\s*return NotImplemented\b
16+
^\s*assert False(,|$)
17+
^\s*assert_never\(
18+
19+
^\s*if TYPE_CHECKING:
20+
^\s*@overload( |$)

Diff for: .github/workflows/tests.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ jobs:
6868
export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH
6969
ls $HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin
7070
tmux -V
71-
poetry run py.test --cov=./ --cov-report=xml
71+
poetry run py.test --cov=./ --cov-append --cov-report=xml
72+
env:
73+
COV_CORE_SOURCE: .
74+
COV_CORE_CONFIG: .coveragerc
75+
COV_CORE_DATAFILE: .coverage.eager
7276
- uses: codecov/codecov-action@v2
7377
with:
7478
token: ${{ secrets.CODECOV_TOKEN }}

Diff for: .prettierrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"printWidth": 100
2+
"printWidth": 100,
3+
"proseWrap": 'always'
34
}

Diff for: README.md

+13-13
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,23 @@ List sessions:
7070

7171
```python
7272
>>> server.list_sessions()
73-
[Session($... libtmux_...), Session($... ...)]
73+
[Session($1 ...), Session($0 ...)]
7474
```
7575

7676
Find session:
7777

7878
```python
79-
>>> server.get_by_id('$0')
80-
Session($... ...)
79+
>>> server.get_by_id('$1')
80+
Session($1 ...)
8181
```
8282

8383
Find session by dict lookup:
8484

8585
```python
8686
>>> server.sessions[0].rename_session('foo')
87-
Session($... foo)
87+
Session($1 foo)
8888
>>> server.find_where({ "session_name": "foo" })
89-
Session($... foo)
89+
Session($1 foo)
9090
```
9191

9292
Control your session:
@@ -103,7 +103,7 @@ Create new window in the background (don't switch to it):
103103

104104
```python
105105
>>> session.new_window(attach=False, window_name="ha in the bg")
106-
Window(@... ...:ha in the bg, Session($... libtmux_...))
106+
Window(@2 2:ha in the bg, Session($1 ...))
107107
```
108108

109109
Close window:
@@ -118,14 +118,14 @@ Grab remaining tmux window:
118118
```python
119119
>>> window = session.attached_window
120120
>>> window.split_window(attach=False)
121-
Pane(%... Window(@... ...:..., Session($... libtmux_...)))
121+
Pane(%2 Window(@1 1:... Session($1 ...)))
122122
```
123123

124124
Rename window:
125125

126126
```python
127127
>>> window.rename_window('libtmuxower')
128-
Window(@... ...:libtmuxower, Session($... ...))
128+
Window(@1 1:libtmuxower, Session($1 ...))
129129
```
130130

131131
Split window (create a new pane):
@@ -134,13 +134,13 @@ Split window (create a new pane):
134134
>>> pane = window.split_window()
135135
>>> pane = window.split_window(attach=False)
136136
>>> pane.select_pane()
137-
Pane(%... Window(@... ...:..., Session($... libtmux_...)))
137+
Pane(%3 Window(@1 1:..., Session($1 ...)))
138138
>>> window = session.new_window(attach=False, window_name="test")
139139
>>> window
140-
Window(@... ...:test, Session($...))
140+
Window(@2 2:test, Session($1 ...))
141141
>>> pane = window.split_window(attach=False)
142142
>>> pane
143-
Pane(%... Window(@... ...:..., Session($... libtmux_...)))
143+
Pane(%5 Window(@2 2:test, Session($1 ...)))
144144
```
145145

146146
Type inside the pane (send key strokes):
@@ -175,9 +175,9 @@ Traverse and navigate:
175175

176176
```python
177177
>>> pane.window
178-
Window(@... ...:..., Session($... ...))
178+
Window(@1 1:..., Session($1 ...))
179179
>>> pane.window.session
180-
Session($... ...)
180+
Session($1 ...)
181181
```
182182

183183
# Python support

Diff for: docs/conf.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@
160160
)
161161
]
162162

163-
intersphinx_mapping = {"http://docs.python.org/": None}
163+
intersphinx_mapping = {
164+
"": ("https://docs.python.org/", None),
165+
"pytest": ("https://docs.pytest.org/en/stable/", None),
166+
}
164167

165168

166169
def linkcode_resolve(

Diff for: docs/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from libtmux.conftest import * # NOQA: F4

Diff for: docs/developing.md

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ Makefile commands prefixed with `watch_` will watch files and rerun.
2323
Helpers: `make test`
2424
Rerun tests on file change: `make watch_test` (requires [entr(1)])
2525

26+
### Pytest plugin
27+
28+
:::{seealso}
29+
30+
See {ref}`pytest_plugin`.
31+
32+
:::
33+
2634
## Documentation
2735

2836
Default preview server: http://localhost:8023

Diff for: docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ api
2828
:hidden:
2929
3030
developing
31+
pytest-plugin
3132
internals/index
3233
history
3334
glossary

Diff for: docs/pytest-plugin.md

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
(pytest_plugin)=
2+
3+
# `pytest` plugin
4+
5+
Testing tmux with libtmux
6+
7+
```{seealso} Using libtmux?
8+
9+
Do you want more flexbility? Correctness? Power? Defaults changed? [Connect with us] on the tracker, we want to know
10+
your case, we won't stabilize APIs until we're sure everything is by the book.
11+
12+
[connect with us]: https://github.com/tmux-python/libtmux/discussions
13+
14+
```
15+
16+
```{module} libtmux.pytest_plugin
17+
18+
```
19+
20+
## Usage
21+
22+
Install `libtmux` via the python package manager of your choosing, e.g.
23+
24+
```console
25+
$ pip install libtmux
26+
```
27+
28+
The pytest plugin will automatically be detected via pytest, and the fixtures will be added.
29+
30+
## Fixtures
31+
32+
`pytest-tmux` works through providing {ref}`pytest fixtures <pytest:fixtures-api>` - so read up on
33+
those!
34+
35+
The plugin's fixtures guarantee a fresh, headless `tmux(1)` server, session, window, or pane is
36+
passed into your test.
37+
38+
(recommended-fixtures)=
39+
40+
## Recommended fixtures
41+
42+
These are fixtures are automatically used when the plugin is enabled and `pytest` is ran.
43+
44+
- Creating temporary, test directories for:
45+
- `/home/` ({func}`home_path`)
46+
- `/home/${user}` ({func}`user_path`)
47+
- Default `.tmux.conf` configuration with these settings ({func}`config_file`):
48+
49+
- `base-index -g 1`
50+
51+
These are set to ensure panes and windows can be reliably referenced and asserted.
52+
53+
## Setting a tmux configuration
54+
55+
If you would like :func:`session` fixture to automatically use a configuration, you have a few
56+
options:
57+
58+
- Pass a `config_file` into :class:`libtmux.server.Server`
59+
- Set the `HOME` directory to a local or temporary pytest path with a configurat configuration file
60+
61+
You could also read the code and override :func:`server`'s fixture in your own doctest. doctest.
62+
63+
(set_home)=
64+
65+
### Setting a temporary home directory
66+
67+
```python
68+
import pathlib
69+
import pytest
70+
71+
@pytest.fixture(autouse=True, scope="function")
72+
def set_home(
73+
monkeypatch: pytest.MonkeyPatch,
74+
user_path: pathlib.Path,
75+
):
76+
monkeypatch.setenv("HOME", str(user_path))
77+
```
78+
79+
## See examples
80+
81+
View libtmux's own [tests/](https://github.com/tmux-python/libtmux/tree/master/tests) as well as
82+
tmuxp's [tests/](https://github.com/tmux-python/tmuxp/tree/master/tests).
83+
84+
libtmux's tests `autouse` the {ref}`recommended-fixtures` above to ensure stable, assertions and
85+
object lookups in the test grid.
86+
87+
## API reference
88+
89+
```{eval-rst}
90+
.. autoapimodule:: libtmux.pytest_plugin
91+
:members:
92+
:inherited-members:
93+
:private-members:
94+
:show-inheritance:
95+
:member-order: bysource
96+
:exclude-members: Server, TEST_SESSION_PREFIX, get_test_session_name,
97+
namer, logger
98+
```

Diff for: docs/quickstart.md

+14-14
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,15 @@ We can list sessions with {meth}`Server.list_sessions`:
139139

140140
```python
141141
>>> server.list_sessions()
142-
[Session($... ...), Session($... ...)]
142+
[Session($1 ...), Session($0 ...)]
143143
```
144144

145145
This returns a list of {class}`Session` objects you can grab. We can
146146
find our current session with:
147147

148148
```python
149149
>>> server.list_sessions()[0]
150-
Session($... ...)
150+
Session($1 ...)
151151
```
152152

153153
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.
162162

163163
```python
164164
>>> server.get_by_id('$1')
165-
Session($... ...)
165+
Session($1 ...)
166166
```
167167

168168
You may `session = server.get_by_id('$<yourId>')` to use the session object.
@@ -172,10 +172,10 @@ You may `session = server.get_by_id('$<yourId>')` to use the session object.
172172
```python
173173
# Just for setting up the example:
174174
>>> server.sessions[0].rename_session('foo')
175-
Session($... foo)
175+
Session($1 foo)
176176

177177
>>> server.find_where({ "session_name": "foo" })
178-
Session($... foo)
178+
Session($1 foo)
179179
```
180180

181181
With `find_where`, pass in a dict and return the first object found. In
@@ -188,11 +188,11 @@ So you may now use:
188188
```python
189189
# Prepping the example:
190190
>>> server.sessions[0].rename_session('foo')
191-
Session($... foo)
191+
Session($1 foo)
192192

193193
>>> session = server.find_where({ "session_name": "foo" })
194194
>>> session
195-
Session($... foo)
195+
Session($1 foo)
196196
```
197197

198198
to give us a `session` object to play with.
@@ -206,7 +206,7 @@ Let's make a {meth}`Session.new_window`, in the background:
206206

207207
```python
208208
>>> session.new_window(attach=False, window_name="ha in the bg")
209-
Window(@... ...:ha in the bg, Session($... ...))
209+
Window(@2 ...:ha in the bg, Session($1 ...))
210210
```
211211

212212
So a few things:
@@ -245,15 +245,15 @@ should have history, so navigate up with the arrow key.
245245

246246
```python
247247
>>> session.new_window(attach=False, window_name="ha in the bg")
248-
Window(@... ...:ha in the bg, Session($... ...))
248+
Window(@2 ...:ha in the bg, Session($1 ...))
249249
```
250250

251251
Try to kill the window by the matching id `@[0-9999]`.
252252

253253
```python
254254
# Setup
255255
>>> session.new_window(attach=False, window_name="ha in the bg")
256-
Window(@... ...:ha in the bg, Session($... ...))
256+
Window(@1 ...:ha in the bg, Session($1 ...))
257257

258258
>>> session.kill_window('ha in the bg')
259259
```
@@ -264,7 +264,7 @@ object:
264264
```python
265265
>>> window = session.new_window(attach=False, window_name="check this out")
266266
>>> window
267-
Window(@... ...:check this out, Session($... ...))
267+
Window(@2 2:check this out, Session($1 ...))
268268
```
269269

270270
And kill:
@@ -291,7 +291,7 @@ Let's create a pane, {meth}`Window.split_window`:
291291

292292
```python
293293
>>> window.split_window(attach=False)
294-
Pane(%... Window(@... ...:..., Session($... ...)))
294+
Pane(%2 Window(@1 ...:..., Session($1 ...)))
295295
```
296296

297297
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:
304304

305305
```python
306306
>>> window.rename_window('libtmuxower')
307-
Window(@... ...:..., Session($... ...))
307+
Window(@1 ...:..., Session($1 ...))
308308
```
309309

310310
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
329329

330330
```python
331331
>>> pane.select_pane()
332-
Pane(%... Window(@... ...:..., Session($... ...)))
332+
Pane(%1 Window(@1 ...:..., Session($1 ...)))
333333
```
334334

335335
```{eval-rst}

0 commit comments

Comments
 (0)