Skip to content

Commit d6cc3a7

Browse files
committed
major overhaul of version detection
1 parent 6693d9c commit d6cc3a7

File tree

2 files changed

+164
-63
lines changed

2 files changed

+164
-63
lines changed

libtmux/common.py

Lines changed: 97 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import re
1212
import sys
1313
import subprocess
14-
from distutils.version import StrictVersion, LooseVersion
14+
from distutils.version import LooseVersion
1515

1616
from . import exc
1717
from ._compat import console_to_str
@@ -395,67 +395,93 @@ def _is_executable_file_or_link(exe):
395395
return None
396396

397397

398-
def is_version(version):
398+
def get_version():
399+
"""Return tmux version.
400+
401+
:returns: tmux version
402+
:rtype: :class:`distutils.version.LooseVersion`
403+
"""
404+
proc = tmux_cmd('-V')
405+
if proc.stderr:
406+
if proc.stderr[0] == 'tmux: unknown option -- V':
407+
if sys.platform.startswith("openbsd"): # openbsd has no tmux -V
408+
return LooseVersion('2.3')
409+
raise exc.LibTmuxException(
410+
'libtmux supports tmux 1.8 and greater. This system'
411+
' is running tmux 1.3 or earlier.')
412+
raise exc.VersionTooLow(proc.stderr)
413+
414+
version = proc.stdout[0].split('tmux ')[1]
415+
416+
# Allow latest tmux HEAD
417+
if version == 'master':
418+
return LooseVersion('master')
419+
420+
version = re.sub(r'[a-z]', '', version)
421+
422+
return LooseVersion(version)
423+
424+
425+
def has_version(version):
399426
"""Return True if tmux version installed.
400427
401428
:param version: version, '1.8'
402-
:param type: string
429+
:type version: string
430+
:returns: True if version matches
403431
:rtype: bool
404-
405432
"""
406-
if sys.platform.startswith("openbsd"):
407-
if LooseVersion(version) > LooseVersion('2.1'):
408-
return 'openbsd'
409-
else:
410-
return False
433+
return get_version() == LooseVersion(version)
411434

412-
proc = tmux_cmd('-V')
413435

414-
if proc.stderr:
415-
raise exc.LibTmuxException(proc.stderr)
436+
def has_gt_version(min_version):
437+
"""Return True if tmux version greater than minimum.
438+
439+
:param min_version: version, e.g. '1.8'
440+
:type min_version: string
441+
:returns: True if version above min_version
442+
:rtype: bool
443+
"""
444+
return get_version() >= LooseVersion(min_version)
416445

417-
installed_version = proc.stdout[0].split('tmux ')[1]
418446

419-
return LooseVersion(installed_version) == LooseVersion(version)
447+
def has_lt_version(max_version):
448+
"""Return True if tmux version less than minimum.
449+
450+
:param max_version: version, e.g. '1.8'
451+
:type max_version: string
452+
:returns: True if version below max_version
453+
:rtype: bool
454+
"""
455+
return get_version() <= LooseVersion(max_version)
420456

421457

422-
def has_required_tmux_version(version=None):
458+
def has_minimum_tmux_version(raises=True):
423459
"""Return if tmux meets version requirement. Version >1.8 or above.
424460
461+
:param raises: Will raise exception if version too low, default ``True``
462+
:type raises: bool
463+
:returns: True if tmux meets minimum required version
464+
:rtype: bool
465+
466+
:versionchanged: 0.7.0
467+
No longer returns version, returns True or False
425468
:versionchanged: 0.1.7
426469
Versions will now remove trailing letters per `Issue 55`_.
427470
428471
.. _Issue 55: https://github.com/tony/tmuxp/issues/55.
429472
430473
"""
431474

432-
if not version:
433-
if sys.platform.startswith("openbsd"): # openbsd has no tmux -V
434-
return '2.3'
435-
436-
proc = tmux_cmd('-V')
437-
438-
if proc.stderr:
439-
if proc.stderr[0] == 'tmux: unknown option -- V':
440-
raise exc.LibTmuxException(
441-
'libtmux supports tmux 1.8 and greater. This system'
442-
' is running tmux 1.3 or earlier.')
443-
raise exc.LibTmuxException(proc.stderr)
444-
445-
version = proc.stdout[0].split('tmux ')[1]
446-
447-
# Allow latest tmux HEAD
448-
if version == 'master':
449-
return version
450-
451-
version = re.sub(r'[a-z]', '', version)
452-
453-
if StrictVersion(version) <= StrictVersion("1.7"):
454-
raise exc.LibTmuxException(
455-
'libtmux only supports tmux 1.8 and greater. This system'
456-
' has %s installed. Upgrade your tmux to use libtmux.' % version
457-
)
458-
return version
475+
if get_version() <= LooseVersion("1.7"):
476+
if raises:
477+
raise exc.VersionTooLow(
478+
'libtmux only supports tmux 1.8 and greater. This system'
479+
' has %s installed. Upgrade your tmux to use libtmux.' %
480+
get_version()
481+
)
482+
else:
483+
return False
484+
return True
459485

460486

461487
def session_check_name(session_name):
@@ -477,3 +503,32 @@ def session_check_name(session_name):
477503
elif ':' in session_name:
478504
raise exc.BadSessionName(
479505
"tmux session name \"%s\" may not contain colons.", session_name)
506+
507+
508+
def handle_option_error(error):
509+
"""Raises exception if error in option command found.
510+
511+
Purpose: As of tmux 2.4, there are now 3 different types of option errors:
512+
513+
- unknown option
514+
- invalid option
515+
- ambiguous option
516+
517+
Before 2.4, unknown option was the user.
518+
519+
All errors raised will have the base error of :exc:`exc.OptionError`. So to
520+
catch any option error, use ``except exc.OptionError``.
521+
522+
:param error: error response from subprocess call
523+
:type error: str
524+
:raises: :exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
525+
:exc:`exc.InvalidOption`, :exc:`excAmbiguousOption`
526+
"""
527+
if 'unknown option' in error:
528+
raise exc.UnknownOption(error)
529+
elif 'invalid option' in error:
530+
raise exc.InvalidOption(error)
531+
elif 'ambiguous option' in error:
532+
raise exc.AmbiguousOption(error)
533+
else:
534+
raise exc.OptionError(error) # Raise generic option error

tests/test_common.py

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,54 @@
22
"""Tests for utility functions in tmux."""
33

44
import re
5+
import sys
56

67
import pytest
78

9+
from distutils.version import LooseVersion
10+
11+
import libtmux
12+
813
from libtmux.common import (
9-
has_required_tmux_version, which, session_check_name, is_version, tmux_cmd
14+
has_minimum_tmux_version, which, session_check_name, tmux_cmd,
15+
has_version, has_gt_version, has_lt_version, get_version
1016
)
1117
from libtmux.exc import LibTmuxException, BadSessionName, TmuxCommandNotFound
1218

1319
version_regex = re.compile(r'([0-9]\.[0-9])|(master)')
1420

1521

16-
def test_no_arg_uses_tmux_version():
17-
"""Test the :meth:`has_required_tmux_version`."""
18-
result = has_required_tmux_version()
19-
assert version_regex.match(result) is not None
22+
def test_allows_master_version(monkeypatch):
23+
def mock_get_version():
24+
return LooseVersion('master')
25+
monkeypatch.setattr(libtmux.common, 'get_version', mock_get_version)
26+
27+
assert has_minimum_tmux_version()
2028

2129

22-
def test_allows_master_version():
23-
result = has_required_tmux_version('master')
24-
assert version_regex.match(result) is not None
30+
def test_get_version_openbsd(monkeypatch):
31+
def mock_tmux_cmd(param):
32+
class Hi(object):
33+
pass
34+
proc = Hi()
35+
proc.stderr = ['tmux: unknown option -- V']
36+
return proc
37+
monkeypatch.setattr(libtmux.common, 'tmux_cmd', mock_tmux_cmd)
38+
monkeypatch.setattr(sys, 'platform', 'openbsd 5.2')
39+
assert get_version() == LooseVersion('2.3')
40+
41+
42+
def test_get_version_too_low(monkeypatch):
43+
def mock_tmux_cmd(param):
44+
class Hi(object):
45+
pass
46+
proc = Hi()
47+
proc.stderr = ['tmux: unknown option -- V']
48+
return proc
49+
monkeypatch.setattr(libtmux.common, 'tmux_cmd', mock_tmux_cmd)
50+
with pytest.raises(LibTmuxException) as exc_info:
51+
get_version()
52+
exc_info.match('is running tmux 1.3 or earlier')
2553

2654

2755
def test_ignores_letter_versions():
@@ -33,29 +61,47 @@ def test_ignores_letter_versions():
3361
allow letters.
3462
3563
"""
36-
result = has_required_tmux_version('1.9a')
37-
assert version_regex.match(result) is not None
64+
result = has_minimum_tmux_version('1.9a')
65+
assert result
3866

39-
result = has_required_tmux_version('1.8a')
40-
assert result == r'1.8'
67+
result = has_minimum_tmux_version('1.8a')
68+
assert result
4169

4270
# Should not throw
43-
assert type(is_version('1.8')) is bool
44-
assert type(is_version('1.8a')) is bool
45-
assert type(is_version('1.9a')) is bool
71+
assert type(has_version('1.8')) is bool
72+
assert type(has_version('1.8a')) is bool
73+
assert type(has_version('1.9a')) is bool
4674

4775

48-
def test_error_version_less_1_7():
76+
def test_error_version_less_1_7(monkeypatch):
77+
def mock_get_version():
78+
return LooseVersion('1.7')
79+
monkeypatch.setattr(libtmux.common, 'get_version', mock_get_version)
4980
with pytest.raises(LibTmuxException) as excinfo:
50-
has_required_tmux_version('1.7')
51-
excinfo.match(r'tmuxp only supports')
81+
has_minimum_tmux_version()
82+
excinfo.match(r'libtmux only supports')
5283

5384
with pytest.raises(LibTmuxException) as excinfo:
54-
has_required_tmux_version('1.6a')
85+
has_minimum_tmux_version()
86+
87+
excinfo.match(r'libtmux only supports')
88+
89+
90+
def test_has_version():
91+
assert has_version(str(get_version()))
92+
93+
94+
def test_has_gt_version():
95+
assert has_gt_version('1.6')
96+
assert has_gt_version('1.6b')
97+
assert not has_gt_version('4.0')
98+
5599

56-
excinfo.match(r'tmuxp only supports')
100+
def test_has_lt_version():
101+
assert has_lt_version('4.0a')
102+
assert has_lt_version('4.0')
57103

58-
has_required_tmux_version('1.9a')
104+
assert not has_lt_version('1.7')
59105

60106

61107
def test_which_no_bin_found():

0 commit comments

Comments
 (0)