diff --git a/.travis.yml b/.travis.yml index 77ccea41b..6e7e78b15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ env: - TMUX_VERSION=2.0 - TMUX_VERSION=1.8 - TMUX_VERSION=1.9a +matrix: + allow_failures: + - env: TMUX_VERSION=master before_install: - export PIP_USE_MIRRORS=true - pip install --upgrade pytest # https://github.com/travis-ci/travis-ci/issues/4873 diff --git a/CHANGES b/CHANGES index dfcb03a32..987653e6e 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ Changelog Here you can find the recent changes to libtmux +- :release:`0.6.0 <2016-09-16>` +- :bug:`-` Remove unused ``target_sesssion`` param in + ``Server.attach_session`` and ``Server.switch_client``. + - :release:`0.5.1 <2016-08-18>` - :bug:`12` - fix logger message when tmux doesn't exist in ``PATH`` diff --git a/libtmux/common.py b/libtmux/common.py index 614aecda1..e9b4416f2 100644 --- a/libtmux/common.py +++ b/libtmux/common.py @@ -420,3 +420,24 @@ def has_required_tmux_version(version=None): ' has %s installed. Upgrade your tmux to use libtmux.' % version ) return version + + +def session_check_name(session_name): + """Raises exception session name invalid, modeled after tmux function. + + tmux(1) session names may not be empty, or include periods or colons. + These delimiters are reserved for noting session, window and pane. + + :param session_name: name of session + :type session_name: string + :returns: void + :raises: :exc:`exc.BadSessionName` + """ + if not session_name or len(session_name) == 0: + raise exc.BadSessionName("tmux session names may not be empty.") + elif '.' in session_name: + raise exc.BadSessionName( + "tmux session name \"%s\" may not contain periods.", session_name) + elif ':' in session_name: + raise exc.BadSessionName( + "tmux session name \"%s\" may not contain colons.", session_name) diff --git a/libtmux/exc.py b/libtmux/exc.py index 9187cfc79..155dd113b 100644 --- a/libtmux/exc.py +++ b/libtmux/exc.py @@ -20,6 +20,13 @@ class TmuxSessionExists(LibTmuxException): pass +class BadSessionName(LibTmuxException): + + """Session name is not allowed.""" + + pass + + class UnknownOption(LibTmuxException): """Option unknown to tmux show-option(s) or show-window-option(s).""" diff --git a/libtmux/server.py b/libtmux/server.py index 48da224f3..5439423a9 100644 --- a/libtmux/server.py +++ b/libtmux/server.py @@ -11,7 +11,8 @@ import os from . import exc, formats -from .common import EnvironmentMixin, TmuxRelationalObject, tmux_cmd +from .common import EnvironmentMixin, TmuxRelationalObject, tmux_cmd, \ + session_check_name from .session import Session logger = logging.getLogger(__name__) @@ -318,10 +319,13 @@ def has_session(self, target_session): """Return True if session exists. ``$ tmux has-session``. :param: target_session: str of session name. + :raises: :exc:`exc.BadSessionName` :rtype: bool """ + session_check_name(target_session) + proc = self.cmd('has-session', '-t%s' % target_session) if not proc.stdout: @@ -335,6 +339,8 @@ def has_session(self, target_session): return False elif 'can\'t find session' in proc.stdout: # tmux 2.1 return False + elif 'bad session name' in proc.stdout: # tmux >= 1.9 + return False elif 'session not found' in proc.stdout: return False else: @@ -349,10 +355,12 @@ def kill_session(self, target_session=None): :param: target_session: str. note this accepts ``fnmatch(3)``. 'asdf' will kill 'asdfasd'. - + :raises: :exc:`exc.BadSessionName` :rtype: :class:`Server` """ + session_check_name(target_session) + proc = self.cmd('kill-session', '-t%s' % target_session) if proc.stderr: @@ -364,8 +372,9 @@ def switch_client(self, target_session): """``$ tmux switch-client``. :param: target_session: str. name of the session. fnmatch(3) works. - + :raises: :exc:`exc.BadSessionName` """ + session_check_name(target_session) proc = self.cmd('switch-client', '-t%s' % target_session) @@ -376,8 +385,10 @@ def attach_session(self, target_session=None): """``$ tmux attach-session`` aka alias: ``$ tmux attach``. :param: target_session: str. name of the session. fnmatch(3) works. - + :raises: :exc:`exc.BadSessionName` """ + session_check_name(target_session) + tmux_args = tuple() if target_session: tmux_args += ('-t%s' % target_session,) @@ -418,9 +429,11 @@ def new_session(self, :param kill_session: Kill current session if ``$ tmux has-session`` Useful for testing workspaces. :type kill_session: bool + :raises: :exc:`exc.BadSessionName` :rtype: :class:`Session` """ + session_check_name(session_name) if self.has_session(session_name): if kill_session: diff --git a/libtmux/session.py b/libtmux/session.py index 581ab26df..5689b65ed 100644 --- a/libtmux/session.py +++ b/libtmux/session.py @@ -11,7 +11,8 @@ import os from . import exc, formats -from .common import EnvironmentMixin, TmuxMappingObject, TmuxRelationalObject +from .common import EnvironmentMixin, TmuxMappingObject, \ + TmuxRelationalObject, session_check_name from .window import Window logger = logging.getLogger(__name__) @@ -75,12 +76,8 @@ def cmd(self, *args, **kwargs): kwargs['-t'] = self.id return self.server.cmd(*args, **kwargs) - def attach_session(self, target_session=None): - """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``. - - :param: target_session: str. name of the session. fnmatch(3) works. - - """ + def attach_session(self): + """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``.""" proc = self.cmd('attach-session', '-t%s' % self.id) if proc.stderr: @@ -94,10 +91,11 @@ def kill_session(self): if proc.stderr: raise exc.LibTmuxException(proc.stderr) - def switch_client(self, target_session=None): + def switch_client(self): """``$ tmux switch-client``. :param: target_session: str. note this accepts fnmatch(3). + :raises: :exc:`exc.LibTmuxException` """ proc = self.cmd('switch-client', '-t%s' % self.id) @@ -107,11 +105,14 @@ def switch_client(self, target_session=None): def rename_session(self, new_name): """Rename session and return new :class:`Session` object. - :param rename_session: new session name - :type rename_session: string + :param new_name: new session name + :type new_name: string + :raises: :exc:`exc.BadSessionName` :rtype: :class:`Session` """ + session_check_name(new_name) + proc = self.cmd( 'rename-session', '-t%s' % self.id, diff --git a/tests/test_common.py b/tests/test_common.py index e519f1b19..79b443ca7 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -5,8 +5,8 @@ import pytest -from libtmux.common import has_required_tmux_version, which -from libtmux.exc import LibTmuxException +from libtmux.common import has_required_tmux_version, which, session_check_name +from libtmux.exc import LibTmuxException, BadSessionName version_regex = re.compile(r'[0-9]\.[0-9]') @@ -50,3 +50,20 @@ def test_which_no_tmuxp_found(monkeypatch): monkeypatch.setenv("PATH", "/") which('tmuxp') which('tmuxp', '/') + + +@pytest.mark.parametrize("session_name,raises,exc_msg_regex", [ + ('', True, 'may not be empty'), + (None, True, 'may not be empty'), + ("my great session.", True, 'may not contain periods'), + ("name: great session", True, 'may not contain colons'), + ("new great session", False, None), + ("ajf8a3fa83fads,,,a", False, None), +]) +def test_session_check_name(session_name, raises, exc_msg_regex): + if raises: + with pytest.raises(BadSessionName) as exc_info: + session_check_name(session_name) + assert exc_info.match(exc_msg_regex) + else: + session_check_name(session_name) diff --git a/tests/test_session.py b/tests/test_session.py index d8356221b..0fe018d41 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -184,3 +184,34 @@ def test_unset_environment(session): assert session.show_environment('BAM') == 'OK' session.unset_environment('BAM') assert session.show_environment('BAM') is None + + +@pytest.mark.parametrize("session_name,raises", [ + ('hey.period', True), + ('hey:its a colon', True), + ('hey moo', False), +]) +def test_periods_raise_badsessionname(server, session, session_name, raises): + new_name = session_name + 'moo' # used for rename / switch + if raises: + with pytest.raises(exc.BadSessionName): + session.rename_session(new_name) + + with pytest.raises(exc.BadSessionName): + server.new_session(session_name) + + with pytest.raises(exc.BadSessionName): + server.has_session(session_name) + + with pytest.raises(exc.BadSessionName): + server.switch_client(new_name) + + with pytest.raises(exc.BadSessionName): + server.attach_session(new_name) + + else: + server.new_session(session_name) + server.has_session(session_name) + session.rename_session(new_name) + with pytest.raises(exc.LibTmuxException): + server.switch_client(new_name)