Skip to content

Split init / exec of tmux_cmd for debuggability #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions libtmux/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,7 @@ def show_environment(self, name=None):
return vars_dict


class tmux_cmd:

class TmuxCommand:
"""
:term:`tmux(1)` command via :py:mod:`subprocess`.

Expand All @@ -165,7 +164,12 @@ class tmux_cmd:

.. code-block:: python

proc = tmux_cmd('new-session', '-s%' % 'my session')
c = tmux_cmd('new-session', '-s%' % 'my session')

# You can actually see the command in the .cmd attribute
print(c.cmd)

proc = c.execute()

if proc.stderr:
raise exc.LibTmuxException(
Expand All @@ -183,8 +187,8 @@ class tmux_cmd:
Notes
-----

.. versionchanged:: 0.8
Renamed from ``tmux`` to ``tmux_cmd``.
.. versionadded:: 0.8.4
Wrap to split execution from command from instance of it
"""

def __init__(self, *args, **kwargs):
Expand All @@ -205,6 +209,8 @@ def __init__(self, *args, **kwargs):

self.cmd = cmd

def execute(self):
cmd = self.cmd
try:
self.process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
Expand All @@ -229,6 +235,39 @@ def __init__(self, *args, **kwargs):
self.stdout = self.stderr[0]

logger.debug("self.stdout for {}: \n{}".format(" ".join(cmd), self.stdout))
return self


def tmux_cmd(*args, **kwargs):
"""Wrapper around TmuxCommand. Executes instantly.

Examples
--------

.. code-block:: python

proc = tmux_cmd('new-session', '-s%' % 'my session')

if proc.stderr:
raise exc.LibTmuxException(
'Command: %s returned error: %s' % (proc.cmd, proc.stderr)
)

print('tmux command returned %s' % proc.stdout)

Equivalent to:

.. code-block:: bash

$ tmux new-session -s my session

Notes
-----

.. versionchanged:: 0.8
Renamed from ``tmux`` to ``tmux_cmd``.
"""
return TmuxCommand(*args, **kwargs).execute()


class TmuxMappingObject(MutableMapping):
Expand Down
10 changes: 5 additions & 5 deletions libtmux/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
EnvironmentMixin,
PaneDict,
SessionDict,
TmuxCommand,
TmuxRelationalObject,
WindowDict,
has_gte_version,
session_check_name,
tmux_cmd,
)
from .session import Session

Expand Down Expand Up @@ -127,7 +127,7 @@ def cmd(self, *args, **kwargs):
else:
raise ValueError("Server.colors must equal 88 or 256")

return tmux_cmd(*args, **kwargs)
return TmuxCommand(*args, **kwargs).execute()

def _list_sessions(self) -> t.List[SessionDict]:
"""
Expand All @@ -136,7 +136,7 @@ def _list_sessions(self) -> t.List[SessionDict]:
Retrieved from ``$ tmux(1) list-sessions`` stdout.

The :py:obj:`list` is derived from ``stdout`` in
:class:`common.tmux_cmd` which wraps :py:class:`subprocess.Popen`.
:class:`common.TmuxCommand` which wraps :py:class:`subprocess.Popen`.

Returns
-------
Expand Down Expand Up @@ -199,7 +199,7 @@ def _list_windows(self) -> t.List[WindowDict]:
Retrieved from ``$ tmux(1) list-windows`` stdout.

The :py:obj:`list` is derived from ``stdout`` in
:class:`common.tmux_cmd` which wraps :py:class:`subprocess.Popen`.
:class:`common.TmuxCommand` which wraps :py:class:`subprocess.Popen`.

Returns
-------
Expand Down Expand Up @@ -261,7 +261,7 @@ def _list_panes(self) -> t.List[PaneDict]:
Retrieved from ``$ tmux(1) list-panes`` stdout.

The :py:obj:`list` is derived from ``stdout`` in
:class:`util.tmux_cmd` which wraps :py:class:`subprocess.Popen`.
:class:`util.TmuxCommand` which wraps :py:class:`subprocess.Popen`.

Returns
-------
Expand Down
34 changes: 31 additions & 3 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,28 @@
version_regex = re.compile(r"([0-9]\.[0-9])|(master)")


def test_allows_master_version(monkeypatch):
def mock_tmux_cmd(param):
@pytest.mark.parametrize("executor", ["mock_tmux_cmd", "mock_TmuxCommand"])
def test_allows_master_version(monkeypatch, executor):
def mock_TmuxCommand(param):
class Hi:
stdout = ["tmux master"]
stderr = None

def execute(self):
return self

return Hi()

monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_tmux_cmd)
def mock_tmux_cmd(param):
class Hi(object):
stdout = ["tmux master"]
stderr = None

return Hi()

mock_cmd = locals()[executor]

monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_cmd)

assert has_minimum_version()
assert has_gte_version(TMUX_MIN_VERSION)
Expand All @@ -51,6 +64,9 @@ class Hi:
stdout = ["tmux next-2.9"]
stderr = None

def execute(self):
return self

return Hi()

monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_tmux_cmd)
Expand All @@ -66,6 +82,9 @@ def mock_tmux_cmd(param):
class Hi:
stderr = ["tmux: unknown option -- V"]

def execute(self):
return self

return Hi()

monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_tmux_cmd)
Expand All @@ -83,6 +102,9 @@ def mock_tmux_cmd(param):
class Hi:
stderr = ["tmux: unknown option -- V"]

def execute(self):
return self

return Hi()

monkeypatch.setattr(libtmux.common, "tmux_cmd", mock_tmux_cmd)
Expand Down Expand Up @@ -183,6 +205,12 @@ def test_tmux_cmd_unicode(session):
session.cmd("new-window", "-t", 3, "-n", "юникод", "-F", "Ελληνικά")


def test_tmux_cmd_makes_cmd_available():
"""tmux_cmd objects should make .cmd attribute available."""
command = tmux_cmd("-V")
assert hasattr(command, "cmd")


@pytest.mark.parametrize(
"session_name,raises,exc_msg_regex",
[
Expand Down