summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py0
-rwxr-xr-xtests/install.sh81
-rw-r--r--tests/modules/__init__.py94
-rw-r--r--tests/modules/lib/__init__.py183
-rw-r--r--tests/modules/lib/config_mock.py230
-rw-r--r--tests/modules/lib/fsconfig.py83
-rw-r--r--tests/modules/lib/terminal.py307
-rw-r--r--tests/modules/lib/vterm.py193
-rw-r--r--tests/modules/matchers.py6
-rw-r--r--tests/modules/vim.py927
-rw-r--r--tests/shlib/common.sh150
-rw-r--r--tests/shlib/vim.sh33
-rw-r--r--tests/shlib/vterm.sh17
-rw-r--r--tests/terminfo/s/screenbin0 -> 1564 bytes
-rw-r--r--tests/terminfo/s/st-256colorbin0 -> 2324 bytes
-rwxr-xr-xtests/test.sh42
-rwxr-xr-xtests/test_awesome/path/awesome-client3
-rw-r--r--tests/test_awesome/powerline/config.json7
-rw-r--r--tests/test_awesome/powerline/themes/wm/default.json18
-rw-r--r--tests/test_awesome/powerline/themes/wm/dvi.json18
-rwxr-xr-xtests/test_awesome/test.sh188
-rwxr-xr-xtests/test_bar/path/lemonbar11
-rwxr-xr-xtests/test_bar/path/xrandr31
-rw-r--r--tests/test_bar/powerline/config.json9
-rw-r--r--tests/test_bar/powerline/themes/wm/default.json18
-rw-r--r--tests/test_bar/powerline/themes/wm/dvi.json18
-rwxr-xr-xtests/test_bar/test.sh195
-rwxr-xr-xtests/test_daemon/test.sh41
-rw-r--r--tests/test_in_vterm/shell/inits/dash16
-rwxr-xr-xtests/test_in_vterm/test.sh13
-rwxr-xr-xtests/test_in_vterm/test_shells.py162
-rwxr-xr-xtests/test_in_vterm/test_shells.sh119
-rwxr-xr-xtests/test_in_vterm/test_tmux.py251
-rwxr-xr-xtests/test_in_vterm/test_tmux.sh44
-rwxr-xr-xtests/test_in_vterm/test_vim.py73
-rwxr-xr-xtests/test_in_vterm/test_vim.sh39
-rwxr-xr-xtests/test_lint/test.sh10
-rw-r--r--tests/test_python/empty0
-rwxr-xr-xtests/test_python/test.sh13
-rw-r--r--tests/test_python/test_cmdline.py149
-rw-r--r--tests/test_python/test_config_merging.py270
-rw-r--r--tests/test_python/test_config_reload.py319
-rw-r--r--tests/test_python/test_configuration.py877
-rw-r--r--tests/test_python/test_lib.py733
-rw-r--r--tests/test_python/test_lib_config.py52
-rw-r--r--tests/test_python/test_listers.py227
-rw-r--r--tests/test_python/test_logging.py467
-rw-r--r--tests/test_python/test_provided_config_files.py201
-rw-r--r--tests/test_python/test_segments.py1711
-rw-r--r--tests/test_python/test_selectors.py36
-rw-r--r--tests/test_python/test_watcher.py245
-rwxr-xr-xtests/test_shells/bgscript.sh5
-rw-r--r--tests/test_shells/inputs/bash69
-rw-r--r--tests/test_shells/inputs/busybox37
-rw-r--r--tests/test_shells/inputs/dash37
-rw-r--r--tests/test_shells/inputs/fish69
-rw-r--r--tests/test_shells/inputs/ipython7
-rw-r--r--tests/test_shells/inputs/mksh38
-rw-r--r--tests/test_shells/inputs/pdb89
-rw-r--r--tests/test_shells/inputs/rc33
-rw-r--r--tests/test_shells/inputs/tcsh24
-rw-r--r--tests/test_shells/inputs/zsh90
-rw-r--r--tests/test_shells/ipython_home/profile_default/ipython_config.py19
-rw-r--r--tests/test_shells/outputs/bash.daemon.ok42
-rw-r--r--tests/test_shells/outputs/bash.nodaemon.ok42
-rw-r--r--tests/test_shells/outputs/busybox.daemon.ok29
-rw-r--r--tests/test_shells/outputs/busybox.nodaemon.ok29
-rw-r--r--tests/test_shells/outputs/dash.daemon.ok28
-rw-r--r--tests/test_shells/outputs/dash.nodaemon.ok28
-rw-r--r--tests/test_shells/outputs/fish.ok52
-rw-r--r--tests/test_shells/outputs/ipython.ok14
-rw-r--r--tests/test_shells/outputs/mksh.daemon.ok32
-rw-r--r--tests/test_shells/outputs/mksh.nodaemon.ok32
-rw-r--r--tests/test_shells/outputs/pdb.module.ok222
-rw-r--r--tests/test_shells/outputs/pdb.subclass.ok217
-rw-r--r--tests/test_shells/outputs/rc.daemon.ok24
-rw-r--r--tests/test_shells/outputs/rc.nodaemon.ok24
-rw-r--r--tests/test_shells/outputs/tcsh.ok17
-rw-r--r--tests/test_shells/outputs/zsh.daemon.ok52
-rw-r--r--tests/test_shells/outputs/zsh.nodaemon.ok52
-rw-r--r--tests/test_shells/outputs/zsh.zpython.ok52
-rw-r--r--tests/test_shells/pdb-main.py24
-rw-r--r--tests/test_shells/pdb-script.py38
-rwxr-xr-xtests/test_shells/postproc.py130
-rwxr-xr-xtests/test_shells/run_script.py125
-rwxr-xr-xtests/test_shells/test.sh491
-rwxr-xr-xtests/test_shells/waitpid.sh4
-rw-r--r--tests/test_shells/zsh_test_script.zsh11
-rw-r--r--tests/test_vim/pyfiles/setup_statusline_catcher.py18
-rwxr-xr-xtests/test_vim/test.sh60
-rwxr-xr-xtests/test_vim/tests/commandt_plugin.vim17
-rwxr-xr-xtests/test_vim/tests/empty_encoding.old.vim32
-rw-r--r--tests/test_vim/tests/foreign_stl_override.vim22
-rw-r--r--tests/test_vim/tests/invalid_unicode.vim19
-rwxr-xr-xtests/test_vim/tests/local_overrides.vim48
-rwxr-xr-xtests/test_vim/tests/nerdtree_plugin.vim11
-rwxr-xr-xtests/test_vim/tests/plugin_file.vim22
-rwxr-xr-xtests/test_vim/tests/tabline.vim56
-rw-r--r--tests/test_vim/vim_utils.vim88
-rw-r--r--tests/vim_sys_path/vim.py7
100 files changed, 11658 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/install.sh b/tests/install.sh
new file mode 100755
index 0000000..6925a34
--- /dev/null
+++ b/tests/install.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+set -e
+set -x
+
+remote_master_hex() {
+ local url="$1"
+ git ls-remote "$url" refs/heads/master | cut -f1
+}
+
+checkout_cached_dir() {
+ local url="$1"
+ local target="$2"
+ if ! test -e "$target/.version" || \
+ test "$(cat "$target/.version")" != "$(remote_master_hex "$url")" ; then
+ rm -rf "$target"
+ fi
+ if ! test -d "$target" ; then
+ git clone --depth=1 "$url" "$target"
+ git rev-parse HEAD > "$target/.version"
+ rm -rf "$target"/.git
+ fi
+}
+
+checkout_cached_dir git://github.com/powerline/bot-ci tests/bot-ci
+checkout_cached_dir git://github.com/powerline/deps tests/bot-ci/deps
+
+. tests/bot-ci/scripts/common/main.sh
+
+mkdir -p "$HOME/opt"
+
+if test -n "$USE_UCS2_PYTHON" ; then
+ if test "$UCS2_PYTHON_VARIANT" = "2.6" ; then
+ pip install 'virtualenvwrapper==4.6.0'
+ else
+ pip install virtualenvwrapper
+ fi
+ set +e
+ . virtualenvwrapper.sh
+ set -e
+ archive="${PWD:-$(pwd)}/tests/bot-ci/deps/cpython-ucs2/cpython-ucs2-${UCS2_PYTHON_VARIANT}.tar.gz"
+ sh -c "cd $HOME/opt && tar xzf $archive"
+ PYTHON="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/bin/python$UCS2_PYTHON_VARIANT"
+ export LD_LIBRARY_PATH="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/lib${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}"
+ set +e
+ mkvirtualenv -p "$PYTHON" cpython-ucs2-$UCS2_PYTHON_VARIANT
+ set -e
+ . tests/bot-ci/scripts/common/main.sh
+ pip install --verbose --verbose --verbose .
+ if test "$UCS2_PYTHON_VARIANT" = "2.6" ; then
+ rm tests/bot-ci/deps/wheels/ucs2-CPython-${UCS2_PYTHON_VARIANT}*/pyuv*.whl
+ fi
+ pip install --no-deps tests/bot-ci/deps/wheels/ucs2-CPython-${UCS2_PYTHON_VARIANT}*/*.whl
+else
+ pip install --verbose --verbose --verbose .
+ # FIXME Uv watcher sometimes misses events and INotify is not available in
+ # Python-2.6, thus pyuv should be removed in order for VCS tests to
+ # pass.
+ if test "$PYTHON_VERSION_MAJOR" -eq 2 && test "$PYTHON_VERSION_MINOR" -lt 7 ; then
+ rm tests/bot-ci/deps/wheels/$PYTHON_SUFFIX/pyuv*.whl
+ fi
+ pip install --no-deps tests/bot-ci/deps/wheels/$PYTHON_SUFFIX/*.whl
+fi
+if test "$PYTHON_IMPLEMENTATION" = "CPython" ; then
+ archive="${PWD:-$(pwd)}/tests/bot-ci/deps/zpython/zsh-${PYTHON_MM}${USE_UCS2_PYTHON:+-ucs2}.tar.gz"
+ sh -c "cd $HOME/opt && tar xzf $archive"
+fi
+
+archive="${PWD:-$(pwd)}/tests/bot-ci/deps/fish/fish.tar.gz"
+sh -c "cd $HOME/opt && tar xzf $archive"
+
+mkdir tests/vim-plugins
+
+for archive in "$ROOT"/tests/bot-ci/deps/vim-plugins/*.tar.gz ; do
+ (
+ cd tests/vim-plugins
+ tar -xzvf "$archive"
+ )
+done
+
+true
diff --git a/tests/modules/__init__.py b/tests/modules/__init__.py
new file mode 100644
index 0000000..12aae20
--- /dev/null
+++ b/tests/modules/__init__.py
@@ -0,0 +1,94 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+
+if sys.version_info < (2, 7):
+ from unittest2 import TestCase as _TestCase # NOQA
+ from unittest2 import main as _main # NOQA
+ from unittest2.case import SkipTest # NOQA
+else:
+ from unittest import TestCase as _TestCase # NOQA
+ from unittest import main as _main # NOQA
+ from unittest.case import SkipTest # NOQA
+
+from tests.modules.lib import PowerlineSingleTest
+
+
+class PowerlineDummyTest(object):
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ pass
+
+ def fail(self, *args, **kwargs):
+ pass
+
+ def exception(self, *args, **kwargs):
+ pass
+
+
+class PowerlineTestSuite(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __enter__(self):
+ self.saved_current_suite = os.environ['POWERLINE_CURRENT_SUITE']
+ os.environ['POWERLINE_CURRENT_SUITE'] = (
+ self.saved_current_suite + '/' + self.name)
+ self.suite = self.saved_current_suite + '/' + self.name
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if exc_type is not None:
+ self.exception(
+ 'suite_noexcept',
+ 'Exception while running test suite: {0!r}'.format(exc_value),
+ )
+ os.environ['POWERLINE_CURRENT_SUITE'] = self.saved_current_suite
+
+ def record_test_failure(self, fail_char, test_name, message, allow_failure=False):
+ if allow_failure:
+ fail_char = 'A' + fail_char
+ full_msg = '{fail_char} {suite}|{test_name} :: {message}'.format(
+ fail_char=fail_char,
+ suite=self.suite,
+ test_name=test_name,
+ message=message,
+ )
+ with open(os.environ['FAILURES_FILE'], 'a') as ffd:
+ ffd.write(full_msg + '\n')
+ return False
+
+ def exception(self, test_name, message, allow_failure=False):
+ return self.record_test_failure('E', test_name, message, allow_failure)
+
+ def fail(self, test_name, message, allow_failure=False):
+ return self.record_test_failure('F', test_name, message, allow_failure)
+
+ def test(self, name, attempts_left=0):
+ if not attempts_left:
+ return PowerlineSingleTest(self, name)
+ else:
+ return PowerlineDummyTest()
+
+ def subsuite(self, name):
+ return PowerlineTestSuite(name)
+
+
+suite = None
+
+
+def main(*args, **kwargs):
+ global suite
+ suite = PowerlineTestSuite(sys.argv[0])
+ _main(*args, **kwargs)
+
+
+class TestCase(_TestCase):
+ def fail(self, msg=None):
+ suite.fail(self.__class__.__name__,
+ msg or 'Test failed without message')
+ super(TestCase, self).fail(*args, **kwargs)
diff --git a/tests/modules/lib/__init__.py b/tests/modules/lib/__init__.py
new file mode 100644
index 0000000..d45ccae
--- /dev/null
+++ b/tests/modules/lib/__init__.py
@@ -0,0 +1,183 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import imp
+import sys
+
+
+class Pl(object):
+ def __init__(self):
+ self.exceptions = []
+ self.errors = []
+ self.warns = []
+ self.debugs = []
+ self.infos = []
+ self.prefix = None
+ self.use_daemon_threads = True
+
+ for meth in ('error', 'warn', 'debug', 'exception', 'info'):
+ exec((
+ 'def {0}(self, msg, *args, **kwargs):\n'
+ ' self.{0}s.append((kwargs.get("prefix") or self.prefix, msg, args, kwargs))\n'
+ ).format(meth))
+
+ def __nonzero__(self):
+ return bool(self.exceptions or self.errors or self.warns)
+
+ __bool__ = __nonzero__
+
+
+class Args(object):
+ theme_override = {}
+ config_override = {}
+ config_path = None
+ ext = ['shell']
+ renderer_module = None
+
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+
+
+def urllib_read(query_url):
+ if query_url.startswith('http://ipv'):
+ if query_url.startswith('http://ipv4.icanhazip.com'):
+ return '127.0.0.1'
+ elif query_url.startswith('http://ipv4.icanhazip.com'):
+ return '2001:4801:7818:6:abc5:ba2c:ff10:275f'
+ elif query_url.startswith('http://geoip.nekudo.com/api/'):
+ return '{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}'
+ elif query_url.startswith('http://query.yahooapis.com/v1/public/'):
+ if 'Meppen' in query_url:
+ return r'{"query":{"count":1,"created":"2016-05-13T19:43:18Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Meppen, NI, DE","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","description":"Yahoo! Weather for Meppen, NI, DE","language":"en-us","lastBuildDate":"Fri, 13 May 2016 09:43 PM CEST","ttl":"60","location":{"city":"Meppen","country":"Germany","region":" NI"},"wind":{"chill":"55","direction":"350","speed":"25"},"atmosphere":{"humidity":"57","pressure":"1004.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"5:35 am","sunset":"9:21 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Meppen, NI, DE at 08:00 PM CEST","lat":"52.68993","long":"7.29115","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","pubDate":"Fri, 13 May 2016 08:00 PM CEST","condition":{"code":"23","date":"Fri, 13 May 2016 08:00 PM CEST","temp":"14","text":"Breezy"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"71","low":"48","text":"Partly Cloudy"},{"code":"28","date":"14 May 2016","day":"Sat","high":"54","low":"44","text":"Mostly Cloudy"},{"code":"11","date":"15 May 2016","day":"Sun","high":"55","low":"43","text":"Showers"},{"code":"28","date":"16 May 2016","day":"Mon","high":"54","low":"42","text":"Mostly Cloudy"},{"code":"28","date":"17 May 2016","day":"Tue","high":"57","low":"43","text":"Mostly Cloudy"},{"code":"12","date":"18 May 2016","day":"Wed","high":"62","low":"45","text":"Rain"},{"code":"28","date":"19 May 2016","day":"Thu","high":"63","low":"48","text":"Mostly Cloudy"},{"code":"28","date":"20 May 2016","day":"Fri","high":"67","low":"50","text":"Mostly Cloudy"},{"code":"30","date":"21 May 2016","day":"Sat","high":"71","low":"50","text":"Partly Cloudy"},{"code":"30","date":"22 May 2016","day":"Sun","high":"74","low":"54","text":"Partly Cloudy"}],"description":"<![CDATA[<img src=\"http://l.yimg.com/a/i/us/we/52/23.gif\"/>\n<BR />\n<b>Current Conditions:</b>\n<BR />Breezy\n<BR />\n<BR />\n<b>Forecast:</b>\n<BR /> Fri - Partly Cloudy. High: 71Low: 48\n<BR /> Sat - Mostly Cloudy. High: 54Low: 44\n<BR /> Sun - Showers. High: 55Low: 43\n<BR /> Mon - Mostly Cloudy. High: 54Low: 42\n<BR /> Tue - Mostly Cloudy. High: 57Low: 43\n<BR />\n<BR />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/\">Full Forecast at Yahoo! Weather</a>\n<BR />\n<BR />\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)\n<BR />\n]]>","guid":{"isPermaLink":"false"}}}}}}'
+ elif 'Moscow' in query_url:
+ return r'{"query":{"count":1,"created":"2016-05-13T19:47:01Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Moscow, Moscow Federal City, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","description":"Yahoo! Weather for Moscow, Moscow Federal City, RU","language":"en-us","lastBuildDate":"Fri, 13 May 2016 10:47 PM MSK","ttl":"60","location":{"city":"Moscow","country":"Russia","region":" Moscow Federal City"},"wind":{"chill":"45","direction":"80","speed":"11"},"atmosphere":{"humidity":"52","pressure":"993.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"4:19 am","sunset":"8:34 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Moscow, Moscow Federal City, RU at 09:00 PM MSK","lat":"55.741638","long":"37.605061","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","pubDate":"Fri, 13 May 2016 09:00 PM MSK","condition":{"code":"33","date":"Fri, 13 May 2016 09:00 PM MSK","temp":"9","text":"Mostly Clear"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"62","low":"41","text":"Partly Cloudy"},{"code":"30","date":"14 May 2016","day":"Sat","high":"64","low":"43","text":"Partly Cloudy"},{"code":"30","date":"15 May 2016","day":"Sun","high":"63","low":"44","text":"Partly Cloudy"},{"code":"12","date":"16 May 2016","day":"Mon","high":"60","low":"47","text":"Rain"},{"code":"12","date":"17 May 2016","day":"Tue","high":"64","low":"48","text":"Rain"},{"code":"28","date":"18 May 2016","day":"Wed","high":"67","low":"48","text":"Mostly Cloudy"},{"code":"12","date":"19 May 2016","day":"Thu","high":"68","low":"49","text":"Rain"},{"code":"39","date":"20 May 2016","day":"Fri","high":"66","low":"50","text":"Scattered Showers"},{"code":"39","date":"21 May 2016","day":"Sat","high":"69","low":"49","text":"Scattered Showers"},{"code":"30","date":"22 May 2016","day":"Sun","high":"73","low":"50","text":"Partly Cloudy"}],"description":"<![CDATA[<img src=\"http://l.yimg.com/a/i/us/we/52/33.gif\"/>\n<BR />\n<b>Current Conditions:</b>\n<BR />Mostly Clear\n<BR />\n<BR />\n<b>Forecast:</b>\n<BR /> Fri - Partly Cloudy. High: 62Low: 41\n<BR /> Sat - Partly Cloudy. High: 64Low: 43\n<BR /> Sun - Partly Cloudy. High: 63Low: 44\n<BR /> Mon - Rain. High: 60Low: 47\n<BR /> Tue - Rain. High: 64Low: 48\n<BR />\n<BR />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/\">Full Forecast at Yahoo! Weather</a>\n<BR />\n<BR />\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)\n<BR />\n]]>","guid":{"isPermaLink":"false"}}}}}}'
+ else:
+ raise NotImplementedError
+
+
+class Process(object):
+ def __init__(self, output, err):
+ self.output = output
+ self.err = err
+
+ def communicate(self):
+ return self.output, self.err
+
+
+class ModuleReplace(object):
+ def __init__(self, name, new):
+ self.name = name
+ self.new = new
+
+ def __enter__(self):
+ self.old = sys.modules.get(self.name)
+ if not self.old:
+ try:
+ self.old = __import__(self.name)
+ except ImportError:
+ pass
+ sys.modules[self.name] = self.new
+
+ def __exit__(self, *args):
+ if self.old:
+ sys.modules[self.name] = self.old
+ else:
+ sys.modules.pop(self.name)
+
+
+def replace_module(name, new=None, **kwargs):
+ if not new:
+ new = new_module(name, **kwargs)
+ return ModuleReplace(name, new)
+
+
+def new_module(name, **kwargs):
+ module = imp.new_module(name)
+ for k, v in kwargs.items():
+ setattr(module, k, v)
+ return module
+
+
+class AttrReplace(object):
+ def __init__(self, obj, *args):
+ self.obj = obj
+ self.attrs = args[::2]
+ self.new = args[1::2]
+
+ def __enter__(self):
+ self.old = {}
+ for i, attr in enumerate(self.attrs):
+ try:
+ self.old[i] = getattr(self.obj, attr)
+ except AttributeError:
+ pass
+ for attr, new in zip(self.attrs, self.new):
+ setattr(self.obj, attr, new)
+
+ def __exit__(self, *args):
+ for i, attr in enumerate(self.attrs):
+ try:
+ old = self.old[i]
+ except KeyError:
+ delattr(self.obj, attr)
+ else:
+ setattr(self.obj, attr, old)
+
+
+replace_attr = AttrReplace
+
+
+def replace_module_module(module, name, **kwargs):
+ return replace_attr(module, name, new_module(name, **kwargs))
+
+
+class ItemReplace(object):
+ def __init__(self, d, key, new, r=None):
+ self.key = key
+ self.new = new
+ self.d = d
+ self.r = r
+
+ def __enter__(self):
+ self.old = self.d.get(self.key)
+ self.d[self.key] = self.new
+ return self.r
+
+ def __exit__(self, *args):
+ if self.old is None:
+ try:
+ self.d.pop(self.key)
+ except KeyError:
+ pass
+ else:
+ self.d[self.key] = self.old
+
+
+def replace_item(d, key, new):
+ return ItemReplace(d, key, new, d)
+
+
+def replace_env(key, new, environ=None, **kwargs):
+ r = kwargs.copy()
+ r['environ'] = environ or {}
+ return ItemReplace(r['environ'], key, new, r)
+
+
+class PowerlineSingleTest(object):
+ def __init__(self, suite, name):
+ self.suite = suite
+ self.name = name
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if exc_type is not None:
+ self.exception('Exception while running test: {0!r}'.format(
+ exc_value))
+
+ def fail(self, message, allow_failure=False):
+ return self.suite.fail(self.name, message, allow_failure)
+
+ def exception(self, message, allow_failure=False):
+ return self.suite.exception(self.name, message, allow_failure)
diff --git a/tests/modules/lib/config_mock.py b/tests/modules/lib/config_mock.py
new file mode 100644
index 0000000..900b60f
--- /dev/null
+++ b/tests/modules/lib/config_mock.py
@@ -0,0 +1,230 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from threading import Lock
+from copy import deepcopy
+from time import sleep
+from functools import wraps
+
+from powerline.renderer import Renderer
+from powerline.lib.config import ConfigLoader
+from powerline import Powerline, get_default_theme
+
+from tests.modules.lib import Args, replace_attr
+
+
+UT = get_default_theme(is_unicode=True)
+AT = get_default_theme(is_unicode=False)
+
+
+class TestHelpers(object):
+ def __init__(self, config):
+ self.config = config
+ self.access_log = []
+ self.access_lock = Lock()
+
+ def loader_condition(self, path):
+ return (path in self.config) and path
+
+ def find_config_files(self, cfg_path, config_loader, loader_callback):
+ if cfg_path.endswith('.json'):
+ cfg_path = cfg_path[:-5]
+ if cfg_path.startswith('/'):
+ cfg_path = cfg_path.lstrip('/')
+ with self.access_lock:
+ self.access_log.append('check:' + cfg_path)
+ if cfg_path in self.config:
+ yield cfg_path
+ else:
+ if config_loader:
+ config_loader.register_missing(self.loader_condition, loader_callback, cfg_path)
+ raise IOError(('fcf:' if cfg_path.endswith('raise') else '') + cfg_path)
+
+ def load_json_config(self, config_file_path, *args, **kwargs):
+ if config_file_path.endswith('.json'):
+ config_file_path = config_file_path[:-5]
+ if config_file_path.startswith('/'):
+ config_file_path = config_file_path.lstrip('/')
+ with self.access_lock:
+ self.access_log.append('load:' + config_file_path)
+ try:
+ return deepcopy(self.config[config_file_path])
+ except KeyError:
+ raise IOError(config_file_path)
+
+ def pop_events(self):
+ with self.access_lock:
+ r = self.access_log[:]
+ self.access_log = []
+ return r
+
+
+def log_call(func):
+ @wraps(func)
+ def ret(self, *args, **kwargs):
+ self._calls.append((func.__name__, args, kwargs))
+ return func(self, *args, **kwargs)
+ return ret
+
+
+class TestWatcher(object):
+ events = set()
+ lock = Lock()
+
+ def __init__(self):
+ self._calls = []
+
+ @log_call
+ def watch(self, file):
+ pass
+
+ @log_call
+ def __call__(self, file):
+ with self.lock:
+ if file in self.events:
+ self.events.remove(file)
+ return True
+ return False
+
+ def _reset(self, files):
+ with self.lock:
+ self.events.clear()
+ self.events.update(files)
+
+ @log_call
+ def unsubscribe(self):
+ pass
+
+
+class Logger(object):
+ def __init__(self):
+ self.messages = []
+ self.lock = Lock()
+
+ def _add_msg(self, attr, msg):
+ with self.lock:
+ self.messages.append(attr + ':' + msg)
+
+ def _pop_msgs(self):
+ with self.lock:
+ r = self.messages
+ self.messages = []
+ return r
+
+ def __getattr__(self, attr):
+ return lambda *args, **kwargs: self._add_msg(attr, *args, **kwargs)
+
+
+class SimpleRenderer(Renderer):
+ def hlstyle(self, fg=None, bg=None, attrs=None):
+ return '<{fg} {bg} {attrs}>'.format(fg=fg and fg[0], bg=bg and bg[0], attrs=attrs)
+
+
+class EvenSimplerRenderer(Renderer):
+ def hlstyle(self, fg=None, bg=None, attrs=None):
+ return '{{{fg}{bg}{attrs}}}'.format(
+ fg=fg and fg[0] or '-',
+ bg=bg and bg[0] or '-',
+ attrs=attrs if attrs else '',
+ )
+
+
+class TestPowerline(Powerline):
+ _created = False
+
+ def __init__(self, _helpers, **kwargs):
+ super(TestPowerline, self).__init__(**kwargs)
+ self._helpers = _helpers
+ self.find_config_files = _helpers.find_config_files
+
+ @staticmethod
+ def get_local_themes(local_themes):
+ return local_themes
+
+ @staticmethod
+ def get_config_paths():
+ return ['']
+
+ def _will_create_renderer(self):
+ return self.cr_kwargs
+
+ def _pop_events(self):
+ return self._helpers.pop_events()
+
+
+renderer = EvenSimplerRenderer
+
+
+class TestConfigLoader(ConfigLoader):
+ def __init__(self, _helpers, **kwargs):
+ watcher = TestWatcher()
+ super(TestConfigLoader, self).__init__(
+ load=_helpers.load_json_config,
+ watcher=watcher,
+ watcher_type='test',
+ **kwargs
+ )
+
+
+def get_powerline(config, **kwargs):
+ helpers = TestHelpers(config)
+ return get_powerline_raw(
+ helpers,
+ TestPowerline,
+ _helpers=helpers,
+ ext='test',
+ renderer_module='tests.modules.lib.config_mock',
+ logger=Logger(),
+ **kwargs
+ )
+
+
+def select_renderer(simpler_renderer=False):
+ global renderer
+ renderer = EvenSimplerRenderer if simpler_renderer else SimpleRenderer
+
+
+def get_powerline_raw(helpers, PowerlineClass, replace_gcp=False, **kwargs):
+ if not isinstance(helpers, TestHelpers):
+ helpers = TestHelpers(helpers)
+ select_renderer(kwargs.pop('simpler_renderer', False))
+
+ if replace_gcp:
+ class PowerlineClass(PowerlineClass):
+ @staticmethod
+ def get_config_paths():
+ return ['/']
+
+ pl = PowerlineClass(
+ config_loader=TestConfigLoader(
+ _helpers=helpers,
+ run_once=kwargs.get('run_once')
+ ),
+ **kwargs
+ )
+ pl._watcher = pl.config_loader.watcher
+ return pl
+
+
+def swap_attributes(config, powerline_module):
+ return replace_attr(powerline_module, 'os', Args(
+ path=Args(
+ isfile=lambda path: path.lstrip('/').replace('.json', '') in config,
+ join=os.path.join,
+ expanduser=lambda path: path,
+ realpath=lambda path: path,
+ dirname=os.path.dirname,
+ ),
+ environ={},
+ ))
+
+
+def add_watcher_events(p, *args, **kwargs):
+ if isinstance(p._watcher, TestWatcher):
+ p._watcher._reset(args)
+ while not p._will_create_renderer():
+ sleep(kwargs.get('interval', 0.1))
+ if not kwargs.get('wait', True):
+ return
diff --git a/tests/modules/lib/fsconfig.py b/tests/modules/lib/fsconfig.py
new file mode 100644
index 0000000..757e874
--- /dev/null
+++ b/tests/modules/lib/fsconfig.py
@@ -0,0 +1,83 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import json
+
+from subprocess import check_call
+from shutil import rmtree
+from itertools import chain
+
+from powerline import Powerline
+
+
+CONFIG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config')
+
+
+class TestPowerline(Powerline):
+ def __init__(self, _paths, *args, **kwargs):
+ super(TestPowerline, self).__init__(*args, **kwargs)
+ self._paths = _paths
+
+ def get_config_paths(self):
+ return self._paths
+
+
+def mkdir_recursive(directory):
+ if os.path.isdir(directory):
+ return
+ mkdir_recursive(os.path.dirname(directory))
+ os.mkdir(directory)
+
+
+class FSTree(object):
+ __slots__ = ('tree', 'p', 'p_kwargs', 'create_p', 'get_config_paths', 'root')
+
+ def __init__(
+ self,
+ tree,
+ p_kwargs={'run_once': True},
+ root=CONFIG_DIR,
+ get_config_paths=lambda p: (p,),
+ create_p=False
+ ):
+ self.tree = tree
+ self.root = root
+ self.get_config_paths = get_config_paths
+ self.create_p = create_p
+ self.p = None
+ self.p_kwargs = p_kwargs
+
+ def __enter__(self, *args):
+ os.mkdir(self.root)
+ for k, v in self.tree.items():
+ fname = os.path.join(self.root, k) + '.json'
+ mkdir_recursive(os.path.dirname(fname))
+ with open(fname, 'w') as F:
+ json.dump(v, F)
+ if self.create_p:
+ self.p = TestPowerline(
+ _paths=self.get_config_paths(self.root),
+ ext='test',
+ renderer_module='tests.modules.lib.config_mock',
+ **self.p_kwargs
+ )
+ if os.environ.get('POWERLINE_RUN_LINT_DURING_TESTS'):
+ try:
+ check_call(chain(['scripts/powerline-lint'], *[
+ ('-p', d) for d in (
+ self.p.get_config_paths() if self.p
+ else self.get_config_paths(self.root)
+ )
+ ]))
+ except:
+ self.__exit__()
+ raise
+ return self.p and self.p.__enter__(*args)
+
+ def __exit__(self, *args):
+ try:
+ rmtree(self.root)
+ finally:
+ if self.p:
+ self.p.__exit__(*args)
diff --git a/tests/modules/lib/terminal.py b/tests/modules/lib/terminal.py
new file mode 100644
index 0000000..540135d
--- /dev/null
+++ b/tests/modules/lib/terminal.py
@@ -0,0 +1,307 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import threading
+import os
+
+from time import sleep
+from itertools import groupby
+from signal import SIGKILL
+from difflib import ndiff
+
+import pexpect
+
+from powerline.lib.unicode import u
+
+from tests.modules.lib.vterm import VTerm, Dimensions
+
+
+class MutableDimensions(object):
+ def __init__(self, rows, cols):
+ super(MutableDimensions, self).__init__()
+ self._list = [rows, cols]
+
+ def __getitem__(self, idx):
+ return self._list[idx]
+
+ def __setitem__(self, idx, val):
+ self._list[idx] = val
+
+ def __iter__(self):
+ return iter(self._list)
+
+ def __len__(self):
+ return 2
+
+ def __nonzero__(self):
+ return True
+
+ __bool__ = __nonzero__
+
+ rows = property(
+ fget = lambda self: self._list[0],
+ fset = lambda self, val: self._list.__setitem__(0, val),
+ )
+ cols = property(
+ fget = lambda self: self._list[1],
+ fset = lambda self, val: self._list.__setitem__(1, val),
+ )
+
+
+class ExpectProcess(threading.Thread):
+ def __init__(self, lib, dim, cmd, args, cwd=None, env=None):
+ super(ExpectProcess, self).__init__()
+ self.vterm = VTerm(lib, dim)
+ self.lock = threading.Lock()
+ self.dim = Dimensions(*dim)
+ self.cmd = cmd
+ self.args = args
+ self.cwd = cwd
+ self.env = env
+ self.buffer = []
+ self.child_lock = threading.Lock()
+ self.shutdown_event = threading.Event()
+ self.started_event = threading.Event()
+
+ def run(self):
+ with self.child_lock:
+ child = pexpect.spawn(self.cmd, self.args, cwd=self.cwd,
+ env=self.env)
+ sleep(0.5)
+ child.setwinsize(self.dim.rows, self.dim.cols)
+ sleep(0.5)
+ self.child = child
+ self.started_event.set()
+ status = None
+ while status is None and not self.shutdown_event.is_set():
+ try:
+ with self.child_lock:
+ s = child.read_nonblocking(size=1024, timeout=0)
+ status = child.status
+ except pexpect.TIMEOUT:
+ pass
+ except pexpect.EOF:
+ break
+ else:
+ with self.lock:
+ self.vterm.push(s)
+ self.buffer.append(s)
+
+ if status is None:
+ child.kill(SIGKILL)
+
+ def kill(self):
+ self.shutdown_event.set()
+
+ def resize(self, dim):
+ with self.child_lock:
+ self.dim = Dimensions(*dim)
+ self.child.setwinsize(self.dim.rows, self.dim.cols)
+ self.vterm.resize(self.dim)
+
+ def __getitem__(self, position):
+ with self.lock:
+ return self.vterm.vtscreen[position]
+
+ def read(self):
+ with self.lock:
+ ret = b''.join(self.buffer)
+ del self.buffer[:]
+ return ret
+
+ def send(self, data):
+ with self.child_lock:
+ self.child.send(data)
+
+ def get_highlighted_text(self, text, attrs, default_props=(),
+ use_escapes=False):
+ ret = []
+ new_attrs = attrs.copy()
+ for cell_properties, segment_text in text:
+ if use_escapes:
+ escapes = ('\033[38;2;{0};{1};{2};48;2;{3};{4};{5}'.format(
+ *(cell_properties[0] + cell_properties[1]))) + (
+ ';1' if cell_properties[2] else ''
+ ) + (
+ ';3' if cell_properties[3] else ''
+ ) + (
+ ';4' if cell_properties[4] else ''
+ ) + 'm'
+ ret.append(escapes + segment_text + '\033[0m')
+ else:
+ segment_text = segment_text.translate({'{': '{{', '}': '}}'})
+ if cell_properties not in new_attrs:
+ new_attrs[cell_properties] = len(new_attrs) + 1
+ props_name = new_attrs[cell_properties]
+ if props_name in default_props:
+ ret.append(segment_text)
+ else:
+ ret.append('{' + str(props_name) + ':' + segment_text + '}')
+ return ''.join(ret), new_attrs
+
+ def get_row(self, row, attrs, default_props=(), use_escapes=False):
+ with self.lock:
+ return self.get_highlighted_text((
+ (key, ''.join((cell.text for cell in subline)))
+ for key, subline in groupby((
+ self.vterm.vtscreen[row, col]
+ for col in range(self.dim.cols)
+ ), lambda cell: cell.cell_properties_key)
+ ), attrs, default_props, use_escapes)
+
+ def get_screen(self, attrs, default_props=(), use_escapes=False):
+ lines = []
+ for row in range(self.dim.rows):
+ line, attrs = self.get_row(row, attrs, default_props, use_escapes)
+ lines.append(line)
+ return '\n'.join(lines), attrs
+
+
+def test_expected_result(p, test, last_attempt, last_attempt_cb, attempts):
+ debugging_tests = not not os.environ.get('_POWERLINE_DEBUGGING_TESTS')
+ expected_text, attrs = test['expected_result']
+ result = None
+ while attempts:
+ if 'row' in test:
+ row = test['row']
+ else:
+ row = p.dim.rows - 1
+ while row >= 0 and not p[row, 0].text:
+ row -= 1
+ if row < 0:
+ row = 0
+ actual_text, all_attrs = p.get_row(row, attrs)
+ if actual_text == expected_text:
+ return True
+ attempts -= 1
+ print('Actual result does not match expected for row {0}. Attempts left: {1}.'.format(
+ row, attempts))
+ sleep(2)
+ print('Result (row {0}):'.format(row))
+ print(actual_text)
+ print('Expected:')
+ print(expected_text)
+ print('Attributes:')
+ for v, k in sorted(
+ ((v, k) for k, v in all_attrs.items()),
+ key=(lambda t: '%02u'.format(t[0]) if isinstance(t[0], int) else t[0]),
+ ):
+ print('{k!r}: {v!r},'.format(v=v, k=k))
+ print('Screen:')
+ screen, screen_attrs = p.get_screen(attrs, use_escapes=debugging_tests)
+ print(screen)
+ print(screen_attrs)
+ print('_' * 80)
+ print('Diff:')
+ print('=' * 80)
+ print(''.join((
+ u(line) for line in ndiff([actual_text + '\n'], [expected_text + '\n']))
+ ))
+ if last_attempt and last_attempt_cb:
+ last_attempt_cb()
+ return False
+
+
+ENV_BASE = {
+ # Reasoning:
+ # 1. vt* TERMs (used to be vt100 here) make tmux-1.9 use different and
+ # identical colors for inactive windows. This is not like tmux-1.6:
+ # foreground color is different from separator color and equal to (0,
+ # 102, 153) for some reason (separator has correct color). tmux-1.8 is
+ # fine, so are older versions (though tmux-1.6 and tmux-1.7 do not have
+ # highlighting for previously active window) and my system tmux-1.9a.
+ # 2. screen, xterm and some other non-256color terminals both have the same
+ # issue and make libvterm emit complains like `Unhandled CSI SGR 3231`.
+ # 3. screen-256color, xterm-256color and other -256color terminals make
+ # libvterm emit complains about unhandled escapes to stderr.
+ # 4. `st-256color` does not have any of the above problems, but it may be
+ # not present on the target system because it is installed with
+ # x11-terms/st and not with sys-libs/ncurses.
+ #
+ # For the given reasons decision was made: to fix tmux-1.9 tests and not
+ # make libvterm emit any data to stderr st-256color $TERM should be used, up
+ # until libvterm has its own terminfo database entry (if it ever will). To
+ # make sure that relevant terminfo entry is present on the target system it
+ # should be distributed with powerline test package. To make distribution
+ # not require modifying anything outside of powerline test directory
+ # TERMINFO variable is set.
+ #
+ # This fix propagates to non-tmux vterm tests just in case.
+ 'TERM': 'st-256color',
+ # Also $TERMINFO definition in get_env
+
+ 'POWERLINE_CONFIG_PATHS': os.path.abspath('powerline/config_files'),
+ 'POWERLINE_COMMAND': 'powerline-render',
+ 'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH', ''),
+ 'PYTHONPATH': os.environ.get('PYTHONPATH', ''),
+}
+
+
+def get_env(vterm_path, test_dir, *args, **kwargs):
+ env = ENV_BASE.copy()
+ env.update({
+ 'TERMINFO': os.path.join(test_dir, 'terminfo'),
+ 'PATH': vterm_path,
+ 'SHELL': os.path.join(vterm_path, 'bash'),
+ })
+ env.update(*args, **kwargs)
+ return env
+
+
+def do_terminal_tests(tests, cmd, dim, args, env, suite, cwd=None, fin_cb=None,
+ last_attempt_cb=None, attempts=None):
+ debugging_tests = not not os.environ.get('_POWERLINE_DEBUGGING_TESTS')
+ default_attempts = 2 if debugging_tests else 3
+ if attempts is None:
+ attempts = default_attempts
+ lib = os.environ.get('POWERLINE_LIBVTERM')
+ if not lib:
+ if os.path.exists('tests/bot-ci/deps/libvterm/libvterm.so'):
+ lib = 'tests/bot-ci/deps/libvterm/libvterm.so'
+ else:
+ lib = 'libvterm.so'
+
+ while attempts:
+ try:
+ p = ExpectProcess(
+ lib=lib,
+ dim=dim,
+ cmd=cmd,
+ args=args,
+ cwd=cwd,
+ env=env,
+ )
+ p.start()
+ p.started_event.wait()
+
+ ret = True
+
+ for i, test in enumerate(tests):
+ with suite.test(test.get('name', 'test_{0}'.format(i)),
+ attempts - 1) as ptest:
+ try:
+ test_prep = test['prep_cb']
+ except KeyError:
+ pass
+ else:
+ test_prep(p)
+ test_result = test_expected_result(
+ p, test, attempts == 0, last_attempt_cb,
+ test.get('attempts', default_attempts)
+ )
+ if not test_result:
+ ptest.fail('Result does not match expected')
+ ret = ret and test_result
+
+ if ret:
+ return ret
+ finally:
+ if fin_cb:
+ fin_cb(p=p, cmd=cmd, env=env)
+ p.kill()
+ p.join(10)
+ assert(not p.isAlive())
+
+ attempts -= 1
+
+ return False
diff --git a/tests/modules/lib/vterm.py b/tests/modules/lib/vterm.py
new file mode 100644
index 0000000..1984e1b
--- /dev/null
+++ b/tests/modules/lib/vterm.py
@@ -0,0 +1,193 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import ctypes
+
+from collections import namedtuple
+
+from powerline.lib.unicode import unicode, unichr, tointiter
+
+
+Dimensions = namedtuple('Dimensions', ('rows', 'cols'))
+
+
+class CTypesFunction(object):
+ def __init__(self, library, name, rettype, args):
+ self.name = name
+ self.prototype = ctypes.CFUNCTYPE(rettype, *[
+ arg[1] for arg in args
+ ])
+ self.args = args
+ self.func = self.prototype((name, library), tuple((
+ (1, arg[0]) for arg in args
+ )))
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ def __repr__(self):
+ return '{cls}(<library>, {name!r}, {rettype!r}, {args!r})'.format(
+ cls=self.__class__.__name__,
+ **self.__dict__
+ )
+
+
+class CTypesLibraryFuncsCollection(object):
+ def __init__(self, lib, **kwargs):
+ self.lib = lib
+ library_loader = ctypes.LibraryLoader(ctypes.CDLL)
+ library = library_loader.LoadLibrary(lib)
+ self.library = library
+ for name, args in kwargs.items():
+ self.__dict__[name] = CTypesFunction(library, name, *args)
+
+
+class VTermPos_s(ctypes.Structure):
+ _fields_ = (
+ ('row', ctypes.c_int),
+ ('col', ctypes.c_int),
+ )
+
+
+class VTermColor_s(ctypes.Structure):
+ _fields_ = (
+ ('red', ctypes.c_uint8),
+ ('green', ctypes.c_uint8),
+ ('blue', ctypes.c_uint8),
+ )
+
+
+class VTermScreenCellAttrs_s(ctypes.Structure):
+ _fields_ = (
+ ('bold', ctypes.c_uint, 1),
+ ('underline', ctypes.c_uint, 2),
+ ('italic', ctypes.c_uint, 1),
+ ('blink', ctypes.c_uint, 1),
+ ('reverse', ctypes.c_uint, 1),
+ ('strike', ctypes.c_uint, 1),
+ ('font', ctypes.c_uint, 4),
+ ('dwl', ctypes.c_uint, 1),
+ ('dhl', ctypes.c_uint, 2),
+ )
+
+
+VTERM_MAX_CHARS_PER_CELL = 6
+
+
+class VTermScreenCell_s(ctypes.Structure):
+ _fields_ = (
+ ('chars', ctypes.ARRAY(ctypes.c_uint32, VTERM_MAX_CHARS_PER_CELL)),
+ ('width', ctypes.c_char),
+ ('attrs', VTermScreenCellAttrs_s),
+ ('fg', VTermColor_s),
+ ('bg', VTermColor_s),
+ )
+
+
+VTerm_p = ctypes.c_void_p
+VTermScreen_p = ctypes.c_void_p
+
+
+def get_functions(lib):
+ return CTypesLibraryFuncsCollection(
+ lib,
+ vterm_new=(VTerm_p, (
+ ('rows', ctypes.c_int),
+ ('cols', ctypes.c_int)
+ )),
+ vterm_obtain_screen=(VTermScreen_p, (('vt', VTerm_p),)),
+ vterm_set_size=(None, (
+ ('vt', VTerm_p),
+ ('rows', ctypes.c_int),
+ ('cols', ctypes.c_int)
+ )),
+ vterm_screen_reset=(None, (
+ ('screen', VTermScreen_p),
+ ('hard', ctypes.c_int)
+ )),
+ vterm_input_write=(ctypes.c_size_t, (
+ ('vt', VTerm_p),
+ ('bytes', ctypes.POINTER(ctypes.c_char)),
+ ('size', ctypes.c_size_t),
+ )),
+ vterm_screen_get_cell=(ctypes.c_int, (
+ ('screen', VTermScreen_p),
+ ('pos', VTermPos_s),
+ ('cell', ctypes.POINTER(VTermScreenCell_s))
+ )),
+ vterm_free=(None, (('vt', VTerm_p),)),
+ vterm_set_utf8=(None, (('vt', VTerm_p), ('is_utf8', ctypes.c_int))),
+ )
+
+
+class VTermColor(object):
+ __slots__ = ('red', 'green', 'blue')
+
+ def __init__(self, color):
+ self.red = color.red
+ self.green = color.green
+ self.blue = color.blue
+
+ @property
+ def color_key(self):
+ return (self.red, self.green, self.blue)
+
+
+class VTermScreenCell(object):
+ def __init__(self, vtsc):
+ for field in VTermScreenCellAttrs_s._fields_:
+ field_name = field[0]
+ setattr(self, field_name, getattr(vtsc.attrs, field_name))
+ self.text = ''.join((
+ unichr(vtsc.chars[i]) for i in range(VTERM_MAX_CHARS_PER_CELL)
+ )).rstrip('\x00')
+ self.width = next(tointiter(vtsc.width))
+ self.fg = VTermColor(vtsc.fg)
+ self.bg = VTermColor(vtsc.bg)
+ self.cell_properties_key = (
+ self.fg.color_key,
+ self.bg.color_key,
+ self.bold,
+ self.underline,
+ self.italic,
+ )
+
+
+class VTermScreen(object):
+ def __init__(self, functions, screen):
+ self.functions = functions
+ self.screen = screen
+
+ def __getitem__(self, position):
+ pos = VTermPos_s(*position)
+ cell = VTermScreenCell_s()
+ ret = self.functions.vterm_screen_get_cell(self.screen, pos, cell)
+ if ret != 1:
+ raise ValueError('vterm_screen_get_cell returned {0}'.format(ret))
+ return VTermScreenCell(cell)
+
+ def reset(self, hard):
+ self.functions.vterm_screen_reset(self.screen, int(bool(hard)))
+
+
+class VTerm(object):
+ def __init__(self, lib, dim):
+ self.functions = get_functions(lib)
+ self.vt = self.functions.vterm_new(dim.rows, dim.cols)
+ self.functions.vterm_set_utf8(self.vt, 1)
+ self.vtscreen = VTermScreen(self.functions, self.functions.vterm_obtain_screen(self.vt))
+ self.vtscreen.reset(True)
+
+ def push(self, data):
+ if isinstance(data, unicode):
+ data = data.encode('utf-8')
+ return self.functions.vterm_input_write(self.vt, data, len(data))
+
+ def resize(self, dim):
+ self.functions.vterm_set_size(self.vt, dim.rows, dim.cols)
+
+ def __del__(self):
+ try:
+ self.functions.vterm_free(self.vt)
+ except AttributeError:
+ pass
diff --git a/tests/modules/matchers.py b/tests/modules/matchers.py
new file mode 100644
index 0000000..e905de3
--- /dev/null
+++ b/tests/modules/matchers.py
@@ -0,0 +1,6 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+
+def always_true(matcher_info):
+ return True
diff --git a/tests/modules/vim.py b/tests/modules/vim.py
new file mode 100644
index 0000000..3f6882c
--- /dev/null
+++ b/tests/modules/vim.py
@@ -0,0 +1,927 @@
+# vim:fileencoding=utf-8:noet
+_log = []
+vars = {}
+vvars = {'version': 703}
+_tabpage = 0
+_mode = 'n'
+_buf_purge_events = set()
+options = {
+ 'paste': 0,
+ 'ambiwidth': 'single',
+ 'columns': 80,
+ 'encoding': 'utf-8',
+}
+_last_bufnr = 0
+_highlights = {}
+from collections import defaultdict as _defaultdict
+_environ = _defaultdict(lambda: '')
+del _defaultdict
+
+
+_thread_id = None
+
+
+def _set_thread_id():
+ global _thread_id
+ from threading import current_thread
+ _thread_id = current_thread().ident
+
+
+# Assuming import is done from the main thread
+_set_thread_id()
+
+
+def _print_log():
+ for item in _log:
+ print (item)
+ _log[:] = ()
+
+
+def _vim(func):
+ from functools import wraps
+ from threading import current_thread
+
+ @wraps(func)
+ def f(*args, **kwargs):
+ global _thread_id
+ if _thread_id != current_thread().ident:
+ raise RuntimeError('Accessing vim from separate threads is not allowed')
+ _log.append((func.__name__, args))
+ return func(*args, **kwargs)
+
+ return f
+
+
+def _unicode(func):
+ from functools import wraps
+ import sys
+
+ if sys.version_info < (3,):
+ return func
+
+ @wraps(func)
+ def f(*args, **kwargs):
+ from powerline.lib.unicode import u
+ ret = func(*args, **kwargs)
+ if isinstance(ret, bytes):
+ ret = u(ret)
+ return ret
+
+ return f
+
+
+class _Buffers(object):
+ @_vim
+ def __init__(self):
+ self.d = {}
+
+ @_vim
+ def __len__(self):
+ return len(self.d)
+
+ @_vim
+ def __getitem__(self, item):
+ return self.d[item]
+
+ @_vim
+ def __setitem__(self, item, value):
+ self.d[item] = value
+
+ @_vim
+ def __iter__(self):
+ return iter(self.d.values())
+
+ @_vim
+ def __contains__(self, item):
+ return item in self.d
+
+ @_vim
+ def _keys(self):
+ return self.d.keys()
+
+ @_vim
+ def _pop(self, *args, **kwargs):
+ return self.d.pop(*args, **kwargs)
+
+
+buffers = _Buffers()
+
+
+class _ObjList(object):
+ @_vim
+ def __init__(self, objtype):
+ self.l = []
+ self.objtype = objtype
+
+ @_vim
+ def __getitem__(self, item):
+ return self.l[item - int(item > 0)]
+
+ @_vim
+ def __len__(self):
+ return len(self.l)
+
+ @_vim
+ def __iter__(self):
+ return iter(self.l)
+
+ @_vim
+ def _pop(self, idx):
+ obj = self.l.pop(idx - 1)
+ for moved_obj in self.l[idx - 1:]:
+ moved_obj.number -= 1
+ return obj
+
+ @_vim
+ def _append(self, *args, **kwargs):
+ return self.l.append(*args, **kwargs)
+
+ @_vim
+ def _new(self, *args, **kwargs):
+ number = len(self) + 1
+ new_obj = self.objtype(number, *args, **kwargs)
+ self._append(new_obj)
+ return new_obj
+
+
+def _construct_result(r):
+ import sys
+ if sys.version_info < (3,):
+ return r
+ else:
+ if isinstance(r, str):
+ return r.encode('utf-8')
+ elif isinstance(r, list):
+ return [_construct_result(i) for i in r]
+ elif isinstance(r, dict):
+ return dict((
+ (_construct_result(k), _construct_result(v))
+ for k, v in r.items()
+ ))
+ return r
+
+
+def _str_func(func):
+ from functools import wraps
+
+ @wraps(func)
+ def f(*args, **kwargs):
+ return _construct_result(func(*args, **kwargs))
+ return f
+
+
+def _log_print():
+ import sys
+ for entry in _log:
+ sys.stdout.write(repr(entry) + '\n')
+
+
+_current_group = None
+_on_wipeout = []
+
+
+@_vim
+def command(cmd):
+ global _current_group
+ cmd = cmd.lstrip()
+ if cmd.startswith('let g:'):
+ import re
+ varname, value = re.compile(r'^let g:(\w+)\s*=\s*(.*)').match(cmd).groups()
+ vars[varname] = value
+ elif cmd.startswith('hi '):
+ sp = cmd.split()
+ _highlights[sp[1]] = sp[2:]
+ elif cmd.startswith('augroup'):
+ augroup = cmd.partition(' ')[2]
+ if augroup.upper() == 'END':
+ _current_group = None
+ else:
+ _current_group = augroup
+ elif cmd.startswith('autocmd'):
+ rest = cmd.partition(' ')[2]
+ auevent, rest = rest.partition(' ')[::2]
+ pattern, aucmd = rest.partition(' ')[::2]
+ if auevent != 'BufWipeout' or pattern != '*':
+ raise NotImplementedError
+ import sys
+ if sys.version_info < (3,):
+ if not aucmd.startswith(':python '):
+ raise NotImplementedError
+ else:
+ if not aucmd.startswith(':python3 '):
+ raise NotImplementedError
+ _on_wipeout.append(aucmd.partition(' ')[2])
+ elif cmd.startswith('set '):
+ if cmd.startswith('set statusline='):
+ options['statusline'] = cmd[len('set statusline='):]
+ elif cmd.startswith('set tabline='):
+ options['tabline'] = cmd[len('set tabline='):]
+ else:
+ raise NotImplementedError(cmd)
+ else:
+ raise NotImplementedError(cmd)
+
+
+@_vim
+@_unicode
+def eval(expr):
+ if expr.startswith('g:'):
+ return vars[expr[2:]]
+ elif expr.startswith('v:'):
+ return vvars[expr[2:]]
+ elif expr.startswith('&'):
+ return options[expr[1:]]
+ elif expr.startswith('$'):
+ return _environ[expr[1:]]
+ elif expr.startswith('PowerlineRegisterCachePurgerEvent'):
+ _buf_purge_events.add(expr[expr.find('"') + 1:expr.rfind('"') - 1])
+ return '0'
+ elif expr.startswith('exists('):
+ return '0'
+ elif expr.startswith('getwinvar('):
+ import re
+ match = re.match(r'^getwinvar\((\d+), "(\w+)"\)$', expr)
+ if not match:
+ raise NotImplementedError(expr)
+ winnr = int(match.group(1))
+ varname = match.group(2)
+ return _emul_getwinvar(winnr, varname)
+ elif expr.startswith('has_key('):
+ import re
+ match = re.match(r'^has_key\(getwinvar\((\d+), ""\), "(\w+)"\)$', expr)
+ if match:
+ winnr = int(match.group(1))
+ varname = match.group(2)
+ return 0 + (varname in current.tabpage.windows[winnr].vars)
+ else:
+ match = re.match(r'^has_key\(gettabwinvar\((\d+), (\d+), ""\), "(\w+)"\)$', expr)
+ if not match:
+ raise NotImplementedError(expr)
+ tabnr = int(match.group(1))
+ winnr = int(match.group(2))
+ varname = match.group(3)
+ return 0 + (varname in tabpages[tabnr].windows[winnr].vars)
+ elif expr == 'getbufvar("%", "NERDTreeRoot").path.str()':
+ import os
+ assert os.path.basename(current.buffer.name).startswith('NERD_tree_')
+ return '/usr/include'
+ elif expr.startswith('getbufvar('):
+ import re
+ match = re.match(r'^getbufvar\((\d+), ["\'](.+)["\']\)$', expr)
+ if not match:
+ raise NotImplementedError(expr)
+ bufnr = int(match.group(1))
+ varname = match.group(2)
+ return _emul_getbufvar(bufnr, varname)
+ elif expr == 'tabpagenr()':
+ return current.tabpage.number
+ elif expr == 'tabpagenr("$")':
+ return len(tabpages)
+ elif expr.startswith('tabpagewinnr('):
+ tabnr = int(expr[len('tabpagewinnr('):-1])
+ return tabpages[tabnr].window.number
+ elif expr.startswith('tabpagebuflist('):
+ import re
+ match = re.match(r'tabpagebuflist\((\d+)\)\[(\d+)\]', expr)
+ tabnr = int(match.group(1))
+ winnr = int(match.group(2)) + 1
+ return tabpages[tabnr].windows[winnr].buffer.number
+ elif expr.startswith('gettabwinvar('):
+ import re
+ match = re.match(r'gettabwinvar\((\d+), (\d+), "(\w+)"\)', expr)
+ tabnr = int(match.group(1))
+ winnr = int(match.group(2))
+ varname = match.group(3)
+ return tabpages[tabnr].windows[winnr].vars[varname]
+ elif expr.startswith('type(function('):
+ import re
+ match = re.match(r'^type\(function\("([^"]+)"\)\) == 2$', expr)
+ if not match:
+ raise NotImplementedError(expr)
+ return 0
+ raise NotImplementedError(expr)
+
+
+@_vim
+def bindeval(expr):
+ if expr == 'g:':
+ return vars
+ elif expr == '{}':
+ return {}
+ elif expr == '[]':
+ return []
+ import re
+ match = re.compile(r'^function\("([^"\\]+)"\)$').match(expr)
+ if match:
+ return globals()['_emul_' + match.group(1)]
+ else:
+ raise NotImplementedError
+
+
+@_vim
+@_str_func
+def _emul_mode(*args):
+ if args and args[0]:
+ return _mode
+ else:
+ return _mode[0]
+
+
+@_vim
+@_str_func
+def _emul_getbufvar(bufnr, varname):
+ import re
+ if varname[0] == '&':
+ if bufnr == '%':
+ bufnr = current.buffer.number
+ if bufnr not in buffers:
+ return ''
+ try:
+ return buffers[bufnr].options[varname[1:]]
+ except KeyError:
+ try:
+ return options[varname[1:]]
+ except KeyError:
+ return ''
+ elif re.match('^[a-zA-Z_]+$', varname):
+ if bufnr == '%':
+ bufnr = current.buffer.number
+ if bufnr not in buffers:
+ return ''
+ return buffers[bufnr].vars[varname]
+ raise NotImplementedError
+
+
+@_vim
+@_str_func
+def _emul_getwinvar(winnr, varname):
+ return current.tabpage.windows[winnr].vars.get(varname, '')
+
+
+@_vim
+def _emul_setwinvar(winnr, varname, value):
+ current.tabpage.windows[winnr].vars[varname] = value
+
+
+@_vim
+def _emul_virtcol(expr):
+ if expr == '.':
+ return current.window.cursor[1] + 1
+ if isinstance(expr, list) and len(expr) == 3:
+ return expr[-2] + expr[-1]
+ raise NotImplementedError
+
+
+_v_pos = None
+
+
+@_vim
+def _emul_getpos(expr):
+ if expr == '.':
+ return [0, current.window.cursor[0] + 1, current.window.cursor[1] + 1, 0]
+ if expr == 'v':
+ return _v_pos or [0, current.window.cursor[0] + 1, current.window.cursor[1] + 1, 0]
+ raise NotImplementedError
+
+
+@_vim
+@_str_func
+def _emul_fnamemodify(path, modstring):
+ import os
+ _modifiers = {
+ '~': lambda path: path.replace(os.environ['HOME'].encode('utf-8'), b'~') if path.startswith(os.environ['HOME'].encode('utf-8')) else path,
+ '.': lambda path: (lambda tpath: path if tpath[:3] == b'..' + os.sep.encode() else tpath)(os.path.relpath(path)),
+ 't': lambda path: os.path.basename(path),
+ 'h': lambda path: os.path.dirname(path),
+ }
+
+ for mods in modstring.split(':')[1:]:
+ path = _modifiers[mods](path)
+ return path
+
+
+@_vim
+@_str_func
+def _emul_expand(expr):
+ global _abuf
+ if expr == '<abuf>':
+ return _abuf or current.buffer.number
+ raise NotImplementedError
+
+
+@_vim
+def _emul_bufnr(expr):
+ if expr == '$':
+ return _last_bufnr
+ raise NotImplementedError
+
+
+@_vim
+def _emul_exists(ident):
+ if ident.startswith('g:'):
+ return ident[2:] in vars
+ elif ident.startswith(':'):
+ return 0
+ raise NotImplementedError
+
+
+@_vim
+def _emul_line2byte(line):
+ buflines = current.buffer._buf_lines
+ if line == len(buflines) + 1:
+ return sum((len(s) for s in buflines)) + 1
+ raise NotImplementedError
+
+
+@_vim
+def _emul_line(expr):
+ cursorline = current.window.cursor[0] + 1
+ numlines = len(current.buffer._buf_lines)
+ if expr == 'w0':
+ return max(cursorline - 5, 1)
+ if expr == 'w$':
+ return min(cursorline + 5, numlines)
+ raise NotImplementedError
+
+
+@_vim
+@_str_func
+def _emul_strtrans(s):
+ # FIXME Do more replaces
+ return s.replace(b'\xFF', b'<ff>')
+
+
+@_vim
+@_str_func
+def _emul_bufname(bufnr):
+ try:
+ return buffers[bufnr]._name or b''
+ except KeyError:
+ return b''
+
+
+_window_id = 0
+
+
+class _Window(object):
+ def __init__(self, number, buffer=None, cursor=(1, 0), width=80):
+ global _window_id
+ self.cursor = cursor
+ self.width = width
+ self.number = number
+ if buffer:
+ if type(buffer) is _Buffer:
+ self.buffer = buffer
+ else:
+ self.buffer = _Buffer(**buffer)
+ else:
+ self.buffer = _Buffer()
+ _window_id += 1
+ self._window_id = _window_id
+ self.options = {}
+ self.vars = {
+ 'powerline_window_id': self._window_id,
+ }
+
+ def __repr__(self):
+ return '<window ' + str(self.number - 1) + '>'
+
+
+class _Tabpage(object):
+ def __init__(self, number):
+ self.windows = _ObjList(_Window)
+ self.number = number
+
+ def _new_window(self, **kwargs):
+ self.window = self.windows._new(**kwargs)
+ return self.window
+
+ def _close_window(self, winnr, open_window=True):
+ curwinnr = self.window.number
+ win = self.windows._pop(winnr)
+ if self.windows and winnr == curwinnr:
+ self.window = self.windows[-1]
+ elif open_window:
+ current.tabpage._new_window()
+ return win
+
+ def _close(self):
+ global _tabpage
+ while self.windows:
+ self._close_window(1, False)
+ tabpages._pop(self.number)
+ _tabpage = len(tabpages)
+
+
+tabpages = _ObjList(_Tabpage)
+
+
+_abuf = None
+
+
+class _Buffer(object):
+ def __init__(self, name=None):
+ global _last_bufnr
+ _last_bufnr += 1
+ bufnr = _last_bufnr
+ self.number = bufnr
+ # FIXME Use unicode() for python-3
+ self.name = name
+ self.vars = {'changedtick': 1}
+ self.options = {
+ 'modified': 0,
+ 'readonly': 0,
+ 'fileformat': 'unix',
+ 'filetype': '',
+ 'buftype': '',
+ 'fileencoding': 'utf-8',
+ 'textwidth': 80,
+ }
+ self._buf_lines = ['']
+ self._undostate = [self._buf_lines[:]]
+ self._undo_written = len(self._undostate)
+ buffers[bufnr] = self
+
+ @property
+ def name(self):
+ import sys
+ if sys.version_info < (3,):
+ return self._name
+ else:
+ return str(self._name, 'utf-8') if self._name else None
+
+ @name.setter
+ def name(self, name):
+ if name is None:
+ self._name = None
+ else:
+ import os
+ if type(name) is not bytes:
+ name = name.encode('utf-8')
+ if b':/' in name:
+ self._name = name
+ else:
+ self._name = os.path.abspath(name)
+
+ def __getitem__(self, line):
+ return self._buf_lines[line]
+
+ def __setitem__(self, line, value):
+ self.options['modified'] = 1
+ self.vars['changedtick'] += 1
+ self._buf_lines[line] = value
+ from copy import copy
+ self._undostate.append(copy(self._buf_lines))
+
+ def __setslice__(self, *args):
+ self.options['modified'] = 1
+ self.vars['changedtick'] += 1
+ self._buf_lines.__setslice__(*args)
+ from copy import copy
+ self._undostate.append(copy(self._buf_lines))
+
+ def __getslice__(self, *args):
+ return self._buf_lines.__getslice__(*args)
+
+ def __len__(self):
+ return len(self._buf_lines)
+
+ def __repr__(self):
+ return '<buffer ' + str(self.name) + '>'
+
+ def __del__(self):
+ global _abuf
+ bufnr = self.number
+ try:
+ import __main__
+ except ImportError:
+ pass
+ except RuntimeError:
+ # Module may have already been garbage-collected
+ pass
+ else:
+ if _on_wipeout:
+ _abuf = bufnr
+ try:
+ for event in _on_wipeout:
+ exec(event, __main__.__dict__)
+ finally:
+ _abuf = None
+
+
+class _Current(object):
+ @property
+ def buffer(self):
+ return self.window.buffer
+
+ @property
+ def window(self):
+ return self.tabpage.window
+
+ @property
+ def tabpage(self):
+ return tabpages[_tabpage - 1]
+
+
+current = _Current()
+
+
+_dict = None
+
+
+@_vim
+def _init():
+ global _dict
+
+ if _dict:
+ return _dict
+
+ _dict = {}
+ for varname, value in globals().items():
+ if varname[0] != '_':
+ _dict[varname] = value
+ _tabnew()
+ return _dict
+
+
+@_vim
+def _get_segment_info():
+ mode_translations = {
+ chr(ord('V') - 0x40): '^V',
+ chr(ord('S') - 0x40): '^S',
+ }
+ mode = _mode
+ mode = mode_translations.get(mode, mode)
+ window = current.window
+ buffer = current.buffer
+ tabpage = current.tabpage
+ return {
+ 'window': window,
+ 'winnr': window.number,
+ 'buffer': buffer,
+ 'bufnr': buffer.number,
+ 'tabpage': tabpage,
+ 'tabnr': tabpage.number,
+ 'window_id': window._window_id,
+ 'mode': mode,
+ 'encoding': options['encoding'],
+ }
+
+
+@_vim
+def _launch_event(event):
+ pass
+
+
+@_vim
+def _start_mode(mode):
+ global _mode
+ if mode == 'i':
+ _launch_event('InsertEnter')
+ elif _mode == 'i':
+ _launch_event('InsertLeave')
+ _mode = mode
+
+
+@_vim
+def _undo():
+ if len(current.buffer._undostate) == 1:
+ return
+ buffer = current.buffer
+ buffer._undostate.pop(-1)
+ buffer._buf_lines = buffer._undostate[-1]
+ if buffer._undo_written == len(buffer._undostate):
+ buffer.options['modified'] = 0
+
+
+@_vim
+def _edit(name=None):
+ if current.buffer.name is None:
+ buffer = current.buffer
+ buffer.name = name
+ else:
+ buffer = _Buffer(name)
+ current.window.buffer = buffer
+
+
+@_vim
+def _tabnew(name=None):
+ global windows
+ global _tabpage
+ tabpage = tabpages._new()
+ windows = tabpage.windows
+ _tabpage = len(tabpages)
+ _new(name)
+ return tabpage
+
+
+@_vim
+def _new(name=None):
+ current.tabpage._new_window(buffer={'name': name})
+
+
+@_vim
+def _split():
+ current.tabpage._new_window(buffer=current.buffer)
+
+
+@_vim
+def _close(winnr, wipe=True):
+ win = current.tabpage._close_window(winnr)
+ if wipe:
+ for w in current.tabpage.windows:
+ if w.buffer.number == win.buffer.number:
+ break
+ else:
+ _bw(win.buffer.number)
+
+
+@_vim
+def _bw(bufnr=None):
+ bufnr = bufnr or current.buffer.number
+ winnr = 1
+ for win in current.tabpage.windows:
+ if win.buffer.number == bufnr:
+ _close(winnr, wipe=False)
+ winnr += 1
+ buffers._pop(bufnr)
+ if not buffers:
+ _Buffer()
+ _b(max(buffers._keys()))
+
+
+@_vim
+def _b(bufnr):
+ current.window.buffer = buffers[bufnr]
+
+
+@_vim
+def _set_cursor(line, col):
+ current.window.cursor = (line, col)
+ if _mode == 'n':
+ _launch_event('CursorMoved')
+ elif _mode == 'i':
+ _launch_event('CursorMovedI')
+
+
+@_vim
+def _get_buffer():
+ return current.buffer
+
+
+@_vim
+def _set_bufoption(option, value, bufnr=None):
+ buffers[bufnr or current.buffer.number].options[option] = value
+ if option == 'filetype':
+ _launch_event('FileType')
+
+
+class _WithNewBuffer(object):
+ def __init__(self, func, *args, **kwargs):
+ self.call = lambda: func(*args, **kwargs)
+
+ def __enter__(self):
+ self.call()
+ self.bufnr = current.buffer.number
+ return _get_segment_info()
+
+ def __exit__(self, *args):
+ _bw(self.bufnr)
+
+
+@_vim
+def _set_dict(d, new, setfunc=None):
+ if not setfunc:
+ def setfunc(k, v):
+ d[k] = v
+
+ old = {}
+ na = []
+ for k, v in new.items():
+ try:
+ old[k] = d[k]
+ except KeyError:
+ na.append(k)
+ setfunc(k, v)
+ return old, na
+
+
+class _WithBufOption(object):
+ def __init__(self, **new):
+ self.new = new
+
+ def __enter__(self):
+ self.buffer = current.buffer
+ self.old = _set_dict(self.buffer.options, self.new, _set_bufoption)[0]
+
+ def __exit__(self, *args):
+ self.buffer.options.update(self.old)
+
+
+class _WithMode(object):
+ def __init__(self, new):
+ self.new = new
+
+ def __enter__(self):
+ self.old = _mode
+ _start_mode(self.new)
+ return _get_segment_info()
+
+ def __exit__(self, *args):
+ _start_mode(self.old)
+
+
+class _WithDict(object):
+ def __init__(self, d, **new):
+ self.new = new
+ self.d = d
+
+ def __enter__(self):
+ self.old, self.na = _set_dict(self.d, self.new)
+
+ def __exit__(self, *args):
+ self.d.update(self.old)
+ for k in self.na:
+ self.d.pop(k)
+
+
+class _WithSplit(object):
+ def __enter__(self):
+ _split()
+
+ def __exit__(self, *args):
+ _close(2, wipe=False)
+
+
+class _WithBufName(object):
+ def __init__(self, new):
+ self.new = new
+
+ def __enter__(self):
+ import os
+ buffer = current.buffer
+ self.buffer = buffer
+ self.old = buffer.name
+ buffer.name = self.new
+
+ def __exit__(self, *args):
+ self.buffer.name = self.old
+
+
+class _WithNewTabPage(object):
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+
+ def __enter__(self):
+ self.tab = _tabnew(*self.args, **self.kwargs)
+
+ def __exit__(self, *args):
+ self.tab._close()
+
+
+class _WithGlobal(object):
+ def __init__(self, **kwargs):
+ self.kwargs = kwargs
+
+ def __enter__(self):
+ self.empty = object()
+ self.old = dict(((key, globals().get(key, self.empty)) for key in self.kwargs))
+ globals().update(self.kwargs)
+
+ def __exit__(self, *args):
+ for k, v in self.old.items():
+ if v is self.empty:
+ globals().pop(k, None)
+ else:
+ globals()[k] = v
+
+
+@_vim
+def _with(key, *args, **kwargs):
+ if key == 'buffer':
+ return _WithNewBuffer(_edit, *args, **kwargs)
+ elif key == 'bufname':
+ return _WithBufName(*args, **kwargs)
+ elif key == 'mode':
+ return _WithMode(*args, **kwargs)
+ elif key == 'bufoptions':
+ return _WithBufOption(**kwargs)
+ elif key == 'options':
+ return _WithDict(options, **kwargs)
+ elif key == 'globals':
+ return _WithDict(vars, **kwargs)
+ elif key == 'wvars':
+ return _WithDict(current.window.vars, **kwargs)
+ elif key == 'environ':
+ return _WithDict(_environ, **kwargs)
+ elif key == 'split':
+ return _WithSplit()
+ elif key == 'tabpage':
+ return _WithNewTabPage(*args, **kwargs)
+ elif key == 'vpos':
+ return _WithGlobal(_v_pos=[0, kwargs['line'], kwargs['col'], kwargs['off']])
+
+
+class error(Exception):
+ pass
diff --git a/tests/shlib/common.sh b/tests/shlib/common.sh
new file mode 100644
index 0000000..6330262
--- /dev/null
+++ b/tests/shlib/common.sh
@@ -0,0 +1,150 @@
+: ${USER:=`id -un`}
+: ${HOME:=`getent passwd $USER | cut -d: -f6`}
+
+if test -z "${PYTHON}" ; then
+ if test -n "$USE_UCS2_PYTHON" ; then
+ LD_LIBRARY_PATH="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/lib${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}"
+ fi
+fi
+
+export LD_LIBRARY_PATH
+export USER
+export HOME
+
+if test -n "$USE_UCS2_PYTHON" ; then
+ POWERLINE_VIRTUALENV="cpython-ucs2-$UCS2_PYTHON_VARIANT"
+ PYTHON="$HOME/.virtualenvs/$POWERLINE_VIRTUALENV/bin/python"
+ if test -n "$BASH_VERSION" ; then
+ set +e
+ . virtualenvwrapper.sh
+ workon "$POWERLINE_VIRTUALENV"
+ set -e
+ fi
+fi
+
+. tests/bot-ci/scripts/common/main.sh silent
+
+export USER HOME
+
+if test -z "$FAILED" ; then
+ FAILED=0
+
+ FAIL_SUMMARY=""
+
+ TMP_ROOT="$ROOT/tests/tmp"
+ export FAILURES_FILE="$ROOT/tests/status"
+fi
+
+ANSI_CLEAR="\033[0K"
+
+travis_fold() {
+ local action="$1"
+ local name="$2"
+ name="$(echo -n "$name" | tr '\n\0' '--' | sed -r 's/[^A-Za-z0-9]+/-/g')"
+ name="$(echo -n "$name" | sed -r 's/-$//')"
+ echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}"
+}
+
+print_environ() {
+ echo "Using $PYTHON_IMPLEMENTATION version $PYTHON_VERSION."
+ echo "Path to Python executable: $PYTHON."
+ echo "Root: $ROOT."
+ echo "Branch: $BRANCH_NAME."
+ echo "sys.path:"
+ "$PYTHON" -c "for path in __import__('sys').path: print(' %r' % path)"
+}
+
+enter_suite() {
+ set +x
+ local suite_name="$1" ; shift
+ local final="$1"
+ export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE}/$suite_name"
+ travis_fold start "$POWERLINE_CURRENT_SUITE"
+ print_environ
+ if test "$final" = final ; then
+ if test -n "$POWERLINE_SUITE_FINAL" ; then
+ fail __suite__/enter/final E "Final suites do not allow nesting"
+ fi
+ export POWERLINE_SUITE_FINAL=1
+ # set -x
+ fi
+}
+
+exit_suite() {
+ if test "$POWERLINE_CURRENT_SUITE" = "$POWERLINE_TMP_DIR_SUITE" ; then
+ rm_test_root
+ fi
+ if test $FAILED -ne 0 ; then
+ echo "Suite ${POWERLINE_CURRENT_SUITE} failed, summary:"
+ echo "${FAIL_SUMMARY}"
+ fi
+ set +x
+ travis_fold end "$POWERLINE_CURRENT_SUITE"
+ export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE%/*}"
+ if test "$1" != "--continue" ; then
+ exit $FAILED
+ else
+ unset POWERLINE_SUITE_FINAL
+ fi
+}
+
+_fail() {
+ local allow_failure=
+ if test "$1" = "--allow-failure" ; then
+ shift
+ allow_failure=A
+ fi
+ local test_name="$1" ; shift
+ local fail_char="$allow_failure$1" ; shift
+ local message="$1" ; shift
+ local verb="$1" ; shift
+ local full_msg="$fail_char $POWERLINE_CURRENT_SUITE|$test_name :: $message"
+ FAIL_SUMMARY="${FAIL_SUMMARY}${NL}${full_msg}"
+ echo "$verb: $full_msg"
+ echo "$full_msg" >> "$FAILURES_FILE"
+ if test -z "$allow_failure" ; then
+ FAILED=1
+ fi
+}
+
+fail() {
+ _fail "$@" "Failed"
+}
+
+skip() {
+ local test_name="$1" ; shift
+ local message="$1" ; shift
+ _fail --allow-failure "$test_name" S "$message" "Skipped"
+}
+
+make_test_root() {
+ local suffix="${POWERLINE_CURRENT_SUITE##*/}"
+
+ local tmpdir="$TMP_ROOT/$suffix/"
+ export POWERLINE_TMP_DIR_SUITE="$POWERLINE_CURRENT_SUITE"
+
+ if test -d "$tmpdir" ; then
+ rm -r "$tmpdir"
+ fi
+
+ mkdir -p "$tmpdir"
+
+ export TEST_ROOT="$tmpdir"
+}
+
+rm_test_root() {
+ if test -e "$FAILURES_FILE" ; then
+ return 0
+ fi
+ local suffix="${POWERLINE_CURRENT_SUITE##*/}"
+ if test -d "$TMP_ROOT/$suffix" ; then
+ rm -r "$TMP_ROOT/$suffix"
+ rmdir "$TMP_ROOT" &>/dev/null || true
+ fi
+}
+
+if ! which realpath ; then
+ realpath() {
+ $PYTHON -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$1"
+ }
+fi
diff --git a/tests/shlib/vim.sh b/tests/shlib/vim.sh
new file mode 100644
index 0000000..49346da
--- /dev/null
+++ b/tests/shlib/vim.sh
@@ -0,0 +1,33 @@
+. tests/bot-ci/scripts/common/main.sh
+
+if test -z "$POWERLINE_VIM_EXE" ; then
+ if test -n "$USE_UCS2_PYTHON" ; then
+ NEW_VIM="$ROOT/tests/bot-ci/deps/vim/master-$UCS2_PYTHON_VARIANT-ucs2-double/vim"
+ OLD_VIM="$ROOT/tests/bot-ci/deps/vim/v7.0.112-$UCS2_PYTHON_VARIANT-ucs2/vim"
+ opt_dir="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT"
+ main_path="$opt_dir/lib/python$UCS2_PYTHON_VARIANT"
+ site_path="$main_path/site-packages"
+ venv_main_path="$VIRTUAL_ENV/lib/python$UCS2_PYTHON_VARIANT"
+ venv_site_path="$venv_main_path/site-packages"
+ new_paths="${main_path}:${site_path}:${venv_main_path}:${venv_site_path}"
+ export PYTHONPATH="$new_paths${PYTHONPATH:+:}$PYTHONPATH"
+ else
+ if test "$PYTHON_IMPLEMENTATION" != "CPython" ; then
+ exit 0
+ fi
+ if test -d "$ROOT/tests/bot-ci/deps" ; then
+ NEW_VIM="$ROOT/tests/bot-ci/deps/vim/master-$PYTHON_MM/vim"
+ OLD_VIM="$ROOT/tests/bot-ci/deps/vim/v7.0.112-$PYTHON_MM/vim"
+ else
+ NEW_VIM="vim"
+ fi
+ if test -e "$OLD_VIM" ; then
+ VIMS="NEW_VIM OLD_VIM"
+ else
+ VIMS="NEW_VIM"
+ fi
+ fi
+else
+ NEW_VIM="$POWERLINE_VIM_EXE"
+ OLD_VIM="$POWERLINE_VIM_EXE"
+fi
diff --git a/tests/shlib/vterm.sh b/tests/shlib/vterm.sh
new file mode 100644
index 0000000..56b6c89
--- /dev/null
+++ b/tests/shlib/vterm.sh
@@ -0,0 +1,17 @@
+. tests/shlib/common.sh
+set +x
+
+vterm_setup() {
+ make_test_root
+
+ mkdir "$TEST_ROOT/path"
+
+ ln -s "$(which "${PYTHON}")" "$TEST_ROOT/path/python"
+ ln -s "$(which bash)" "$TEST_ROOT/path"
+
+ cp -r "$ROOT/tests/terminfo" "$TEST_ROOT"
+}
+
+vterm_shutdown() {
+ rm_test_root
+}
diff --git a/tests/terminfo/s/screen b/tests/terminfo/s/screen
new file mode 100644
index 0000000..d380072
--- /dev/null
+++ b/tests/terminfo/s/screen
Binary files differ
diff --git a/tests/terminfo/s/st-256color b/tests/terminfo/s/st-256color
new file mode 100644
index 0000000..9c2db04
--- /dev/null
+++ b/tests/terminfo/s/st-256color
Binary files differ
diff --git a/tests/test.sh b/tests/test.sh
new file mode 100755
index 0000000..1beb5c6
--- /dev/null
+++ b/tests/test.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+. tests/shlib/common.sh
+
+enter_suite root
+
+if test "$TRAVIS" = true ; then
+ export PATH="$HOME/opt/fish/bin:${PATH}"
+ export PATH="$PWD/tests/bot-ci/deps/rc:$PATH"
+
+ if test "$PYTHON_IMPLEMENTATION" = "CPython" ; then
+ export PATH="$HOME/opt/zsh-${PYTHON_MM}${USE_UCS2_PYTHON:+-ucs2}/bin:${PATH}"
+ fi
+
+ if test -n "$USE_UCS2_PYTHON" ; then
+ export LD_LIBRARY_PATH="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/lib${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH"
+ set +e
+ . virtualenvwrapper.sh
+ workon cpython-ucs2-$UCS2_PYTHON_VARIANT
+ set -e
+ else
+ LIBRARY_PATH="$(ldd "$(which python)" | grep libpython | sed 's/^.* => //;s/ .*$//')"
+ LIBRARY_DIR="$(dirname "${LIBRARY_PATH}")"
+ export LD_LIBRARY_PATH="$LIBRARY_DIR${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH"
+ fi
+fi
+
+export PYTHON="${PYTHON:=python}"
+export PYTHONPATH="${PYTHONPATH}${PYTHONPATH:+:}`realpath .`"
+for script in "$ROOT"/tests/test_*/test.sh ; do
+ test_name="${script##*/run_}"
+ if ! sh $script ; then
+ fail "${test_name%_tests.sh}" F "Failed $script"
+ fi
+done
+
+if test -e "$FAILURES_FILE" ; then
+ echo "Fails and skips summary:"
+ cat "$FAILURES_FILE"
+ rm "$FAILURES_FILE"
+fi
+
+exit_suite
diff --git a/tests/test_awesome/path/awesome-client b/tests/test_awesome/path/awesome-client
new file mode 100755
index 0000000..2088ff0
--- /dev/null
+++ b/tests/test_awesome/path/awesome-client
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo "$@" >> "$TEST_ROOT/results/args"
+cat >> "$TEST_ROOT/results/requests"
diff --git a/tests/test_awesome/powerline/config.json b/tests/test_awesome/powerline/config.json
new file mode 100644
index 0000000..b8dfd01
--- /dev/null
+++ b/tests/test_awesome/powerline/config.json
@@ -0,0 +1,7 @@
+{
+ "ext": {
+ "wm": {
+ "update_interval": 0.5
+ }
+ }
+}
diff --git a/tests/test_awesome/powerline/themes/wm/default.json b/tests/test_awesome/powerline/themes/wm/default.json
new file mode 100644
index 0000000..d9eaf5f
--- /dev/null
+++ b/tests/test_awesome/powerline/themes/wm/default.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "default-left"
+ }
+ ],
+ "right": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "default-right"
+ }
+ ]
+ }
+}
diff --git a/tests/test_awesome/powerline/themes/wm/dvi.json b/tests/test_awesome/powerline/themes/wm/dvi.json
new file mode 100644
index 0000000..4cf3731
--- /dev/null
+++ b/tests/test_awesome/powerline/themes/wm/dvi.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "dvi-left"
+ }
+ ],
+ "right": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "dvi-right"
+ }
+ ]
+ }
+}
diff --git a/tests/test_awesome/test.sh b/tests/test_awesome/test.sh
new file mode 100755
index 0000000..fc85dc2
--- /dev/null
+++ b/tests/test_awesome/test.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite awesome
+
+make_test_root
+
+TEST_PATH="$TEST_ROOT/path"
+TEST_STATIC_ROOT="$ROOT/tests/test_awesome"
+
+cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT"
+cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT"
+
+export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH"
+
+ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python
+ln -s "$(which cat)" "$TEST_PATH"
+ln -s "$(which sh)" "$TEST_PATH"
+ln -s "$(which env)" "$TEST_PATH"
+if which socat ; then
+ ln -s "$(which socat)" "$TEST_PATH"
+fi
+for pexe in powerline powerline.sh powerline.py ; do
+ if test -e scripts/$pexe ; then
+ ln -s "$PWD/scripts/$pexe" $TEST_ROOT/path
+ elif test -e client/$pexe ; then
+ ln -s "$PWD/client/$pexe" $TEST_ROOT/path
+ elif which $pexe ; then
+ ln -s "$(which $pexe)" $TEST_ROOT/path
+ else
+ continue
+ fi
+ if test "$pexe" != 'powerline.sh' || test -e "$TEST_PATH/socat" ; then
+ POWERLINE_COMMAND="$pexe"
+ break
+ fi
+done
+
+DEPRECATED_SCRIPT="$ROOT/powerline/bindings/awesome/powerline-awesome.py"
+POWERLINE_DAEMON="scripts/powerline-daemon"
+
+run() {
+ env -i \
+ LANG=C \
+ PATH="$TEST_PATH" \
+ XDG_CONFIG_HOME="$TEST_ROOT" \
+ XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \
+ PYTHONPATH="$PYTHONPATH" \
+ TEST_ROOT="$TEST_ROOT" \
+ LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \
+ "$@" || true
+}
+
+display_log() {
+ local log_file="$1"
+ echo "$log_file:"
+ echo '============================================================'
+ cat -v "$log_file"
+ echo
+ echo '____________________________________________________________'
+}
+
+check_log() {
+ local args_file="$TEST_ROOT/results/args"
+ local log_file="$TEST_ROOT/results/requests"
+ local line="$(head -n1 "$log_file")"
+ local linenum="$(cat "$log_file" | wc -l)"
+ echo "Number of runs: $linenum (expected approx 5 / 0.5 = 10 runs)"
+ if test $linenum -lt 5 ; then
+ fail "log:lt" F "Script was run not enough times: $linenum < 5"
+ return 1
+ elif test $linenum -gt 15 ; then
+ fail "log:gt" E "Script was run too many times: $linenum > 15"
+ return 1
+ fi
+ local expline="powerline_widget:set_markup('<span foreground=\"#303030\"> </span><span foreground=\"#d0d0d0\" background=\"#303030\" font_weight=\"bold\"> default-right </span>')"
+ if test "$expline" != "$line" ; then
+ echo "Line: '$line'"
+ echo "Expected: '$expline'"
+ fail "log:line" F "Unexpected line"
+ return 1
+ fi
+ local ret=0
+ while test $linenum -gt 0 ; do
+ echo "$line" >> "$TEST_ROOT/ok"
+ linenum=$(( linenum - 1 ))
+ done
+ if ! diff "$TEST_ROOT/ok" "$log_file" ; then
+ fail "log:diff" F "Unexpected output"
+ ret=1
+ fi
+ rm "$TEST_ROOT/ok"
+ return $ret
+}
+
+killscript() {
+ kill -KILL $1 || true
+}
+
+if ! test -e "$DEPRECATED_SCRIPT" ; then
+ # TODO: uncomment when skip is available
+ # skip "deprecated" "Missing deprecated bar bindings script"
+ :
+else
+ enter_suite "deprecated" final
+ for args in "" "0.5"; do
+ rm -rf "$TEST_ROOT/results"
+ mkdir "$TEST_ROOT/results"
+ DEPRECATED_LOG="$TEST_ROOT/deprecated.log"
+ run env \
+ DEPRECATED_SCRIPT="$DEPRECATED_SCRIPT" \
+ args="$args" \
+ DEPRECATED_LOG="$DEPRECATED_LOG" \
+ TEST_ROOT="$TEST_ROOT" \
+ sh -c '
+ echo $$ > "$TEST_ROOT/$args-pid"
+ exec "$DEPRECATED_SCRIPT" $args > "$DEPRECATED_LOG" 2>&1
+ ' &
+ sleep 5
+ killscript "$(cat "$TEST_ROOT/$args-pid")"
+ rm "$TEST_ROOT/$args-pid"
+ if test -n "$(cat "$DEPRECATED_LOG")" ; then
+ display_log "$DEPRECATED_LOG"
+ fail "output" E "Nonempty $DEPRECATED_SCRIPT output"
+ fi
+ rm "$DEPRECATED_LOG"
+ if ! check_log ; then
+ display_log "$TEST_ROOT/results/args"
+ fail "log" F "Checking log failed"
+ fi
+ done
+ exit_suite --continue
+fi
+
+enter_suite "awesome" final
+ADDRESS="powerline-ipc-test-$$"
+echo "Powerline address: $ADDRESS"
+rm -rf "$TEST_ROOT/results"
+mkdir "$TEST_ROOT/results"
+run env \
+ POWERLINE_DAEMON="$POWERLINE_DAEMON" \
+ TEST_ROOT="$TEST_ROOT" \
+ ADDRESS="$ADDRESS" \
+ sh -c '
+ echo $$ > "$TEST_ROOT/dpid"
+ exec python "$POWERLINE_DAEMON" --socket $ADDRESS --foreground > "$TEST_ROOT/daemon.log" 2>&1
+ ' &
+DPID=$!
+sleep 2
+run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.1" 2>&1
+run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.2" 2>&1
+run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.3" 2>&1
+run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.4" 2>&1
+run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.5" 2>&1
+for log_file in "$TEST_ROOT"/output.log.* ; do
+ if test -n "$(cat "$log_file")" ; then
+ display_log "$log_file"
+ fail "output" E "Nonempty $POWERLINE_COMMAND output at run ${log_file#*.}"
+ fi
+ rm "$log_file"
+done
+sleep 5
+run python "$POWERLINE_DAEMON" --socket $ADDRESS --quiet --kill > "$TEST_ROOT/kill.log" 2>&1
+if test -n "$(cat "$TEST_ROOT/kill.log")" ; then
+ display_log "$TEST_ROOT/kill.log"
+ fail "daemonlog" E "Nonempty kill log"
+fi
+rm "$TEST_ROOT/kill.log"
+wait $DPID
+if test -n "$(cat "$TEST_ROOT/daemon.log")" ; then
+ display_log "$TEST_ROOT/daemon.log"
+ fail "daemonlog" E "Nonempty daemon log"
+fi
+rm "$TEST_ROOT/daemon.log"
+if ! check_log ; then
+ display_log "$TEST_ROOT/results/args"
+ fail "log" F "Checking log failed"
+fi
+exit_suite --continue
+
+if ! powerline-lint \
+ -p "$ROOT/powerline/config_files" \
+ -p "$TEST_STATIC_ROOT/powerline"
+then
+ fail "lint" F "Checking test config failed"
+fi
+
+exit_suite
diff --git a/tests/test_bar/path/lemonbar b/tests/test_bar/path/lemonbar
new file mode 100755
index 0000000..13c6030
--- /dev/null
+++ b/tests/test_bar/path/lemonbar
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+RES_DIR="$TEST_ROOT/results"
+mkdir -p "$RES_DIR"
+RES_FILE="$RES_DIR/$$"
+while test -e "$RES_FILE.log" ; do
+ RES_FILE="$RES_FILE.${RANDOM:-`date +%N | sed s/^0*//`}"
+done
+
+echo $(basename $0) "$@" > "$RES_FILE.args"
+cat > "$RES_FILE.log"
diff --git a/tests/test_bar/path/xrandr b/tests/test_bar/path/xrandr
new file mode 100755
index 0000000..d02e300
--- /dev/null
+++ b/tests/test_bar/path/xrandr
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+cat << EOF
+Screen 0: minimum 8 x 8, current 1920 x 1200, maximum 16384 x 16384
+DVI-I-0 disconnected (normal left inverted right x axis y axis)
+VGA-0 connected 1920x1200+1+0 (normal left inverted right x axis y axis) 520mm x 330mm
+ 1920x1200 59.95*+
+ 1920x1080 60.00
+ 1680x1050 59.95
+ 1600x1200 60.00
+ 1440x900 59.89
+ 1280x1024 75.02 60.02
+ 1280x800 59.81
+ 1152x864 75.00
+ 1024x768 75.03 60.00
+ 800x600 75.00 60.32
+ 640x480 75.00 59.94
+DVI-I-1 connected 1920x1200+0+0 (normal left inverted right x axis y axis) 520mm x 330mm
+ 1920x1200 59.95*+
+ 1920x1080 60.00
+ 1680x1050 59.95
+ 1600x1200 60.00
+ 1440x900 59.89
+ 1280x1024 75.02 60.02
+ 1280x800 59.81
+ 1152x864 75.00
+ 1024x768 75.03 60.00
+ 800x600 75.00 60.32
+ 640x480 75.00 59.94
+HDMI-0 disconnected (normal left inverted right x axis y axis)
+EOF
diff --git a/tests/test_bar/powerline/config.json b/tests/test_bar/powerline/config.json
new file mode 100644
index 0000000..0b35d56
--- /dev/null
+++ b/tests/test_bar/powerline/config.json
@@ -0,0 +1,9 @@
+{
+ "ext": {
+ "wm": {
+ "local_themes": {
+ "DVI-I-1": "dvi"
+ }
+ }
+ }
+}
diff --git a/tests/test_bar/powerline/themes/wm/default.json b/tests/test_bar/powerline/themes/wm/default.json
new file mode 100644
index 0000000..d9eaf5f
--- /dev/null
+++ b/tests/test_bar/powerline/themes/wm/default.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "default-left"
+ }
+ ],
+ "right": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "default-right"
+ }
+ ]
+ }
+}
diff --git a/tests/test_bar/powerline/themes/wm/dvi.json b/tests/test_bar/powerline/themes/wm/dvi.json
new file mode 100644
index 0000000..4cf3731
--- /dev/null
+++ b/tests/test_bar/powerline/themes/wm/dvi.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "dvi-left"
+ }
+ ],
+ "right": [
+ {
+ "type": "string",
+ "highlight_groups": ["time"],
+ "contents": "dvi-right"
+ }
+ ]
+ }
+}
diff --git a/tests/test_bar/test.sh b/tests/test_bar/test.sh
new file mode 100755
index 0000000..a0838fa
--- /dev/null
+++ b/tests/test_bar/test.sh
@@ -0,0 +1,195 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite bar
+
+make_test_root
+TEST_PATH="$TEST_ROOT/path"
+TEST_STATIC_ROOT="$ROOT/tests/test_bar"
+
+cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT"
+cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT"
+
+export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH"
+
+ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python
+ln -s "$(which sed)" "$TEST_PATH"
+ln -s "$(which cat)" "$TEST_PATH"
+ln -s "$(which mkdir)" "$TEST_PATH"
+ln -s "$(which basename)" "$TEST_PATH"
+ln -s "$TEST_PATH/lemonbar" "$TEST_PATH/bar-aint-recursive"
+
+DEPRECATED_SCRIPT="$ROOT/powerline/bindings/bar/powerline-bar.py"
+
+run() {
+ env -i \
+ LANG=C \
+ PATH="$TEST_PATH" \
+ XDG_CONFIG_HOME="$TEST_ROOT" \
+ XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \
+ PYTHONPATH="$PYTHONPATH" \
+ TEST_ROOT="$TEST_ROOT" \
+ LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \
+ "$@" || true
+}
+
+display_log() {
+ local log_file="$1"
+ echo "$log_file:"
+ echo '============================================================'
+ cat -v "$log_file"
+ echo
+ echo '____________________________________________________________'
+}
+
+check_log() {
+ local log_file="$1"
+ local text="$2"
+ local warns="$3"
+ if test "$warns" = "warns" ; then
+ local warning="$(head -n1 "$log_file" | sed 's/.*://')"
+ local expwarning="The 'bar' bindings are deprecated, please switch to 'lemonbar'"
+ if test "$warning" != "$expwarning" ; then
+ echo "Got: $warning"
+ echo "Exp: $expwarning"
+ fail "warn" F "Expected warning"
+ fi
+ sed -r -i -e '1d' "$log_file"
+ fi
+ local line="$(head -n1 "$log_file")"
+ local linenum="$(cat "$log_file" | wc -l)"
+ if test $linenum -lt 5 ; then
+ fail "log:lt" F "Script was run not enough times"
+ return 1
+ elif test $linenum -gt 15 ; then
+ fail "log:gt" E "Script was run too many times"
+ return 1
+ fi
+ local expline="%{l}%{F#ffd0d0d0}%{B#ff303030} $text-left %{F-B--u}%{F#ff303030} %{F-B--u}%{r}%{F#ff303030} %{F-B--u}%{F#ffd0d0d0}%{B#ff303030} $text-right %{F-B--u}"
+ if test "$expline" != "$line" ; then
+ echo "Line: '$line'"
+ echo "Expected: '$expline'"
+ fail "log:line" F "Unexpected line"
+ return 1
+ fi
+ local ret=0
+ while test $linenum -gt 0 ; do
+ echo "$line" >> "$TEST_ROOT/ok"
+ linenum=$(( linenum - 1 ))
+ done
+ if ! diff "$TEST_ROOT/ok" "$log_file" ; then
+ fail "log:diff" F "Unexpected output"
+ ret=1
+ fi
+ rm "$TEST_ROOT/ok"
+ return $ret
+}
+
+killscript() {
+ kill -KILL $1 || true
+}
+
+if ! test -e "$DEPRECATED_SCRIPT" ; then
+ # TODO: uncomment when skip is available
+ # skip "deprecated" "Missing deprecated bar bindings script"
+ :
+else
+ enter_suite "deprecated" final
+ run python "$DEPRECATED_SCRIPT" $args > "$TEST_ROOT/deprecated.log" 2>&1 &
+ SPID=$!
+ sleep 5
+ killscript $SPID
+ if ! check_log "$TEST_ROOT/deprecated.log" "default" warns ; then
+ display_log "$TEST_ROOT/deprecated.log"
+ fail "log" F "Checking log failed"
+ fi
+ rm "$TEST_ROOT/deprecated.log"
+ exit_suite --continue
+fi
+
+LEMONBAR_SCRIPT="$ROOT/powerline/bindings/lemonbar/powerline-lemonbar.py"
+
+if ! test -e "$LEMONBAR_SCRIPT" ; then
+ # TODO: uncomment when skip is available
+ # skip "lemonbar" "Missing lemonbar bindings script"
+ :
+else
+ enter_suite "lemonbar"
+ for args in "" "-i0.5" "--interval=0.5" "-- test args" "--bar-command bar-aint-recursive" "--height=10"; do
+ rm -rf "$TEST_ROOT/results"
+ run python "$LEMONBAR_SCRIPT" $args > "$TEST_ROOT/lemonbar.log" 2>&1 &
+ SPID=$!
+ sleep 5
+ killscript $SPID
+ sleep 0.5
+ enter_suite "args($args)" final
+ fnum=0
+ for file in "$TEST_ROOT/results"/*.log ; do
+ if ! test -e "$file" ; then
+ fail "log" E "Log file is missing"
+ break
+ fi
+ fnum=$(( fnum + 1 ))
+ args_file="${file%.log}.args"
+ if ! test -e "$args_file" ; then
+ fail "args" E "$args_file is missing"
+ else
+ cat "$args_file" >> "$TEST_ROOT/args.log"
+ fi
+ text="dvi"
+ if cat "$args_file" | grep -q +1 ; then
+ text="default"
+ fi
+ if ! check_log "$file" "$text" ; then
+ display_log "$file"
+ fail "log" F "Checking log failed"
+ fi
+ rm "$file"
+ done
+ if test "$fnum" -ne 2 ; then
+ fail "fnum" F "Expected two output files"
+ fi
+ if test "${args#--height}" != "$args" ; then
+ height="${args#--height}"
+ height="${height# }"
+ height="${height#=}"
+ height="${height%% *}"
+ fi
+ command="lemonbar"
+ if test "${args#--bar-command}" != "$args" ; then
+ command="${args#--bar-command}"
+ command="${command# }"
+ command="${command#=}"
+ command="${command%% *}"
+ fi
+ received_args="$(cat "$TEST_ROOT/args.log" | sort)"
+ rm "$TEST_ROOT/args.log"
+ script_args="${args#*-- }"
+ script_args="${script_args# }"
+ if test "${script_args}" = "$args" ; then
+ script_args=
+ fi
+ expected_args="$command -g 1920x$height+0${script_args:+ }$script_args${NL}$command -g 1920x$height+1${script_args:+ }$script_args"
+ if test "$expected_args" != "$received_args" ; then
+ echo "args:${NL}<$received_args>"
+ echo "expected:${NL}<$expected_args>"
+ fail "args" F "Expected different args"
+ fi
+ if ! test -z "$(cat "$TEST_ROOT/lemonbar.log")" ; then
+ display_log "$TEST_ROOT/lemonbar.log"
+ fail "stderr" E "Unexpected script output"
+ fi
+ rm "$TEST_ROOT/lemonbar.log"
+ exit_suite --continue
+ done
+ exit_suite --continue
+fi
+
+if ! powerline-lint \
+ -p "$ROOT/powerline/config_files" \
+ -p "$TEST_STATIC_ROOT/powerline"
+then
+ fail "lint" F "Checking test config failed"
+fi
+
+exit_suite
diff --git a/tests/test_daemon/test.sh b/tests/test_daemon/test.sh
new file mode 100755
index 0000000..a5c12c1
--- /dev/null
+++ b/tests/test_daemon/test.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite daemon final
+
+export ADDRESS="powerline-ipc-test-$$"
+echo "Powerline address: $ADDRESS"
+if "$PYTHON" "$ROOT/scripts/powerline-daemon" -s"$ADDRESS" ; then
+ sleep 1
+ if ! ( \
+ "$PYTHON" "$ROOT/client/powerline.py" \
+ --socket "$ADDRESS" -p/dev/null shell left \
+ | grep "file not found"
+ ) ; then
+ fail "devnull" F "-p/dev/null argument ignored or not treated properly"
+ fi
+ if ( \
+ "$PYTHON" "$ROOT/client/powerline.py" --socket "$ADDRESS" \
+ -p"$ROOT/powerline/config_files" shell left \
+ | grep "file not found"
+ ) ; then
+ fail "nodevnull" F "-p/dev/null argument remembered while it should not"
+ fi
+ if ! ( \
+ cd "$ROOT/tests/test_daemon" \
+ && "$PYTHON" "$ROOT/client/powerline.py" --socket "$ADDRESS" \
+ -p"$ROOT/powerline/config_files" shell left \
+ | grep "test_daemon"
+ ) ; then
+ fail "segment" F "Output lacks string “tests”"
+ fi
+else
+ fail "exitcode" E "Daemon exited with status $?"
+fi
+if "$PYTHON" "$ROOT/scripts/powerline-daemon" -s"$ADDRESS" -k ; then
+ :
+else
+ fail "-k" F "powerline-daemon -k failed with exit code $?"
+fi
+
+exit_suite
diff --git a/tests/test_in_vterm/shell/inits/dash b/tests/test_in_vterm/shell/inits/dash
new file mode 100644
index 0000000..7b146ff
--- /dev/null
+++ b/tests/test_in_vterm/shell/inits/dash
@@ -0,0 +1,16 @@
+# vim: ft=sh
+
+set_theme_option() {
+ export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2"
+}
+set_theme() {
+ export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
+}
+set_virtual_env() {
+ export VIRTUAL_ENV="$HOME/.virtenvs/$1"
+}
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+set_theme default_leftonly
+. "$ROOT/powerline/bindings/shell/powerline.sh"
+export VIRTUAL_ENV=
+cd "$TEST_ROOT/3rd"
diff --git a/tests/test_in_vterm/test.sh b/tests/test_in_vterm/test.sh
new file mode 100755
index 0000000..bbd4958
--- /dev/null
+++ b/tests/test_in_vterm/test.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite vterm
+
+for t in "$ROOT"/tests/test_in_vterm/test_*.sh ; do
+ test_name="${t##*/test_}"
+ if ! "$t" ; then
+ fail "${test_name%.sh}" F "Failed running $t"
+ fi
+done
+
+exit_suite
diff --git a/tests/test_in_vterm/test_shells.py b/tests/test_in_vterm/test_shells.py
new file mode 100755
index 0000000..faf7976
--- /dev/null
+++ b/tests/test_in_vterm/test_shells.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+
+from time import sleep
+from subprocess import check_call
+from glob import glob1
+from traceback import print_exc
+
+from argparse import ArgumentParser
+
+from powerline.lib.dict import updated
+
+from tests.modules.lib.terminal import (ExpectProcess, MutableDimensions,
+ do_terminal_tests, get_env)
+from tests.modules import PowerlineTestSuite
+
+
+TEST_ROOT = os.path.abspath(os.environ['TEST_ROOT'])
+
+
+def get_parser():
+ parser = ArgumentParser()
+ parser.add_argument('--type', action='store')
+ parser.add_argument('--client', action='store')
+ parser.add_argument('--binding', action='store')
+ parser.add_argument('args', action='append')
+ return parser
+
+
+BINDING_OPTIONS = {
+ 'dash': {
+ 'cmd': 'dash',
+ 'args': ['-i'],
+ 'init': [
+ '. "$ROOT/tests/test_in_vterm/shell/inits/dash"',
+ ],
+ },
+}
+
+
+def main(argv):
+ script_args = get_parser().parse_args(argv)
+
+ vterm_path = os.path.join(TEST_ROOT, 'path')
+
+ env = get_env(vterm_path, TEST_ROOT)
+ env['ROOT'] = os.path.abspath('.')
+ env['TEST_ROOT'] = TEST_ROOT
+ env['TEST_TYPE'] = script_args.type
+ env['TEST_CLIENT'] = script_args.client
+ env['LANG'] = 'en_US.UTF_8'
+ env['_POWERLINE_RUNNING_SHELL_TESTS'] = (
+ 'ee5bcdc6-b749-11e7-9456-50465d597777')
+
+ dim = MutableDimensions(rows=50, cols=200)
+
+ binding_opts = BINDING_OPTIONS[script_args.binding]
+
+ cmd = os.path.join(vterm_path, binding_opts['cmd'])
+ args = binding_opts['args']
+
+ def gen_init(binding):
+ def init(p):
+ for line in binding_opts['init']:
+ p.send(line + '\n')
+ sleep(1)
+
+ return init
+
+ def gen_feed(line):
+ def feed(p):
+ p.send(line + '\n')
+ sleep(0.1)
+
+ return feed
+
+ base_attrs = {
+ ((255, 204,0), (204, 51, 0), 0, 0, 0): 'H',
+ ((204, 51, 0), (0, 102, 153), 0, 0, 0): 'sHU',
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 'U',
+ ((0, 102, 153), (44, 44, 44), 0, 0, 0): 'sUB',
+ ((199, 199, 199), (44, 44, 44), 0, 0, 0): 'B',
+ ((44, 44, 44), (88, 88, 88), 0, 0, 0): 'sBD',
+ ((199, 199, 199), (88, 88, 88), 0, 0, 0): 'D',
+ ((144, 144, 144), (88, 88, 88), 0, 0, 0): 'sD',
+ ((221, 221, 221), (88, 88, 88), 1, 0, 0): 'C',
+ ((88, 88, 88), (0, 0, 0), 0, 0, 0): 'sDN',
+ ((240, 240, 240), (0, 0, 0), 0, 0, 0): 'N',
+ ((0, 102, 153), (51, 153, 204), 0, 0, 0): 'sUE',
+ ((255, 255, 255), (51, 153, 204), 0, 0, 0): 'E',
+ ((51, 153, 204), (44, 44, 44), 0, 0, 0): 'sEB',
+ }
+
+ tests = (
+ {
+ 'expected_result': (
+ '{H:  hostname }{sHU: }'
+ '{U:user }{sUB: }'
+ '{B: BRANCH }{sBD: }'
+ '{D:… }{sD: }{D:tmp }{sD: }{D:vshells }{sD: }{C:3rd }{sDN: }'
+ '{N:}',
+ base_attrs,
+ ),
+ 'prep_cb': gen_init(script_args.binding),
+ },
+ {
+ 'expected_result': (
+ '{H:  hostname }{sHU: }'
+ '{U:user }{sUB: }'
+ '{B: BRANCH }{sBD: }'
+ '{D:… }{sD: }{D:vshells }{sD: }{D:3rd }{sD: }{C:.git }{sDN: }'
+ '{N:}',
+ base_attrs
+ ),
+ 'prep_cb': gen_feed('cd .git'),
+ },
+ {
+ 'expected_result': (
+ '{H:  hostname }{sHU: }'
+ '{U:user }{sUB: }'
+ '{B: BRANCH }{sBD: }'
+ '{D:… }{sD: }{D:tmp }{sD: }{D:vshells }{sD: }{C:3rd }{sDN: }'
+ '{N:}',
+ base_attrs,
+ ),
+ 'prep_cb': gen_feed('cd ..'),
+ },
+ {
+ 'expected_result': (
+ '{H:  hostname }{sHU: }'
+ '{U:user }{sUE: }'
+ '{E:(e) some-venv }{sEB: }'
+ '{B: BRANCH }{sBD: }'
+ '{D:… }{sD: }{D:tmp }{sD: }{D:vshells }{sD: }{C:3rd }{sDN: }'
+ '{N:}',
+ base_attrs,
+ ),
+ 'prep_cb': gen_feed('set_virtual_env some-venv'),
+ },
+ )
+
+ with PowerlineTestSuite('shell') as suite:
+ return do_terminal_tests(
+ tests=tests,
+ cmd=cmd,
+ dim=dim,
+ args=args,
+ env=env,
+ cwd=TEST_ROOT,
+ suite=suite,
+ )
+
+
+if __name__ == '__main__':
+ if main(sys.argv[1:]):
+ raise SystemExit(0)
+ else:
+ raise SystemExit(1)
diff --git a/tests/test_in_vterm/test_shells.sh b/tests/test_in_vterm/test_shells.sh
new file mode 100755
index 0000000..d4d0eef
--- /dev/null
+++ b/tests/test_in_vterm/test_shells.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+. tests/shlib/common.sh
+. tests/shlib/vterm.sh
+
+enter_suite vshells
+
+vterm_setup
+
+HAS_SOCAT=
+HAS_C_CLIENT=
+
+git init "$TEST_ROOT/3rd"
+git --git-dir="$TEST_ROOT/3rd/.git" checkout -b BRANCH
+export DIR1=""
+export DIR2=""
+mkdir "$TEST_ROOT/3rd/$DIR1"
+mkdir "$TEST_ROOT/3rd/$DIR2"
+mkdir "$TEST_ROOT"/3rd/'\[\]'
+mkdir "$TEST_ROOT"/3rd/'%%'
+mkdir "$TEST_ROOT"/3rd/'#[bold]'
+mkdir "$TEST_ROOT"/3rd/'(echo)'
+mkdir "$TEST_ROOT"/3rd/'$(echo)'
+mkdir "$TEST_ROOT"/3rd/'`echo`'
+mkdir "$TEST_ROOT"/3rd/'«Unicode!»'
+mkdir "$TEST_ROOT/fish_home"
+mkdir "$TEST_ROOT/fish_home/fish"
+mkdir "$TEST_ROOT/fish_home/fish/generated_completions"
+cp -r "$ROOT/tests/test_shells/ipython_home" "$TEST_ROOT"
+
+ln -s "$(which env)" "$TEST_ROOT/path"
+ln -s "$(which git)" "$TEST_ROOT/path"
+ln -s "$(which sleep)" "$TEST_ROOT/path"
+ln -s "$(which cat)" "$TEST_ROOT/path"
+ln -s "$(which false)" "$TEST_ROOT/path"
+ln -s "$(which true)" "$TEST_ROOT/path"
+ln -s "$(which kill)" "$TEST_ROOT/path"
+ln -s "$(which echo)" "$TEST_ROOT/path"
+ln -s "$(which which)" "$TEST_ROOT/path"
+ln -s "$(which dirname)" "$TEST_ROOT/path"
+ln -s "$(which wc)" "$TEST_ROOT/path"
+ln -s "$(which stty)" "$TEST_ROOT/path"
+ln -s "$(which cut)" "$TEST_ROOT/path"
+ln -s "$(which bc)" "$TEST_ROOT/path"
+ln -s "$(which expr)" "$TEST_ROOT/path"
+ln -s "$(which mktemp)" "$TEST_ROOT/path"
+ln -s "$(which grep)" "$TEST_ROOT/path"
+ln -s "$(which sed)" "$TEST_ROOT/path"
+ln -s "$(which rm)" "$TEST_ROOT/path"
+ln -s "$(which tr)" "$TEST_ROOT/path"
+ln -s "$(which uname)" "$TEST_ROOT/path"
+ln -s "$(which test)" "$TEST_ROOT/path"
+ln -s "$(which pwd)" "$TEST_ROOT/path"
+ln -s "$(which hostname)" "$TEST_ROOT/path"
+ln -s "$ROOT/tests/test_shells/bgscript.sh" "$TEST_ROOT/path"
+ln -s "$ROOT/tests/test_shells/waitpid.sh" "$TEST_ROOT/path"
+
+ln -s "$ROOT/scripts/powerline-config" "$TEST_ROOT/path"
+ln -s "$ROOT/scripts/powerline-render" "$TEST_ROOT/path"
+ln -s "$ROOT/client/powerline.py" "$TEST_ROOT/path"
+
+if test -e "$ROOT/scripts/powerline" ; then
+ ln -s "$ROOT/scripts/powerline" "$TEST_ROOT/path"
+elif test -e client/powerline ; then
+ ln -s "$ROOT/client/powerline" "$TEST_ROOT/path"
+elif which powerline ; then
+ ln -s "$(which powerline)" "$TEST_ROOT/path"
+else
+ echo "Executable powerline was not found"
+ exit 1
+fi
+
+if test "$(
+ file --mime-type --brief --dereference "$TEST_ROOT/path/powerline" \
+ | cut -d/ -f1)" = "application" ; then
+ HAS_C_CLIENT=1
+fi
+
+if which socat ; then
+ HAS_SOCAT=1
+ ln -s "$(which socat)" "$TEST_ROOT/path"
+ ln -s "$ROOT/client/powerline.sh" "$TEST_ROOT/path"
+fi
+
+# Test type: daemon, renderer, …
+# Test client: python, shell, c, none
+# Test binding: *sh, ipython, pdb, …
+test_shell() {
+ local test_type="$1" ; shift
+ local test_client="$1" ; shift
+ local test_binding="$1" ; shift
+
+ if test "$test_client" = shell && test -z "$HAS_SOCAT" ; then
+ echo "Skipping test, socat not available"
+ return
+ fi
+ if test "$test_client" = c && test -z "$HAS_C_CLIENT" ; then
+ echo "Skipping test, C client not available"
+ return
+ fi
+ if which "$test_binding" ; then
+ ln -s "$(which "$test_binding")" "$TEST_ROOT/path"
+ fi
+
+ if ! "${PYTHON}" "$ROOT/tests/test_in_vterm/test_shells.py" \
+ --type=$test_type \
+ --client=$test_client \
+ --binding=$test_binding \
+ -- "$@"
+ then
+ local test_name="$test_type-$test_client-$test_binding"
+ fail "$test_name" F "Failed vterm shell test"
+ fi
+}
+
+test_shell renderer python dash -i || true
+
+vterm_shutdown
+
+exit_suite
diff --git a/tests/test_in_vterm/test_tmux.py b/tests/test_in_vterm/test_tmux.py
new file mode 100755
index 0000000..c1e126b
--- /dev/null
+++ b/tests/test_in_vterm/test_tmux.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+import json
+
+from time import sleep
+from subprocess import check_call
+from glob import glob1
+from traceback import print_exc
+
+from powerline.lib.dict import updated
+from powerline.bindings.tmux import get_tmux_version
+from powerline import get_fallback_logger
+
+from tests.modules.lib.terminal import (ExpectProcess, MutableDimensions,
+ do_terminal_tests, get_env)
+from tests.modules import PowerlineTestSuite
+
+
+TEST_ROOT = os.path.abspath(os.environ['TEST_ROOT'])
+
+
+def tmux_logs_iter(test_dir):
+ for tail in glob1(test_dir, '*.log'):
+ yield os.path.join(test_dir, tail)
+
+
+def print_tmux_logs():
+ for f in tmux_logs_iter(TEST_ROOT):
+ print('_' * 80)
+ print(os.path.basename(f) + ':')
+ print('=' * 80)
+ with open(f, 'r') as fp:
+ for line in fp:
+ sys.stdout.write(line)
+ os.unlink(f)
+
+
+def get_expected_result(tmux_version,
+ expected_result_old,
+ expected_result_1_7=None,
+ expected_result_1_8=None,
+ expected_result_2_0=None):
+ if tmux_version >= (2, 0) and expected_result_2_0:
+ return expected_result_2_0
+ elif tmux_version >= (1, 8) and expected_result_1_8:
+ return expected_result_1_8
+ elif tmux_version >= (1, 7) and expected_result_1_7:
+ return expected_result_1_7
+ else:
+ return expected_result_old
+
+
+def tmux_fin_cb(p, cmd, env):
+ try:
+ check_call([
+ cmd, '-S', env['POWERLINE_TMUX_SOCKET_PATH'], 'kill-server'
+ ], env=env, cwd=TEST_ROOT)
+ except Exception:
+ print_exc()
+ for f in tmux_logs_iter(TEST_ROOT):
+ os.unlink(f)
+
+
+def main(attempts=3):
+ vterm_path = os.path.join(TEST_ROOT, 'path')
+
+ tmux_exe = os.path.join(vterm_path, 'tmux')
+
+ socket_path = os.path.abspath('tmux-socket-{0}'.format(attempts))
+ if os.path.exists(socket_path):
+ os.unlink(socket_path)
+
+ env = get_env(vterm_path, TEST_ROOT, {
+ 'POWERLINE_THEME_OVERRIDES': ';'.join((
+ key + '=' + json.dumps(val)
+ for key, val in (
+ ('default.segments.right', [{
+ 'type': 'string',
+ 'name': 's1',
+ 'highlight_groups': ['cwd'],
+ 'priority':50,
+ }]),
+ ('default.segments.left', [{
+ 'type': 'string',
+ 'name': 's2',
+ 'highlight_groups': ['background'],
+ 'priority':20,
+ }]),
+ ('default.segment_data.s1.contents', 'S1 string here'),
+ ('default.segment_data.s2.contents', 'S2 string here'),
+ )
+ )),
+ 'POWERLINE_TMUX_SOCKET_PATH': socket_path,
+ })
+
+ conf_path = os.path.abspath('powerline/bindings/tmux/powerline.conf')
+ conf_line = 'source "' + (
+ conf_path.replace('\\', '\\\\').replace('"', '\\"')) + '"\n'
+ conf_file = os.path.realpath(os.path.join(TEST_ROOT, 'tmux.conf'))
+ with open(conf_file, 'w') as cf_fd:
+ cf_fd.write(conf_line)
+
+ tmux_version = get_tmux_version(get_fallback_logger())
+
+ dim = MutableDimensions(rows=50, cols=200)
+
+ def prepare_test_1(p):
+ sleep(5)
+
+ def prepare_test_2(p):
+ dim.cols = 40
+ p.resize(dim)
+ sleep(5)
+
+ base_attrs = {
+ ((0, 0, 0), (243, 243, 243), 1, 0, 0): 'lead',
+ ((243, 243, 243), (11, 11, 11), 0, 0, 0): 'leadsep',
+ ((255, 255, 255), (11, 11, 11), 0, 0, 0): 'bg',
+ ((199, 199, 199), (88, 88, 88), 0, 0, 0): 'cwd',
+ ((88, 88, 88), (11, 11, 11), 0, 0, 0): 'cwdhsep',
+ ((0, 0, 0), (0, 224, 0), 0, 0, 0): 'defstl',
+ }
+ tests = (
+ {
+ 'expected_result': get_expected_result(
+ tmux_version,
+ expected_result_old=(
+ '{lead: 0 }{leadsep: }{bg: S2 string here }'
+ '{4: 0 }{cwdhsep:| }{6:bash }'
+ '{bg: }{4: 1- }{cwdhsep:| }{6:bash }'
+ '{bg: }{7: }{8:2* | }{9:bash }{10: }'
+ '{bg:' + (' ' * 124) + '}'
+ '{cwdhsep: }{cwd: S1 string here }', updated(base_attrs, {
+ ((133, 133, 133), (11, 11, 11), 0, 0, 0): 4,
+ ((188, 188, 188), (11, 11, 11), 0, 0, 0): 6,
+ ((11, 11, 11), (0, 102, 153), 0, 0, 0): 7,
+ ((102, 204, 255), (0, 102, 153), 0, 0, 0): 8,
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 9,
+ ((0, 102, 153), (11, 11, 11), 0, 0, 0): 10,
+ })),
+ expected_result_1_8=(
+ '{lead: 0 }{leadsep: }{bg: S2 string here }'
+ '{4: 0 }{cwdhsep:| }{6:bash }'
+ '{bg: }{4: 1- }{cwdhsep:| }{7:bash }'
+ '{bg: }{8: }{9:2* | }{10:bash }{7: }'
+ '{bg:' + (' ' * 124) + '}'
+ '{cwdhsep: }{cwd: S1 string here }', updated(base_attrs, {
+ ((133, 133, 133), (11, 11, 11), 0, 0, 0): 4,
+ ((188, 188, 188), (11, 11, 11), 0, 0, 0): 6,
+ ((0, 102, 153), (11, 11, 11), 0, 0, 0): 7,
+ ((11, 11, 11), (0, 102, 153), 0, 0, 0): 8,
+ ((102, 204, 255), (0, 102, 153), 0, 0, 0): 9,
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 10,
+ })),
+ expected_result_2_0=(
+ '{lead: 0 }{leadsep: }{bg: S2 string here }'
+ '{4: 0 }{cwdhsep:| }{6:bash }'
+ '{bg: }{4: 1- }{cwdhsep:| }{7:bash }'
+ '{bg: }{8: }{9:2* | }{10:bash }{7: }'
+ '{bg:' + (' ' * 125) + '}'
+ '{cwdhsep: }{cwd: S1 string here }', updated(base_attrs, {
+ ((133, 133, 133), (11, 11, 11), 0, 0, 0): 4,
+ ((188, 188, 188), (11, 11, 11), 0, 0, 0): 6,
+ ((0, 102, 153), (11, 11, 11), 0, 0, 0): 7,
+ ((11, 11, 11), (0, 102, 153), 0, 0, 0): 8,
+ ((102, 204, 255), (0, 102, 153), 0, 0, 0): 9,
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 10,
+ })),
+ ),
+ 'prep_cb': prepare_test_1,
+ 'row': dim.rows - 1,
+ }, {
+ 'expected_result': get_expected_result(
+ tmux_version,
+ expected_result_old=('{bg:' + (' ' * 40) + '}', base_attrs),
+ expected_result_1_7=(
+ '{lead: 0 }'
+ '{leadsep: }{bg: <}{4:h }{bg: }{5: }'
+ '{6:2* | }{7:bash }{8: }{bg: }{cwdhsep: }'
+ '{cwd: S1 string here }', updated(base_attrs, {
+ ((188, 188, 188), (11, 11, 11), 0, 0, 0): 4,
+ ((11, 11, 11), (0, 102, 153), 0, 0, 0): 5,
+ ((102, 204, 255), (0, 102, 153), 0, 0, 0): 6,
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 7,
+ ((0, 102, 153), (11, 11, 11), 0, 0, 0): 8,
+ })),
+ expected_result_1_8=(
+ '{lead: 0 }'
+ '{leadsep: }{bg: <}{4:h }{bg: }{5: }'
+ '{6:2* | }{7:bash }{4: }{bg: }{cwdhsep: }'
+ '{cwd: S1 string here }', updated(base_attrs, {
+ ((0, 102, 153), (11, 11, 11), 0, 0, 0): 4,
+ ((11, 11, 11), (0, 102, 153), 0, 0, 0): 5,
+ ((102, 204, 255), (0, 102, 153), 0, 0, 0): 6,
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 7,
+ })),
+ expected_result_2_0=(
+ '{lead: 0 }'
+ '{leadsep: }{bg:<}{4:ash }{bg: }{5: }'
+ '{6:2* | }{7:bash }{4: }{cwdhsep: }'
+ '{cwd: S1 string here }', updated(base_attrs, {
+ ((0, 102, 153), (11, 11, 11), 0, 0, 0): 4,
+ ((11, 11, 11), (0, 102, 153), 0, 0, 0): 5,
+ ((102, 204, 255), (0, 102, 153), 0, 0, 0): 6,
+ ((255, 255, 255), (0, 102, 153), 1, 0, 0): 7,
+ })),
+ ),
+ 'prep_cb': prepare_test_2,
+ 'row': dim.rows - 1,
+ }
+ )
+
+ args = [
+ # Specify full path to tmux socket (testing tmux instance must not
+ # interfere with user one)
+ '-S', socket_path,
+ # Force 256-color mode
+ '-2',
+ # Request verbose logging just in case
+ '-v',
+ # Specify configuration file
+ '-f', conf_file,
+ # Run bash three times
+ 'new-session', 'bash --norc --noprofile -i', ';',
+ 'new-window', 'bash --norc --noprofile -i', ';',
+ 'new-window', 'bash --norc --noprofile -i', ';',
+ ]
+
+ with PowerlineTestSuite('tmux') as suite:
+ return do_terminal_tests(
+ tests=tests,
+ cmd=tmux_exe,
+ dim=dim,
+ args=args,
+ env=env,
+ cwd=TEST_ROOT,
+ fin_cb=tmux_fin_cb,
+ last_attempt_cb=print_tmux_logs,
+ suite=suite,
+ )
+
+
+if __name__ == '__main__':
+ if main():
+ raise SystemExit(0)
+ else:
+ raise SystemExit(1)
diff --git a/tests/test_in_vterm/test_tmux.sh b/tests/test_in_vterm/test_tmux.sh
new file mode 100755
index 0000000..062e02b
--- /dev/null
+++ b/tests/test_in_vterm/test_tmux.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+. tests/shlib/common.sh
+. tests/shlib/vterm.sh
+
+enter_suite tmux final
+
+vterm_setup
+
+ln -s "$(which env)" "$TEST_ROOT/path"
+ln -s "$(which cut)" "$TEST_ROOT/path"
+ln -s "$ROOT/scripts/powerline-render" "$TEST_ROOT/path"
+ln -s "$ROOT/scripts/powerline-config" "$TEST_ROOT/path"
+
+test_tmux() {
+ if test "$PYTHON_IMPLEMENTATION" = PyPy; then
+ # FIXME PyPy3 segfaults for some reason, PyPy does it as well, but
+ # occasionally.
+ return 0
+ fi
+ if ! which "${POWERLINE_TMUX_EXE}" ; then
+ return 0
+ fi
+ ln -sf "$(which "${POWERLINE_TMUX_EXE}")" "$TEST_ROOT/path/tmux"
+ f="$ROOT/tests/test_in_vterm/test_tmux.py"
+ if ! "${PYTHON}" "$f" ; then
+ local test_name="$("$POWERLINE_TMUX_EXE" -V 2>&1 | cut -d' ' -f2)"
+ fail "$test_name" F "Failed vterm test $f"
+ fi
+}
+
+if test -z "$POWERLINE_TMUX_EXE" && test -d "$ROOT/tests/bot-ci/deps/tmux"
+then
+ for tmux in "$ROOT"/tests/bot-ci/deps/tmux/tmux-*/tmux ; do
+ export POWERLINE_TMUX_EXE="$tmux"
+ test_tmux || true
+ done
+else
+ export POWERLINE_TMUX_EXE="${POWERLINE_TMUX_EXE:-tmux}"
+ test_tmux || true
+fi
+
+vterm_shutdown
+
+exit_suite
diff --git a/tests/test_in_vterm/test_vim.py b/tests/test_in_vterm/test_vim.py
new file mode 100755
index 0000000..0fbc319
--- /dev/null
+++ b/tests/test_in_vterm/test_vim.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+
+from time import sleep
+from subprocess import check_call
+from glob import glob1
+from traceback import print_exc
+
+from powerline.lib.dict import updated
+
+from tests.modules.lib.terminal import (ExpectProcess, MutableDimensions,
+ do_terminal_tests, get_env)
+from tests.modules import PowerlineTestSuite
+
+
+TEST_ROOT = os.path.abspath(os.environ['TEST_ROOT'])
+
+
+def main(attempts=3):
+ vterm_path = os.path.join(TEST_ROOT, 'path')
+
+ vim_exe = os.path.join(vterm_path, 'vim')
+
+ env = get_env(vterm_path, TEST_ROOT)
+ env['ROOT'] = os.path.abspath('.')
+
+ dim = MutableDimensions(rows=50, cols=200)
+
+ vimrc = os.path.join(TEST_ROOT, 'init.vim')
+ vimrc_contents = '''
+ set laststatus=2
+ set runtimepath=$ROOT/powerline/bindings/vim
+ '''
+ with open(vimrc, 'w') as vd:
+ vd.write(vimrc_contents)
+
+ base_attrs = {
+ (( 64, 64, 255), (0, 0, 0), 0, 0, 0): 'NT', # NonText
+ ((240, 240, 240), (0, 0, 0), 0, 0, 0): 'N', # Normal
+ }
+
+ args = [
+ '-u', vimrc,
+ '-i', 'NONE',
+ ]
+
+ def feed(p):
+ p.send(':echo strtrans(eval(&statusline[2:]))\n')
+
+ tests = (
+ )
+
+ with PowerlineTestSuite('vim') as suite:
+ return do_terminal_tests(
+ tests=tests,
+ cmd=vim_exe,
+ dim=dim,
+ args=args,
+ env=env,
+ cwd=TEST_ROOT,
+ suite=suite,
+ )
+
+
+if __name__ == '__main__':
+ if main():
+ raise SystemExit(0)
+ else:
+ raise SystemExit(1)
diff --git a/tests/test_in_vterm/test_vim.sh b/tests/test_in_vterm/test_vim.sh
new file mode 100755
index 0000000..a7e6168
--- /dev/null
+++ b/tests/test_in_vterm/test_vim.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+. tests/shlib/common.sh
+. tests/shlib/vterm.sh
+. tests/shlib/vim.sh
+
+enter_suite vvim final
+
+vterm_setup
+
+test_vim() {
+ if test "$PYTHON_IMPLEMENTATION" != CPython ; then
+ # Can only link with cpython
+ return 0
+ fi
+ if ! which "$POWERLINE_VIM_EXE" ; then
+ return 0
+ fi
+ ln -sf "$(which "${POWERLINE_VIM_EXE}")" "$TEST_ROOT/path/vim"
+ f="$ROOT/tests/test_in_vterm/test_vim.py"
+ if ! "${PYTHON}" "$f" ; then
+ local test_name="$(LANG=C "$POWERLINE_VIM_EXE" --cmd 'echo version' --cmd qa 2>&1 | tail -n2)"
+ fail "$test_name" F "Failed vterm test $f"
+ fi
+}
+
+if test -z "$POWERLINE_VIM_EXE" && test -d "$ROOT/tests/bot-ci/deps/vim"
+then
+ for vim in "$OLD_VIM" "$NEW_VIM" ; do
+ export POWERLINE_VIM_EXE="$vim"
+ test_vim || true
+ done
+else
+ export POWERLINE_VIM_EXE="${POWERLINE_VIM_EXE:-vim}"
+ test_vim || true
+fi
+
+vterm_shutdown
+
+exit_suite
diff --git a/tests/test_lint/test.sh b/tests/test_lint/test.sh
new file mode 100755
index 0000000..03c2f8a
--- /dev/null
+++ b/tests/test_lint/test.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite lint final
+
+if ! "$PYTHON" "$ROOT/scripts/powerline-lint" -p "$ROOT/powerline/config_files" ; then
+ fail "test" F "Running powerline-lint failed"
+fi
+
+exit_suite
diff --git a/tests/test_python/empty b/tests/test_python/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_python/empty
diff --git a/tests/test_python/test.sh b/tests/test_python/test.sh
new file mode 100755
index 0000000..f042237
--- /dev/null
+++ b/tests/test_python/test.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite python final
+
+for file in "$ROOT"/tests/test_python/test_*.py ; do
+ test_name="${file##*/test_}"
+ if ! "$PYTHON" "$file" --verbose --catch ; then
+ fail "${test_name%.py}" F "Failed test(s) from $file"
+ fi
+done
+
+exit_suite
diff --git a/tests/test_python/test_cmdline.py b/tests/test_python/test_cmdline.py
new file mode 100644
index 0000000..470a7b4
--- /dev/null
+++ b/tests/test_python/test_cmdline.py
@@ -0,0 +1,149 @@
+# vim:fileencoding=utf-8:noet
+
+'''Tests for shell.py parser'''
+
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+if sys.version_info < (3,):
+ from io import BytesIO as StrIO
+else:
+ from io import StringIO as StrIO
+
+from powerline.commands.main import get_argparser, finish_args
+
+from tests.modules import TestCase
+from tests.modules.lib import replace_attr
+
+
+class TestParser(TestCase):
+ def test_main_err(self):
+ parser = get_argparser()
+ out = StrIO()
+ err = StrIO()
+
+ def flush():
+ out.truncate(0)
+ err.truncate(0)
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ for raising_args, raising_reg in [
+ ([], 'too few arguments|the following arguments are required: ext'),
+ (['-r'], 'expected one argument'),
+ (['shell', '-r'], 'expected one argument'),
+ (['shell', '-w'], 'expected one argument'),
+ (['shell', '-c'], 'expected one argument'),
+ (['shell', '-t'], 'expected one argument'),
+ (['shell', '-p'], 'expected one argument'),
+ (['shell', '-R'], 'expected one argument'),
+ (['shell', '--renderer-module'], 'expected one argument'),
+ (['shell', '--width'], 'expected one argument'),
+ (['shell', '--last-exit-code'], 'expected one argument'),
+ (['shell', '--last-pipe-status'], 'expected one argument'),
+ (['shell', '--config-override'], 'expected one argument'),
+ (['shell', '--theme-override'], 'expected one argument'),
+ (['shell', '--config-path'], 'expected one argument'),
+ (['shell', '--renderer-arg'], 'expected one argument'),
+ (['shell', '--jobnum'], 'expected one argument'),
+ (['-r', '.zsh'], 'too few arguments|the following arguments are required: ext'),
+ (['shell', '--last-exit-code', 'i'], 'invalid int_or_sig value'),
+ (['shell', '--last-pipe-status', '1 i'], 'invalid <lambda> value'),
+ ]:
+ self.assertRaises(SystemExit, parser.parse_args, raising_args)
+ self.assertFalse(out.getvalue())
+ self.assertRegexpMatches(err.getvalue(), raising_reg)
+ flush()
+
+ def test_main_normal(self):
+ parser = get_argparser()
+ out = StrIO()
+ err = StrIO()
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ for argv, expargs in [
+ (['shell', 'left'], {'ext': ['shell'], 'side': 'left'}),
+ (['shell', 'left', '-r', '.zsh'], {'ext': ['shell'], 'renderer_module': '.zsh', 'side': 'left'}),
+ ([
+ 'shell',
+ 'left',
+ '-r', '.zsh',
+ '--last-exit-code', '10',
+ '--last-pipe-status', '10 20 30',
+ '--jobnum=10',
+ '-w', '100',
+ '-c', 'common.term_truecolor=true',
+ '-c', 'common.spaces=4',
+ '-t', 'default.segment_data.hostname.before=H:',
+ '-p', '.',
+ '-p', '..',
+ '-R', 'smth={"abc":"def"}',
+ ], {
+ 'ext': ['shell'],
+ 'side': 'left',
+ 'renderer_module': '.zsh',
+ 'last_exit_code': 10,
+ 'last_pipe_status': [10, 20, 30],
+ 'jobnum': 10,
+ 'width': 100,
+ 'config_override': {'common': {'term_truecolor': True, 'spaces': 4}},
+ 'theme_override': {
+ 'default': {
+ 'segment_data': {
+ 'hostname': {
+ 'before': 'H:'
+ }
+ }
+ }
+ },
+ 'config_path': ['.', '..'],
+ 'renderer_arg': {'smth': {'abc': 'def'}},
+ }),
+ (['shell', 'left', '-R', 'arg=true'], {
+ 'ext': ['shell'],
+ 'side': 'left',
+ 'renderer_arg': {'arg': True},
+ }),
+ (['shell', 'left', '-R', 'arg=true', '-R', 'arg='], {
+ 'ext': ['shell'],
+ 'side': 'left',
+ 'renderer_arg': {},
+ }),
+ (['shell', 'left', '-R', 'arg='], {'ext': ['shell'], 'renderer_arg': {}, 'side': 'left'}),
+ (['shell', 'left', '-t', 'default.segment_info={"hostname": {}}'], {
+ 'ext': ['shell'],
+ 'side': 'left',
+ 'theme_override': {
+ 'default': {
+ 'segment_info': {
+ 'hostname': {}
+ }
+ }
+ },
+ }),
+ (['shell', 'left', '-c', 'common={ }'], {
+ 'ext': ['shell'],
+ 'side': 'left',
+ 'config_override': {'common': {}},
+ }),
+ (['shell', 'left', '--last-pipe-status='], {
+ 'ext': ['shell'],
+ 'side': 'left',
+ 'last_pipe_status': [],
+ }),
+ ]:
+ args = parser.parse_args(argv)
+ finish_args(parser, {}, args)
+ for key, val in expargs.items():
+ self.assertEqual(getattr(args, key), val)
+ for key, val in args.__dict__.items():
+ if key not in expargs:
+ self.assertFalse(val, msg='key {0} is {1} while it should be something false'.format(key, val))
+ self.assertFalse(err.getvalue() + out.getvalue(), msg='unexpected output: {0!r} {1!r}'.format(
+ err.getvalue(),
+ out.getvalue(),
+ ))
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_config_merging.py b/tests/test_python/test_config_merging.py
new file mode 100644
index 0000000..3f4fa2a
--- /dev/null
+++ b/tests/test_python/test_config_merging.py
@@ -0,0 +1,270 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import json
+
+from subprocess import check_call
+from operator import add
+from shutil import rmtree
+
+from powerline.lib.dict import mergedicts_copy as mdc
+from powerline import Powerline
+
+from tests.modules import TestCase
+from tests.modules.lib.config_mock import select_renderer, UT
+
+
+CONFIG_DIR = 'tests/config'
+
+
+root_config = lambda: {
+ 'common': {
+ 'interval': None,
+ 'watcher': 'auto',
+ },
+ 'ext': {
+ 'test': {
+ 'theme': 'default',
+ 'colorscheme': 'default',
+ },
+ },
+}
+
+
+colors_config = lambda: {
+ 'colors': {
+ 'c1': 1,
+ 'c2': 2,
+ },
+ 'gradients': {
+ },
+}
+
+
+colorscheme_config = lambda: {
+ 'groups': {
+ 'g': {'fg': 'c1', 'bg': 'c2', 'attrs': []},
+ }
+}
+
+
+theme_config = lambda: {
+ 'segment_data': {
+ 's': {
+ 'before': 'b',
+ },
+ },
+ 'segments': {
+ 'left': [
+ {
+ 'type': 'string',
+ 'name': 's',
+ 'contents': 't',
+ 'highlight_groups': ['g'],
+ },
+ ],
+ 'right': [],
+ }
+}
+
+top_theme_config = lambda: {
+ 'dividers': {
+ 'left': {
+ 'hard': '#>',
+ 'soft': '|>',
+ },
+ 'right': {
+ 'hard': '<#',
+ 'soft': '<|',
+ },
+ },
+ 'spaces': 0,
+}
+
+
+main_tree = lambda: {
+ '1/config': root_config(),
+ '1/colors': colors_config(),
+ '1/colorschemes/default': colorscheme_config(),
+ '1/themes/test/default': theme_config(),
+ '1/themes/' + UT: top_theme_config(),
+ '1/themes/other1': mdc(top_theme_config(), {
+ 'dividers': {
+ 'left': {
+ 'hard': '!>',
+ }
+ }
+ }),
+ '1/themes/other2': mdc(top_theme_config(), {
+ 'dividers': {
+ 'left': {
+ 'hard': '>>',
+ }
+ }
+ }),
+}
+
+
+def mkdir_recursive(directory):
+ if os.path.isdir(directory):
+ return
+ mkdir_recursive(os.path.dirname(directory))
+ os.mkdir(directory)
+
+
+class TestPowerline(Powerline):
+ def get_config_paths(self):
+ return tuple(sorted([
+ os.path.join(CONFIG_DIR, d)
+ for d in os.listdir(CONFIG_DIR)
+ ]))
+
+
+class WithConfigTree(object):
+ __slots__ = ('tree', 'p', 'p_kwargs')
+
+ def __init__(self, tree, p_kwargs={'run_once': True}):
+ self.tree = tree
+ self.p = None
+ self.p_kwargs = p_kwargs
+
+ def __enter__(self, *args):
+ os.mkdir(CONFIG_DIR)
+ for k, v in self.tree.items():
+ fname = os.path.join(CONFIG_DIR, k) + '.json'
+ mkdir_recursive(os.path.dirname(fname))
+ with open(fname, 'w') as F:
+ json.dump(v, F)
+ select_renderer(simpler_renderer=True)
+ self.p = TestPowerline(
+ ext='test',
+ renderer_module='tests.modules.lib.config_mock',
+ **self.p_kwargs
+ )
+ if os.environ.get('POWERLINE_RUN_LINT_DURING_TESTS'):
+ try:
+ check_call(['scripts/powerline-lint'] + reduce(add, (
+ ['-p', d] for d in self.p.get_config_paths()
+ )))
+ except:
+ self.__exit__()
+ raise
+ return self.p.__enter__(*args)
+
+ def __exit__(self, *args):
+ try:
+ rmtree(CONFIG_DIR)
+ finally:
+ if self.p:
+ self.p.__exit__(*args)
+
+
+class TestMerging(TestCase):
+ def assertRenderEqual(self, p, output, **kwargs):
+ self.assertEqual(p.render(**kwargs).replace(' ', ' '), output)
+
+ def test_not_merged_config(self):
+ with WithConfigTree(main_tree()) as p:
+ self.assertRenderEqual(p, '{12} bt{2-}#>{--}')
+
+ def test_root_config_merging(self):
+ with WithConfigTree(mdc(main_tree(), {
+ '2/config': {
+ 'common': {
+ 'default_top_theme': 'other1',
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{12} bt{2-}!>{--}')
+ with WithConfigTree(mdc(main_tree(), {
+ '2/config': {
+ 'common': {
+ 'default_top_theme': 'other1',
+ }
+ },
+ '3/config': {
+ 'common': {
+ 'default_top_theme': 'other2',
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{12} bt{2-}>>{--}')
+
+ def test_top_theme_merging(self):
+ with WithConfigTree(mdc(main_tree(), {
+ '2/themes/' + UT: {
+ 'spaces': 1,
+ },
+ '3/themes/' + UT: {
+ 'dividers': {
+ 'left': {
+ 'hard': '>>',
+ }
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{12} bt {2-}>>{--}')
+
+ def test_colors_config_merging(self):
+ with WithConfigTree(mdc(main_tree(), {
+ '2/colors': {
+ 'colors': {
+ 'c1': 3,
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{32} bt{2-}#>{--}')
+ with WithConfigTree(mdc(main_tree(), {
+ '2/colors': {
+ 'colors': {
+ 'c1': 3,
+ }
+ },
+ '3/colors': {
+ 'colors': {
+ 'c1': 4,
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{42} bt{2-}#>{--}')
+ with WithConfigTree(mdc(main_tree(), {
+ '2/colors': {
+ 'colors': {
+ 'c1': 3,
+ }
+ },
+ '3/colors': {
+ 'colors': {
+ 'c2': 4,
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{34} bt{4-}#>{--}')
+
+ def test_colorschemes_merging(self):
+ with WithConfigTree(mdc(main_tree(), {
+ '2/colorschemes/default': {
+ 'groups': {
+ 'g': {'fg': 'c2', 'bg': 'c1', 'attrs': []},
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{21} bt{1-}#>{--}')
+
+ def test_theme_merging(self):
+ with WithConfigTree(mdc(main_tree(), {
+ '2/themes/test/default': {
+ 'segment_data': {
+ 's': {
+ 'after': 'a',
+ }
+ }
+ },
+ })) as p:
+ self.assertRenderEqual(p, '{12} bta{2-}#>{--}')
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_config_reload.py b/tests/test_python/test_config_reload.py
new file mode 100644
index 0000000..a418d49
--- /dev/null
+++ b/tests/test_python/test_config_reload.py
@@ -0,0 +1,319 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from time import sleep
+from copy import deepcopy
+from functools import wraps
+
+from tests.modules import TestCase
+from tests.modules.lib.config_mock import get_powerline, add_watcher_events, UT
+
+
+config = {
+ 'config': {
+ 'common': {
+ 'interval': 0,
+ 'watcher': 'test',
+ },
+ 'ext': {
+ 'test': {
+ 'theme': 'default',
+ 'colorscheme': 'default',
+ },
+ },
+ },
+ 'colors': {
+ 'colors': {
+ "col1": 1,
+ "col2": 2,
+ "col3": 3,
+ "col4": 4,
+ },
+ 'gradients': {
+ },
+ },
+ 'colorschemes/test/default': {
+ 'groups': {
+ 'str1': {'fg': 'col1', 'bg': 'col2', 'attrs': ['bold']},
+ 'str2': {'fg': 'col3', 'bg': 'col4', 'attrs': ['underline']},
+ },
+ },
+ 'colorschemes/test/2': {
+ 'groups': {
+ 'str1': {'fg': 'col2', 'bg': 'col3', 'attrs': ['bold']},
+ 'str2': {'fg': 'col1', 'bg': 'col4', 'attrs': ['underline']},
+ },
+ },
+ 'themes/test/default': {
+ 'segments': {
+ "left": [
+ {
+ "type": "string",
+ "contents": "s",
+ "highlight_groups": ["str1"],
+ },
+ {
+ "type": "string",
+ "contents": "g",
+ "highlight_groups": ["str2"],
+ },
+ ],
+ "right": [
+ ],
+ },
+ },
+ 'themes/' + UT: {
+ 'dividers': {
+ "left": {
+ "hard": ">>",
+ "soft": ">",
+ },
+ "right": {
+ "hard": "<<",
+ "soft": "<",
+ },
+ },
+ 'spaces': 0,
+ },
+ 'themes/other': {
+ 'dividers': {
+ "left": {
+ "hard": ">>",
+ "soft": ">",
+ },
+ "right": {
+ "hard": "<<",
+ "soft": "<",
+ },
+ },
+ 'spaces': 1,
+ },
+ 'themes/test/2': {
+ 'segments': {
+ "left": [
+ {
+ "type": "string",
+ "contents": "t",
+ "highlight_groups": ["str1"],
+ },
+ {
+ "type": "string",
+ "contents": "b",
+ "highlight_groups": ["str2"],
+ },
+ ],
+ "right": [
+ ],
+ },
+ },
+}
+
+
+def with_new_config(func):
+ @wraps(func)
+ def f(self):
+ return func(self, deepcopy(config))
+ return f
+
+
+class TestConfigReload(TestCase):
+ def assertAccessEvents(self, p, *args):
+ events = set()
+ for event in args:
+ if ':' not in event:
+ events.add('check:' + event)
+ events.add('load:' + event)
+ else:
+ events.add(event)
+ self.assertEqual(set(p._pop_events()), events)
+
+ @with_new_config
+ def test_noreload(self, config):
+ with get_powerline(config, run_once=True) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+ config['config']['common']['spaces'] = 1
+ add_watcher_events(p, 'config', wait=False, interval=0.05)
+ # When running once thread should not start
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p)
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @with_new_config
+ def test_reload_main(self, config):
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['config']['common']['default_top_theme'] = 'other'
+ add_watcher_events(p, 'config')
+ p.render()
+ self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'themes/other', 'check:themes/test/__main__', 'themes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ config['config']['ext']['test']['theme'] = 'nonexistent'
+ add_watcher_events(p, 'config')
+ self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'check:themes/test/nonexistent', 'themes/other', 'check:themes/test/__main__')
+ # It should normally handle file missing error
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load theme: themes/test/__main__',
+ 'exception:test:powerline:Failed to load theme: themes/test/nonexistent',
+ 'exception:test:powerline:Failed to create renderer: themes/test/nonexistent'
+ ])
+
+ config['config']['ext']['test']['theme'] = 'default'
+ add_watcher_events(p, 'config')
+ self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'themes/test/default', 'themes/other', 'check:themes/test/__main__')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ config['config']['ext']['test']['colorscheme'] = 'nonexistent'
+ add_watcher_events(p, 'config')
+ self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'check:colorschemes/nonexistent', 'check:colorschemes/test/__main__', 'check:colorschemes/test/nonexistent')
+ # It should normally handle file missing error
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load colorscheme: colorschemes/nonexistent',
+ 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/__main__',
+ 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/nonexistent',
+ 'exception:test:powerline:Failed to create renderer: colorschemes/test/nonexistent'
+ ])
+
+ config['config']['ext']['test']['colorscheme'] = '2'
+ add_watcher_events(p, 'config')
+ self.assertEqual(p.render(), '<2 3 1> s <3 4 False>>><1 4 4>g <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'check:colorschemes/2', 'check:colorschemes/test/__main__', 'colorschemes/test/2')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ config['config']['ext']['test']['theme'] = '2'
+ add_watcher_events(p, 'config')
+ self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'themes/test/2', 'themes/other', 'check:themes/test/__main__')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ self.assertEqual(p.renderer.local_themes, None)
+ config['config']['ext']['test']['local_themes'] = 'something'
+ add_watcher_events(p, 'config')
+ self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config')
+ self.assertEqual(p.logger._pop_msgs(), [])
+ self.assertEqual(p.renderer.local_themes, 'something')
+
+ @with_new_config
+ def test_reload_unexistent(self, config):
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['config']['ext']['test']['colorscheme'] = 'nonexistentraise'
+ add_watcher_events(p, 'config')
+ # It may appear that p.logger._pop_msgs() is called after given
+ # exception is added to the mesagges, but before config_loader
+ # exception was added (this one:
+ # “exception:test:config_loader:Error while running condition
+ # function for key colorschemes/test/nonexistentraise:
+ # fcf:colorschemes/test/nonexistentraise”).
+ # sleep(0.1)
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ # For colorschemes/{test/,}*raise find_config_file raises
+ # IOError, but it does not do so for check:colorschemes/test/__main__,
+ # so powerline is trying to load this, but not other
+ # colorschemes/*
+ self.assertAccessEvents(p, 'config', 'check:colorschemes/test/__main__', 'check:colorschemes/nonexistentraise', 'check:colorschemes/test/nonexistentraise')
+ self.assertIn('exception:test:powerline:Failed to create renderer: fcf:colorschemes/test/nonexistentraise', p.logger._pop_msgs())
+
+ config['colorschemes/nonexistentraise'] = {}
+ config['colorschemes/test/nonexistentraise'] = {
+ 'groups': {
+ 'str1': {'fg': 'col1', 'bg': 'col3', 'attrs': ['bold']},
+ 'str2': {'fg': 'col2', 'bg': 'col4', 'attrs': ['underline']},
+ },
+ }
+ while not p._will_create_renderer():
+ sleep(0.1)
+ self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><2 4 4>g<4 False False>>><None None None>')
+ # Same as above
+ self.assertAccessEvents(p, 'colorschemes/nonexistentraise', 'colorschemes/test/nonexistentraise', 'check:colorschemes/test/__main__')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @with_new_config
+ def test_reload_colors(self, config):
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['colors']['colors']['col1'] = 5
+ add_watcher_events(p, 'colors')
+ self.assertEqual(p.render(), '<5 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'colors')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @with_new_config
+ def test_reload_colorscheme(self, config):
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3'
+ add_watcher_events(p, 'colorschemes/test/default')
+ self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @with_new_config
+ def test_reload_theme(self, config):
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['themes/test/default']['segments']['left'][0]['contents'] = 'col3'
+ add_watcher_events(p, 'themes/test/default')
+ self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @with_new_config
+ def test_reload_top_theme(self, config):
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['themes/' + UT]['dividers']['left']['hard'] = '|>'
+ add_watcher_events(p, 'themes/' + UT)
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>|><3 4 4>g<4 False False>|><None None None>')
+ self.assertAccessEvents(p, 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @with_new_config
+ def test_reload_theme_main(self, config):
+ config['config']['common']['interval'] = None
+ with get_powerline(config, run_once=False) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['themes/test/default']['segments']['left'][0]['contents'] = 'col3'
+ add_watcher_events(p, 'themes/test/default', wait=False)
+ self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+ self.assertEqual(p.logger._pop_msgs(), [])
+ self.assertTrue(p._watcher._calls)
+
+ @with_new_config
+ def test_run_once_no_theme_reload(self, config):
+ config['config']['common']['interval'] = None
+ with get_powerline(config, run_once=True) as p:
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__')
+
+ config['themes/test/default']['segments']['left'][0]['contents'] = 'col3'
+ add_watcher_events(p, 'themes/test/default', wait=False)
+ self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>')
+ self.assertAccessEvents(p)
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_configuration.py b/tests/test_python/test_configuration.py
new file mode 100644
index 0000000..aa9e844
--- /dev/null
+++ b/tests/test_python/test_configuration.py
@@ -0,0 +1,877 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+
+from functools import wraps
+from copy import deepcopy
+
+import tests.modules.vim as vim_module
+
+from tests.modules import TestCase
+from tests.modules.lib.config_mock import (get_powerline, get_powerline_raw,
+ swap_attributes, UT)
+from tests.modules.lib import Args, replace_item
+
+
+def highlighted_string(s, group, **kwargs):
+ ret = {
+ 'type': 'string',
+ 'contents': s,
+ 'highlight_groups': [group],
+ }
+ ret.update(kwargs)
+ return ret
+
+
+config = {
+ 'config': {
+ 'common': {
+ 'interval': 0,
+ 'watcher': 'test',
+ },
+ 'ext': {
+ 'test': {
+ 'theme': 'default',
+ 'colorscheme': 'default',
+ },
+ 'vim': {
+ 'theme': 'default',
+ 'colorscheme': 'default',
+ },
+ 'shell': {
+ 'theme': 'default',
+ 'colorscheme': 'default',
+ },
+ 'wm': {
+ 'theme': 'default',
+ 'colorscheme': 'default',
+ },
+ },
+ },
+ 'colors': {
+ 'colors': {
+ 'col1': 1,
+ 'col2': 2,
+ 'col3': 3,
+ 'col4': 4,
+ 'col5': 5,
+ 'col6': 6,
+ 'col7': 7,
+ 'col8': 8,
+ 'col9': 9,
+ 'col10': 10,
+ 'col11': 11,
+ 'col12': 12,
+ },
+ 'gradients': {
+ },
+ },
+ 'colorschemes/test/__main__': {
+ 'groups': {
+ 'm1': 'g1',
+ 'm2': 'g3',
+ 'm3': {'fg': 'col11', 'bg': 'col12', 'attrs': []},
+ }
+ },
+ 'colorschemes/default': {
+ 'groups': {
+ 'g1': {'fg': 'col5', 'bg': 'col6', 'attrs': []},
+ 'g2': {'fg': 'col7', 'bg': 'col8', 'attrs': []},
+ 'g3': {'fg': 'col9', 'bg': 'col10', 'attrs': []},
+ }
+ },
+ 'colorschemes/test/default': {
+ 'groups': {
+ 'str1': {'fg': 'col1', 'bg': 'col2', 'attrs': ['bold']},
+ 'str2': {'fg': 'col3', 'bg': 'col4', 'attrs': ['underline']},
+ 'd1': 'g2',
+ 'd2': 'm2',
+ 'd3': 'm3',
+ },
+ },
+ 'colorschemes/vim/default': {
+ 'groups': {
+ 'environment': {'fg': 'col3', 'bg': 'col4', 'attrs': ['underline']},
+ },
+ },
+ 'colorschemes/wm/default': {
+ 'groups': {
+ 'hl1': {'fg': 'col1', 'bg': 'col2', 'attrs': ['underline']},
+ 'hl2': {'fg': 'col2', 'bg': 'col1', 'attrs': []},
+ 'hl3': {'fg': 'col3', 'bg': 'col1', 'attrs': ['underline']},
+ 'hl4': {'fg': 'col2', 'bg': 'col4', 'attrs': []},
+ },
+ },
+ 'themes/test/default': {
+ 'segments': {
+ 'left': [
+ highlighted_string('s', 'str1', width='auto'),
+ highlighted_string('g', 'str2'),
+ ],
+ 'right': [
+ highlighted_string('f', 'str2', width='auto', align='r'),
+ ],
+ },
+ },
+ 'themes/' + UT: {
+ 'dividers': {
+ 'left': {
+ 'hard': '>>',
+ 'soft': '>',
+ },
+ 'right': {
+ 'hard': '<<',
+ 'soft': '<',
+ },
+ },
+ 'spaces': 0,
+ },
+ 'themes/test/__main__': {
+ 'dividers': {
+ 'right': {
+ 'soft': '|',
+ },
+ },
+ },
+ 'themes/vim/default': {
+ 'segments': {
+ 'left': [
+ {
+ 'function': 'powerline.segments.common.env.environment',
+ 'args': {
+ 'variable': 'TEST',
+ },
+ },
+ ],
+ },
+ },
+ 'themes/shell/default': {
+ 'default_module': 'powerline.segments.common',
+ 'segments': {
+ 'left': [
+ highlighted_string('s', 'g1', width='auto'),
+ ],
+ },
+ },
+ 'themes/wm/default': {
+ 'default_module': 'powerline.segments.common',
+ 'segments': {
+ 'left': [
+ highlighted_string('A', 'hl1'),
+ highlighted_string('B', 'hl2'),
+ ],
+ 'right': [
+ highlighted_string('C', 'hl3'),
+ highlighted_string('D', 'hl4'),
+ ],
+ },
+ },
+}
+
+
+def with_new_config(func):
+ @wraps(func)
+ def f(self):
+ return func(self, deepcopy(config))
+ return f
+
+
+def add_args(func):
+ @wraps(func)
+ def f(self):
+ new_config = deepcopy(config)
+ with get_powerline(new_config, run_once=True, simpler_renderer=True) as p:
+ func(self, p, new_config)
+ return f
+
+
+class TestRender(TestCase):
+ def assertRenderEqual(self, p, output, **kwargs):
+ self.assertEqual(p.render(**kwargs).replace(' ', ' '), output)
+
+ def assertRenderLinesEqual(self, p, output, **kwargs):
+ self.assertEqual([l.replace(' ', ' ') for l in p.render_above_lines(**kwargs)], output)
+
+
+class TestLines(TestRender):
+ @add_args
+ def test_without_above(self, p, config):
+ self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}|{344}f {--}')
+ self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', width=10)
+ # self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344} f {--}', width=11)
+ self.assertEqual(list(p.render_above_lines()), [])
+
+ @with_new_config
+ def test_with_above(self, config):
+ old_segments = deepcopy(config['themes/test/default']['segments'])
+ config['themes/test/default']['segments']['above'] = [old_segments]
+ with get_powerline(config, run_once=True, simpler_renderer=True) as p:
+ self.assertRenderLinesEqual(p, [
+ '{121} s{24}>>{344}g{34}>{34}|{344}f {--}',
+ ])
+ self.assertRenderLinesEqual(p, [
+ '{121} s {24}>>{344}g{34}>{34}|{344}f {--}',
+ ], width=10)
+
+ config['themes/test/default']['segments']['above'] = [old_segments] * 2
+ with get_powerline(config, run_once=True, simpler_renderer=True) as p:
+ self.assertRenderLinesEqual(p, [
+ '{121} s{24}>>{344}g{34}>{34}|{344}f {--}',
+ '{121} s{24}>>{344}g{34}>{34}|{344}f {--}',
+ ])
+ self.assertRenderLinesEqual(p, [
+ '{121} s {24}>>{344}g{34}>{34}|{344}f {--}',
+ '{121} s {24}>>{344}g{34}>{34}|{344}f {--}',
+ ], width=10)
+
+
+class TestSegments(TestRender):
+ @add_args
+ def test_display(self, p, config):
+ config['themes/test/default']['segments']['left'][0]['display'] = False
+ config['themes/test/default']['segments']['left'][1]['display'] = True
+ config['themes/test/default']['segments']['right'][0]['display'] = False
+ self.assertRenderEqual(p, '{344} g{4-}>>{--}')
+
+
+class TestColorschemesHierarchy(TestRender):
+ @add_args
+ def test_group_redirects(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('a', 'd1', draw_hard_divider=False),
+ highlighted_string('b', 'd2', draw_hard_divider=False),
+ highlighted_string('c', 'd3', draw_hard_divider=False),
+ highlighted_string('A', 'm1', draw_hard_divider=False),
+ highlighted_string('B', 'm2', draw_hard_divider=False),
+ highlighted_string('C', 'm3', draw_hard_divider=False),
+ highlighted_string('1', 'g1', draw_hard_divider=False),
+ highlighted_string('2', 'g2', draw_hard_divider=False),
+ highlighted_string('3', 'g3', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ self.assertRenderEqual(p, '{78} a{910}b{1112}c{56}A{910}B{1112}C{56}1{78}2{910}3{--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_group_redirects_no_main(self, p, config):
+ del config['colorschemes/test/__main__']
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('a', 'd1', draw_hard_divider=False),
+ highlighted_string('1', 'g1', draw_hard_divider=False),
+ highlighted_string('2', 'g2', draw_hard_divider=False),
+ highlighted_string('3', 'g3', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ self.assertRenderEqual(p, '{78} a{56}1{78}2{910}3{--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_group_redirects_no_top_default(self, p, config):
+ del config['colorschemes/default']
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('c', 'd3', draw_soft_divider=False),
+ highlighted_string('C', 'm3', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ self.assertRenderEqual(p, '{1112} c{1112}C{--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_group_redirects_no_test_default(self, p, config):
+ del config['colorschemes/test/default']
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('A', 'm1', draw_hard_divider=False),
+ highlighted_string('B', 'm2', draw_hard_divider=False),
+ highlighted_string('C', 'm3', draw_hard_divider=False),
+ highlighted_string('1', 'g1', draw_hard_divider=False),
+ highlighted_string('2', 'g2', draw_hard_divider=False),
+ highlighted_string('3', 'g3', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ self.assertRenderEqual(p, '{56} A{910}B{1112}C{56}1{78}2{910}3{--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_group_redirects_only_main(self, p, config):
+ del config['colorschemes/default']
+ del config['colorschemes/test/default']
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('C', 'm3', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ # Powerline is not able to work without default colorscheme
+ # somewhere, thus it will output error string
+ self.assertRenderEqual(p, 'colorschemes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load colorscheme: colorschemes/default',
+ 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/default',
+ 'exception:test:powerline:Failed to create renderer: colorschemes/test/default',
+ 'exception:test:powerline:Failed to render: colorschemes/test/default',
+ ])
+
+ @add_args
+ def test_group_redirects_only_top_default(self, p, config):
+ del config['colorschemes/test/__main__']
+ del config['colorschemes/test/default']
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('1', 'g1', draw_hard_divider=False),
+ highlighted_string('2', 'g2', draw_hard_divider=False),
+ highlighted_string('3', 'g3', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ self.assertRenderEqual(p, '{56} 1{78}2{910}3{--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_group_redirects_only_test_default(self, p, config):
+ del config['colorschemes/default']
+ del config['colorschemes/test/__main__']
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s', 'str1', draw_hard_divider=False),
+ ],
+ 'right': [],
+ }
+ self.assertRenderEqual(p, '{121} s{--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+
+class TestThemeHierarchy(TestRender):
+ @add_args
+ def test_hierarchy(self, p, config):
+ self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}|{344}f {--}')
+
+ @add_args
+ def test_no_main(self, p, config):
+ del config['themes/test/__main__']
+ self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}<{344}f {--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_no_powerline(self, p, config):
+ config['themes/test/__main__']['dividers'] = config['themes/' + UT]['dividers']
+ config['themes/test/__main__']['spaces'] = 1
+ del config['themes/' + UT]
+ self.assertRenderEqual(p, '{121} s {24}>>{344}g {34}>{34}<{344} f {--}')
+ self.assertEqual(p.logger._pop_msgs(), [])
+
+ @add_args
+ def test_no_default(self, p, config):
+ del config['themes/test/default']
+ self.assertRenderEqual(p, 'themes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load theme: themes/test/default',
+ 'exception:test:powerline:Failed to create renderer: themes/test/default',
+ 'exception:test:powerline:Failed to render: themes/test/default',
+ ])
+
+ @add_args
+ def test_only_default(self, p, config):
+ config['themes/test/default']['dividers'] = config['themes/' + UT]['dividers']
+ config['themes/test/default']['spaces'] = 1
+ del config['themes/test/__main__']
+ del config['themes/' + UT]
+ self.assertRenderEqual(p, '{121} s {24}>>{344}g {34}>{34}<{344} f {--}')
+
+ @add_args
+ def test_only_main(self, p, config):
+ del config['themes/test/default']
+ del config['themes/' + UT]
+ self.assertRenderEqual(p, 'themes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load theme: themes/' + UT,
+ 'exception:test:powerline:Failed to load theme: themes/test/default',
+ 'exception:test:powerline:Failed to create renderer: themes/test/default',
+ 'exception:test:powerline:Failed to render: themes/test/default',
+ ])
+
+ @add_args
+ def test_only_powerline(self, p, config):
+ del config['themes/test/default']
+ del config['themes/test/__main__']
+ self.assertRenderEqual(p, 'themes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load theme: themes/test/__main__',
+ 'exception:test:powerline:Failed to load theme: themes/test/default',
+ 'exception:test:powerline:Failed to create renderer: themes/test/default',
+ 'exception:test:powerline:Failed to render: themes/test/default',
+ ])
+
+ @add_args
+ def test_nothing(self, p, config):
+ del config['themes/test/default']
+ del config['themes/' + UT]
+ del config['themes/test/__main__']
+ self.assertRenderEqual(p, 'themes/test/default')
+ self.assertEqual(p.logger._pop_msgs(), [
+ 'exception:test:powerline:Failed to load theme: themes/' + UT,
+ 'exception:test:powerline:Failed to load theme: themes/test/__main__',
+ 'exception:test:powerline:Failed to load theme: themes/test/default',
+ 'exception:test:powerline:Failed to create renderer: themes/test/default',
+ 'exception:test:powerline:Failed to render: themes/test/default',
+ ])
+
+
+class TestDisplayCondition(TestRender):
+ @add_args
+ def test_include_modes(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s1', 'g1', include_modes=['m1']),
+ highlighted_string('s2', 'g1', include_modes=['m1', 'm2']),
+ highlighted_string('s3', 'g1', include_modes=['m3']),
+ ]
+ }
+ self.assertRenderEqual(p, '{--}')
+ self.assertRenderEqual(p, '{56} s1{56}>{56}s2{6-}>>{--}', mode='m1')
+ self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m2')
+ self.assertRenderEqual(p, '{56} s3{6-}>>{--}', mode='m3')
+
+ @add_args
+ def test_exclude_modes(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s1', 'g1', exclude_modes=['m1']),
+ highlighted_string('s2', 'g1', exclude_modes=['m1', 'm2']),
+ highlighted_string('s3', 'g1', exclude_modes=['m3']),
+ ]
+ }
+ self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}')
+ self.assertRenderEqual(p, '{56} s3{6-}>>{--}', mode='m1')
+ self.assertRenderEqual(p, '{56} s1{56}>{56}s3{6-}>>{--}', mode='m2')
+ self.assertRenderEqual(p, '{56} s1{56}>{56}s2{6-}>>{--}', mode='m3')
+
+ @add_args
+ def test_exinclude_modes(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s1', 'g1', exclude_modes=['m1'], include_modes=['m2']),
+ highlighted_string('s2', 'g1', exclude_modes=['m1', 'm2'], include_modes=['m3']),
+ highlighted_string('s3', 'g1', exclude_modes=['m3'], include_modes=['m3']),
+ ]
+ }
+ self.assertRenderEqual(p, '{--}')
+ self.assertRenderEqual(p, '{--}', mode='m1')
+ self.assertRenderEqual(p, '{56} s1{6-}>>{--}', mode='m2')
+ self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m3')
+
+ @add_args
+ def test_exinclude_function_nonexistent_module(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s1', 'g1', exclude_function='xxx_nonexistent_module.foo'),
+ highlighted_string('s2', 'g1', exclude_function='xxx_nonexistent_module.foo', include_function='xxx_nonexistent_module.bar'),
+ highlighted_string('s3', 'g1', include_function='xxx_nonexistent_module.bar'),
+ ]
+ }
+ self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}')
+
+ @add_args
+ def test_exinclude_function(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s1', 'g1', exclude_function='mod.foo'),
+ highlighted_string('s2', 'g1', exclude_function='mod.foo', include_function='mod.bar'),
+ highlighted_string('s3', 'g1', include_function='mod.bar'),
+ ]
+ }
+ launched = set()
+ fool = [None]
+ barl = [None]
+
+ def foo(*args, **kwargs):
+ launched.add('foo')
+ self.assertEqual(set(kwargs.keys()), set(('pl', 'segment_info', 'mode')))
+ self.assertEqual(args, ())
+ return fool[0]
+
+ def bar(*args, **kwargs):
+ launched.add('bar')
+ self.assertEqual(set(kwargs.keys()), set(('pl', 'segment_info', 'mode')))
+ self.assertEqual(args, ())
+ return barl[0]
+
+ with replace_item(sys.modules, 'mod', Args(foo=foo, bar=bar)):
+ fool[0] = True
+ barl[0] = True
+ self.assertRenderEqual(p, '{56} s3{6-}>>{--}')
+ self.assertEqual(launched, set(('foo', 'bar')))
+
+ fool[0] = False
+ barl[0] = True
+ self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}')
+ self.assertEqual(launched, set(('foo', 'bar')))
+
+ fool[0] = False
+ barl[0] = False
+ self.assertRenderEqual(p, '{56} s1{6-}>>{--}')
+ self.assertEqual(launched, set(('foo', 'bar')))
+
+ fool[0] = True
+ barl[0] = False
+ self.assertRenderEqual(p, '{--}')
+ self.assertEqual(launched, set(('foo', 'bar')))
+
+ @add_args
+ def test_exinclude_modes_override_functions(self, p, config):
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ highlighted_string('s1', 'g1', exclude_function='mod.foo', exclude_modes=['m2']),
+ highlighted_string('s2', 'g1', exclude_function='mod.foo', include_modes=['m2']),
+ highlighted_string('s3', 'g1', include_function='mod.foo', exclude_modes=['m2']),
+ highlighted_string('s4', 'g1', include_function='mod.foo', include_modes=['m2']),
+ ]
+ }
+ fool = [None]
+
+ def foo(*args, **kwargs):
+ return fool[0]
+
+ with replace_item(sys.modules, 'mod', Args(foo=foo)):
+ fool[0] = True
+ self.assertRenderEqual(p, '{56} s4{6-}>>{--}', mode='m2')
+ self.assertRenderEqual(p, '{56} s3{56}>{56}s4{6-}>>{--}', mode='m1')
+
+ fool[0] = False
+ self.assertRenderEqual(p, '{56} s2{56}>{56}s4{6-}>>{--}', mode='m2')
+ self.assertRenderEqual(p, '{56} s1{6-}>>{--}', mode='m1')
+
+
+class TestOuterPadding(TestRender):
+ @add_args
+ def test_outer_padding_left(self, p, config):
+ config['themes/' + UT]['outer_padding'] = 5
+ self.assertRenderEqual(p, '{121} s{24}>>{344}g{4-}>>{--}', side='left')
+
+ @add_args
+ def test_outer_padding_right(self, p, config):
+ config['themes/' + UT]['outer_padding'] = 5
+ self.assertRenderEqual(p, '{4-}<<{344}f {--}', side='right')
+
+ @add_args
+ def test_outer_padding_ten(self, p, config):
+ config['themes/' + UT]['outer_padding'] = 10
+ self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344} f {--}', width=30)
+
+ @add_args
+ def test_outer_padding_zero(self, p, config):
+ config['themes/' + UT]['outer_padding'] = 0
+ self.assertRenderEqual(p, '{121}s {24}>>{344}g{34}>{34}|{344} f{--}', width=30)
+
+
+class TestSegmentAttributes(TestRender):
+ @add_args
+ def test_no_attributes(self, p, config):
+ def m1(divider=',', **kwargs):
+ return divider.join(kwargs.keys()) + divider
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ {
+ 'function': 'bar.m1'
+ }
+ ]
+ }
+ with replace_item(sys.modules, 'bar', Args(m1=m1)):
+ self.assertRenderEqual(p, '{56} pl,{6-}>>{--}')
+
+ @add_args
+ def test_segment_datas(self, p, config):
+ def m1(divider=',', **kwargs):
+ return divider.join(kwargs.keys()) + divider
+ m1.powerline_segment_datas = {
+ UT: {
+ 'args': {
+ 'divider': ';'
+ }
+ },
+ 'ascii': {
+ 'args': {
+ 'divider': '--'
+ }
+ }
+ }
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ {
+ 'function': 'bar.m1'
+ }
+ ]
+ }
+ with replace_item(sys.modules, 'bar', Args(m1=m1)):
+ self.assertRenderEqual(p, '{56} pl;{6-}>>{--}')
+
+ @add_args
+ def test_expand(self, p, config):
+ def m1(divider=',', **kwargs):
+ return divider.join(kwargs.keys()) + divider
+
+ def expand(pl, amount, segment, **kwargs):
+ return ('-' * amount) + segment['contents']
+
+ m1.expand = expand
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ {
+ 'function': 'bar.m1',
+ 'width': 'auto'
+ }
+ ]
+ }
+ with replace_item(sys.modules, 'bar', Args(m1=m1)):
+ self.assertRenderEqual(p, '{56} ----pl,{6-}>>{--}', width=10)
+
+ @add_args
+ def test_truncate(self, p, config):
+ def m1(divider=',', **kwargs):
+ return divider.join(kwargs.keys()) + divider
+
+ def truncate(pl, amount, segment, **kwargs):
+ return segment['contents'][:-amount]
+
+ m1.truncate = truncate
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ {
+ 'function': 'bar.m1'
+ }
+ ]
+ }
+ with replace_item(sys.modules, 'bar', Args(m1=m1)):
+ self.assertRenderEqual(p, '{56} p{6-}>>{--}', width=4)
+
+
+class TestSegmentData(TestRender):
+ @add_args
+ def test_segment_data(self, p, config):
+ def m1(**kwargs):
+ return 'S'
+
+ def m2(**kwargs):
+ return 'S'
+ sys.modules['bar'] = Args(m1=m1, m2=m2)
+ config['themes/' + UT]['segment_data'] = {
+ 'm1': {
+ 'before': '1'
+ },
+ 'bar.m2': {
+ 'before': '2'
+ },
+ 'n': {
+ 'before': '3'
+ },
+ 'm2': {
+ 'before': '4'
+ },
+ }
+ config['themes/test/default']['segments'] = {
+ 'left': [
+ {
+ 'function': 'bar.m1'
+ },
+ {
+ 'function': 'bar.m1',
+ 'name': 'n'
+ },
+ {
+ 'function': 'bar.m2',
+ 'name': 'n'
+ },
+ {
+ 'function': 'bar.m2'
+ }
+ ]
+ }
+ self.assertRenderEqual(p, '{56} 1S{56}>{56}3S{610}>>{910}3S{910}>{910}2S{10-}>>{--}')
+
+
+class TestShellEscapes(TestCase):
+ @with_new_config
+ def test_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0;38;5;5;48;5;6m\xa0s\x1b[0;38;5;6;49;22m>>\x1b[0m')
+
+ @with_new_config
+ def test_tmux_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['additional_escapes'] = 'tmux'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0;38;5;5;48;5;6m\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0;38;5;6;49;22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\')
+
+ @with_new_config
+ def test_screen_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['additional_escapes'] = 'screen'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0;38;5;5;48;5;6m\x1b\\\xa0s\x1bP\x1b\x1b[0;38;5;6;49;22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\')
+
+ @with_new_config
+ def test_fbterm_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_escape_style'] = 'fbterm'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0m\x1b[1;5}\x1b[2;6}\xa0s\x1b[0m\x1b[1;6}\x1b[49m\x1b[22m>>\x1b[0m')
+
+ @with_new_config
+ def test_fbterm_tmux_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_escape_style'] = 'fbterm'
+ config['config']['common']['additional_escapes'] = 'tmux'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0m\x1b\x1b[1;5}\x1b\x1b[2;6}\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0m\x1b\x1b[1;6}\x1b\x1b[49m\x1b\x1b[22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\')
+
+ @with_new_config
+ def test_fbterm_screen_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_escape_style'] = 'fbterm'
+ config['config']['common']['additional_escapes'] = 'screen'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0m\x1b\x1b[1;5}\x1b\x1b[2;6}\x1b\\\xa0s\x1bP\x1b\x1b[0m\x1b\x1b[1;6}\x1b\x1b[49m\x1b\x1b[22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\')
+
+ @with_new_config
+ def test_term_truecolor_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_truecolor'] = True
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0;38;2;192;0;192;48;2;0;128;128m\xa0s\x1b[0;38;2;0;128;128;49;22m>>\x1b[0m')
+
+ @with_new_config
+ def test_term_truecolor_fbterm_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_escape_style'] = 'fbterm'
+ config['config']['common']['term_truecolor'] = True
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0m\x1b[1;5}\x1b[2;6}\xa0s\x1b[0m\x1b[1;6}\x1b[49m\x1b[22m>>\x1b[0m')
+
+ @with_new_config
+ def test_term_truecolor_tmux_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_truecolor'] = True
+ config['config']['common']['additional_escapes'] = 'tmux'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0;38;2;192;0;192;48;2;0;128;128m\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0;38;2;0;128;128;49;22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\')
+
+ @with_new_config
+ def test_term_truecolor_screen_escapes(self, config):
+ from powerline.shell import ShellPowerline
+ import powerline as powerline_module
+ config['config']['common']['term_truecolor'] = True
+ config['config']['common']['additional_escapes'] = 'screen'
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline:
+ self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0;38;2;192;0;192;48;2;0;128;128m\x1b\\\xa0s\x1bP\x1b\x1b[0;38;2;0;128;128;49;22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\')
+
+
+class TestVim(TestCase):
+ def test_environ_update(self):
+ # Regression test: test that segment obtains environment from vim, not
+ # from os.environ.
+ with vim_module._with('globals', powerline_config_paths=['/']):
+ from powerline.vim import VimPowerline
+ import powerline as powerline_module
+ with swap_attributes(config, powerline_module):
+ with vim_module._with('environ', TEST='abc'):
+ with get_powerline_raw(config, VimPowerline) as powerline:
+ window = vim_module.current.window
+ window_id = 1
+ winnr = window.number
+ self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_3_8404992_4_192_underline#\xc2\xa0abc%#Pl_4_192_NONE_None_NONE#>>')
+ vim_module._environ['TEST'] = 'def'
+ self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_3_8404992_4_192_underline#\xc2\xa0def%#Pl_4_192_NONE_None_NONE#>>')
+
+ def test_local_themes(self):
+ # Regression test: VimPowerline.add_local_theme did not work properly.
+ from powerline.vim import VimPowerline
+ import powerline as powerline_module
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, VimPowerline, replace_gcp=True) as powerline:
+ powerline.add_local_theme('tests.modules.matchers.always_true', {
+ 'segment_data': {
+ 'foo': {
+ 'contents': '“bar”'
+ }
+ },
+ 'segments': {
+ 'left': [
+ {
+ 'type': 'string',
+ 'name': 'foo',
+ 'highlight_groups': ['g1']
+ }
+ ]
+ }
+ })
+ window = vim_module.current.window
+ window_id = 1
+ winnr = window.number
+ self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_5_12583104_6_32896_NONE#\xc2\xa0\xe2\x80\x9cbar\xe2\x80\x9d%#Pl_6_32896_NONE_None_NONE#>>')
+
+ @classmethod
+ def setUpClass(cls):
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path')))
+
+ @classmethod
+ def tearDownClass(cls):
+ sys.path.pop(0)
+
+
+class TestLemonbar(TestRender):
+ def test_lemonbar(self):
+ import powerline as powerline_module
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline:
+ self.assertRenderEqual(
+ powerline,
+ '%{l}%{F#ffc00000}%{B#ff008000}%{+u} A%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}B%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
+ )
+
+ @with_new_config
+ def test_lemonbar_escape(self, config):
+ import powerline as powerline_module
+ config['themes/wm/default']['segments']['left'] = (
+ highlighted_string('%{asd}', 'hl1'),
+ highlighted_string('10% %', 'hl2'),
+ )
+ with swap_attributes(config, powerline_module):
+ with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline:
+ self.assertRenderEqual(
+ powerline,
+ '%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{}{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%%{} %%{}%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
+ )
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_lib.py b/tests/test_python/test_lib.py
new file mode 100644
index 0000000..6dd6190
--- /dev/null
+++ b/tests/test_python/test_lib.py
@@ -0,0 +1,733 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import threading
+import os
+import sys
+import re
+import shutil
+import unicodedata
+
+from time import sleep
+from subprocess import call, PIPE
+
+from powerline.lib import add_divider_highlight_group
+from powerline.lib.dict import mergedicts, REMOVE_THIS_KEY
+from powerline.lib.humanize_bytes import humanize_bytes
+from powerline.lib.vcs import guess, get_fallback_create_watcher
+from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
+from powerline.lib.monotonic import monotonic
+from powerline.lib.vcs.git import git_directory
+from powerline.lib.shell import run_cmd
+
+import powerline.lib.unicode as plu
+
+from tests.modules.lib import Pl, replace_attr
+from tests.modules import TestCase, SkipTest
+
+
+try:
+ __import__('bzrlib')
+except ImportError:
+ use_bzr = False
+else:
+ use_bzr = True
+
+try:
+ __import__('hglib')
+except ImportError:
+ use_mercurial = False
+else:
+ use_mercurial = True
+
+
+GIT_REPO = 'git_repo'
+HG_REPO = 'hg_repo'
+BZR_REPO = 'bzr_repo'
+
+
+def thread_number():
+ return len(threading.enumerate())
+
+
+class TestShell(TestCase):
+ def test_run_cmd(self):
+ pl = Pl()
+ self.assertEqual(run_cmd(pl, ['xxx_nonexistent_command_xxx']), None)
+ self.assertEqual(len(pl.exceptions), 1)
+ pl = Pl()
+ self.assertEqual(run_cmd(pl, ['echo', ' test ']), 'test')
+ self.assertFalse(pl)
+ self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=True), 'test')
+ self.assertFalse(pl)
+ self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=False), ' test \n')
+ self.assertFalse(pl)
+ self.assertEqual(run_cmd(pl, ['cat'], stdin='test'), 'test')
+ self.assertFalse(pl)
+ self.assertEqual(run_cmd(pl, ['sh', '-c', 'cat >&2'], stdin='test'), '')
+ self.assertFalse(pl)
+
+
+class TestThreaded(TestCase):
+ def test_threaded_segment(self):
+ log = []
+ pl = Pl()
+ updates = [(None,)]
+ lock = threading.Lock()
+ event = threading.Event()
+ block_event = threading.Event()
+
+ class TestSegment(ThreadedSegment):
+ interval = 10
+
+ def set_state(self, **kwargs):
+ event.clear()
+ log.append(('set_state', kwargs))
+ return super(TestSegment, self).set_state(**kwargs)
+
+ def update(self, update_value):
+ block_event.wait()
+ event.set()
+ # Make sleep first to prevent some race conditions
+ log.append(('update', update_value))
+ with lock:
+ ret = updates[0]
+ if isinstance(ret, Exception):
+ raise ret
+ else:
+ return ret[0]
+
+ def render(self, update, **kwargs):
+ log.append(('render', update, kwargs))
+ if isinstance(update, Exception):
+ raise update
+ else:
+ return update
+
+ # Non-threaded tests
+ segment = TestSegment()
+ block_event.set()
+ updates[0] = (None,)
+ self.assertEqual(segment(pl=pl), None)
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('set_state', {}),
+ ('update', None),
+ ('render', None, {'pl': pl, 'update_first': True}),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ updates[0] = ('abc',)
+ self.assertEqual(segment(pl=pl), 'abc')
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('set_state', {}),
+ ('update', None),
+ ('render', 'abc', {'pl': pl, 'update_first': True}),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ updates[0] = ('abc',)
+ self.assertEqual(segment(pl=pl, update_first=False), 'abc')
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('set_state', {}),
+ ('update', None),
+ ('render', 'abc', {'pl': pl, 'update_first': False}),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ updates[0] = ValueError('abc')
+ self.assertEqual(segment(pl=pl), None)
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(len(pl.exceptions), 1)
+ self.assertEqual(log, [
+ ('set_state', {}),
+ ('update', None),
+ ])
+ log[:] = ()
+ pl.exceptions[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ updates[0] = (TypeError('def'),)
+ self.assertRaises(TypeError, segment, pl=pl)
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('set_state', {}),
+ ('update', None),
+ ('render', updates[0][0], {'pl': pl, 'update_first': True}),
+ ])
+ log[:] = ()
+
+ # Threaded tests
+ segment = TestSegment()
+ block_event.clear()
+ kwargs = {'pl': pl, 'update_first': False, 'other': 1}
+ with lock:
+ updates[0] = ('abc',)
+ segment.startup(**kwargs)
+ ret = segment(**kwargs)
+ self.assertEqual(thread_number(), 2)
+ block_event.set()
+ event.wait()
+ segment.shutdown_event.set()
+ segment.thread.join()
+ self.assertEqual(ret, None)
+ self.assertEqual(log, [
+ ('set_state', {'update_first': False, 'other': 1}),
+ ('render', None, {'pl': pl, 'update_first': False, 'other': 1}),
+ ('update', None),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ kwargs = {'pl': pl, 'update_first': True, 'other': 1}
+ with lock:
+ updates[0] = ('def',)
+ segment.startup(**kwargs)
+ ret = segment(**kwargs)
+ self.assertEqual(thread_number(), 2)
+ segment.shutdown_event.set()
+ segment.thread.join()
+ self.assertEqual(ret, 'def')
+ self.assertEqual(log, [
+ ('set_state', {'update_first': True, 'other': 1}),
+ ('update', None),
+ ('render', 'def', {'pl': pl, 'update_first': True, 'other': 1}),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ kwargs = {'pl': pl, 'update_first': True, 'interval': 0.2}
+ with lock:
+ updates[0] = ('abc',)
+ segment.startup(**kwargs)
+ start = monotonic()
+ ret1 = segment(**kwargs)
+ with lock:
+ updates[0] = ('def',)
+ self.assertEqual(thread_number(), 2)
+ sleep(0.5)
+ ret2 = segment(**kwargs)
+ segment.shutdown_event.set()
+ segment.thread.join()
+ end = monotonic()
+ duration = end - start
+ self.assertEqual(ret1, 'abc')
+ self.assertEqual(ret2, 'def')
+ self.assertEqual(log[:5], [
+ ('set_state', {'update_first': True, 'interval': 0.2}),
+ ('update', None),
+ ('render', 'abc', {'pl': pl, 'update_first': True, 'interval': 0.2}),
+ ('update', 'abc'),
+ ('update', 'def'),
+ ])
+ num_runs = len([e for e in log if e[0] == 'update'])
+ self.assertAlmostEqual(duration / 0.2, num_runs, delta=1)
+ log[:] = ()
+
+ segment = TestSegment()
+ block_event.set()
+ kwargs = {'pl': pl, 'update_first': True, 'interval': 0.2}
+ with lock:
+ updates[0] = ('ghi',)
+ segment.startup(**kwargs)
+ start = monotonic()
+ ret1 = segment(**kwargs)
+ with lock:
+ updates[0] = TypeError('jkl')
+ self.assertEqual(thread_number(), 2)
+ sleep(0.5)
+ ret2 = segment(**kwargs)
+ segment.shutdown_event.set()
+ segment.thread.join()
+ end = monotonic()
+ duration = end - start
+ self.assertEqual(ret1, 'ghi')
+ self.assertEqual(ret2, None)
+ self.assertEqual(log[:5], [
+ ('set_state', {'update_first': True, 'interval': 0.2}),
+ ('update', None),
+ ('render', 'ghi', {'pl': pl, 'update_first': True, 'interval': 0.2}),
+ ('update', 'ghi'),
+ ('update', 'ghi'),
+ ])
+ num_runs = len([e for e in log if e[0] == 'update'])
+ self.assertAlmostEqual(duration / 0.2, num_runs, delta=1)
+ self.assertEqual(num_runs - 1, len(pl.exceptions))
+ log[:] = ()
+
+ def test_kw_threaded_segment(self):
+ log = []
+ pl = Pl()
+ event = threading.Event()
+
+ class TestSegment(KwThreadedSegment):
+ interval = 10
+
+ @staticmethod
+ def key(_key=(None,), **kwargs):
+ log.append(('key', _key, kwargs))
+ return _key
+
+ def compute_state(self, key):
+ event.set()
+ sleep(0.1)
+ log.append(('compute_state', key))
+ ret = key
+ if isinstance(ret, Exception):
+ raise ret
+ else:
+ return ret[0]
+
+ def render_one(self, state, **kwargs):
+ log.append(('render_one', state, kwargs))
+ if isinstance(state, Exception):
+ raise state
+ else:
+ return state
+
+ # Non-threaded tests
+ segment = TestSegment()
+ event.clear()
+ self.assertEqual(segment(pl=pl), None)
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('key', (None,), {'pl': pl}),
+ ('compute_state', (None,)),
+ ('render_one', None, {'pl': pl}),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ kwargs = {'pl': pl, '_key': ('abc',), 'update_first': False}
+ event.clear()
+ self.assertEqual(segment(**kwargs), 'abc')
+ kwargs.update(_key=('def',))
+ self.assertEqual(segment(**kwargs), 'def')
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('key', ('abc',), {'pl': pl}),
+ ('compute_state', ('abc',)),
+ ('render_one', 'abc', {'pl': pl, '_key': ('abc',)}),
+ ('key', ('def',), {'pl': pl}),
+ ('compute_state', ('def',)),
+ ('render_one', 'def', {'pl': pl, '_key': ('def',)}),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ kwargs = {'pl': pl, '_key': ValueError('xyz'), 'update_first': False}
+ event.clear()
+ self.assertEqual(segment(**kwargs), None)
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('key', kwargs['_key'], {'pl': pl}),
+ ('compute_state', kwargs['_key']),
+ ])
+ log[:] = ()
+
+ segment = TestSegment()
+ kwargs = {'pl': pl, '_key': (ValueError('abc'),), 'update_first': False}
+ event.clear()
+ self.assertRaises(ValueError, segment, **kwargs)
+ self.assertEqual(thread_number(), 1)
+ self.assertEqual(log, [
+ ('key', kwargs['_key'], {'pl': pl}),
+ ('compute_state', kwargs['_key']),
+ ('render_one', kwargs['_key'][0], {'pl': pl, '_key': kwargs['_key']}),
+ ])
+ log[:] = ()
+
+ # Threaded tests
+ segment = TestSegment()
+ kwargs = {'pl': pl, 'update_first': False, '_key': ('_abc',)}
+ event.clear()
+ segment.startup(**kwargs)
+ ret = segment(**kwargs)
+ self.assertEqual(thread_number(), 2)
+ segment.shutdown_event.set()
+ segment.thread.join()
+ self.assertEqual(ret, None)
+ self.assertEqual(log[:2], [
+ ('key', kwargs['_key'], {'pl': pl}),
+ ('render_one', None, {'pl': pl, '_key': kwargs['_key']}),
+ ])
+ self.assertLessEqual(len(log), 3)
+ if len(log) > 2:
+ self.assertEqual(log[2], ('compute_state', kwargs['_key']))
+ log[:] = ()
+
+ segment = TestSegment()
+ kwargs = {'pl': pl, 'update_first': True, '_key': ('_abc',)}
+ event.clear()
+ segment.startup(**kwargs)
+ ret1 = segment(**kwargs)
+ kwargs.update(_key=('_def',))
+ ret2 = segment(**kwargs)
+ self.assertEqual(thread_number(), 2)
+ segment.shutdown_event.set()
+ segment.thread.join()
+ self.assertEqual(ret1, '_abc')
+ self.assertEqual(ret2, '_def')
+ self.assertEqual(log, [
+ ('key', ('_abc',), {'pl': pl}),
+ ('compute_state', ('_abc',)),
+ ('render_one', '_abc', {'pl': pl, '_key': ('_abc',)}),
+ ('key', ('_def',), {'pl': pl}),
+ ('compute_state', ('_def',)),
+ ('render_one', '_def', {'pl': pl, '_key': ('_def',)}),
+ ])
+ log[:] = ()
+
+
+class TestLib(TestCase):
+ def test_mergedicts(self):
+ d = {}
+ mergedicts(d, {'abc': {'def': 'ghi'}})
+ self.assertEqual(d, {'abc': {'def': 'ghi'}})
+ mergedicts(d, {'abc': {'def': {'ghi': 'jkl'}}})
+ self.assertEqual(d, {'abc': {'def': {'ghi': 'jkl'}}})
+ mergedicts(d, {})
+ self.assertEqual(d, {'abc': {'def': {'ghi': 'jkl'}}})
+ mergedicts(d, {'abc': {'mno': 'pqr'}})
+ self.assertEqual(d, {'abc': {'def': {'ghi': 'jkl'}, 'mno': 'pqr'}})
+ mergedicts(d, {'abc': {'def': REMOVE_THIS_KEY}})
+ self.assertEqual(d, {'abc': {'mno': 'pqr'}})
+
+ def test_add_divider_highlight_group(self):
+ def decorated_function_name(**kwargs):
+ return str(kwargs)
+ func = add_divider_highlight_group('hl_group')(decorated_function_name)
+ self.assertEqual(func.__name__, 'decorated_function_name')
+ self.assertEqual(func(kw={}), [{'contents': repr({str('kw'): {}}), 'divider_highlight_group': 'hl_group'}])
+
+ def test_humanize_bytes(self):
+ self.assertEqual(humanize_bytes(0), '0 B')
+ self.assertEqual(humanize_bytes(1), '1 B')
+ self.assertEqual(humanize_bytes(1, suffix='bit'), '1 bit')
+ self.assertEqual(humanize_bytes(1000, si_prefix=True), '1 kB')
+ self.assertEqual(humanize_bytes(1024, si_prefix=True), '1 kB')
+ self.assertEqual(humanize_bytes(1000000000, si_prefix=True), '1.00 GB')
+ self.assertEqual(humanize_bytes(1000000000, si_prefix=False), '953.7 MiB')
+
+
+width_data = {
+ 'N': 1, # Neutral
+ 'Na': 1, # Narrow
+ 'A': 1, # Ambigious
+ 'H': 1, # Half-width
+ 'W': 2, # Wide
+ 'F': 2, # Fullwidth
+}
+
+
+class TestUnicode(TestCase):
+ def assertStringsIdentical(self, s1, s2):
+ self.assertTrue(type(s1) is type(s2), msg='string types differ')
+ self.assertEqual(s1, s2)
+
+ def test_unicode(self):
+ self.assertTrue(type('abc') is plu.unicode)
+
+ def test_unichr(self):
+ self.assertStringsIdentical('\U0010FFFF', plu.unichr(0x10FFFF))
+ self.assertStringsIdentical('\uFFFF', plu.unichr(0xFFFF))
+ self.assertStringsIdentical('\x20', plu.unichr(0x20))
+
+ def test_u(self):
+ self.assertStringsIdentical('Test', plu.u('Test'))
+ self.assertStringsIdentical('Test', plu.u(b'Test'))
+ self.assertStringsIdentical('«»', plu.u(b'\xC2\xAB\xC2\xBB'))
+ self.assertRaises(UnicodeDecodeError, plu.u, b'\xFF')
+
+ def test_tointiter(self):
+ self.assertEqual([1, 2, 3], list(plu.tointiter(b'\x01\x02\x03')))
+
+ def test_decode_error(self):
+ self.assertStringsIdentical('<FF>', b'\xFF'.decode('utf-8', 'powerline_decode_error'))
+ self.assertStringsIdentical('abc', b'abc'.decode('utf-8', 'powerline_decode_error'))
+
+ def test_register_strwidth_error(self):
+ ename = plu.register_strwidth_error(lambda s: 3)
+ self.assertStringsIdentical(b'???', 'A'.encode('latin1', ename))
+ self.assertStringsIdentical(b'abc', 'abc'.encode('latin1', ename))
+
+ def test_out_u(self):
+ self.assertStringsIdentical('abc', plu.out_u('abc'))
+ self.assertStringsIdentical('abc', plu.out_u(b'abc'))
+ self.assertRaises(TypeError, plu.out_u, None)
+
+ def test_safe_unicode(self):
+ self.assertStringsIdentical('abc', plu.safe_unicode('abc'))
+ self.assertStringsIdentical('abc', plu.safe_unicode(b'abc'))
+ self.assertStringsIdentical('«»', plu.safe_unicode(b'\xc2\xab\xc2\xbb'))
+ with replace_attr(plu, 'get_preferred_output_encoding', lambda: 'latin1'):
+ self.assertStringsIdentical('ÿ', plu.safe_unicode(b'\xFF'))
+ self.assertStringsIdentical('None', plu.safe_unicode(None))
+
+ class FailingStr(object):
+ def __str__(self):
+ raise NotImplementedError('Fail!')
+
+ self.assertStringsIdentical('Fail!', plu.safe_unicode(FailingStr()))
+
+ def test_FailedUnicode(self):
+ self.assertTrue(isinstance(plu.FailedUnicode('abc'), plu.unicode))
+ self.assertEqual('abc', plu.FailedUnicode('abc'))
+
+ def test_string(self):
+ self.assertStringsIdentical(str('abc'), plu.string('abc'))
+ self.assertStringsIdentical(str('abc'), plu.string(b'abc'))
+
+ def test_surrogate_pair_to_character(self):
+ self.assertEqual(0x1F48E, plu.surrogate_pair_to_character(0xD83D, 0xDC8E))
+
+ def test_strwidth_ucs_4(self):
+ self.assertEqual(4, plu.strwidth_ucs_4(width_data, 'abcd'))
+ self.assertEqual(4, plu.strwidth_ucs_4(width_data, 'AB'))
+ if sys.maxunicode < 0x10FFFF:
+ raise SkipTest('Can only test strwidth_ucs_4 in UCS-4 Pythons')
+
+ self.assertEqual(1, plu.strwidth_ucs_4(width_data, '\U0001F063'))
+
+ def test_strwidth_ucs_2(self):
+ self.assertEqual(4, plu.strwidth_ucs_2(width_data, 'abcd'))
+ self.assertEqual(4, plu.strwidth_ucs_2(width_data, 'AB'))
+ if not sys.maxunicode < 0x10FFFF:
+ raise SkipTest('Can only test strwidth_ucs_2 in UCS-2 Pythons')
+ self.assertEqual(1, plu.strwidth_ucs_2(width_data, '\ud83c\udc30'))
+
+
+class TestVCS(TestCase):
+ def do_branch_rename_test(self, repo, q):
+ st = monotonic()
+ while monotonic() - st < 1:
+ # Give inotify time to deliver events
+ ans = repo.branch()
+ if hasattr(q, '__call__'):
+ if q(ans):
+ break
+ else:
+ if ans == q:
+ break
+ sleep(0.01)
+ if hasattr(q, '__call__'):
+ self.assertTrue(q(ans))
+ else:
+ self.assertEqual(ans, q)
+
+ def test_git(self):
+ create_watcher = get_fallback_create_watcher()
+ repo = guess(path=GIT_REPO, create_watcher=create_watcher)
+ self.assertNotEqual(repo, None)
+ self.assertEqual(repo.branch(), 'master')
+ self.assertEqual(repo.status(), None)
+ self.assertEqual(repo.status('file'), None)
+ with open(os.path.join(GIT_REPO, 'file'), 'w') as f:
+ f.write('abc')
+ f.flush()
+ self.assertEqual(repo.status(), ' U')
+ self.assertEqual(repo.status('file'), '??')
+ call(['git', 'add', '.'], cwd=GIT_REPO)
+ self.assertEqual(repo.status(), ' I ')
+ self.assertEqual(repo.status('file'), 'A ')
+ f.write('def')
+ f.flush()
+ self.assertEqual(repo.status(), 'DI ')
+ self.assertEqual(repo.status('file'), 'AM')
+ os.remove(os.path.join(GIT_REPO, 'file'))
+ # Test changing branch
+ self.assertEqual(repo.branch(), 'master')
+ try:
+ call(['git', 'branch', 'branch1'], cwd=GIT_REPO)
+ call(['git', 'checkout', '-q', 'branch1'], cwd=GIT_REPO)
+ self.do_branch_rename_test(repo, 'branch1')
+ call(['git', 'branch', 'branch2'], cwd=GIT_REPO)
+ call(['git', 'checkout', '-q', 'branch2'], cwd=GIT_REPO)
+ self.do_branch_rename_test(repo, 'branch2')
+ call(['git', 'checkout', '-q', '--detach', 'branch1'], cwd=GIT_REPO)
+ self.do_branch_rename_test(repo, lambda b: re.match(r'^[a-f0-9]+$', b))
+ finally:
+ call(['git', 'checkout', '-q', 'master'], cwd=GIT_REPO)
+ # Test stashing
+ self.assertEqual(repo.stash(), 0)
+
+ def stash_save():
+ with open(os.path.join(GIT_REPO, 'file'), 'w') as f:
+ f.write('abc')
+ return call(['git', 'stash', '-u'], cwd=GIT_REPO, stdout=PIPE)
+
+ def stash_drop():
+ return call(['git', 'stash', 'drop'], cwd=GIT_REPO, stdout=PIPE)
+
+ def stash_list():
+ return call(['git', 'stash', 'list'], cwd=GIT_REPO, stdout=PIPE)
+
+ try:
+ stash_save()
+ self.assertEqual(repo.stash(), 1)
+ stash_save()
+ self.assertEqual(repo.stash(), 2)
+ stash_drop()
+ self.assertEqual(repo.stash(), 1)
+ stash_drop()
+ self.assertEqual(repo.stash(), 0)
+ finally:
+ while stash_list():
+ stash_drop()
+
+ def test_git_sym(self):
+ create_watcher = get_fallback_create_watcher()
+ dotgit = os.path.join(GIT_REPO, '.git')
+ spacegit = os.path.join(GIT_REPO, ' .git ')
+ os.rename(dotgit, spacegit)
+ try:
+ with open(dotgit, 'w') as F:
+ F.write('gitdir: .git \n')
+ gitdir = git_directory(GIT_REPO)
+ self.assertTrue(os.path.isdir(gitdir))
+ self.assertEqual(gitdir, os.path.abspath(spacegit))
+ repo = guess(path=GIT_REPO, create_watcher=create_watcher)
+ self.assertEqual(repo.branch(), 'master')
+ finally:
+ os.remove(dotgit)
+ os.rename(spacegit, dotgit)
+
+ def test_mercurial(self):
+ if not use_mercurial:
+ raise SkipTest('Mercurial is not available')
+ create_watcher = get_fallback_create_watcher()
+ repo = guess(path=HG_REPO, create_watcher=create_watcher)
+ self.assertNotEqual(repo, None)
+ self.assertEqual(repo.branch(), 'default')
+ self.assertEqual(repo.status(), None)
+ with open(os.path.join(HG_REPO, 'file'), 'w') as f:
+ f.write('abc')
+ f.flush()
+ self.assertEqual(repo.status(), ' U')
+ self.assertEqual(repo.status('file'), 'U')
+ call(['hg', 'add', '.'], cwd=HG_REPO, stdout=PIPE)
+ self.assertEqual(repo.status(), 'D ')
+ self.assertEqual(repo.status('file'), 'A')
+ os.remove(os.path.join(HG_REPO, 'file'))
+
+ def test_bzr(self):
+ if not use_bzr:
+ raise SkipTest('Bazaar is not available')
+ create_watcher = get_fallback_create_watcher()
+ repo = guess(path=BZR_REPO, create_watcher=create_watcher)
+ self.assertNotEqual(repo, None, 'No bzr repo found. Do you have bzr installed?')
+ self.assertEqual(repo.branch(), 'test_powerline')
+ self.assertEqual(repo.status(), None)
+ with open(os.path.join(BZR_REPO, 'file'), 'w') as f:
+ f.write('abc')
+ self.assertEqual(repo.status(), ' U')
+ self.assertEqual(repo.status('file'), '? ')
+ call(['bzr', 'add', '-q', '.'], cwd=BZR_REPO, stdout=PIPE)
+ self.assertEqual(repo.status(), 'D ')
+ self.assertEqual(repo.status('file'), '+N')
+ call(['bzr', 'commit', '-q', '-m', 'initial commit'], cwd=BZR_REPO)
+ self.assertEqual(repo.status(), None)
+ with open(os.path.join(BZR_REPO, 'file'), 'w') as f:
+ f.write('def')
+ self.assertEqual(repo.status(), 'D ')
+ self.assertEqual(repo.status('file'), ' M')
+ self.assertEqual(repo.status('notexist'), None)
+ with open(os.path.join(BZR_REPO, 'ignored'), 'w') as f:
+ f.write('abc')
+ self.assertEqual(repo.status('ignored'), '? ')
+ # Test changing the .bzrignore file should update status
+ with open(os.path.join(BZR_REPO, '.bzrignore'), 'w') as f:
+ f.write('ignored')
+ self.assertEqual(repo.status('ignored'), None)
+ # Test changing the dirstate file should invalidate the cache for
+ # all files in the repo
+ with open(os.path.join(BZR_REPO, 'file2'), 'w') as f:
+ f.write('abc')
+ call(['bzr', 'add', 'file2'], cwd=BZR_REPO, stdout=PIPE)
+ call(['bzr', 'commit', '-q', '-m', 'file2 added'], cwd=BZR_REPO)
+ with open(os.path.join(BZR_REPO, 'file'), 'a') as f:
+ f.write('hello')
+ with open(os.path.join(BZR_REPO, 'file2'), 'a') as f:
+ f.write('hello')
+ self.assertEqual(repo.status('file'), ' M')
+ self.assertEqual(repo.status('file2'), ' M')
+ call(['bzr', 'commit', '-q', '-m', 'multi'], cwd=BZR_REPO)
+ self.assertEqual(repo.status('file'), None)
+ self.assertEqual(repo.status('file2'), None)
+
+ # Test changing branch
+ call(['bzr', 'nick', 'branch1'], cwd=BZR_REPO, stdout=PIPE, stderr=PIPE)
+ self.do_branch_rename_test(repo, 'branch1')
+
+ # Test branch name/status changes when swapping repos
+ for x in ('b1', 'b2'):
+ d = os.path.join(BZR_REPO, x)
+ os.mkdir(d)
+ call(['bzr', 'init', '-q'], cwd=d)
+ call(['bzr', 'nick', '-q', x], cwd=d)
+ repo = guess(path=d, create_watcher=create_watcher)
+ self.assertEqual(repo.branch(), x)
+ self.assertFalse(repo.status())
+ if x == 'b1':
+ open(os.path.join(d, 'dirty'), 'w').close()
+ self.assertTrue(repo.status())
+ os.rename(os.path.join(BZR_REPO, 'b1'), os.path.join(BZR_REPO, 'b'))
+ os.rename(os.path.join(BZR_REPO, 'b2'), os.path.join(BZR_REPO, 'b1'))
+ os.rename(os.path.join(BZR_REPO, 'b'), os.path.join(BZR_REPO, 'b2'))
+ for x, y in (('b1', 'b2'), ('b2', 'b1')):
+ d = os.path.join(BZR_REPO, x)
+ repo = guess(path=d, create_watcher=create_watcher)
+ self.do_branch_rename_test(repo, y)
+ if x == 'b1':
+ self.assertFalse(repo.status())
+ else:
+ self.assertTrue(repo.status())
+
+ @classmethod
+ def setUpClass(cls):
+ cls.powerline_old_cwd = os.getcwd()
+ os.chdir(os.path.dirname(os.path.dirname(__file__)))
+ call(['git', 'init', '--quiet', GIT_REPO])
+ assert os.path.isdir(GIT_REPO)
+ call(['git', 'config', '--local', 'user.name', 'Foo'], cwd=GIT_REPO)
+ call(['git', 'config', '--local', 'user.email', 'bar@example.org'], cwd=GIT_REPO)
+ call(['git', 'commit', '--allow-empty', '--message', 'Initial commit', '--quiet'], cwd=GIT_REPO)
+ if use_mercurial:
+ cls.powerline_old_HGRCPATH = os.environ.get('HGRCPATH')
+ os.environ['HGRCPATH'] = ''
+ call(['hg', 'init', HG_REPO])
+ with open(os.path.join(HG_REPO, '.hg', 'hgrc'), 'w') as hgrc:
+ hgrc.write('[ui]\n')
+ hgrc.write('username = Foo <bar@example.org>\n')
+ if use_bzr:
+ call(['bzr', 'init', '--quiet', BZR_REPO])
+ call(['bzr', 'config', 'email=Foo <bar@example.org>'], cwd=BZR_REPO)
+ call(['bzr', 'config', 'nickname=test_powerline'], cwd=BZR_REPO)
+ call(['bzr', 'config', 'create_signatures=0'], cwd=BZR_REPO)
+
+ @classmethod
+ def tearDownClass(cls):
+ for repo_dir in [GIT_REPO] + ([HG_REPO] if use_mercurial else []) + ([BZR_REPO] if use_bzr else []):
+ shutil.rmtree(repo_dir)
+ if use_mercurial:
+ if cls.powerline_old_HGRCPATH is None:
+ os.environ.pop('HGRCPATH')
+ else:
+ os.environ['HGRCPATH'] = cls.powerline_old_HGRCPATH
+ os.chdir(cls.powerline_old_cwd)
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_lib_config.py b/tests/test_python/test_lib_config.py
new file mode 100644
index 0000000..053462a
--- /dev/null
+++ b/tests/test_python/test_lib_config.py
@@ -0,0 +1,52 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from powerline.lib.config import ConfigLoader
+
+from tests.modules import TestCase
+from tests.modules.lib.fsconfig import FSTree
+
+
+FILE_ROOT = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'cfglib')
+
+
+class LoadedList(list):
+ def pop_all(self):
+ try:
+ return self[:]
+ finally:
+ self[:] = ()
+
+
+loaded = LoadedList()
+
+
+def on_load(key):
+ loaded.append(key)
+
+
+def check_file(path):
+ if os.path.exists(path):
+ return path
+ else:
+ raise IOError
+
+
+class TestLoaderCondition(TestCase):
+ def test_update_missing(self):
+ loader = ConfigLoader(run_once=True)
+ fpath = os.path.join(FILE_ROOT, 'file.json')
+ self.assertRaises(IOError, loader.load, fpath)
+ loader.register_missing(check_file, on_load, fpath)
+ loader.update() # This line must not raise IOError
+ with FSTree({'file': {'test': 1}}, root=FILE_ROOT):
+ loader.update()
+ self.assertEqual(loader.load(fpath), {'test': 1})
+ self.assertEqual(loaded.pop_all(), [fpath])
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_listers.py b/tests/test_python/test_listers.py
new file mode 100644
index 0000000..a33f033
--- /dev/null
+++ b/tests/test_python/test_listers.py
@@ -0,0 +1,227 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import powerline.listers.i3wm as i3wm
+
+from tests.modules.lib import Args, replace_attr, Pl
+from tests.modules import TestCase
+
+
+class TestI3WM(TestCase):
+ @staticmethod
+ def get_workspaces():
+ return iter([
+ {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
+ {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
+ {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
+ {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
+ ])
+
+ @staticmethod
+ def get_outputs(pl):
+ return iter([
+ {'name': 'LVDS1'},
+ {'name': 'HDMI1'},
+ {'name': 'DVI01'},
+ ])
+
+ def test_output_lister(self):
+ pl = Pl()
+ with replace_attr(i3wm, 'get_connected_xrandr_outputs', self.get_outputs):
+ self.assertEqual(
+ list(i3wm.output_lister(pl=pl, segment_info={'a': 1})),
+ [
+ ({'a': 1, 'output': 'LVDS1'}, {'draw_inner_divider': None}),
+ ({'a': 1, 'output': 'HDMI1'}, {'draw_inner_divider': None}),
+ ({'a': 1, 'output': 'DVI01'}, {'draw_inner_divider': None}),
+ ]
+ )
+
+ def test_workspace_lister(self):
+ pl = Pl()
+ with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
+ self.assertEqual(
+ list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1})),
+ [
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '1: w1',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': False
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '2: w2',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'HDMI1',
+ 'workspace': {
+ 'name': '3: w3',
+ 'focused': False,
+ 'urgent': True,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'DVI01',
+ 'workspace': {
+ 'name': '4: w4',
+ 'focused': True,
+ 'urgent': True,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ]
+ )
+
+ self.assertEqual(
+ list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1}, output='LVDS1')),
+ [
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '1: w1',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': False
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '2: w2',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ]
+ )
+
+ self.assertEqual(
+ list(i3wm.workspace_lister(
+ pl=pl,
+ segment_info={'a': 1, 'output': 'LVDS1'}
+ )),
+ [
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '1: w1',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': False
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '2: w2',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ]
+ )
+
+ self.assertEqual(
+ list(i3wm.workspace_lister(
+ pl=pl,
+ segment_info={'a': 1, 'output': 'LVDS1'},
+ output=False
+ )),
+ [
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '1: w1',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': False
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'LVDS1',
+ 'workspace': {
+ 'name': '2: w2',
+ 'focused': False,
+ 'urgent': False,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'HDMI1',
+ 'workspace': {
+ 'name': '3: w3',
+ 'focused': False,
+ 'urgent': True,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'DVI01',
+ 'workspace': {
+ 'name': '4: w4',
+ 'focused': True,
+ 'urgent': True,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ]
+ )
+
+ self.assertEqual(
+ list(i3wm.workspace_lister(
+ pl=pl,
+ segment_info={'a': 1},
+ only_show=['focused', 'urgent']
+ )),
+ [
+ ({
+ 'a': 1,
+ 'output': 'HDMI1',
+ 'workspace': {
+ 'name': '3: w3',
+ 'focused': False,
+ 'urgent': True,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ({
+ 'a': 1,
+ 'output': 'DVI01',
+ 'workspace': {
+ 'name': '4: w4',
+ 'focused': True,
+ 'urgent': True,
+ 'visible': True
+ }
+ }, {'draw_inner_divider': None}),
+ ]
+ )
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_logging.py b/tests/test_python/test_logging.py
new file mode 100644
index 0000000..d7cfe4a
--- /dev/null
+++ b/tests/test_python/test_logging.py
@@ -0,0 +1,467 @@
+# vim:fileencoding=utf-8:noet
+
+'''Tests for various logging features'''
+
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import re
+import codecs
+import os
+
+from io import StringIO
+from shutil import rmtree
+
+from powerline import finish_common_config, create_logger
+
+from tests.modules import TestCase
+from tests.modules.lib import replace_attr
+
+
+TIMESTAMP_RE = r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3}'
+
+
+class TestRE(TestCase):
+ def assertMatches(self, text, regexp):
+ self.assertTrue(
+ re.match(regexp, text),
+ '{0!r} did not match {1!r}'.format(text, regexp),
+ )
+
+
+def close_handlers(logger):
+ for handler in logger.handlers:
+ handler.close()
+
+
+class TestHandlers(TestRE):
+ def test_stderr_handler_is_default(self):
+ out = StringIO()
+ err = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config)
+ pl.error('Foo')
+ close_handlers(logger)
+ self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_stream_override(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_explicit_none(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [None]})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_explicit_stream_handler(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', [[]]]]})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ self.assertEqual(stream.getvalue(), '')
+ self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_explicit_stream_handler_implicit_stream(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', []]]})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_file_handler(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ file_name = 'test_logging-test_file_handler'
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': file_name})
+ try:
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ with codecs.open(file_name, encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ finally:
+ os.unlink(file_name)
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_file_handler_create_dir(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ file_name = 'test_logging-test_file_handler_create_dir/file'
+
+ self.assertFalse(os.path.isdir(os.path.dirname(file_name)))
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': file_name})
+ try:
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ self.assertTrue(os.path.isdir(os.path.dirname(file_name)))
+ with codecs.open(file_name, encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ finally:
+ rmtree(os.path.dirname(file_name))
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_multiple_files(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ file_name_1 = 'test_logging-test_multiple_files-1'
+ file_name_2 = file_name_1[:-1] + '2'
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2]})
+ try:
+ try:
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ for file_name in (file_name_1, file_name_2):
+ with codecs.open(file_name, encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ finally:
+ os.unlink(file_name_1)
+ finally:
+ os.unlink(file_name_2)
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_multiple_files_and_stream(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ file_name_1 = 'test_logging-test_multiple_files_and_stream-1'
+ file_name_2 = file_name_1[:-1] + '2'
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2, None]})
+ try:
+ try:
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ for file_name in (file_name_1, file_name_2):
+ with codecs.open(file_name, encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ finally:
+ os.unlink(file_name_1)
+ finally:
+ os.unlink(file_name_2)
+ self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_handler_args(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ file_name = 'test_logging-test_handler_args'
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['RotatingFileHandler', [[file_name]]]
+ ]})
+ try:
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ close_handlers(logger)
+ with codecs.open(file_name, encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ finally:
+ os.unlink(file_name)
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_handler_args_kwargs(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ file_name = 'test_logging-test_handler_args_kwargs'
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['RotatingFileHandler', [[file_name], {'maxBytes': 1, 'backupCount': 1}]]
+ ]})
+ try:
+ try:
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.error('Foo')
+ pl.error('Bar')
+ close_handlers(logger)
+ with codecs.open(file_name, encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
+ with codecs.open(file_name + '.1', encoding='utf-8') as fp:
+ self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
+ finally:
+ os.unlink(file_name + '.1')
+ finally:
+ os.unlink(file_name)
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_logger_level(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ stream1 = StringIO()
+ stream2 = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['logging.StreamHandler', [[stream1]], 'WARNING'],
+ ['logging.StreamHandler', [[stream2]], 'ERROR'],
+ ]})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.warn('Foo')
+ pl.error('Bar')
+ close_handlers(logger)
+ self.assertMatches(stream1.getvalue(), (
+ '^' + TIMESTAMP_RE + ':WARNING:__unknown__:Foo\n'
+ + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$'
+ ))
+ self.assertMatches(stream2.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_logger_level_not_overriding_default(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ stream1 = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['logging.StreamHandler', [[stream1]], 'DEBUG'],
+ ]})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.debug('Foo')
+ pl.error('Bar')
+ close_handlers(logger)
+ self.assertMatches(stream1.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_top_log_level(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ stream1 = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['logging.StreamHandler', [[stream1]], 'DEBUG'],
+ ], 'log_level': 'DEBUG'})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.debug('Foo')
+ pl.error('Bar')
+ close_handlers(logger)
+ self.assertMatches(stream1.getvalue(), (
+ '^' + TIMESTAMP_RE + ':DEBUG:__unknown__:Foo\n'
+ + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$'
+ ))
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_logger_format(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ stream1 = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'],
+ ]})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.warn('Foo')
+ pl.error('Bar')
+ close_handlers(logger)
+ self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n')
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+ def test_top_log_format(self):
+ out = StringIO()
+ err = StringIO()
+ stream = StringIO()
+ stream1 = StringIO()
+ stream2 = StringIO()
+
+ with replace_attr(sys, 'stdout', out, 'stderr', err):
+ common_config = finish_common_config('utf-8', {'log_file': [
+ ['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'],
+ ['logging.StreamHandler', [[stream2]], 'WARNING'],
+ ], 'log_format': 'BAR'})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.warn('Foo')
+ pl.error('Bar')
+ close_handlers(logger)
+ self.assertEqual(stream2.getvalue(), 'BAR\nBAR\n')
+ self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n')
+ self.assertEqual(stream.getvalue(), '')
+ self.assertEqual(err.getvalue(), '')
+ self.assertEqual(out.getvalue(), '')
+
+
+class TestPowerlineLogger(TestRE):
+ def test_args_formatting(self):
+ stream = StringIO()
+
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.warn('foo {0}', 'Test')
+ pl.warn('bar {0!r}', 'Test')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), (
+ '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n'
+ + TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$'
+ ))
+
+ def test_prefix_formatting(self):
+ stream = StringIO()
+
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.prefix = '1'
+ pl.warn('foo')
+ pl.prefix = '2'
+ pl.warn('bar')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), (
+ '^' + TIMESTAMP_RE + ':WARNING:__unknown__:1:foo\n'
+ + TIMESTAMP_RE + ':WARNING:__unknown__:2:bar\n$'
+ ))
+
+ def test_kwargs_formatting(self):
+ stream = StringIO()
+
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.warn('foo {arg}', arg='Test')
+ pl.warn('bar {arg!r}', arg='Test')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), (
+ '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n'
+ + TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$'
+ ))
+
+ def test_args_kwargs_formatting(self):
+ stream = StringIO()
+
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.warn('foo {0!r} {arg}', 'Test0', arg='Test')
+ pl.warn('bar {0} {arg!r}', 'Test0', arg='Test')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), (
+ '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo u?\'Test0\' Test\n'
+ + TIMESTAMP_RE + ':WARNING:__unknown__:bar Test0 u?\'Test\'\n$'
+ ))
+
+ def test_exception_formatting(self):
+ stream = StringIO()
+
+ common_config = finish_common_config('utf-8', {})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ try:
+ raise ValueError('foo')
+ except ValueError:
+ pl.exception('Message')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), (
+ '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Message\n'
+ + 'Traceback \\(most recent call last\\):\n'
+ + '(?: File ".*?", line \\d+, in \\w+\n [^\n]*\n)+'
+ + 'ValueError: foo\n$'
+ ))
+
+ def test_levels(self):
+ stream = StringIO()
+
+ common_config = finish_common_config('utf-8', {'log_level': 'DEBUG'})
+ logger, pl, get_module_attr = create_logger(common_config, stream=stream)
+ pl.debug('1')
+ pl.info('2')
+ pl.warn('3')
+ pl.error('4')
+ pl.critical('5')
+ close_handlers(logger)
+ self.assertMatches(stream.getvalue(), (
+ '^' + TIMESTAMP_RE + ':DEBUG:__unknown__:1\n'
+ + TIMESTAMP_RE + ':INFO:__unknown__:2\n'
+ + TIMESTAMP_RE + ':WARNING:__unknown__:3\n'
+ + TIMESTAMP_RE + ':ERROR:__unknown__:4\n'
+ + TIMESTAMP_RE + ':CRITICAL:__unknown__:5\n$'
+ ))
+
+
+old_cwd = None
+
+
+def setUpModule():
+ global old_cwd
+ global __file__
+ old_cwd = os.getcwd()
+ __file__ = os.path.abspath(__file__)
+ os.chdir(os.path.dirname(os.path.dirname(__file__)))
+
+
+def tearDownModule():
+ global old_cwd
+ os.chdir(old_cwd)
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_provided_config_files.py b/tests/test_python/test_provided_config_files.py
new file mode 100644
index 0000000..fd8b16e
--- /dev/null
+++ b/tests/test_python/test_provided_config_files.py
@@ -0,0 +1,201 @@
+# vim:fileencoding=utf-8:noet
+
+'''Dynamic configuration files tests.'''
+
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+import json
+import logging
+
+import tests.modules.vim as vim_module
+
+from tests.modules.lib import Args, urllib_read, replace_attr
+from tests.modules import TestCase
+
+from powerline import NotInterceptedError
+from powerline.segments.common import wthr
+
+
+VBLOCK = chr(ord('V') - 0x40)
+SBLOCK = chr(ord('S') - 0x40)
+
+
+class FailingLogger(logging.Logger):
+ def exception(self, *args, **kwargs):
+ super(FailingLogger, self).exception(*args, **kwargs)
+ raise NotInterceptedError('Unexpected exception occurred')
+
+
+def get_logger(stream=None):
+ log_format = '%(asctime)s:%(levelname)s:%(message)s'
+ formatter = logging.Formatter(log_format)
+
+ level = logging.WARNING
+ handler = logging.StreamHandler(stream)
+ handler.setLevel(level)
+ handler.setFormatter(formatter)
+
+ logger = FailingLogger('powerline')
+ logger.setLevel(level)
+ logger.addHandler(handler)
+ return logger
+
+
+class TestVimConfig(TestCase):
+ def test_vim(self):
+ from powerline.vim import VimPowerline
+ cfg_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'powerline', 'config_files')
+ buffers = (
+ (('bufoptions',), {'buftype': 'help'}),
+ (('bufname', '[Command Line]'), {}),
+ (('bufoptions',), {'buftype': 'quickfix'}),
+ (('bufname', 'NERD_tree_1'), {}),
+ (('bufname', '__Gundo__'), {}),
+ (('bufname', '__Gundo_Preview__'), {}),
+ # No Command-T tests here: requires +ruby or emulation
+ # No tabline here: tablines are tested separately
+ )
+ with open(os.path.join(cfg_path, 'config.json'), 'r') as f:
+ local_themes_raw = json.load(f)['ext']['vim']['local_themes']
+ # Don’t run tests on external/plugin segments
+ local_themes = dict((k, v) for (k, v) in local_themes_raw.items())
+ # See end of the buffers definition above for `- 2`
+ self.assertEqual(len(buffers), len(local_themes) - 2)
+ outputs = {}
+ i = 0
+
+ with vim_module._with('split'):
+ with VimPowerline(logger=get_logger()) as powerline:
+ def check_output(mode, args, kwargs):
+ if mode == 'nc':
+ window = vim_module.windows[0]
+ window_id = 2
+ else:
+ vim_module._start_mode(mode)
+ window = vim_module.current.window
+ window_id = 1
+ winnr = window.number
+ out = powerline.render(window, window_id, winnr)
+ if out in outputs:
+ self.fail('Duplicate in set #{0} ({1}) for mode {2!r} (previously defined in set #{3} ({4!r}) for mode {5!r})'.format(i, (args, kwargs), mode, *outputs[out]))
+ outputs[out] = (i, (args, kwargs), mode)
+
+ with vim_module._with('bufname', '/tmp/foo.txt'):
+ out = powerline.render(vim_module.current.window, 1, vim_module.current.window.number, is_tabline=True)
+ outputs[out] = (-1, (None, None), 'tab')
+ with vim_module._with('globals', powerline_config_paths=[cfg_path]):
+ exclude = set(('no', 'v', 'V', VBLOCK, 's', 'S', SBLOCK, 'R', 'Rv', 'c', 'cv', 'ce', 'r', 'rm', 'r?', '!'))
+ try:
+ for mode in ['n', 'nc', 'no', 'v', 'V', VBLOCK, 's', 'S', SBLOCK, 'i', 'R', 'Rv', 'c', 'cv', 'ce', 'r', 'rm', 'r?', '!']:
+ check_output(mode, None, None)
+ for args, kwargs in buffers:
+ i += 1
+ if mode in exclude:
+ continue
+ with vim_module._with(*args, **kwargs):
+ check_output(mode, args, kwargs)
+ finally:
+ vim_module._start_mode('n')
+
+ @classmethod
+ def setUpClass(cls):
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path')))
+
+ @classmethod
+ def tearDownClass(cls):
+ sys.path.pop(0)
+
+
+class TestConfig(TestCase):
+ def test_tmux(self):
+ from powerline.segments import common
+ from imp import reload
+ reload(common)
+ from powerline.shell import ShellPowerline
+ with replace_attr(common, 'urllib_read', urllib_read):
+ with ShellPowerline(Args(ext=['tmux']), logger=get_logger(), run_once=False) as powerline:
+ powerline.render()
+ with ShellPowerline(Args(ext=['tmux']), logger=get_logger(), run_once=False) as powerline:
+ powerline.render()
+
+ def test_zsh(self):
+ from powerline.shell import ShellPowerline
+ args = Args(last_pipe_status=[1, 0], jobnum=0, ext=['shell'], renderer_module='.zsh')
+ segment_info = {'args': args}
+ with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline:
+ powerline.render(segment_info=segment_info)
+ with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline:
+ powerline.render(segment_info=segment_info)
+ segment_info['local_theme'] = 'select'
+ with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline:
+ powerline.render(segment_info=segment_info)
+ segment_info['local_theme'] = 'continuation'
+ segment_info['parser_state'] = 'if cmdsubst'
+ with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline:
+ powerline.render(segment_info=segment_info)
+
+ def test_bash(self):
+ from powerline.shell import ShellPowerline
+ args = Args(last_exit_code=1, last_pipe_status=[], jobnum=0, ext=['shell'], renderer_module='.bash', config_override={'ext': {'shell': {'theme': 'default_leftonly'}}})
+ with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline:
+ powerline.render(segment_info={'args': args})
+ with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline:
+ powerline.render(segment_info={'args': args})
+
+ def test_ipython(self):
+ from powerline.ipython import IPythonPowerline
+
+ class IpyPowerline(IPythonPowerline):
+ config_paths = None
+ config_overrides = None
+ theme_overrides = {}
+
+ segment_info = Args(prompt_count=1)
+
+ with IpyPowerline(logger=get_logger(), renderer_module='.pre_5') as powerline:
+ for prompt_type in ['in', 'in2']:
+ powerline.render(is_prompt=True, matcher_info=prompt_type, segment_info=segment_info)
+ powerline.render(is_prompt=True, matcher_info=prompt_type, segment_info=segment_info)
+ with IpyPowerline(logger=get_logger(), renderer_module='.pre_5') as powerline:
+ for prompt_type in ['out', 'rewrite']:
+ powerline.render(is_prompt=False, matcher_info=prompt_type, segment_info=segment_info)
+ powerline.render(is_prompt=False, matcher_info=prompt_type, segment_info=segment_info)
+
+ def test_wm(self):
+ from powerline.segments import common
+ from imp import reload
+ reload(common)
+ from powerline import Powerline
+ with replace_attr(wthr, 'urllib_read', urllib_read):
+ Powerline(logger=get_logger(), ext='wm', renderer_module='pango_markup', run_once=True).render()
+ reload(common)
+
+
+old_cwd = None
+saved_get_config_paths = None
+
+
+def setUpModule():
+ global old_cwd
+ global saved_get_config_paths
+ import powerline
+ saved_get_config_paths = powerline.get_config_paths
+ path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'powerline', 'config_files')
+ powerline.get_config_paths = lambda: [path]
+ old_cwd = os.getcwd()
+
+
+def tearDownModule():
+ global old_cwd
+ global saved_get_config_paths
+ import powerline
+ powerline.get_config_paths = saved_get_config_paths
+ os.chdir(old_cwd)
+ old_cwd = None
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py
new file mode 100644
index 0000000..3f09470
--- /dev/null
+++ b/tests/test_python/test_segments.py
@@ -0,0 +1,1711 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+
+from functools import partial
+from collections import namedtuple
+from time import sleep
+from platform import python_implementation
+
+from powerline.segments import shell, tmux, pdb, i3wm
+from powerline.lib.vcs import get_fallback_create_watcher
+from powerline.lib.unicode import out_u
+
+import tests.modules.vim as vim_module
+
+from tests.modules.lib import (Args, urllib_read, replace_attr, new_module,
+ replace_module_module, replace_env, Pl)
+from tests.modules import TestCase, SkipTest
+
+
+def get_dummy_guess(**kwargs):
+ if 'directory' in kwargs:
+ def guess(path, create_watcher):
+ return Args(branch=lambda: out_u(os.path.basename(path)), **kwargs)
+ else:
+ def guess(path, create_watcher):
+ return Args(branch=lambda: out_u(os.path.basename(path)), directory=path, **kwargs)
+ return guess
+
+
+class TestShell(TestCase):
+ def test_last_status(self):
+ pl = Pl()
+ segment_info = {'args': Args(last_exit_code=10)}
+ self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), [
+ {'contents': '10', 'highlight_groups': ['exit_fail']}
+ ])
+ segment_info['args'].last_exit_code = 0
+ self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), None)
+ segment_info['args'].last_exit_code = None
+ self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), None)
+ segment_info['args'].last_exit_code = 'sigsegv'
+ self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), [
+ {'contents': 'sigsegv', 'highlight_groups': ['exit_fail']}
+ ])
+ segment_info['args'].last_exit_code = 'sigsegv+core'
+ self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), [
+ {'contents': 'sigsegv+core', 'highlight_groups': ['exit_fail']}
+ ])
+
+ def test_last_pipe_status(self):
+ pl = Pl()
+ segment_info = {'args': Args(last_pipe_status=[], last_exit_code=0)}
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None)
+ segment_info['args'].last_pipe_status = [0, 0, 0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None)
+ segment_info['args'].last_pipe_status = [0, 0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None)
+ segment_info['args'].last_pipe_status = [0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None)
+ segment_info['args'].last_pipe_status = [0, 2, 0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ ])
+ segment_info['args'].last_pipe_status = [2, 0, 0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ ])
+ segment_info['args'].last_pipe_status = [0, 0, 2]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ ])
+ segment_info['args'].last_pipe_status = [2]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ ])
+ segment_info['args'].last_pipe_status = [0, 'sigsegv', 'sigsegv+core']
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': 'sigsegv', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ {'contents': 'sigsegv+core', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}
+ ])
+ segment_info['args'].last_pipe_status = [0, 'sigsegv', 0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': 'sigsegv', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}
+ ])
+ segment_info['args'].last_pipe_status = [0, 'sigsegv+core', 0]
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True},
+ {'contents': 'sigsegv+core', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}
+ ])
+ segment_info['args'].last_pipe_status = []
+ segment_info['args'].last_exit_code = 5
+ self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [
+ {'contents': '5', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True},
+ ])
+
+ def test_jobnum(self):
+ pl = Pl()
+ segment_info = {'args': Args(jobnum=0)}
+ self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info), None)
+ self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=False), None)
+ self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=True), '0')
+ segment_info = {'args': Args(jobnum=1)}
+ self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info), '1')
+ self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=False), '1')
+ self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=True), '1')
+
+ def test_continuation(self):
+ pl = Pl()
+ self.assertEqual(shell.continuation(pl=pl, segment_info={}), [{
+ 'contents': '',
+ 'width': 'auto',
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ }])
+ segment_info = {'parser_state': 'if cmdsubst'}
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info), [
+ {
+ 'contents': 'if',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'l',
+ },
+ ])
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, right_align=True), [
+ {
+ 'contents': 'if',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'r',
+ },
+ ])
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=False), [
+ {
+ 'contents': 'if',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation'],
+ },
+ {
+ 'contents': 'cmdsubst',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'l',
+ },
+ ])
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=False, right_align=True), [
+ {
+ 'contents': 'if',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation'],
+ 'width': 'auto',
+ 'align': 'r',
+ },
+ {
+ 'contents': 'cmdsubst',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ },
+ ])
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=True, right_align=True), [
+ {
+ 'contents': 'if',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'r',
+ },
+ ])
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=True, right_align=True, renames={'if': 'IF'}), [
+ {
+ 'contents': 'IF',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'r',
+ },
+ ])
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=True, right_align=True, renames={'if': None}), [
+ {
+ 'contents': '',
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'r',
+ },
+ ])
+ segment_info = {'parser_state': 'then then then cmdsubst'}
+ self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info), [
+ {
+ 'contents': 'then',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation'],
+ },
+ {
+ 'contents': 'then',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation'],
+ },
+ {
+ 'contents': 'then',
+ 'draw_inner_divider': True,
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ 'width': 'auto',
+ 'align': 'l',
+ },
+ ])
+
+ def test_cwd(self):
+ new_os = new_module('os', path=os.path, sep='/')
+ pl = Pl()
+ cwd = [None]
+
+ def getcwd():
+ wd = cwd[0]
+ if isinstance(wd, Exception):
+ raise wd
+ else:
+ return wd
+
+ segment_info = {'getcwd': getcwd, 'home': None}
+ with replace_attr(shell, 'os', new_os):
+ cwd[0] = '/abc/def/ghi/foo/bar'
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'abc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'def', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ segment_info['home'] = '/abc/def/ghi'
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ segment_info.update(shortened_path='~foo/ghi')
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info), [
+ {'contents': '~foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_shortened_path=False), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ segment_info.pop('shortened_path')
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3, shorten_home=False), [
+ {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1), [
+ {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis='---'), [
+ {'contents': '---', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis=None), [
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True), [
+ {'contents': '.../', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis='---'), [
+ {'contents': '---/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis=None), [
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'fo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2, use_path_separator=True), [
+ {'contents': '~/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'fo/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ cwd[0] = '/etc'
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ cwd[0] = '/'
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ ose = OSError()
+ ose.errno = 2
+ cwd[0] = ose
+ self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [
+ {'contents': '[not found]', 'divider_highlight_group': 'cwd:divider', 'highlight_groups': ['cwd:current_folder', 'cwd'], 'draw_inner_divider': True}
+ ])
+ cwd[0] = OSError()
+ self.assertRaises(OSError, shell.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2)
+ cwd[0] = ValueError()
+ self.assertRaises(ValueError, shell.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2)
+
+
+class TestTmux(TestCase):
+ def test_attached_clients(self):
+ def get_tmux_output(pl, cmd, *args):
+ if cmd == 'list-panes':
+ return 'session_name\n'
+ elif cmd == 'list-clients':
+ return '/dev/pts/2: 0 [191x51 xterm-256color] (utf8)\n/dev/pts/3: 0 [191x51 xterm-256color] (utf8)'
+
+ pl = Pl()
+ with replace_attr(tmux, 'get_tmux_output', get_tmux_output):
+ self.assertEqual(tmux.attached_clients(pl=pl), '2')
+ self.assertEqual(tmux.attached_clients(pl=pl, minimum=3), None)
+
+
+class TestCommon(TestCase):
+ @classmethod
+ def setUpClass(cls):
+ module = __import__(str('powerline.segments.common.{0}'.format(cls.module_name)))
+ cls.module = getattr(module.segments.common, str(cls.module_name))
+
+
+class TestNet(TestCommon):
+ module_name = 'net'
+
+ def test_hostname(self):
+ pl = Pl()
+ with replace_env('SSH_CLIENT', '192.168.0.12 40921 22') as segment_info:
+ with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc'):
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True), 'abc')
+ with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc.mydomain'):
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc.mydomain')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, exclude_domain=True), 'abc')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True), 'abc.mydomain')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True, exclude_domain=True), 'abc')
+ segment_info['environ'].pop('SSH_CLIENT')
+ with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc'):
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True), None)
+ with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc.mydomain'):
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc.mydomain')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, exclude_domain=True), 'abc')
+ self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True, exclude_domain=True), None)
+
+ def test_external_ip(self):
+ pl = Pl()
+ with replace_attr(self.module, 'urllib_read', urllib_read):
+ self.assertEqual(self.module.external_ip(pl=pl), [{'contents': '127.0.0.1', 'divider_highlight_group': 'background:divider'}])
+
+ def test_internal_ip(self):
+ try:
+ import netifaces
+ except ImportError:
+ raise SkipTest('netifaces module is not available')
+ pl = Pl()
+ addr = {
+ 'enp2s0': {
+ netifaces.AF_INET: [{'addr': '192.168.100.200'}],
+ netifaces.AF_INET6: [{'addr': 'feff::5446:5eff:fe5a:7777%enp2s0'}]
+ },
+ 'lo': {
+ netifaces.AF_INET: [{'addr': '127.0.0.1'}],
+ netifaces.AF_INET6: [{'addr': '::1'}]
+ },
+ 'teredo': {
+ netifaces.AF_INET6: [{'addr': 'feff::5446:5eff:fe5a:7777'}]
+ },
+ }
+ interfaces = ['lo', 'enp2s0', 'teredo']
+ with replace_module_module(
+ self.module, 'netifaces',
+ interfaces=(lambda: interfaces),
+ ifaddresses=(lambda interface: addr[interface]),
+ AF_INET=netifaces.AF_INET,
+ AF_INET6=netifaces.AF_INET6,
+ ):
+ self.assertEqual(self.module.internal_ip(pl=pl), '192.168.100.200')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='auto'), '192.168.100.200')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='lo'), '127.0.0.1')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='teredo'), None)
+ self.assertEqual(self.module.internal_ip(pl=pl, ipv=4), '192.168.100.200')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='auto', ipv=4), '192.168.100.200')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='lo', ipv=4), '127.0.0.1')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='teredo', ipv=4), None)
+ self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), 'feff::5446:5eff:fe5a:7777%enp2s0')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='auto', ipv=6), 'feff::5446:5eff:fe5a:7777%enp2s0')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='lo', ipv=6), '::1')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='teredo', ipv=6), 'feff::5446:5eff:fe5a:7777')
+ interfaces[1:2] = ()
+ self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), 'feff::5446:5eff:fe5a:7777')
+ interfaces[1:2] = ()
+ self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), '::1')
+ interfaces[:] = ()
+ self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), None)
+
+ gateways = {
+ 'default': {
+ netifaces.AF_INET: ('192.168.100.1', 'enp2s0'),
+ netifaces.AF_INET6: ('feff::5446:5eff:fe5a:0001', 'enp2s0')
+ }
+ }
+
+ with replace_module_module(
+ self.module, 'netifaces',
+ interfaces=(lambda: interfaces),
+ ifaddresses=(lambda interface: addr[interface]),
+ gateways=(lambda: gateways),
+ AF_INET=netifaces.AF_INET,
+ AF_INET6=netifaces.AF_INET6,
+ ):
+ # default gateway has specified address family
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=4), '192.168.100.200')
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=6), 'feff::5446:5eff:fe5a:7777%enp2s0')
+ # default gateway doesn't have specified address family
+ gateways['default'] = {}
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=4), None)
+ self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=6), None)
+
+ def test_network_load(self):
+ def gb(interface):
+ return None
+
+ f = [gb]
+
+ def _get_bytes(interface):
+ return f[0](interface)
+
+ pl = Pl()
+
+ with replace_attr(self.module, '_get_bytes', _get_bytes):
+ self.module.network_load.startup(pl=pl)
+ try:
+ self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), None)
+ sleep(self.module.network_load.interval)
+ self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), None)
+ while 'prev' not in self.module.network_load.interfaces.get('eth0', {}):
+ sleep(0.1)
+ self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), None)
+
+ l = [0, 0]
+
+ def gb2(interface):
+ l[0] += 1200
+ l[1] += 2400
+ return tuple(l)
+ f[0] = gb2
+
+ while not self.module.network_load.interfaces.get('eth0', {}).get('prev', (None, None))[1]:
+ sleep(0.1)
+ self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), [
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'DL 1 KiB/s', 'highlight_groups': ['network_load_recv', 'network_load']},
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'UL 2 KiB/s', 'highlight_groups': ['network_load_sent', 'network_load']},
+ ])
+ self.assertEqual(self.module.network_load(pl=pl, interface='eth0', recv_format='r {value}', sent_format='s {value}'), [
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 KiB/s', 'highlight_groups': ['network_load_recv', 'network_load']},
+ {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 KiB/s', 'highlight_groups': ['network_load_sent', 'network_load']},
+ ])
+ self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', suffix='bps', interface='eth0'), [
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 Kibps', 'highlight_groups': ['network_load_recv', 'network_load']},
+ {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 Kibps', 'highlight_groups': ['network_load_sent', 'network_load']},
+ ])
+ self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', si_prefix=True, interface='eth0'), [
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 kB/s', 'highlight_groups': ['network_load_recv', 'network_load']},
+ {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 kB/s', 'highlight_groups': ['network_load_sent', 'network_load']},
+ ])
+ self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', recv_max=0, interface='eth0'), [
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 KiB/s', 'highlight_groups': ['network_load_recv_gradient', 'network_load_gradient', 'network_load_recv', 'network_load'], 'gradient_level': 100},
+ {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 KiB/s', 'highlight_groups': ['network_load_sent', 'network_load']},
+ ])
+
+ class ApproxEqual(object):
+ def __eq__(self, i):
+ return abs(i - 50.0) < 1
+
+ self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', sent_max=4800, interface='eth0'), [
+ {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 KiB/s', 'highlight_groups': ['network_load_recv', 'network_load']},
+ {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 KiB/s', 'highlight_groups': ['network_load_sent_gradient', 'network_load_gradient', 'network_load_sent', 'network_load'], 'gradient_level': ApproxEqual()},
+ ])
+ finally:
+ self.module.network_load.shutdown()
+
+
+class TestEnv(TestCommon):
+ module_name = 'env'
+
+ def test_user(self):
+ new_os = new_module('os', getpid=lambda: 1)
+
+ class Process(object):
+ def __init__(self, pid):
+ pass
+
+ def username(self):
+ return 'def@DOMAIN.COM'
+
+ if hasattr(self.module, 'psutil') and not callable(self.module.psutil.Process.username):
+ username = property(username)
+
+ segment_info = {'environ': {}}
+
+ def user(*args, **kwargs):
+ return self.module.user(pl=pl, segment_info=segment_info, *args, **kwargs)
+
+ struct_passwd = namedtuple('struct_passwd', ('pw_name',))
+ new_psutil = new_module('psutil', Process=Process)
+ new_pwd = new_module('pwd', getpwuid=lambda uid: struct_passwd(pw_name='def@DOMAIN.COM'))
+ new_getpass = new_module('getpass', getuser=lambda: 'def@DOMAIN.COM')
+ pl = Pl()
+ with replace_attr(self.module, 'pwd', new_pwd):
+ with replace_attr(self.module, 'getpass', new_getpass):
+ with replace_attr(self.module, 'os', new_os):
+ with replace_attr(self.module, 'psutil', new_psutil):
+ with replace_attr(self.module, '_geteuid', lambda: 5):
+ self.assertEqual(user(), [
+ {'contents': 'def@DOMAIN.COM', 'highlight_groups': ['user']}
+ ])
+ self.assertEqual(user(hide_user='abc'), [
+ {'contents': 'def@DOMAIN.COM', 'highlight_groups': ['user']}
+ ])
+ self.assertEqual(user(hide_domain=False), [
+ {'contents': 'def@DOMAIN.COM', 'highlight_groups': ['user']}
+ ])
+ self.assertEqual(user(hide_user='def@DOMAIN.COM'), None)
+ self.assertEqual(user(hide_domain=True), [
+ {'contents': 'def', 'highlight_groups': ['user']}
+ ])
+ with replace_attr(self.module, '_geteuid', lambda: 0):
+ self.assertEqual(user(), [
+ {'contents': 'def', 'highlight_groups': ['superuser', 'user']}
+ ])
+
+ def test_cwd(self):
+ new_os = new_module('os', path=os.path, sep='/')
+ pl = Pl()
+ cwd = [None]
+
+ def getcwd():
+ wd = cwd[0]
+ if isinstance(wd, Exception):
+ raise wd
+ else:
+ return wd
+
+ segment_info = {'getcwd': getcwd, 'home': None}
+ with replace_attr(self.module, 'os', new_os):
+ cwd[0] = '/abc/def/ghi/foo/bar'
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'abc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'def', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ segment_info['home'] = '/abc/def/ghi'
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3, shorten_home=False), [
+ {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1), [
+ {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis='---'), [
+ {'contents': '---', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis=None), [
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True), [
+ {'contents': '.../', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis='---'), [
+ {'contents': '---/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis=None), [
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [
+ {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'fo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2, use_path_separator=True), [
+ {'contents': '~/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'fo/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}
+ ])
+ cwd[0] = '/etc'
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True},
+ {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False},
+ {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ cwd[0] = '/'
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [
+ {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']},
+ ])
+ ose = OSError()
+ ose.errno = 2
+ cwd[0] = ose
+ self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [
+ {'contents': '[not found]', 'divider_highlight_group': 'cwd:divider', 'highlight_groups': ['cwd:current_folder', 'cwd'], 'draw_inner_divider': True}
+ ])
+ cwd[0] = OSError()
+ self.assertRaises(OSError, self.module.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2)
+ cwd[0] = ValueError()
+ self.assertRaises(ValueError, self.module.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2)
+
+ def test_virtualenv(self):
+ pl = Pl()
+ with replace_env('VIRTUAL_ENV', '/abc/def/ghi') as segment_info:
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'ghi')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None)
+
+ segment_info['environ'].pop('VIRTUAL_ENV')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None)
+
+ with replace_env('CONDA_DEFAULT_ENV', 'foo') as segment_info:
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'foo')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), 'foo')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None)
+
+ segment_info['environ'].pop('CONDA_DEFAULT_ENV')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None)
+
+ with replace_env('CONDA_DEFAULT_ENV', 'foo', environ={'VIRTUAL_ENV': '/sbc/def/ghi'}) as segment_info:
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'ghi')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), 'foo')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None)
+
+ segment_info['environ'].pop('CONDA_DEFAULT_ENV')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'ghi')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi')
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None)
+ self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None)
+
+ def test_environment(self):
+ pl = Pl()
+ variable = 'FOO'
+ value = 'bar'
+ with replace_env(variable, value) as segment_info:
+ self.assertEqual(self.module.environment(pl=pl, segment_info=segment_info, variable=variable), value)
+ segment_info['environ'].pop(variable)
+ self.assertEqual(self.module.environment(pl=pl, segment_info=segment_info, variable=variable), None)
+
+
+class TestVcs(TestCommon):
+ module_name = 'vcs'
+
+ def test_branch(self):
+ pl = Pl()
+ create_watcher = get_fallback_create_watcher()
+ segment_info = {'getcwd': os.getcwd}
+ branch = partial(self.module.branch, pl=pl, create_watcher=create_watcher)
+ with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: None, directory='/tmp/tests')):
+ with replace_attr(self.module, 'tree_status', lambda repo, pl: None):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{
+ 'highlight_groups': ['branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{
+ 'contents': 'tests',
+ 'highlight_groups': ['branch_clean', 'branch'],
+ 'divider_highlight_group': None
+ }])
+ with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: 'D ', directory='/tmp/tests')):
+ with replace_attr(self.module, 'tree_status', lambda repo, pl: 'D '):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{
+ 'highlight_groups': ['branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{
+ 'contents': 'tests',
+ 'highlight_groups': ['branch_dirty', 'branch'],
+ 'divider_highlight_group': None
+ }])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{
+ 'highlight_groups': ['branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+ with replace_attr(self.module, 'guess', lambda path, create_watcher: None):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False), None)
+ with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: 'U')):
+ with replace_attr(self.module, 'tree_status', lambda repo, pl: 'U'):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False, ignore_statuses=['U']), [{
+ 'highlight_groups': ['branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['DU']), [{
+ 'highlight_groups': ['branch_dirty', 'branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{
+ 'highlight_groups': ['branch_dirty', 'branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['U']), [{
+ 'highlight_groups': ['branch_clean', 'branch'],
+ 'contents': 'tests',
+ 'divider_highlight_group': None
+ }])
+
+ def test_stash(self):
+ pl = Pl()
+ create_watcher = get_fallback_create_watcher()
+ stash = partial(self.module.stash, pl=pl, create_watcher=create_watcher, segment_info={'getcwd': os.getcwd})
+
+ def forge_stash(n):
+ return replace_attr(self.module, 'guess', get_dummy_guess(stash=lambda: n, directory='/tmp/tests'))
+
+ with forge_stash(0):
+ self.assertEqual(stash(), None)
+ with forge_stash(1):
+ self.assertEqual(stash(), [{
+ 'highlight_groups': ['stash'],
+ 'contents': '1',
+ 'divider_highlight_group': None
+ }])
+ with forge_stash(2):
+ self.assertEqual(stash(), [{
+ 'highlight_groups': ['stash'],
+ 'contents': '2',
+ 'divider_highlight_group': None
+ }])
+
+
+class TestTime(TestCommon):
+ module_name = 'time'
+
+ def test_date(self):
+ pl = Pl()
+ with replace_attr(self.module, 'datetime', Args(now=lambda: Args(strftime=lambda fmt: fmt))):
+ self.assertEqual(self.module.date(pl=pl), [{'contents': '%Y-%m-%d', 'highlight_groups': ['date'], 'divider_highlight_group': None}])
+ self.assertEqual(self.module.date(pl=pl, format='%H:%M', istime=True), [{'contents': '%H:%M', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}])
+ unicode_date = self.module.date(pl=pl, format='\u231a', istime=True)
+ expected_unicode_date = [{'contents': '\u231a', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}]
+ if python_implementation() == 'PyPy' and sys.version_info >= (3,):
+ if unicode_date != expected_unicode_date:
+ raise SkipTest('Dates do not match, see https://bitbucket.org/pypy/pypy/issues/2161/pypy3-strftime-does-not-accept-unicode')
+ self.assertEqual(unicode_date, expected_unicode_date)
+
+ def test_fuzzy_time(self):
+ time = Args(hour=0, minute=45)
+ pl = Pl()
+ with replace_attr(self.module, 'datetime', Args(now=lambda: time)):
+ self.assertEqual(self.module.fuzzy_time(pl=pl), 'quarter to one')
+ time.hour = 23
+ time.minute = 59
+ self.assertEqual(self.module.fuzzy_time(pl=pl), 'round about midnight')
+ time.minute = 33
+ self.assertEqual(self.module.fuzzy_time(pl=pl), 'twenty-five to twelve')
+ time.minute = 60
+ self.assertEqual(self.module.fuzzy_time(pl=pl), 'twelve o\'clock')
+ time.minute = 33
+ self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twenty-five to twelve')
+ time.minute = 60
+ self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twelve o\'clock')
+ time.minute = 33
+ self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twenty‐five to twelve')
+ time.minute = 60
+ self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twelve o’clock')
+
+
+class TestSys(TestCommon):
+ module_name = 'sys'
+
+ def test_uptime(self):
+ pl = Pl()
+ with replace_attr(self.module, '_get_uptime', lambda: 259200):
+ self.assertEqual(self.module.uptime(pl=pl), [{'contents': '3d', 'divider_highlight_group': 'background:divider'}])
+ with replace_attr(self.module, '_get_uptime', lambda: 93784):
+ self.assertEqual(self.module.uptime(pl=pl), [{'contents': '1d 2h 3m', 'divider_highlight_group': 'background:divider'}])
+ self.assertEqual(self.module.uptime(pl=pl, shorten_len=4), [{'contents': '1d 2h 3m 4s', 'divider_highlight_group': 'background:divider'}])
+ with replace_attr(self.module, '_get_uptime', lambda: 65536):
+ self.assertEqual(self.module.uptime(pl=pl), [{'contents': '18h 12m 16s', 'divider_highlight_group': 'background:divider'}])
+ self.assertEqual(self.module.uptime(pl=pl, shorten_len=2), [{'contents': '18h 12m', 'divider_highlight_group': 'background:divider'}])
+ self.assertEqual(self.module.uptime(pl=pl, shorten_len=1), [{'contents': '18h', 'divider_highlight_group': 'background:divider'}])
+
+ def _get_uptime():
+ raise NotImplementedError
+
+ with replace_attr(self.module, '_get_uptime', _get_uptime):
+ self.assertEqual(self.module.uptime(pl=pl), None)
+
+ def test_system_load(self):
+ pl = Pl()
+ with replace_module_module(self.module, 'os', getloadavg=lambda: (7.5, 3.5, 1.5)):
+ with replace_attr(self.module, '_cpu_count', lambda: 2):
+ self.assertEqual(self.module.system_load(pl=pl), [
+ {'contents': '7.5 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
+ {'contents': '3.5 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0},
+ {'contents': '1.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 0}
+ ])
+ self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1), [
+ {'contents': '8 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
+ {'contents': '4 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
+ {'contents': '2', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}
+ ])
+ self.assertEqual(self.module.system_load(pl=pl, short=True), [
+ {'contents': '7.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
+ ])
+ self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1, short=True), [
+ {'contents': '8', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
+ ])
+
+ def test_cpu_load_percent(self):
+ try:
+ __import__('psutil')
+ except ImportError as e:
+ raise SkipTest('Failed to import psutil: {0}'.format(e))
+ pl = Pl()
+ with replace_module_module(self.module, 'psutil', cpu_percent=lambda **kwargs: 52.3):
+ self.assertEqual(self.module.cpu_load_percent(pl=pl), [{
+ 'contents': '52%',
+ 'gradient_level': 52.3,
+ 'highlight_groups': ['cpu_load_percent_gradient', 'cpu_load_percent'],
+ }])
+ self.assertEqual(self.module.cpu_load_percent(pl=pl, format='{0:.1f}%'), [{
+ 'contents': '52.3%',
+ 'gradient_level': 52.3,
+ 'highlight_groups': ['cpu_load_percent_gradient', 'cpu_load_percent'],
+ }])
+
+
+class TestWthr(TestCommon):
+ module_name = 'wthr'
+
+ def test_weather(self):
+ pl = Pl()
+ with replace_attr(self.module, 'urllib_read', urllib_read):
+ self.assertEqual(self.module.weather(pl=pl), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, temp_coldest=0, temp_hottest=100), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 14.0}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, temp_coldest=-100, temp_hottest=-50), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 100}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, icons={'blustery': 'o'}), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'o '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, icons={'windy': 'x'}), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'x '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, unit='F'), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '57°F', 'gradient_level': 62.857142857142854}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, unit='K'), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '287K', 'gradient_level': 62.857142857142854}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, temp_format='{temp:.1e}C'), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '1.4e+01C', 'gradient_level': 62.857142857142854}
+ ])
+ with replace_attr(self.module, 'urllib_read', urllib_read):
+ self.module.weather.startup(pl=pl, location_query='Meppen,06,DE')
+ self.assertEqual(self.module.weather(pl=pl), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854}
+ ])
+ self.assertEqual(self.module.weather(pl=pl, location_query='Moscow,RU'), [
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_fair_night', 'weather_condition_night', 'weather_conditions', 'weather'], 'contents': 'NIGHT '},
+ {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '9°C', 'gradient_level': 55.714285714285715}
+ ])
+ self.module.weather.shutdown()
+
+
+class TestI3WM(TestCase):
+ @staticmethod
+ def get_workspaces():
+ return iter([
+ {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
+ {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
+ {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
+ {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
+ ])
+
+ def test_workspaces(self):
+ pl = Pl()
+ with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
+ segment_info = {}
+
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [
+ {'contents': '1: w1', 'highlight_groups': ['workspace']},
+ {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
+ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [
+ {'contents': '1: w1', 'highlight_groups': ['workspace']},
+ {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
+ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [
+ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [
+ {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
+ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
+ {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
+ {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ {'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
+ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [
+ {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
+ ])
+ segment_info['output'] = 'LVDS1'
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
+ {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
+ {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
+ ])
+
+ def test_workspace(self):
+ pl = Pl()
+ with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
+ segment_info = {}
+
+ self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [
+ {'contents': '1: w1', 'highlight_groups': ['workspace']},
+ ])
+ self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [
+ {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None)
+ self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ segment_info['workspace'] = next(self.get_workspaces())
+ self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [
+ {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
+ ])
+ self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [
+ {'contents': 'w1', 'highlight_groups': ['workspace']},
+ ])
+
+ def test_mode(self):
+ pl = Pl()
+ self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}), None)
+ self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}), 'test')
+ self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}, names={'default': 'test'}), 'test')
+ self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}, names={'default': 'test', 'test': 't'}), 't')
+
+ def test_scratchpad(self):
+ class Conn(object):
+ def get_tree(self):
+ return self
+
+ def descendents(self):
+ nodes_unfocused = [Args(focused = False)]
+ nodes_focused = [Args(focused = True)]
+
+ workspace_scratch = lambda: Args(name='__i3_scratch')
+ workspace_noscratch = lambda: Args(name='2: www')
+ return [
+ Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
+ Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused),
+ Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
+ Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused),
+ Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused),
+ Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused),
+ ]
+
+ pl = Pl()
+ with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()):
+ self.assertEqual(i3wm.scratchpad(pl=pl), [
+ {'contents': 'O', 'highlight_groups': ['scratchpad']},
+ {'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']},
+ {'contents': 'O', 'highlight_groups': ['scratchpad']},
+ {'contents': 'X', 'highlight_groups': ['scratchpad:visible', 'scratchpad']},
+ {'contents': 'O', 'highlight_groups': ['scratchpad:focused', 'scratchpad']},
+ {'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']},
+ ])
+ self.assertEqual(i3wm.scratchpad(pl=pl, icons={'changed': '-', 'fresh': 'o'}), [
+ {'contents': 'o', 'highlight_groups': ['scratchpad']},
+ {'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']},
+ {'contents': 'o', 'highlight_groups': ['scratchpad']},
+ {'contents': '-', 'highlight_groups': ['scratchpad:visible', 'scratchpad']},
+ {'contents': 'o', 'highlight_groups': ['scratchpad:focused', 'scratchpad']},
+ {'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']},
+ ])
+
+
+class TestMail(TestCommon):
+ module_name = 'mail'
+
+ def test_email_imap_alert(self):
+ # TODO
+ pass
+
+
+class TestPlayers(TestCommon):
+ module_name = 'players'
+
+ def test_now_playing(self):
+ # TODO
+ pass
+
+
+class TestBat(TestCommon):
+ module_name = 'bat'
+
+ def test_battery(self):
+ pl = Pl()
+
+ def _get_battery_status(pl):
+ return 86, False
+
+ with replace_attr(self.module, '_get_battery_status', _get_battery_status):
+ self.assertEqual(self.module.battery(pl=pl), [{
+ 'contents': ' 86%',
+ 'highlight_groups': ['battery_gradient', 'battery'],
+ 'gradient_level': 14,
+ }])
+ self.assertEqual(self.module.battery(pl=pl, format='{capacity:.2f}'), [{
+ 'contents': '0.86',
+ 'highlight_groups': ['battery_gradient', 'battery'],
+ 'gradient_level': 14,
+ }])
+ self.assertEqual(self.module.battery(pl=pl, steps=7), [{
+ 'contents': ' 86%',
+ 'highlight_groups': ['battery_gradient', 'battery'],
+ 'gradient_level': 14,
+ }])
+ self.assertEqual(self.module.battery(pl=pl, gamify=True), [
+ {
+ 'contents': ' ',
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'],
+ 'gradient_level': 0
+ },
+ {
+ 'contents': 'OOOO',
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_full', 'battery_gradient', 'battery'],
+ 'gradient_level': 0
+ },
+ {
+ 'contents': 'O',
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_empty', 'battery_gradient', 'battery'],
+ 'gradient_level': 100
+ }
+ ])
+ self.assertEqual(self.module.battery(pl=pl, gamify=True, full_heart='+', empty_heart='-', steps='10'), [
+ {
+ 'contents': ' ',
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'],
+ 'gradient_level': 0
+ },
+ {
+ 'contents': '++++++++',
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_full', 'battery_gradient', 'battery'],
+ 'gradient_level': 0
+ },
+ {
+ 'contents': '--',
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_empty', 'battery_gradient', 'battery'],
+ 'gradient_level': 100
+ }
+ ])
+
+ def test_battery_with_ac_online(self):
+ pl = Pl()
+
+ def _get_battery_status(pl):
+ return 86, True
+
+ with replace_attr(self.module, '_get_battery_status', _get_battery_status):
+ self.assertEqual(self.module.battery(pl=pl, online='C', offline=' '), [
+ {
+ 'contents': 'C 86%',
+ 'highlight_groups': ['battery_gradient', 'battery'],
+ 'gradient_level': 14,
+ }])
+
+ def test_battery_with_ac_offline(self):
+ pl = Pl()
+
+ def _get_battery_status(pl):
+ return 86, False
+
+ with replace_attr(self.module, '_get_battery_status', _get_battery_status):
+ self.assertEqual(self.module.battery(pl=pl, online='C', offline=' '), [
+ {
+ 'contents': ' 86%',
+ 'highlight_groups': ['battery_gradient', 'battery'],
+ 'gradient_level': 14,
+ }])
+
+
+class TestVim(TestCase):
+ def test_mode(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'NORMAL')
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info, override={'i': 'INS'}), 'NORMAL')
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info, override={'n': 'NORM'}), 'NORM')
+ with vim_module._with('mode', 'i') as segment_info:
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'INSERT')
+ with vim_module._with('mode', 'i\0') as segment_info:
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'INSERT')
+ with vim_module._with('mode', chr(ord('V') - 0x40)) as segment_info:
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'V-BLCK')
+ self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info, override={'^V': 'VBLK'}), 'VBLK')
+
+ def test_visual_range(self):
+ pl = Pl()
+ vr = partial(self.vim.visual_range, pl=pl)
+ vim_module.current.window.cursor = [0, 0]
+ try:
+ with vim_module._with('mode', 'i') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), '')
+ with vim_module._with('mode', '^V') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), '1 x 1')
+ with vim_module._with('vpos', line=5, col=5, off=0):
+ self.assertEqual(vr(segment_info=segment_info), '5 x 5')
+ with vim_module._with('vpos', line=5, col=4, off=0):
+ self.assertEqual(vr(segment_info=segment_info), '5 x 4')
+ with vim_module._with('mode', '^S') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), '1 x 1')
+ with vim_module._with('vpos', line=5, col=5, off=0):
+ self.assertEqual(vr(segment_info=segment_info), '5 x 5')
+ with vim_module._with('vpos', line=5, col=4, off=0):
+ self.assertEqual(vr(segment_info=segment_info), '5 x 4')
+ with vim_module._with('mode', 'V') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), 'L:1')
+ with vim_module._with('vpos', line=5, col=5, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('vpos', line=5, col=4, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('mode', 'S') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), 'L:1')
+ with vim_module._with('vpos', line=5, col=5, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('vpos', line=5, col=4, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('mode', 'v') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), 'C:1')
+ with vim_module._with('vpos', line=5, col=5, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('vpos', line=5, col=4, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('mode', 's') as segment_info:
+ self.assertEqual(vr(segment_info=segment_info), 'C:1')
+ with vim_module._with('vpos', line=5, col=5, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ with vim_module._with('vpos', line=5, col=4, off=0):
+ self.assertEqual(vr(segment_info=segment_info), 'L:5')
+ finally:
+ vim_module._close(1)
+
+ def test_modified_indicator(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info), None)
+ segment_info['buffer'][0] = 'abc'
+ try:
+ self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info), '+')
+ self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info, text='-'), '-')
+ finally:
+ vim_module._bw(segment_info['bufnr'])
+
+ def test_paste_indicator(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.paste_indicator(pl=pl, segment_info=segment_info), None)
+ with vim_module._with('options', paste=1):
+ self.assertEqual(self.vim.paste_indicator(pl=pl, segment_info=segment_info), 'PASTE')
+ self.assertEqual(self.vim.paste_indicator(pl=pl, segment_info=segment_info, text='P'), 'P')
+
+ def test_readonly_indicator(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.readonly_indicator(pl=pl, segment_info=segment_info), None)
+ with vim_module._with('bufoptions', readonly=1):
+ self.assertEqual(self.vim.readonly_indicator(pl=pl, segment_info=segment_info), 'RO')
+ self.assertEqual(self.vim.readonly_indicator(pl=pl, segment_info=segment_info, text='L'), 'L')
+
+ def test_file_scheme(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.file_scheme(pl=pl, segment_info=segment_info), None)
+ with vim_module._with('buffer', '/tmp/’’/abc') as segment_info:
+ self.assertEqual(self.vim.file_scheme(pl=pl, segment_info=segment_info), None)
+ with vim_module._with('buffer', 'zipfile:/tmp/abc.zip::abc/abc.vim') as segment_info:
+ self.assertEqual(self.vim.file_scheme(pl=pl, segment_info=segment_info), 'zipfile')
+
+ def test_file_directory(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), None)
+ with replace_env('HOME', '/home/foo', os.environ):
+ with vim_module._with('buffer', '/tmp/’’/abc') as segment_info:
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/’’/')
+ with vim_module._with('buffer', b'/tmp/\xFF\xFF/abc') as segment_info:
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/<ff><ff>/')
+ with vim_module._with('buffer', '/tmp/abc') as segment_info:
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/')
+ os.environ['HOME'] = '/tmp'
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '~/')
+ with vim_module._with('buffer', 'zipfile:/tmp/abc.zip::abc/abc.vim') as segment_info:
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=False), 'zipfile:/tmp/abc.zip::abc/')
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=True), '/tmp/abc.zip::abc/')
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/abc.zip::abc/')
+ os.environ['HOME'] = '/tmp'
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=False), 'zipfile:/tmp/abc.zip::abc/')
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=True), '/tmp/abc.zip::abc/')
+ self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/abc.zip::abc/')
+
+ def test_file_name(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), None)
+ self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info, display_no_file=True), [
+ {'contents': '[No file]', 'highlight_groups': ['file_name_no_file', 'file_name']}
+ ])
+ self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info, display_no_file=True, no_file_text='X'), [
+ {'contents': 'X', 'highlight_groups': ['file_name_no_file', 'file_name']}
+ ])
+ with vim_module._with('buffer', '/tmp/abc') as segment_info:
+ self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), 'abc')
+ with vim_module._with('buffer', '/tmp/’’') as segment_info:
+ self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), '’’')
+ with vim_module._with('buffer', b'/tmp/\xFF\xFF') as segment_info:
+ self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), '<ff><ff>')
+
+ def test_file_size(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.file_size(pl=pl, segment_info=segment_info), '0 B')
+ with vim_module._with(
+ 'buffer',
+ os.path.join(
+ os.path.dirname(os.path.dirname(__file__)), 'empty')
+ ) as segment_info:
+ self.assertEqual(self.vim.file_size(pl=pl, segment_info=segment_info), '0 B')
+
+ def test_file_opts(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.file_format(pl=pl, segment_info=segment_info), [
+ {'divider_highlight_group': 'background:divider', 'contents': 'unix'}
+ ])
+ self.assertEqual(self.vim.file_encoding(pl=pl, segment_info=segment_info), [
+ {'divider_highlight_group': 'background:divider', 'contents': 'utf-8'}
+ ])
+ self.assertEqual(self.vim.file_type(pl=pl, segment_info=segment_info), None)
+ with vim_module._with('bufoptions', filetype='python'):
+ self.assertEqual(self.vim.file_type(pl=pl, segment_info=segment_info), [
+ {'divider_highlight_group': 'background:divider', 'contents': 'python'}
+ ])
+
+ def test_window_title(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.window_title(pl=pl, segment_info=segment_info), None)
+ with vim_module._with('wvars', quickfix_title='Abc'):
+ self.assertEqual(self.vim.window_title(pl=pl, segment_info=segment_info), 'Abc')
+
+ def test_line_percent(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ segment_info['buffer'][0:-1] = [str(i) for i in range(100)]
+ try:
+ self.assertEqual(self.vim.line_percent(pl=pl, segment_info=segment_info), '1')
+ vim_module._set_cursor(50, 0)
+ self.assertEqual(self.vim.line_percent(pl=pl, segment_info=segment_info), '50')
+ self.assertEqual(self.vim.line_percent(pl=pl, segment_info=segment_info, gradient=True), [
+ {'contents': '50', 'highlight_groups': ['line_percent_gradient', 'line_percent'], 'gradient_level': 50 * 100.0 / 101}
+ ])
+ finally:
+ vim_module._bw(segment_info['bufnr'])
+
+ def test_line_count(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ segment_info['buffer'][0:-1] = [str(i) for i in range(99)]
+ try:
+ self.assertEqual(self.vim.line_count(pl=pl, segment_info=segment_info), '100')
+ vim_module._set_cursor(50, 0)
+ self.assertEqual(self.vim.line_count(pl=pl, segment_info=segment_info), '100')
+ finally:
+ vim_module._bw(segment_info['bufnr'])
+
+ def test_position(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ try:
+ segment_info['buffer'][0:-1] = [str(i) for i in range(99)]
+ vim_module._set_cursor(49, 0)
+ self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info), '50%')
+ self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, gradient=True), [
+ {'contents': '50%', 'highlight_groups': ['position_gradient', 'position'], 'gradient_level': 50.0}
+ ])
+ vim_module._set_cursor(0, 0)
+ self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info), 'Top')
+ vim_module._set_cursor(97, 0)
+ self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, position_strings={'top': 'Comienzo', 'bottom': 'Final', 'all': 'Todo'}), 'Final')
+ segment_info['buffer'][0:-1] = [str(i) for i in range(2)]
+ vim_module._set_cursor(0, 0)
+ self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, position_strings={'top': 'Comienzo', 'bottom': 'Final', 'all': 'Todo'}), 'Todo')
+ self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, gradient=True), [
+ {'contents': 'All', 'highlight_groups': ['position_gradient', 'position'], 'gradient_level': 0.0}
+ ])
+ finally:
+ vim_module._bw(segment_info['bufnr'])
+
+ def test_cursor_current(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.line_current(pl=pl, segment_info=segment_info), '1')
+ self.assertEqual(self.vim.col_current(pl=pl, segment_info=segment_info), '1')
+ self.assertEqual(self.vim.virtcol_current(pl=pl, segment_info=segment_info), [{
+ 'highlight_groups': ['virtcol_current_gradient', 'virtcol_current', 'col_current'], 'contents': '1', 'gradient_level': 100.0 / 80,
+ }])
+ self.assertEqual(self.vim.virtcol_current(pl=pl, segment_info=segment_info, gradient=False), [{
+ 'highlight_groups': ['virtcol_current', 'col_current'], 'contents': '1',
+ }])
+
+ def test_modified_buffers(self):
+ pl = Pl()
+ self.assertEqual(self.vim.modified_buffers(pl=pl), None)
+
+ def test_branch(self):
+ pl = Pl()
+ create_watcher = get_fallback_create_watcher()
+ branch = partial(self.vim.branch, pl=pl, create_watcher=create_watcher)
+ with vim_module._with('buffer', '/foo') as segment_info:
+ with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: None)):
+ with replace_attr(self.vcs, 'tree_status', lambda repo, pl: None):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch'], 'contents': 'foo'}
+ ])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_clean', 'branch'], 'contents': 'foo'}
+ ])
+ with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: 'DU')):
+ with replace_attr(self.vcs, 'tree_status', lambda repo, pl: 'DU'):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch'], 'contents': 'foo'}
+ ])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_dirty', 'branch'], 'contents': 'foo'}
+ ])
+ with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: 'U')):
+ with replace_attr(self.vcs, 'tree_status', lambda repo, pl: 'U'):
+ self.assertEqual(branch(segment_info=segment_info, status_colors=False, ignore_statuses=['U']), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch'], 'contents': 'foo'}
+ ])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['DU']), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_dirty', 'branch'], 'contents': 'foo'}
+ ])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_dirty', 'branch'], 'contents': 'foo'}
+ ])
+ self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['U']), [
+ {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_clean', 'branch'], 'contents': 'foo'}
+ ])
+
+ def test_stash(self):
+ pl = Pl()
+ create_watcher = get_fallback_create_watcher()
+ with vim_module._with('buffer', '/foo') as segment_info:
+ stash = partial(self.vim.stash, pl=pl, create_watcher=create_watcher, segment_info=segment_info)
+
+ def forge_stash(n):
+ return replace_attr(self.vcs, 'guess', get_dummy_guess(stash=lambda: n))
+
+ with forge_stash(0):
+ self.assertEqual(stash(), None)
+ with forge_stash(1):
+ self.assertEqual(stash(), [{
+ 'divider_highlight_group': 'stash:divider',
+ 'highlight_groups': ['stash'],
+ 'contents': '1'
+ }])
+ with forge_stash(2):
+ self.assertEqual(stash(), [{
+ 'divider_highlight_group': 'stash:divider',
+ 'highlight_groups': ['stash'],
+ 'contents': '2'
+ }])
+
+ def test_file_vcs_status(self):
+ pl = Pl()
+ create_watcher = get_fallback_create_watcher()
+ file_vcs_status = partial(self.vim.file_vcs_status, pl=pl, create_watcher=create_watcher)
+ with vim_module._with('buffer', '/foo') as segment_info:
+ with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda file: 'M')):
+ self.assertEqual(file_vcs_status(segment_info=segment_info), [
+ {'highlight_groups': ['file_vcs_status_M', 'file_vcs_status'], 'contents': 'M'}
+ ])
+ with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda file: None)):
+ self.assertEqual(file_vcs_status(segment_info=segment_info), None)
+ with vim_module._with('buffer', '/bar') as segment_info:
+ with vim_module._with('bufoptions', buftype='nofile'):
+ with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda file: 'M')):
+ self.assertEqual(file_vcs_status(segment_info=segment_info), None)
+
+ def test_trailing_whitespace(self):
+ pl = Pl()
+ with vim_module._with('buffer', 'tws') as segment_info:
+ trailing_whitespace = partial(self.vim.trailing_whitespace, pl=pl, segment_info=segment_info)
+ self.assertEqual(trailing_whitespace(), None)
+ self.assertEqual(trailing_whitespace(), None)
+ vim_module.current.buffer[0] = ' '
+ self.assertEqual(trailing_whitespace(), [{
+ 'highlight_groups': ['trailing_whitespace', 'warning'],
+ 'contents': '1',
+ }])
+ self.assertEqual(trailing_whitespace(), [{
+ 'highlight_groups': ['trailing_whitespace', 'warning'],
+ 'contents': '1',
+ }])
+ vim_module.current.buffer[0] = ''
+ self.assertEqual(trailing_whitespace(), None)
+ self.assertEqual(trailing_whitespace(), None)
+
+ def test_tabnr(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.tabnr(pl=pl, segment_info=segment_info, show_current=True), '1')
+ self.assertEqual(self.vim.tabnr(pl=pl, segment_info=segment_info, show_current=False), None)
+
+ def test_tab(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.tab(pl=pl, segment_info=segment_info), [{
+ 'contents': None,
+ 'literal_contents': (0, '%1T'),
+ }])
+ self.assertEqual(self.vim.tab(pl=pl, segment_info=segment_info, end=True), [{
+ 'contents': None,
+ 'literal_contents': (0, '%T'),
+ }])
+
+ def test_bufnr(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.bufnr(pl=pl, segment_info=segment_info, show_current=True), str(segment_info['bufnr']))
+ self.assertEqual(self.vim.bufnr(pl=pl, segment_info=segment_info, show_current=False), None)
+
+ def test_winnr(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.winnr(pl=pl, segment_info=segment_info, show_current=True), str(segment_info['winnr']))
+ self.assertEqual(self.vim.winnr(pl=pl, segment_info=segment_info, show_current=False), None)
+
+ def test_segment_info(self):
+ pl = Pl()
+ with vim_module._with('tabpage'):
+ with vim_module._with('buffer', '1') as segment_info:
+ self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), None)
+ vim_module.current.buffer[0] = ' '
+ self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), [{
+ 'contents': '+',
+ 'highlight_groups': ['tab_modified_indicator', 'modified_indicator'],
+ }])
+ vim_module._undo()
+ self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), None)
+ old_buffer = vim_module.current.buffer
+ vim_module._new('2')
+ segment_info = vim_module._get_segment_info()
+ self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), None)
+ old_buffer[0] = ' '
+ self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info), None)
+ self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), [{
+ 'contents': '+',
+ 'highlight_groups': ['tab_modified_indicator', 'modified_indicator'],
+ }])
+
+ def test_csv_col_current(self):
+ pl = Pl()
+ segment_info = vim_module._get_segment_info()
+
+ def csv_col_current(**kwargs):
+ self.vim.csv_cache and self.vim.csv_cache.clear()
+ return self.vim.csv_col_current(pl=pl, segment_info=segment_info, **kwargs)
+
+ buffer = segment_info['buffer']
+ try:
+ self.assertEqual(csv_col_current(), None)
+ buffer.options['filetype'] = 'csv'
+ self.assertEqual(csv_col_current(), None)
+ buffer[:] = ['1;2;3', '4;5;6']
+ vim_module._set_cursor(1, 1)
+ self.assertEqual(csv_col_current(), [{
+ 'contents': '1', 'highlight_groups': ['csv:column_number', 'csv']
+ }])
+ vim_module._set_cursor(2, 3)
+ self.assertEqual(csv_col_current(), [{
+ 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv']
+ }])
+ vim_module._set_cursor(2, 3)
+ self.assertEqual(csv_col_current(display_name=True), [{
+ 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv']
+ }, {
+ 'contents': ' (2)', 'highlight_groups': ['csv:column_name', 'csv']
+ }])
+ buffer[:0] = ['Foo;Bar;Baz']
+ vim_module._set_cursor(2, 3)
+ self.assertEqual(csv_col_current(), [{
+ 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv']
+ }, {
+ 'contents': ' (Bar)', 'highlight_groups': ['csv:column_name', 'csv']
+ }])
+ if sys.version_info < (2, 7):
+ raise SkipTest('csv module in Python-2.6 does not handle multiline csv files well')
+ buffer[len(buffer):] = ['1;"bc', 'def', 'ghi', 'jkl";3']
+ vim_module._set_cursor(5, 1)
+ self.assertEqual(csv_col_current(), [{
+ 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv']
+ }, {
+ 'contents': ' (Bar)', 'highlight_groups': ['csv:column_name', 'csv']
+ }])
+ vim_module._set_cursor(7, 6)
+ self.assertEqual(csv_col_current(), [{
+ 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv']
+ }, {
+ 'contents': ' (Baz)', 'highlight_groups': ['csv:column_name', 'csv']
+ }])
+ self.assertEqual(csv_col_current(name_format=' ({column_name:.1})'), [{
+ 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv']
+ }, {
+ 'contents': ' (B)', 'highlight_groups': ['csv:column_name', 'csv']
+ }])
+ self.assertEqual(csv_col_current(display_name=True, name_format=' ({column_name:.1})'), [{
+ 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv']
+ }, {
+ 'contents': ' (B)', 'highlight_groups': ['csv:column_name', 'csv']
+ }])
+ self.assertEqual(csv_col_current(display_name=False, name_format=' ({column_name:.1})'), [{
+ 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv']
+ }])
+ self.assertEqual(csv_col_current(display_name=False), [{
+ 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv']
+ }])
+ finally:
+ vim_module._bw(segment_info['bufnr'])
+
+ @classmethod
+ def setUpClass(cls):
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path')))
+ from powerline.segments import vim
+ cls.vim = vim
+ from powerline.segments.common import vcs
+ cls.vcs = vcs
+
+ @classmethod
+ def tearDownClass(cls):
+ sys.path.pop(0)
+
+
+class TestPDB(TestCase):
+ def test_current_line(self):
+ pl = Pl()
+ self.assertEqual(pdb.current_line(pl=pl, segment_info={'curframe': Args(f_lineno=10)}), '10')
+
+ def test_current_file(self):
+ pl = Pl()
+ cf = lambda **kwargs: pdb.current_file(
+ pl=pl,
+ segment_info={'curframe': Args(f_code=Args(co_filename='/tmp/abc.py'))},
+ **kwargs
+ )
+ self.assertEqual(cf(), 'abc.py')
+ self.assertEqual(cf(basename=True), 'abc.py')
+ self.assertEqual(cf(basename=False), '/tmp/abc.py')
+
+ def test_current_code_name(self):
+ pl = Pl()
+ ccn = lambda **kwargs: pdb.current_code_name(
+ pl=pl,
+ segment_info={'curframe': Args(f_code=Args(co_name='<module>'))},
+ **kwargs
+ )
+ self.assertEqual(ccn(), '<module>')
+
+ def test_current_context(self):
+ pl = Pl()
+ cc = lambda **kwargs: pdb.current_context(
+ pl=pl,
+ segment_info={'curframe': Args(f_code=Args(co_name='<module>', co_filename='/tmp/abc.py'))},
+ **kwargs
+ )
+ self.assertEqual(cc(), 'abc.py')
+
+ def test_stack_depth(self):
+ pl = Pl()
+ sd = lambda **kwargs: pdb.stack_depth(
+ pl=pl,
+ segment_info={'pdb': Args(stack=[1, 2, 3]), 'initial_stack_length': 1},
+ **kwargs
+ )
+ self.assertEqual(sd(), '2')
+ self.assertEqual(sd(full_stack=False), '2')
+ self.assertEqual(sd(full_stack=True), '3')
+
+
+old_cwd = None
+
+
+def setUpModule():
+ global old_cwd
+ global __file__
+ old_cwd = os.getcwd()
+ __file__ = os.path.abspath(__file__)
+ os.chdir(os.path.dirname(os.path.dirname(__file__)))
+
+
+def tearDownModule():
+ global old_cwd
+ os.chdir(old_cwd)
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_selectors.py b/tests/test_python/test_selectors.py
new file mode 100644
index 0000000..74ace8d
--- /dev/null
+++ b/tests/test_python/test_selectors.py
@@ -0,0 +1,36 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+
+from functools import partial
+
+import tests.modules.vim as vim_module
+
+from tests.modules.lib import Pl
+from tests.modules import TestCase
+
+
+class TestVim(TestCase):
+ def test_single_tab(self):
+ pl = Pl()
+ single_tab = partial(self.vim.single_tab, pl=pl, segment_info=None, mode=None)
+ with vim_module._with('tabpage'):
+ self.assertEqual(single_tab(), False)
+ self.assertEqual(single_tab(), True)
+
+ @classmethod
+ def setUpClass(cls):
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path')))
+ from powerline.selectors import vim
+ cls.vim = vim
+
+ @classmethod
+ def tearDownClass(cls):
+ sys.path.pop(0)
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_python/test_watcher.py b/tests/test_python/test_watcher.py
new file mode 100644
index 0000000..a246d0b
--- /dev/null
+++ b/tests/test_python/test_watcher.py
@@ -0,0 +1,245 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import shutil
+import os
+
+from time import sleep
+from functools import partial
+from errno import ENOENT
+
+from powerline.lib.watcher import create_file_watcher, create_tree_watcher, INotifyError
+from powerline.lib.watcher.uv import UvNotFound
+from powerline import get_fallback_logger
+from powerline.lib.monotonic import monotonic
+
+from tests.modules import TestCase, SkipTest
+
+
+INOTIFY_DIR = 'inotify' + os.path.basename(os.environ.get('PYTHON', ''))
+
+
+def clear_dir(dir):
+ for root, dirs, files in list(os.walk(dir, topdown=False)):
+ for f in files:
+ os.remove(os.path.join(root, f))
+ for d in dirs:
+ os.rmdir(os.path.join(root, d))
+
+
+def set_watcher_tests(l):
+ byte_tests = (('bytes', True), ('unicode', False))
+
+ for btn, use_bytes in byte_tests:
+ def test_inotify_file_watcher(self, use_bytes=use_bytes):
+ try:
+ w = create_file_watcher(pl=get_fallback_logger(), watcher_type='inotify')
+ except INotifyError:
+ raise SkipTest('This test is not suitable for a stat based file watcher')
+ self.do_test_file_watcher(w, use_bytes)
+
+ def test_uv_file_watcher(self, use_bytes=use_bytes):
+ raise SkipTest('Uv watcher tests are not stable')
+ try:
+ w = create_file_watcher(pl=get_fallback_logger(), watcher_type='uv')
+ except UvNotFound:
+ raise SkipTest('Pyuv is not available')
+ self.do_test_file_watcher(w, use_bytes)
+
+ def test_inotify_tree_watcher(self, use_bytes=use_bytes):
+ try:
+ tw = create_tree_watcher(get_fallback_logger(), watcher_type='inotify')
+ except INotifyError:
+ raise SkipTest('INotify is not available')
+ self.do_test_tree_watcher(tw, use_bytes)
+
+ def test_uv_tree_watcher(self, use_bytes=use_bytes):
+ raise SkipTest('Uv watcher tests are not stable')
+ try:
+ tw = create_tree_watcher(get_fallback_logger(), 'uv')
+ except UvNotFound:
+ raise SkipTest('Pyuv is not available')
+ self.do_test_tree_watcher(tw, use_bytes)
+
+ def test_inotify_file_watcher_is_watching(self, use_bytes=use_bytes):
+ try:
+ w = create_file_watcher(pl=get_fallback_logger(), watcher_type='inotify')
+ except INotifyError:
+ raise SkipTest('INotify is not available')
+ self.do_test_file_watcher_is_watching(w, use_bytes)
+
+ def test_stat_file_watcher_is_watching(self, use_bytes=use_bytes):
+ w = create_file_watcher(pl=get_fallback_logger(), watcher_type='stat')
+ self.do_test_file_watcher_is_watching(w, use_bytes)
+
+ def test_uv_file_watcher_is_watching(self, use_bytes=use_bytes):
+ try:
+ w = create_file_watcher(pl=get_fallback_logger(), watcher_type='uv')
+ except UvNotFound:
+ raise SkipTest('Pyuv is not available')
+ self.do_test_file_watcher_is_watching(w, use_bytes)
+
+ for wt in ('uv', 'inotify'):
+ l['test_{0}_file_watcher_{1}'.format(wt, btn)] = locals()['test_{0}_file_watcher'.format(wt)]
+ l['test_{0}_tree_watcher_{1}'.format(wt, btn)] = locals()['test_{0}_tree_watcher'.format(wt)]
+ l['test_{0}_file_watcher_is_watching_{1}'.format(wt, btn)] = (
+ locals()['test_{0}_file_watcher_is_watching'.format(wt)])
+ l['test_{0}_file_watcher_is_watching_{1}'.format('stat', btn)] = (
+ locals()['test_{0}_file_watcher_is_watching'.format('stat')])
+
+
+class TestFilesystemWatchers(TestCase):
+ def do_test_for_change(self, watcher, path):
+ st = monotonic()
+ while monotonic() - st < 1:
+ if watcher(path):
+ return
+ sleep(0.1)
+ self.fail('The change to {0} was not detected'.format(path))
+
+ def do_test_file_watcher(self, w, use_bytes=False):
+ try:
+ f1, f2, f3 = map(lambda x: os.path.join(INOTIFY_DIR, 'file%d' % x), (1, 2, 3))
+ ne = os.path.join(INOTIFY_DIR, 'notexists')
+ if use_bytes:
+ f1 = f1.encode('utf-8')
+ f2 = f2.encode('utf-8')
+ f3 = f3.encode('utf-8')
+ ne = ne.encode('utf-8')
+ with open(f1, 'wb'):
+ with open(f2, 'wb'):
+ with open(f3, 'wb'):
+ pass
+ self.assertRaises(OSError, w, ne)
+ self.assertTrue(w(f1))
+ self.assertTrue(w(f2))
+ os.utime(f1, None), os.utime(f2, None)
+ self.do_test_for_change(w, f1)
+ self.do_test_for_change(w, f2)
+ # Repeat once
+ os.utime(f1, None), os.utime(f2, None)
+ self.do_test_for_change(w, f1)
+ self.do_test_for_change(w, f2)
+ # Check that no false changes are reported
+ self.assertFalse(w(f1), 'Spurious change detected')
+ self.assertFalse(w(f2), 'Spurious change detected')
+ # Check that open the file with 'w' triggers a change
+ with open(f1, 'wb'):
+ with open(f2, 'wb'):
+ pass
+ self.do_test_for_change(w, f1)
+ self.do_test_for_change(w, f2)
+ # Check that writing to a file with 'a' triggers a change
+ with open(f1, 'ab') as f:
+ f.write(b'1')
+ self.do_test_for_change(w, f1)
+ # Check that deleting a file registers as a change
+ os.unlink(f1)
+ self.do_test_for_change(w, f1)
+ # Test that changing the inode of a file does not cause it to stop
+ # being watched
+ os.rename(f3, f2)
+ self.do_test_for_change(w, f2)
+ self.assertFalse(w(f2), 'Spurious change detected')
+ os.utime(f2, None)
+ self.do_test_for_change(w, f2)
+ finally:
+ clear_dir(INOTIFY_DIR)
+
+ def do_test_tree_watcher(self, tw, use_bytes=False):
+ try:
+ inotify_dir = INOTIFY_DIR
+ subdir = os.path.join(inotify_dir, 'subdir')
+ t1 = os.path.join(inotify_dir, 'tree1')
+ ts1 = os.path.join(subdir, 'tree1')
+ suffix = '1'
+ f = os.path.join(subdir, 'f')
+ if use_bytes:
+ inotify_dir = inotify_dir.encode('utf-8')
+ subdir = subdir.encode('utf-8')
+ t1 = t1.encode('utf-8')
+ ts1 = ts1.encode('utf-8')
+ suffix = suffix.encode('utf-8')
+ f = f.encode('utf-8')
+ os.mkdir(subdir)
+ try:
+ if tw.watch(inotify_dir).is_dummy:
+ raise SkipTest('No tree watcher available')
+ except UvNotFound:
+ raise SkipTest('Pyuv is not available')
+ except INotifyError:
+ raise SkipTest('INotify is not available')
+ self.assertTrue(tw(inotify_dir))
+ self.assertFalse(tw(inotify_dir))
+ changed = partial(self.do_test_for_change, tw, inotify_dir)
+ open(t1, 'w').close()
+ changed()
+ open(ts1, 'w').close()
+ changed()
+ os.unlink(ts1)
+ changed()
+ os.rmdir(subdir)
+ changed()
+ os.mkdir(subdir)
+ changed()
+ os.rename(subdir, subdir + suffix)
+ changed()
+ shutil.rmtree(subdir + suffix)
+ changed()
+ os.mkdir(subdir)
+ open(f, 'w').close()
+ changed()
+ with open(f, 'a') as s:
+ s.write(' ')
+ changed()
+ os.rename(f, f + suffix)
+ changed()
+ finally:
+ clear_dir(inotify_dir)
+
+ def do_test_file_watcher_is_watching(self, w, use_bytes=False):
+ try:
+ f1, f2, f3 = map(lambda x: os.path.join(INOTIFY_DIR, 'file%d' % x), (1, 2, 3))
+ ne = os.path.join(INOTIFY_DIR, 'notexists')
+ if use_bytes:
+ f1 = f1.encode('utf-8')
+ f2 = f2.encode('utf-8')
+ f3 = f3.encode('utf-8')
+ ne = ne.encode('utf-8')
+ with open(f1, 'wb'):
+ with open(f2, 'wb'):
+ with open(f3, 'wb'):
+ pass
+ self.assertRaises(OSError, w, ne)
+ try:
+ w(ne)
+ except OSError as e:
+ self.assertEqual(e.errno, ENOENT)
+ self.assertTrue(w(f1))
+ self.assertFalse(w.is_watching(ne))
+ self.assertTrue(w.is_watching(f1))
+ self.assertFalse(w.is_watching(f2))
+ finally:
+ clear_dir(INOTIFY_DIR)
+
+ set_watcher_tests(locals())
+
+
+old_cwd = None
+
+
+def setUpModule():
+ global old_cwd
+ old_cwd = os.getcwd()
+ os.chdir(os.path.dirname(os.path.dirname(__file__)))
+ os.mkdir(INOTIFY_DIR)
+
+
+def tearDownModule():
+ shutil.rmtree(INOTIFY_DIR)
+ os.chdir(old_cwd)
+
+
+if __name__ == '__main__':
+ from tests.modules import main
+ main()
diff --git a/tests/test_shells/bgscript.sh b/tests/test_shells/bgscript.sh
new file mode 100755
index 0000000..71886e6
--- /dev/null
+++ b/tests/test_shells/bgscript.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+echo $$ > pid
+while true ; do
+ sleep 0.1s
+done
diff --git a/tests/test_shells/inputs/bash b/tests/test_shells/inputs/bash
new file mode 100644
index 0000000..1b68b6f
--- /dev/null
+++ b/tests/test_shells/inputs/bash
@@ -0,0 +1,69 @@
+set_theme_option() {
+ export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2"
+}
+set_theme() {
+ export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
+}
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+ABOVE_LEFT='[{
+ "left": [
+ {
+ "function": "powerline.segments.common.env.environment",
+ "args": {"variable": "DISPLAYED_ENV_VAR"}
+ }
+ ]
+}]'
+ABOVE_FULL='[{
+ "left": [
+ {
+ "type": "string",
+ "name": "background",
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "function": "powerline.segments.common.env.environment",
+ "args": {"variable": "DISPLAYED_ENV_VAR"}
+ }
+ ]
+}]'
+set_theme default_leftonly
+export VIRTUAL_ENV=
+source powerline/bindings/bash/powerline.sh
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+VIRTUAL_ENV=
+bgscript.sh & waitpid.sh
+false
+kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+set_theme_option default_leftonly.segment_data.user.display false
+echo '
+abc
+def
+'
+cd "$DIR1"
+cd ../"$DIR2"
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+(exit 42)|(exit 43)
+set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"
+export DISPLAYED_ENV_VAR=foo
+unset DISPLAYED_ENV_VAR
+set_theme_option default_leftonly.segments.above "$ABOVE_FULL"
+export DISPLAYED_ENV_VAR=foo
+unset DISPLAYED_ENV_VAR
+set_theme_option default_leftonly.segments.above
+set_theme_option default_leftonly.dividers.left.hard \$ABC
+false
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/busybox b/tests/test_shells/inputs/busybox
new file mode 100644
index 0000000..5d1495a
--- /dev/null
+++ b/tests/test_shells/inputs/busybox
@@ -0,0 +1,37 @@
+set_theme_option() {
+ export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2"
+}
+set_theme() {
+ export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
+}
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+set_theme default_leftonly
+. powerline/bindings/shell/powerline.sh
+export VIRTUAL_ENV=
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+VIRTUAL_ENV=
+bgscript.sh & waitpid.sh
+false
+kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+set_theme_option default_leftonly.segment_data.user.display false
+echo '
+abc
+def
+'
+cd "$DIR1"
+cd ../"$DIR2"
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+set_theme_option default_leftonly.dividers.left.hard \$ABC
+false
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/dash b/tests/test_shells/inputs/dash
new file mode 100644
index 0000000..5d1495a
--- /dev/null
+++ b/tests/test_shells/inputs/dash
@@ -0,0 +1,37 @@
+set_theme_option() {
+ export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2"
+}
+set_theme() {
+ export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
+}
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+set_theme default_leftonly
+. powerline/bindings/shell/powerline.sh
+export VIRTUAL_ENV=
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+VIRTUAL_ENV=
+bgscript.sh & waitpid.sh
+false
+kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+set_theme_option default_leftonly.segment_data.user.display false
+echo '
+abc
+def
+'
+cd "$DIR1"
+cd ../"$DIR2"
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+set_theme_option default_leftonly.dividers.left.hard \$ABC
+false
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/fish b/tests/test_shells/inputs/fish
new file mode 100644
index 0000000..9a20613
--- /dev/null
+++ b/tests/test_shells/inputs/fish
@@ -0,0 +1,69 @@
+function set_theme_option
+ set -g -x POWERLINE_THEME_OVERRIDES "$POWERLINE_THEME_OVERRIDES;$argv[1]=$argv[2]"
+end
+function set_theme
+ set -g -x POWERLINE_CONFIG_OVERRIDES "ext.shell.theme=$argv"
+end
+set -g -x POWERLINE_
+set -g ABOVE_LEFT '[{
+ "left": [
+ {
+ "function": "powerline.segments.common.env.environment",
+ "args": {"variable": "DISPLAYED_ENV_VAR"}
+ }
+ ]
+}]'
+set -g ABOVE_FULL '[{
+ "left": [
+ {
+ "type": "string",
+ "name": "background",
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "function": "powerline.segments.common.env.environment",
+ "args": {"variable": "DISPLAYED_ENV_VAR"}
+ }
+ ]
+}]'
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+set_theme default_leftonly
+set fish_function_path "$PWD/powerline/bindings/fish" $fish_function_path
+while jobs | grep fish_update_completions
+ sleep 1
+end
+powerline-setup
+setenv VIRTUAL_ENV
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+setenv VIRTUAL_ENV "$HOME/.virtenvs/some-virtual-environment"
+setenv VIRTUAL_ENV
+bgscript.sh & waitpid.sh
+false
+kill (cat pid) ; sleep 1s
+cd "$DIR1"
+cd ../"$DIR2"
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+set_theme default
+set_theme_option default.segments.above "$ABOVE_LEFT"
+set -g -x DISPLAYED_ENV_VAR foo
+set -g -x -e DISPLAYED_ENV_VAR
+set_theme_option default.segments.above "$ABOVE_FULL"
+set -g -x DISPLAYED_ENV_VAR foo
+set -g -x -e DISPLAYED_ENV_VAR
+set_theme_option default.segments.above ''
+set -g fish_key_bindings fish_vi_key_bindings
+ii
+false
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/ipython b/tests/test_shells/inputs/ipython
new file mode 100644
index 0000000..257cba6
--- /dev/null
+++ b/tests/test_shells/inputs/ipython
@@ -0,0 +1,7 @@
+print ('cd ' + '"$TEST_ROOT"/3rd') # Start of the test marker
+bool 42
+bool 44
+class Test(object):
+pass
+
+exit
diff --git a/tests/test_shells/inputs/mksh b/tests/test_shells/inputs/mksh
new file mode 100644
index 0000000..ca45783
--- /dev/null
+++ b/tests/test_shells/inputs/mksh
@@ -0,0 +1,38 @@
+set_theme_option() {
+ export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2"
+}
+set_theme() {
+ export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
+}
+set_theme default_leftonly
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+. powerline/bindings/shell/powerline.sh
+export VIRTUAL_ENV=
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+VIRTUAL_ENV=
+bgscript.sh & waitpid.sh
+false
+kill `cat pid` ; sleep 1
+set_theme_option default_leftonly.segment_data.hostname.display false
+set_theme_option default_leftonly.segment_data.user.display false
+echo -n
+echo '
+abc
+def
+'
+cd "$DIR1"
+cd ../"$DIR2"
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+set_theme_option default_leftonly.dividers.left.hard \$ABC
+false
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/pdb b/tests/test_shells/inputs/pdb
new file mode 100644
index 0000000..e9ac498
--- /dev/null
+++ b/tests/test_shells/inputs/pdb
@@ -0,0 +1,89 @@
+s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_shells/inputs/rc b/tests/test_shells/inputs/rc
new file mode 100644
index 0000000..c88bcf9
--- /dev/null
+++ b/tests/test_shells/inputs/rc
@@ -0,0 +1,33 @@
+fn set_theme_option {
+ POWERLINE_THEME_OVERRIDES = $POWERLINE_THEME_OVERRIDES';'$1'='$2
+}
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+POWERLINE_CONFIG_OVERRIDES = 'ext.shell.theme=default_leftonly'
+. powerline/bindings/rc/powerline.rc
+VIRTUAL_ENV = ()
+cd $TEST_ROOT/3rd
+true cd "$TEST_ROOT"/3rd # Test start marker
+cd .git
+cd ..
+VIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment'
+VIRTUAL_ENV = ()
+bgscript.sh & waitpid.sh
+false
+kill `{cat pid} ; sleep 1s
+cd $DIR1
+cd ../$DIR2
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+false
+set_theme_option default_leftonly.segment_data.hostname.display false
+set_theme_option default_leftonly.segment_data.user.display false
+echo `{
+ echo Continuation!
+}
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/tcsh b/tests/test_shells/inputs/tcsh
new file mode 100644
index 0000000..c7d722a
--- /dev/null
+++ b/tests/test_shells/inputs/tcsh
@@ -0,0 +1,24 @@
+setenv POWERLINE_THEME_OVERRIDES "default_leftonly.segment_data.hostname.args.only_if_ssh=false"
+setenv POWERLINE_CONFIG_OVERRIDES "ext.shell.theme=default_leftonly"
+source powerline/bindings/tcsh/powerline.tcsh
+unsetenv VIRTUAL_ENV
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+setenv VIRTUAL_ENV "/home/foo/.virtenvs/some-virtual-environment"
+unsetenv VIRTUAL_ENV
+bgscript.sh & waitpid.sh
+false # Warning: currently tcsh bindings do not support job count
+kill `cat pid` ; sleep 1s
+cd $DIR1:q
+cd ../$DIR2:q
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode\!»'
+false
+true is the last line
+exit
diff --git a/tests/test_shells/inputs/zsh b/tests/test_shells/inputs/zsh
new file mode 100644
index 0000000..811684e
--- /dev/null
+++ b/tests/test_shells/inputs/zsh
@@ -0,0 +1,90 @@
+unset HOME
+unsetopt promptsp notransientrprompt
+setopt interactivecomments
+setopt autonamedirs
+setopt warncreateglobal
+function set_theme_option() {
+ export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2"
+ powerline-reload-config
+}
+function set_theme() {
+ export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
+ powerline-reload-config
+}
+if test -n "$POWERLINE_NO_ZSH_ZPYTHON" ; then
+ powerline-reload-config():
+fi
+source powerline/bindings/zsh/powerline.zsh
+set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
+set_theme_option default.segment_data.hostname.args.only_if_ssh false
+ABOVE_LEFT='[{
+ "left": [
+ {
+ "function": "powerline.segments.common.env.environment",
+ "args": {"variable": "DISPLAYED_ENV_VAR"}
+ }
+ ]
+}]'
+ABOVE_FULL='[{
+ "left": [
+ {
+ "type": "string",
+ "name": "background",
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "function": "powerline.segments.common.env.environment",
+ "args": {"variable": "DISPLAYED_ENV_VAR"}
+ }
+ ]
+}]'
+set_theme default_leftonly
+export VIRTUAL_ENV=
+cd "$TEST_ROOT"/3rd
+cd .git
+cd ..
+VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+VIRTUAL_ENV=
+bgscript.sh & waitpid.sh
+false
+kill `cat pid` ; sleep 1s
+cd "$DIR1"
+cd ../"$DIR2"
+cd ../'\[\]'
+cd ../'%%'
+cd ../'#[bold]'
+cd ../'(echo)'
+cd ../'$(echo)'
+cd ../'`echo`'
+cd ../'«Unicode!»'
+cd ..
+bindkey -v ; set_theme default
+
+
+echo abc
+false
+set_theme_option default.segment_data.hostname.display false
+set_theme_option default.segment_data.user.display false
+select abc in def ghi jkl
+do
+ echo $abc
+ break
+done
+1
+cd .
+cd .
+set_theme_option default.segments.above "$ABOVE_LEFT"
+export DISPLAYED_ENV_VAR=foo
+unset DISPLAYED_ENV_VAR
+set_theme_option default.segments.above "$ABOVE_FULL"
+export DISPLAYED_ENV_VAR=foo
+unset DISPLAYED_ENV_VAR
+set_theme_option default.segments.above
+hash -d foo=$PWD:h ; cd .
+set_theme_option default.dividers.left.hard \$ABC
+true
+true is the last line
+exit
diff --git a/tests/test_shells/ipython_home/profile_default/ipython_config.py b/tests/test_shells/ipython_home/profile_default/ipython_config.py
new file mode 100644
index 0000000..6d4a262
--- /dev/null
+++ b/tests/test_shells/ipython_home/profile_default/ipython_config.py
@@ -0,0 +1,19 @@
+import os
+c = get_config()
+c.InteractiveShellApp.extensions = ['powerline.bindings.ipython.post_0_11']
+c.TerminalInteractiveShell.autocall = 1
+c.Powerline.config_paths = [os.path.abspath('powerline/config_files')]
+c.Powerline.theme_overrides = {
+ 'in': {
+ 'segment_data': {
+ 'virtualenv': {
+ 'display': False
+ }
+ }
+ }
+}
+c.Powerline.config_overrides = {
+ 'common': {
+ 'default_top_theme': 'ascii'
+ }
+}
diff --git a/tests/test_shells/outputs/bash.daemon.ok b/tests/test_shells/outputs/bash.daemon.ok
new file mode 100644
index 0000000..89907c8
--- /dev/null
+++ b/tests/test_shells/outputs/bash.daemon.ok
@@ -0,0 +1,42 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)
+  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+ foo  
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                       foo 
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/bash.nodaemon.ok b/tests/test_shells/outputs/bash.nodaemon.ok
new file mode 100644
index 0000000..c65dcc1
--- /dev/null
+++ b/tests/test_shells/outputs/bash.nodaemon.ok
@@ -0,0 +1,42 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)
+  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+ foo  
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                       foo 
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/busybox.daemon.ok b/tests/test_shells/outputs/busybox.daemon.ok
new file mode 100644
index 0000000..446d88e
--- /dev/null
+++ b/tests/test_shells/outputs/busybox.daemon.ok
@@ -0,0 +1,29 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/busybox.nodaemon.ok b/tests/test_shells/outputs/busybox.nodaemon.ok
new file mode 100644
index 0000000..afda9a5
--- /dev/null
+++ b/tests/test_shells/outputs/busybox.nodaemon.ok
@@ -0,0 +1,29 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/dash.daemon.ok b/tests/test_shells/outputs/dash.daemon.ok
new file mode 100644
index 0000000..71ca500
--- /dev/null
+++ b/tests/test_shells/outputs/dash.daemon.ok
@@ -0,0 +1,28 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1   USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/dash.nodaemon.ok b/tests/test_shells/outputs/dash.nodaemon.ok
new file mode 100644
index 0000000..c289cd2
--- /dev/null
+++ b/tests/test_shells/outputs/dash.nodaemon.ok
@@ -0,0 +1,28 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1   USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/fish.ok b/tests/test_shells/outputs/fish.ok
new file mode 100644
index 0000000..4d208bb
--- /dev/null
+++ b/tests/test_shells/outputs/fish.ok
@@ -0,0 +1,52 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+ foo  
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+                                                                                                                                                                                                                                                                                                           
+                                                                                                                                                                                                                                                                                                      foo 
+                                                                                                                                                                                                                                                                                                           
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ DEFAULT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ DEFAULT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
diff --git a/tests/test_shells/outputs/ipython.ok b/tests/test_shells/outputs/ipython.ok
new file mode 100644
index 0000000..166604d
--- /dev/null
+++ b/tests/test_shells/outputs/ipython.ok
@@ -0,0 +1,14 @@
+
+ In [2]  bool 42
+ 2>  bool(42)
+ Out[2]  True
+
+ In [3]  bool 44
+ 3>  bool(44)
+ Out[3]  True
+
+ In [4]  class Test(object):
+   pass
+  
+
+ In [5]  exit
diff --git a/tests/test_shells/outputs/mksh.daemon.ok b/tests/test_shells/outputs/mksh.daemon.ok
new file mode 100644
index 0000000..264dff8
--- /dev/null
+++ b/tests/test_shells/outputs/mksh.daemon.ok
@@ -0,0 +1,32 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1
+[1] + Terminated bash -c ...
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo -n
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/mksh.nodaemon.ok b/tests/test_shells/outputs/mksh.nodaemon.ok
new file mode 100644
index 0000000..d8d9d70
--- /dev/null
+++ b/tests/test_shells/outputs/mksh.nodaemon.ok
@@ -0,0 +1,32 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1
+[1] + Terminated bash -c ...
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo -n
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
diff --git a/tests/test_shells/outputs/pdb.module.ok b/tests/test_shells/outputs/pdb.module.ok
new file mode 100644
index 0000000..7554dd6
--- /dev/null
+++ b/tests/test_shells/outputs/pdb.module.ok
@@ -0,0 +1,222 @@
+ 1  pdb-script.py:6 <module>  
+--Call--
+-> class Foo(object):
+ 2  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> class Foo(object):
+ 2  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> def __init__(self):
+ 2  pdb-script.py:6 <module>  pdb-script.py:7 Foo  
+-> @classmethod
+ 2  pdb-script.py:6 <module>  pdb-script.py:13 Foo  
+-> @staticmethod
+ 2  pdb-script.py:6 <module>  pdb-script.py:17 Foo  
+-> def bra(self):
+ 2  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+--Return--
+-> def bra(self):
+ 2  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+-> def brah():
+ 1  pdb-script.py:25 <module>  
+-> f = Foo()
+ 1  pdb-script.py:29 <module>  
+--Call--
+-> def __init__(self):
+ 2  pdb-script.py:29 <module>  pdb-script.py:7 __init__  
+-> nop('__init__')
+ 2  pdb-script.py:29 <module>  pdb-script.py:8 __init__  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+-> self.bar()
+ 2  pdb-script.py:29 <module>  pdb-script.py:9 __init__  
+--Call--
+-> @classmethod
+ 3  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+-> self.baz()
+ 2  pdb-script.py:29 <module>  pdb-script.py:10 __init__  
+--Call--
+-> @staticmethod
+ 3  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:17 baz  
+-> nop(1)
+ 3  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 3  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+-> self.bra()
+ 2  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+--Call--
+-> def bra(self):
+ 3  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Return--
+-> self.bra()
+ 2  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+-> Foo.bar()
+ 1  pdb-script.py:30 <module>  
+--Call--
+-> @classmethod
+ 2  pdb-script.py:30 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 2  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 2  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+-> Foo.baz()
+ 1  pdb-script.py:31 <module>  
+--Call--
+-> @staticmethod
+ 2  pdb-script.py:31 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 2  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 2  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+-> Foo.bra(f)
+ 1  pdb-script.py:32 <module>  
+--Call--
+-> def bra(self):
+ 2  pdb-script.py:32 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+-> f.bar()
+ 1  pdb-script.py:34 <module>  
+--Call--
+-> @classmethod
+ 2  pdb-script.py:34 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 2  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 2  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+-> f.baz()
+ 1  pdb-script.py:35 <module>  
+--Call--
+-> @staticmethod
+ 2  pdb-script.py:35 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 2  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 2  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+-> f.bra()
+ 1  pdb-script.py:36 <module>  
+--Call--
+-> def bra(self):
+ 2  pdb-script.py:36 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+-> brah()
+ 1  pdb-script.py:38 <module>  
+--Call--
+-> def brah():
+ 2  pdb-script.py:38 <module>  pdb-script.py:25 brah  
+-> nop('brah')
+ 2  pdb-script.py:38 <module>  pdb-script.py:26 brah  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> nop('brah')
+ 2  pdb-script.py:38 <module>  pdb-script.py:26 brah  
+--Return--
+-> brah()
+ 1  pdb-script.py:38 <module>  
+--Return--
+ 0  
diff --git a/tests/test_shells/outputs/pdb.subclass.ok b/tests/test_shells/outputs/pdb.subclass.ok
new file mode 100644
index 0000000..d8eba5e
--- /dev/null
+++ b/tests/test_shells/outputs/pdb.subclass.ok
@@ -0,0 +1,217 @@
+ 2  <string>:1 <module>  pdb-script.py:6 <module>  
+--Call--
+-> class Foo(object):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> class Foo(object):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> def __init__(self):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:7 Foo  
+-> @classmethod
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:13 Foo  
+-> @staticmethod
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:17 Foo  
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+--Return--
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+-> def brah():
+ 2  <string>:1 <module>  pdb-script.py:25 <module>  
+-> f = Foo()
+ 2  <string>:1 <module>  pdb-script.py:29 <module>  
+--Call--
+-> def __init__(self):
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:7 __init__  
+-> nop('__init__')
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:8 __init__  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+-> self.bar()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:9 __init__  
+--Call--
+-> @classmethod
+ 4  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 5  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 5  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 5  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+-> self.baz()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:10 __init__  
+--Call--
+-> @staticmethod
+ 4  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:17 baz  
+-> nop(1)
+ 4  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 5  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 5  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 5  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 4  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+-> self.bra()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+--Call--
+-> def bra(self):
+ 4  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 5  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 5  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 5  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Return--
+-> self.bra()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+-> Foo.bar()
+ 2  <string>:1 <module>  pdb-script.py:30 <module>  
+--Call--
+-> @classmethod
+ 3  <string>:1 <module>  pdb-script.py:30 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+-> Foo.baz()
+ 2  <string>:1 <module>  pdb-script.py:31 <module>  
+--Call--
+-> @staticmethod
+ 3  <string>:1 <module>  pdb-script.py:31 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+-> Foo.bra(f)
+ 2  <string>:1 <module>  pdb-script.py:32 <module>  
+--Call--
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:32 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+-> f.bar()
+ 2  <string>:1 <module>  pdb-script.py:34 <module>  
+--Call--
+-> @classmethod
+ 3  <string>:1 <module>  pdb-script.py:34 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+-> f.baz()
+ 2  <string>:1 <module>  pdb-script.py:35 <module>  
+--Call--
+-> @staticmethod
+ 3  <string>:1 <module>  pdb-script.py:35 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+-> f.bra()
+ 2  <string>:1 <module>  pdb-script.py:36 <module>  
+--Call--
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:36 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+-> brah()
+ 2  <string>:1 <module>  pdb-script.py:38 <module>  
+--Call--
+-> def brah():
+ 3  <string>:1 <module>  pdb-script.py:38 <module>  pdb-script.py:25 brah  
+-> nop('brah')
+ 3  <string>:1 <module>  pdb-script.py:38 <module>  pdb-script.py:26 brah  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> nop('brah')
+ 3  <string>:1 <module>  pdb-script.py:38 <module>  pdb-script.py:26 brah  
diff --git a/tests/test_shells/outputs/rc.daemon.ok b/tests/test_shells/outputs/rc.daemon.ok
new file mode 100644
index 0000000..c49b9a3
--- /dev/null
+++ b/tests/test_shells/outputs/rc.daemon.ok
@@ -0,0 +1,24 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment'
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = ()
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `{cat pid} ; sleep 1s
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd $DIR1
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../$DIR2
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  false
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  shell  3rd  «Unicode!»  echo `{
+                                           echo Continuation!
+                                          }
+Continuation!
diff --git a/tests/test_shells/outputs/rc.nodaemon.ok b/tests/test_shells/outputs/rc.nodaemon.ok
new file mode 100644
index 0000000..28376cb
--- /dev/null
+++ b/tests/test_shells/outputs/rc.nodaemon.ok
@@ -0,0 +1,24 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment'
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = ()
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `{cat pid} ; sleep 1s
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd $DIR1
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../$DIR2
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  false
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  shell  3rd  «Unicode!»  echo `{
+    echo Continuation!
+   }
+Continuation!
diff --git a/tests/test_shells/outputs/tcsh.ok b/tests/test_shells/outputs/tcsh.ok
new file mode 100644
index 0000000..07089bf
--- /dev/null
+++ b/tests/test_shells/outputs/tcsh.ok
@@ -0,0 +1,17 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  
diff --git a/tests/test_shells/outputs/zsh.daemon.ok b/tests/test_shells/outputs/zsh.daemon.ok
new file mode 100644
index 0000000..32e80d8
--- /dev/null
+++ b/tests/test_shells/outputs/zsh.daemon.ok
@@ -0,0 +1,52 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1] + terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bindkey -v ; set_theme default
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd   COMMND   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  echo abc
+abc
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  false
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
+ INSERT  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
+ INSERT  …  tmp  shell  3rd  select abc in def ghi jkl
+ select                          do
+ select                           echo $abc
+ select                           break
+ select                          done
+1) def 2) ghi 3) jkl
+                 Select variant  1
+def
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_LEFT"
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+ foo  
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                      foo 
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above
+ INSERT  …  tmp  shell  3rd  hash -d foo=$PWD:h ; cd .
+ INSERT  ~foo  3rd  set_theme_option default.dividers.left.hard \$ABC
+ INSERT $ABC~foo  3rd $ABCtrue
diff --git a/tests/test_shells/outputs/zsh.nodaemon.ok b/tests/test_shells/outputs/zsh.nodaemon.ok
new file mode 100644
index 0000000..3aa285f
--- /dev/null
+++ b/tests/test_shells/outputs/zsh.nodaemon.ok
@@ -0,0 +1,52 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1] + terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bindkey -v ; set_theme default
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd   COMMND   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  echo abc
+abc
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  false
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
+ INSERT  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
+ INSERT  …  tmp  shell  3rd  select abc in def ghi jkl
+ select  do
+ select   echo $abc
+ select   break
+ select  done
+1) def 2) ghi 3) jkl
+ Select variant  1
+def
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_LEFT"
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+ foo  
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                      foo 
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above
+ INSERT  …  tmp  shell  3rd  hash -d foo=$PWD:h ; cd .
+ INSERT  ~foo  3rd  set_theme_option default.dividers.left.hard \$ABC
+ INSERT $ABC~foo  3rd $ABCtrue
diff --git a/tests/test_shells/outputs/zsh.zpython.ok b/tests/test_shells/outputs/zsh.zpython.ok
new file mode 100644
index 0000000..32e80d8
--- /dev/null
+++ b/tests/test_shells/outputs/zsh.zpython.ok
@@ -0,0 +1,52 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1] + terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bindkey -v ; set_theme default
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd   COMMND   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  echo abc
+abc
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  false
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
+ INSERT  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
+ INSERT  …  tmp  shell  3rd  select abc in def ghi jkl
+ select                          do
+ select                           echo $abc
+ select                           break
+ select                          done
+1) def 2) ghi 3) jkl
+                 Select variant  1
+def
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_LEFT"
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+ foo  
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                      foo 
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above
+ INSERT  …  tmp  shell  3rd  hash -d foo=$PWD:h ; cd .
+ INSERT  ~foo  3rd  set_theme_option default.dividers.left.hard \$ABC
+ INSERT $ABC~foo  3rd $ABCtrue
diff --git a/tests/test_shells/pdb-main.py b/tests/test_shells/pdb-main.py
new file mode 100644
index 0000000..37af785
--- /dev/null
+++ b/tests/test_shells/pdb-main.py
@@ -0,0 +1,24 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import pdb
+import os
+import sys
+
+from powerline.bindings.pdb import use_powerline_prompt
+
+
+@use_powerline_prompt
+class Pdb(pdb.Pdb):
+ pass
+
+
+p = Pdb()
+
+
+script = os.path.join(os.path.dirname(__file__), 'pdb-script.py')
+with open(script, 'r') as fd:
+ code = compile(fd.read(), script, 'exec')
+
+
+p.run('exec(code)', globals={'code': code})
diff --git a/tests/test_shells/pdb-script.py b/tests/test_shells/pdb-script.py
new file mode 100644
index 0000000..40db5e8
--- /dev/null
+++ b/tests/test_shells/pdb-script.py
@@ -0,0 +1,38 @@
+# vim:fileencoding=utf-8:noet
+def nop(_):
+ pass
+
+
+class Foo(object):
+ def __init__(self):
+ nop('__init__')
+ self.bar()
+ self.baz()
+ self.bra()
+
+ @classmethod
+ def bar(cls):
+ nop(cls.__name__)
+
+ @staticmethod
+ def baz():
+ nop(1)
+
+ def bra(self):
+ nop(self.__class__.__name__)
+
+
+def brah():
+ nop('brah')
+
+
+f = Foo()
+Foo.bar()
+Foo.baz()
+Foo.bra(f)
+
+f.bar()
+f.baz()
+f.bra()
+
+brah()
diff --git a/tests/test_shells/postproc.py b/tests/test_shells/postproc.py
new file mode 100755
index 0000000..7926155
--- /dev/null
+++ b/tests/test_shells/postproc.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import socket
+import sys
+import codecs
+import platform
+import re
+
+
+test_root = os.environ['TEST_ROOT']
+test_type = sys.argv[1]
+test_client = sys.argv[2]
+shell = sys.argv[3]
+fname = os.path.join(test_root, '.'.join((shell, test_type, test_client, 'full.log')))
+new_fname = os.path.join(test_root, '.'.join((shell, test_type, test_client, 'log')))
+pid_fname = os.path.join(test_root, '3rd', 'pid')
+
+is_pypy = platform.python_implementation() == 'PyPy'
+
+
+try:
+ with open(pid_fname, 'r') as P:
+ pid = P.read().strip()
+except IOError:
+ pid = None
+hostname = socket.gethostname()
+user = os.environ['USER']
+
+REFS_RE = re.compile(r'^\[\d+ refs\]\n')
+IPYPY_DEANSI_RE = re.compile(r'\033(?:\[(?:\?\d+[lh]|[^a-zA-Z]+[a-ln-zA-Z])|[=>])')
+ZSH_HL_RE = re.compile(r'\033\[\?\d+[hl]')
+
+start_str = 'cd "$TEST_ROOT"/3rd'
+if shell == 'pdb':
+ start_str = 'class Foo(object):'
+
+with codecs.open(fname, 'r', encoding='utf-8') as R:
+ with codecs.open(new_fname, 'w', encoding='utf-8') as W:
+ found_cd = False
+ i = -1
+ for line in (R if shell != 'fish' else R.read().split('\n')):
+ i += 1
+ if not found_cd:
+ found_cd = (start_str in line)
+ continue
+ if 'true is the last line' in line:
+ break
+ line = line.translate({
+ ord('\r'): None
+ })
+ if REFS_RE.match(line):
+ continue
+ line = line.replace(hostname, 'HOSTNAME')
+ line = line.replace(user, 'USER')
+ if pid is not None:
+ line = line.replace(pid, 'PID')
+ if shell == 'zsh':
+ line = line.replace('\033[0m\033[23m\033[24m\033[J', '')
+ line = ZSH_HL_RE.subn('', line)[0]
+ elif shell == 'fish':
+ res = ''
+ try:
+ while line.index('\033[0;'):
+ start = line.index('\033[0;')
+ end = line.index('\033[0m', start)
+ res += line[start:end + 4] + '\n'
+ line = line[end + 4:]
+ except ValueError:
+ pass
+ line = res
+ elif shell == 'tcsh':
+ try:
+ start = line.index('\033[0;')
+ end = line.index(' ', start)
+ line = line[start:end] + '\n'
+ except ValueError:
+ line = ''
+ elif shell == 'mksh':
+ # Output is different in travis: on my machine I see full
+ # command, in travis it is truncated just after `true`.
+ if line.startswith('[1] + Terminated'):
+ line = '[1] + Terminated bash -c ...\n'
+ elif shell == 'dash':
+ # Position of this line is not stable: it may go both before and
+ # after the next line
+ if line.startswith('[1] + Terminated'):
+ continue
+ elif shell == 'ipython' and is_pypy:
+ try:
+ end_idx = line.rindex('\033[0m')
+ try:
+ idx = line[:end_idx].rindex('\033[1;1H')
+ except ValueError:
+ idx = line[:end_idx].rindex('\033[?25h')
+ line = line[idx + len('\033[1;1H'):]
+ except ValueError:
+ pass
+ try:
+ data_end_idx = line.rindex('\033[1;1H')
+ line = line[:data_end_idx] + '\n'
+ except ValueError:
+ pass
+ if line == '\033[1;1H\n':
+ continue
+ was_empty = line == '\n'
+ line = IPYPY_DEANSI_RE.subn('', line)[0]
+ if line == '\n' and not was_empty:
+ line = ''
+ elif shell == 'rc':
+ if line == 'read() failed: Connection reset by peer\n':
+ line = ''
+ elif shell == 'pdb':
+ if is_pypy:
+ if line == '\033[?1h\033=\033[?25l\033[1A\n':
+ line = ''
+ line = IPYPY_DEANSI_RE.subn('', line)[0]
+ if line == '\n':
+ line = ''
+ if line.startswith(('>',)):
+ line = ''
+ elif line == '-> self.quitting = 1\n':
+ line = '-> self.quitting = True\n'
+ elif line == '\n':
+ line = ''
+ if line == '-> self.quitting = True\n':
+ break
+ W.write(line)
diff --git a/tests/test_shells/run_script.py b/tests/test_shells/run_script.py
new file mode 100755
index 0000000..2eebca1
--- /dev/null
+++ b/tests/test_shells/run_script.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import argparse
+import os
+import re
+
+from time import sleep
+from subprocess import check_call
+from io import BytesIO
+
+import pexpect
+
+
+def get_argparser(ArgumentParser=argparse.ArgumentParser):
+ parser = ArgumentParser(description='Run powerline shell test using pexpect')
+ parser.add_argument('--wait-for-echo', action='store_true', help='Wait until the input is echoed back.')
+ parser.add_argument('--type', metavar='TYPE', help='Test type (daemon, nodaemon, …).')
+ parser.add_argument('--client', metavar='CLIENT', help='Type of the client used (C, shell, zpython, …).')
+ parser.add_argument('--shell', metavar='SHELL', help='Shell name.')
+ parser.add_argument('command', nargs=argparse.REMAINDER, metavar='COMMAND',
+ help='Command to run and its argument.')
+ return parser
+
+
+def main():
+ test_root = os.environ['TEST_ROOT']
+ parser = get_argparser()
+ args = parser.parse_args()
+
+ shell = args.shell or args.command[0]
+ test_type = args.type or shell
+ test_client = args.client or test_type
+
+ log_file_base = '{0}.{1}.{2}'.format(shell, test_type, test_client)
+ full_log_file_name = os.path.join(test_root, '{0}.full.log'.format(log_file_base))
+
+ local_paths = [
+ os.path.abspath(os.path.join(test_root, 'path')),
+ os.path.abspath('scripts'),
+ ]
+
+ if test_type == 'fish':
+ local_paths += ['/usr/bin', '/bin']
+
+ python_paths = os.environ.get('PYTHONPATH', '')
+ if python_paths:
+ python_paths = ':' + python_paths
+ python_paths = os.path.abspath('.') + python_paths
+
+ environ = {
+ 'LANG': 'en_US.UTF-8',
+ 'PATH': os.pathsep.join(local_paths),
+ 'TERM': 'screen-256color',
+ 'DIR1': os.environ['DIR1'],
+ 'DIR2': os.environ['DIR2'],
+ 'XDG_CONFIG_HOME': os.path.abspath(os.path.join(test_root, 'fish_home')),
+ 'IPYTHONDIR': os.path.abspath(os.path.join(test_root, 'ipython_home')),
+ 'PYTHONPATH': python_paths,
+ 'POWERLINE_CONFIG_OVERRIDES': os.environ.get('POWERLINE_CONFIG_OVERRIDES', ''),
+ 'POWERLINE_THEME_OVERRIDES': os.environ.get('POWERLINE_THEME_OVERRIDES', ''),
+ 'POWERLINE_CONFIG_PATHS': os.path.abspath(os.path.join('powerline', 'config_files')),
+ 'POWERLINE_COMMAND_ARGS': os.environ.get('POWERLINE_COMMAND_ARGS', ''),
+ 'POWERLINE_COMMAND': os.environ.get('POWERLINE_COMMAND', ''),
+ 'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH', ''),
+ 'TEST_ROOT': test_root,
+ }
+
+ os.environ['PATH'] = environ['PATH']
+
+ if test_type == 'daemon':
+ environ['POWERLINE_SHELL_CONTINUATION'] = '1'
+ environ['POWERLINE_SHELL_SELECT'] = '1'
+
+ if test_type != 'zpython' and shell == 'zsh':
+ environ['POWERLINE_NO_ZSH_ZPYTHON'] = '1'
+
+ sio = BytesIO()
+
+ child = pexpect.spawn(
+ args.command[0],
+ args.command[1:],
+ env=environ,
+ logfile=sio,
+ timeout=30,
+ )
+ child.expect(re.compile(b'.*'))
+ sleep(0.5)
+ child.setwinsize(1, 300)
+
+ with open(os.path.join('tests', 'test_shells', 'inputs', shell), 'rb') as F:
+ if not args.wait_for_echo:
+ child.send(F.read())
+ else:
+ for line in F:
+ child.send(line)
+ sleep(1)
+ # TODO Implement something more smart
+
+ with open(full_log_file_name, 'wb') as LF:
+ while True:
+ try:
+ s = child.read_nonblocking(1000)
+ except pexpect.TIMEOUT:
+ break
+ except pexpect.EOF:
+ break
+ else:
+ LF.write(s)
+
+ child.close(force=True)
+
+ check_call([
+ os.path.join(test_root, 'path', 'python'),
+ os.path.join('tests', 'test_shells', 'postproc.py'),
+ test_type, test_client, shell
+ ])
+ pidfile = os.path.join(test_root, '3rd', 'pid')
+ if os.path.exists(pidfile):
+ os.unlink(pidfile)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/test_shells/test.sh b/tests/test_shells/test.sh
new file mode 100755
index 0000000..4494302
--- /dev/null
+++ b/tests/test_shells/test.sh
@@ -0,0 +1,491 @@
+#!/bin/sh
+. tests/shlib/common.sh
+
+enter_suite shell final
+
+if test $# -eq 0 ; then
+ FAST=1
+fi
+ONLY_SHELL="$1"
+ONLY_TEST_TYPE="$2"
+ONLY_TEST_CLIENT="$3"
+
+export PYTHON
+
+if test "$ONLY_SHELL" = "--help" ; then
+cat << EOF
+Usage:
+ $0 [[[ONLY_SHELL | ""] (ONLY_TEST_TYPE | "")] (ONLY_TEST_CLIENT | "")]
+
+ONLY_SHELL: execute only tests for given shell
+ONLY_TEST_TYPE: execute only "daemon" or "nodaemon" tests
+ONLY_TEST_CLIENT: use only given test client (one of C, python, render, shell)
+EOF
+exit 0
+fi
+
+check_screen_log() {
+ TEST_TYPE="$1"
+ TEST_CLIENT="$2"
+ SH="$3"
+ if test -e "$ROOT/tests/test_shells/outputs/${SH}.${TEST_TYPE}.ok" ; then
+ diff -a -u "$ROOT/tests/test_shells/outputs/${SH}.${TEST_TYPE}.ok" \
+ "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log"
+ return $?
+ elif test -e "$ROOT/tests/test_shells/outputs/${SH}.ok" ; then
+ diff -a -u "$ROOT/tests/test_shells/outputs/${SH}.ok" \
+ "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log"
+ return $?
+ else
+ cat "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log"
+ return 1
+ fi
+}
+
+# HACK: get newline for use in strings given that "\n" and $'' do not work.
+NL="$(printf '\nE')"
+NL="${NL%E}"
+
+print_full_output() {
+ TEST_TYPE="$1"
+ TEST_CLIENT="$2"
+ SH="$3"
+ echo "Full output:"
+ echo '============================================================'
+ cat "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.full.log"
+ echo
+ echo '____________________________________________________________'
+ if test "$POWERLINE_TEST_NO_CAT_V" != "1" ; then
+ echo "Full output (cat -v):"
+ echo '============================================================'
+ cat -v "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.full.log"
+ echo
+ echo '____________________________________________________________'
+ fi
+}
+
+do_run_test() {
+ TEST_TYPE="$1"
+ shift
+ TEST_CLIENT="$1"
+ shift
+ SH="$1"
+
+ local wait_for_echo_arg=
+ if ( \
+ test "${SH}" = "dash" \
+ || ( \
+ test "${SH}" = "pdb" \
+ && ( \
+ ( \
+ test "$PYTHON_VERSION_MAJOR" -eq 3 \
+ && test "$PYTHON_VERSION_MINOR" -eq 2 \
+ && test "$PYTHON_IMPLEMENTATION" = "CPython" \
+ ) \
+ || test "$PYTHON_IMPLEMENTATION" = "PyPy" \
+ ) \
+ ) \
+ || ( \
+ test "${SH}" = "ipython" \
+ && test "$("${PYTHON}" -mIPython --version | head -n1 | cut -d. -f1)" -ge 5 \
+ ) \
+ ) ; then
+ wait_for_echo_arg="--wait-for-echo"
+ fi
+ "${PYTHON}" tests/test_shells/run_script.py \
+ $wait_for_echo_arg --type=${TEST_TYPE} --client=${TEST_CLIENT} --shell=${SH} \
+ "$@"
+ if ! check_screen_log ${TEST_TYPE} ${TEST_CLIENT} ${SH} ; then
+ echo '____________________________________________________________'
+ if test "$POWERLINE_TEST_NO_CAT_V" != "1" ; then
+ # Repeat the diff to make it better viewable in travis output
+ echo "Diff (cat -v):"
+ echo '============================================================'
+ check_screen_log ${TEST_TYPE} ${TEST_CLIENT} ${SH} | cat -v
+ echo '____________________________________________________________'
+ fi
+ echo -n "Failed ${SH}. "
+ print_full_output ${TEST_TYPE} ${TEST_CLIENT} ${SH}
+ case "${SH}" in
+ *ksh)
+ "$TEST_ROOT/path/${SH}" -c 'echo ${KSH_VERSION}'
+ ;;
+ dash)
+ # ?
+ ;;
+ busybox)
+ busybox --help
+ ;;
+ *)
+ "$TEST_ROOT/path/${SH}" --version
+ ;;
+ esac
+ if which dpkg >/dev/null ; then
+ dpkg -s ${SH}
+ fi
+ return 1
+ fi
+ return 0
+}
+
+run_test() {
+ TEST_TYPE="$1"
+ TEST_CLIENT="$2"
+ SH="$3"
+ local attempts=3
+ if test -n "$ONLY_SHELL$ONLY_TEST_TYPE$ONLY_TEST_CLIENT" ; then
+ attempts=1
+ fi
+ while test $attempts -gt 0 ; do
+ rm -f "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log"
+ rm -f "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.full.log"
+ do_run_test "$@" && return 0
+ attempts=$(( attempts - 1 ))
+ done
+ return 1
+}
+
+make_test_root
+
+git init "$TEST_ROOT/3rd"
+git --git-dir="$TEST_ROOT/3rd/.git" checkout -b BRANCH
+export DIR1=""
+export DIR2=""
+mkdir "$TEST_ROOT/3rd/$DIR1"
+mkdir "$TEST_ROOT/3rd/$DIR2"
+mkdir "$TEST_ROOT"/3rd/'\[\]'
+mkdir "$TEST_ROOT"/3rd/'%%'
+mkdir "$TEST_ROOT"/3rd/'#[bold]'
+mkdir "$TEST_ROOT"/3rd/'(echo)'
+mkdir "$TEST_ROOT"/3rd/'$(echo)'
+mkdir "$TEST_ROOT"/3rd/'`echo`'
+mkdir "$TEST_ROOT"/3rd/'«Unicode!»'
+
+mkdir "$TEST_ROOT/fish_home"
+mkdir "$TEST_ROOT/fish_home/fish"
+mkdir "$TEST_ROOT/fish_home/fish/generated_completions"
+cp -r "$ROOT/tests/test_shells/ipython_home" "$TEST_ROOT"
+
+mkdir "$TEST_ROOT/path"
+ln -s "$(which "${PYTHON}")" "$TEST_ROOT/path/python"
+ln -s "$(which env)" "$TEST_ROOT/path"
+ln -s "$(which git)" "$TEST_ROOT/path"
+ln -s "$(which sleep)" "$TEST_ROOT/path"
+ln -s "$(which cat)" "$TEST_ROOT/path"
+ln -s "$(which false)" "$TEST_ROOT/path"
+ln -s "$(which true)" "$TEST_ROOT/path"
+ln -s "$(which kill)" "$TEST_ROOT/path"
+ln -s "$(which echo)" "$TEST_ROOT/path"
+ln -s "$(which which)" "$TEST_ROOT/path"
+ln -s "$(which dirname)" "$TEST_ROOT/path"
+ln -s "$(which wc)" "$TEST_ROOT/path"
+ln -s "$(which stty)" "$TEST_ROOT/path"
+ln -s "$(which cut)" "$TEST_ROOT/path"
+ln -s "$(which bc)" "$TEST_ROOT/path"
+ln -s "$(which expr)" "$TEST_ROOT/path"
+ln -s "$(which mktemp)" "$TEST_ROOT/path"
+ln -s "$(which grep)" "$TEST_ROOT/path"
+ln -s "$(which sed)" "$TEST_ROOT/path"
+ln -s "$(which rm)" "$TEST_ROOT/path"
+ln -s "$(which tr)" "$TEST_ROOT/path"
+ln -s "$(which uname)" "$TEST_ROOT/path"
+ln -s "$(which test)" "$TEST_ROOT/path"
+ln -s "$(which pwd)" "$TEST_ROOT/path"
+ln -s "$(which hostname)" "$TEST_ROOT/path"
+ln -s "$ROOT/tests/test_shells/bgscript.sh" "$TEST_ROOT/path"
+ln -s "$ROOT/tests/test_shells/waitpid.sh" "$TEST_ROOT/path"
+if which socat ; then
+ ln -s "$(which socat)" "$TEST_ROOT/path"
+fi
+for pexe in powerline powerline-config powerline-render powerline.sh powerline.py ; do
+ if test -e "$ROOT/scripts/$pexe" ; then
+ ln -s "$ROOT/scripts/$pexe" "$TEST_ROOT/path"
+ elif test -e client/$pexe ; then
+ ln -s "$ROOT/client/$pexe" "$TEST_ROOT/path"
+ elif which $pexe ; then
+ ln -s "$(which $pexe)" "$TEST_ROOT/path"
+ else
+ echo "Executable $pexe was not found"
+ exit 1
+ fi
+done
+
+ln -s python "$TEST_ROOT/path/pdb"
+PDB_PYTHON=pdb
+ln -s python "$TEST_ROOT/path/ipython"
+IPYTHON_PYTHON=ipython
+
+if test -z "$POWERLINE_RC_EXE" ; then
+ if which rc-status >/dev/null ; then
+ # On Gentoo `rc` executable is from OpenRC. Thus app-shells/rc instals
+ # `rcsh` executable.
+ POWERLINE_RC_EXE=rcsh
+ else
+ POWERLINE_RC_EXE=rc
+ fi
+fi
+
+if which "$POWERLINE_RC_EXE" >/dev/null ; then
+ ln -s "$(which $POWERLINE_RC_EXE)" "$TEST_ROOT/path/rc"
+fi
+
+exes="bash zsh busybox tcsh mksh"
+
+if test "$TRAVIS" != "true" ; then
+ # For some reason fish does not work on travis
+ exes="$exes fish"
+fi
+
+# dash has some problems with job control
+#exes="$exes dash"
+
+for exe in $exes ; do
+ if which $exe >/dev/null ; then
+ if test "$exe" = "fish" ; then
+ fish_version="$(fish --version 2>&1)"
+ fish_version="${fish_version##* }"
+ fish_version_major="${fish_version%%.*}"
+ if test "$fish_version_major" != "$fish_version" ; then
+ # No dot is in development version compiled by bot-ci
+ fish_version_minor="${fish_version#*.}"
+ fish_version_patch="${fish_version_minor#*.}"
+ fish_version_dev="${fish_version_patch#*-}"
+ if test "$fish_version_dev" = "$fish_version_patch" ; then
+ fish_version_dev=""
+ fi
+ fish_version_minor="${fish_version_minor%%.*}"
+ fish_version_patch="${fish_version_patch%%-*}"
+ if test $fish_version_major -lt 2 || ( \
+ test $fish_version_major -eq 2 && (\
+ test $fish_version_minor -lt 1 || (\
+ test $fish_version_minor -eq 1 &&
+ test $fish_version_patch -lt 2 && \
+ test -z "$fish_version_dev"
+ ) \
+ ) \
+ ) ; then
+ continue
+ fi
+ fi
+ fi
+ ln -s "$(which $exe)" "$TEST_ROOT/path"
+ fi
+done
+
+mkdir "$TEST_ROOT/home"
+export HOME="$TEST_ROOT/home"
+
+unset ENV
+
+export ADDRESS="powerline-ipc-test-$$"
+export PYTHON
+echo "Powerline address: $ADDRESS"
+
+check_test_client() {
+ local executable="$1"
+ local client_type="$2"
+ local actual_mime_type="$(
+ file --mime-type --brief --dereference "$TEST_ROOT/path/$executable" \
+ | cut -d/ -f1
+ )"
+ local expected_mime_type
+ case "$client_type" in
+ C) expected_mime_type="application/x-executable" ;;
+ python) expected_mime_type="text/x-python" ;;
+ render) expected_mime_type="text/x-python" ;;
+ shell) expected_mime_type="text/x-shellscript" ;;
+ esac
+ expected_mime_type="${expected_mime_type%/*}"
+ if test "$expected_mime_type" != "$actual_mime_type" ; then
+ fail "MIME-$executable" "M" "Expected $executable to have MIME type $expected_mime_type, but got $actual_mime_type"
+ fi
+}
+
+if ( \
+ test -z "${ONLY_SHELL}" \
+ || test "${ONLY_SHELL%sh}" != "${ONLY_SHELL}" \
+ || test "${ONLY_SHELL}" = "busybox" \
+ || test "${ONLY_SHELL}" = "rc" \
+) ; then
+ scripts/powerline-config shell command
+
+ for TEST_TYPE in "daemon" "nodaemon" ; do
+ if test -n "$ONLY_TEST_TYPE" && test "$ONLY_TEST_TYPE" != "$TEST_TYPE"
+ then
+ continue
+ fi
+ if test "$FAST" = 1 ; then
+ if test $TEST_TYPE = daemon ; then
+ VARIANTS=3
+ else
+ VARIANTS=4
+ fi
+ EXETEST="$(( ${RANDOM:-`date +%N | sed s/^0*//`} % $VARIANTS ))"
+ echo "Execute tests: $EXETEST"
+ fi
+
+ if test $TEST_TYPE = daemon ; then
+ sh -c '
+ echo $$ > "$TEST_ROOT/daemon_pid"
+ exec "$PYTHON" ./scripts/powerline-daemon -s"$ADDRESS" -f >"$TEST_ROOT/daemon_log" 2>&1
+ ' &
+ fi
+ echo "> Testing $TEST_TYPE"
+ I=-1
+ for POWERLINE_COMMAND in \
+ powerline \
+ powerline-render \
+ powerline.py \
+ powerline.sh
+ do
+ case "$POWERLINE_COMMAND" in
+ powerline) TEST_CLIENT=C ;;
+ powerline-render) TEST_CLIENT=render ;;
+ powerline.py) TEST_CLIENT=python ;;
+ powerline.sh) TEST_CLIENT=shell ;;
+ esac
+ check_test_client "$POWERLINE_COMMAND" $TEST_CLIENT
+ if test "$TEST_CLIENT" = render && test "$TEST_TYPE" = daemon ; then
+ continue
+ fi
+ I="$(( I + 1 ))"
+ if test "$TEST_CLIENT" = "C" && ! test -x "$ROOT/scripts/powerline"
+ then
+ if which powerline >/dev/null ; then
+ POWERLINE_COMMAND=powerline
+ else
+ continue
+ fi
+ fi
+ if ( \
+ test "$TEST_CLIENT" = "shell" \
+ && ! test -x "$TEST_ROOT/path/socat" \
+ ) ; then
+ continue
+ fi
+ if ( \
+ test -n "$ONLY_TEST_CLIENT" \
+ && test "$TEST_CLIENT" != "$ONLY_TEST_CLIENT" \
+ ) ; then
+ continue
+ fi
+ export POWERLINE_COMMAND_ARGS="--socket $ADDRESS"
+ export POWERLINE_COMMAND="$POWERLINE_COMMAND"
+ echo ">> powerline command is ${POWERLINE_COMMAND:-empty}"
+ J=-1
+ for TEST_COMMAND in \
+ "bash --norc --noprofile -i" \
+ "zsh -f -i" \
+ "fish -i" \
+ "tcsh -f -i" \
+ "busybox ash -i" \
+ "mksh -i" \
+ "dash -i" \
+ "rc -i -p"
+ do
+ J="$(( J + 1 ))"
+ if test "$FAST" = 1 ; then
+ if test $(( (I + J) % $VARIANTS )) -ne $EXETEST ; then
+ continue
+ fi
+ fi
+ SH="${TEST_COMMAND%% *}"
+ if test -n "$ONLY_SHELL" && test "$ONLY_SHELL" != "$SH" ; then
+ continue
+ fi
+ if ! test -x "$TEST_ROOT/path/$SH" ; then
+ continue
+ fi
+ echo ">>> $(readlink "$TEST_ROOT/path/$SH")"
+ if ! run_test $TEST_TYPE $TEST_CLIENT $TEST_COMMAND ; then
+ fail "$SH-$TEST_TYPE-$TEST_CLIENT:test" F \
+ "Failed checking $TEST_COMMAND"
+ fi
+ done
+ done
+ if test $TEST_TYPE = daemon ; then
+ "$PYTHON" ./scripts/powerline-daemon -s"$ADDRESS" -k
+ wait $(cat "$TEST_ROOT/daemon_pid")
+ if ! test -z "$(cat "$TEST_ROOT/daemon_log")" ; then
+ echo '____________________________________________________________'
+ echo "Daemon log:"
+ echo '============================================================'
+ cat "$TEST_ROOT/daemon_log"
+ fail "$SH-$TEST_TYPE-$TEST_CLIENT:log" E \
+ "Non-empty daemon log for ${TEST_COMMAND}"
+ fi
+ fi
+ done
+fi
+
+if "$PYTHON" scripts/powerline-daemon -s"$ADDRESS" \
+ > "$TEST_ROOT/daemon_log_2" 2>&1
+then
+ sleep 1
+ "$PYTHON" scripts/powerline-daemon -s"$ADDRESS" -k
+else
+ fail "daemon:run" F "Daemon exited with status $?"
+fi
+
+if ! test -z "$(cat "$TEST_ROOT/daemon_log_2")" ; then
+ echo '____________________________________________________________'
+ echo "Daemon log (2nd):"
+ echo '============================================================'
+ cat "$TEST_ROOT/daemon_log_2"
+ fail "daemon:log" E "Daemon run with non-empty log"
+fi
+
+if ( test -z "${ONLY_SHELL}" || test "${ONLY_SHELL}" = "zsh" ) \
+ && ( test -z "${ONLY_TEST_TYPE}" || test "${ONLY_TEST_TYPE}" = "zpython" ) \
+ && "$TEST_ROOT/path/zsh" "$ROOT/tests/test_shells/zsh_test_script.zsh"
+then
+ echo "> zpython"
+ if ! run_test zpython zpython zsh -f -i ; then
+ fail "zsh-zpython:test" F "Failed checking zsh -f -i"
+ fi
+fi
+
+if test -z "${ONLY_SHELL}" || test "${ONLY_SHELL}" = "pdb" ; then
+ if test "$PYTHON_IMPLEMENTATION" != "PyPy" ; then
+ if test -z "${ONLY_TEST_TYPE}" || test "${ONLY_TEST_TYPE}" = "subclass"
+ then
+ echo "> pdb subclass"
+ if ! run_test subclass python $PDB_PYTHON \
+ "$ROOT/tests/test_shells/pdb-main.py"
+ then
+ fail --allow-failure "pdb-subclass:test" F \
+ "Failed checking $PDB_PYTHON $ROOT/tests/test_shells/pdb-main.py"
+ fi
+ fi
+ if test -z "${ONLY_TEST_TYPE}" || test "${ONLY_TEST_TYPE}" = "module" ; then
+ echo "> pdb module"
+ MODULE="powerline.bindings.pdb"
+ if test "$PYTHON_MM" = "2.6" ; then
+ MODULE="powerline.bindings.pdb.__main__"
+ fi
+ if ! run_test module python "$PDB_PYTHON" -m"$MODULE" \
+ "$ROOT/tests/test_shells/pdb-script.py"
+ then
+ fail --allow-failure "pdb-module:test" F \
+ "Failed checking $PDB_PYTHON -m$MODULE $ROOT/tests/test_shells/pdb-script"
+ fi
+ fi
+ fi
+fi
+
+if test -z "${ONLY_SHELL}" || test "${ONLY_SHELL}" = "ipython" ; then
+ if "${PYTHON}" -c "try: import IPython${NL}except ImportError: raise SystemExit(1)" ; then
+ # Define some overrides which should be ignored by IPython.
+ export POWERLINE_CONFIG_OVERRIDES='common.term_escape_style=fbterm'
+ export POWERLINE_THEME_OVERRIDES='in.segments.left=[]'
+ echo "> ipython"
+ if ! run_test ipython ipython ${IPYTHON_PYTHON} -mIPython ; then
+ # Do not allow ipython tests to spoil the build
+ fail --allow-failure "ipython:test" F "Failed checking ${IPYTHON_PYTHON} -mIPython"
+ fi
+ unset POWERLINE_THEME_OVERRIDES
+ unset POWERLINE_CONFIG_OVERRIDES
+ fi
+fi
+
+exit_suite
diff --git a/tests/test_shells/waitpid.sh b/tests/test_shells/waitpid.sh
new file mode 100755
index 0000000..8d98e21
--- /dev/null
+++ b/tests/test_shells/waitpid.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+while ! test -e pid ; do
+ sleep 0.1s
+done
diff --git a/tests/test_shells/zsh_test_script.zsh b/tests/test_shells/zsh_test_script.zsh
new file mode 100644
index 0000000..3957f56
--- /dev/null
+++ b/tests/test_shells/zsh_test_script.zsh
@@ -0,0 +1,11 @@
+set -e
+set -x
+. tests/bot-ci/scripts/common/main.sh
+zmodload zpython || zmodload libzpython
+zpython 'import zsh'
+zpython 'import platform'
+zpython 'zsh.setvalue("ZSH_PYTHON_VERSION", platform.python_version())'
+zpython 'zsh.setvalue("ZSH_PYTHON_IMPLEMENTATION", platform.python_implementation())'
+
+[[ $ZSH_PYTHON_IMPLEMENTATION = $PYTHON_IMPLEMENTATION ]]
+[[ $ZSH_PYTHON_VERSION = $PYTHON_VERSION ]]
diff --git a/tests/test_vim/pyfiles/setup_statusline_catcher.py b/tests/test_vim/pyfiles/setup_statusline_catcher.py
new file mode 100644
index 0000000..014a2e9
--- /dev/null
+++ b/tests/test_vim/pyfiles/setup_statusline_catcher.py
@@ -0,0 +1,18 @@
+# vim:fileencoding=utf-8:noet
+import json
+
+import vim
+
+from powerline.lib.unicode import u
+
+
+_powerline_old_render = powerline.render # NOQA
+
+
+def _powerline_test_render_function(*args, **kwargs):
+ ret = _powerline_old_render(*args, **kwargs)
+ vim.eval('add(g:statusline_values, %s)' % json.dumps(u(ret)))
+ return ret
+
+
+powerline.render = _powerline_test_render_function # NOQA
diff --git a/tests/test_vim/test.sh b/tests/test_vim/test.sh
new file mode 100755
index 0000000..ecd0285
--- /dev/null
+++ b/tests/test_vim/test.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+. tests/shlib/common.sh
+. tests/shlib/vterm.sh
+. tests/shlib/vim.sh
+
+enter_suite vim final
+
+vterm_setup vim
+
+# Define some overrides. These ones must be ignored and do not affect Vim
+# status/tab lines.
+export POWERLINE_CONFIG_OVERRIDES='common.default_top_theme=ascii'
+export POWERLINE_THEME_OVERRIDES='default.segments.left=[]'
+
+test_script() {
+ local vim="$1" ; shift
+ local script="$1" ; shift
+ local allow_failure_arg="$1" ; shift
+ echo "Running script $script with $vim"
+ if ! test -e "$vim" ; then
+ return 0
+ fi
+ if ! script="$script" "$vim" -u NONE -c 'source $script' \
+ || test -f message.fail
+ then
+ local test_name="${script##*/}"
+ fail $allow_failure_arg "${test_name%.vim}" \
+ F "Failed script $script run with $vim"
+ if test -e message.fail ; then
+ cat message.fail >&2
+ rm message.fail
+ fi
+ fi
+}
+
+TEST_SCRIPT_ROOT="$ROOT/tests/test_vim/tests"
+
+cd "$TEST_ROOT"
+
+for script in "$TEST_SCRIPT_ROOT"/*.vim ; do
+ if test "${script%.old.vim}" = "${script}" ; then
+ test_script "$NEW_VIM" "$script" ""
+ fi
+done
+
+if test "$PYTHON_VERSION_MAJOR.$PYTHON_VERSION_MINOR" = "2.7" ; then
+ ALLOW_FAILURE_ARG=--allow-failure
+else
+ ALLOW_FAILURE_ARG=
+fi
+
+if test -e "$OLD_VIM" ; then
+ for script in "$TEST_SCRIPT_ROOT"/*.old.vim ; do
+ test_script "$OLD_VIM" "$script" "$ALLOW_FAILURE_ARG"
+ done
+fi
+
+vterm_shutdown
+
+exit_suite
diff --git a/tests/test_vim/tests/commandt_plugin.vim b/tests/test_vim/tests/commandt_plugin.vim
new file mode 100755
index 0000000..9f944b9
--- /dev/null
+++ b/tests/test_vim/tests/commandt_plugin.vim
@@ -0,0 +1,17 @@
+#!/usr/bin/vim -S
+set nocompatible
+set columns=80
+execute 'source' fnameescape(expand('<sfile>:p:h:h').'/vim_utils.vim')
+call EnablePlugins('command-t')
+call SourcePowerline()
+let g:statusline_values = []
+call PyFile('setup_statusline_catcher')
+execute 'CommandTBuffer'|call feedkeys("\<C-c>")
+call RunPython('powerline.render = _powerline_old_render')
+let g:expected_statusline = '%#Pl_231_16777215_240_5789784_bold# Command-T %#Pl_231_16777215_240_5789784_NONE# %#Pl_231_16777215_240_5789784_bold#BufferFinder %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                                    '
+call CheckMessages()
+if index(g:statusline_values, g:expected_statusline) == -1
+ call CheckStatuslineValue(get(g:statusline_values, -1, ''), g:expected_statusline)
+ cquit
+endif
+qall
diff --git a/tests/test_vim/tests/empty_encoding.old.vim b/tests/test_vim/tests/empty_encoding.old.vim
new file mode 100755
index 0000000..124a10a
--- /dev/null
+++ b/tests/test_vim/tests/empty_encoding.old.vim
@@ -0,0 +1,32 @@
+#!/usr/bin/vim -S
+if has('multi_byte')
+ if empty(&encoding)
+ call writefile(['&encoding option value is empty, even though Vim has +multibyte'], 'message.fail')
+ cquit
+ endif
+ qall
+endif
+if !empty(&encoding)
+ call writefile(['&encoding option value is not empty, even though Vim does not have +multibyte'], 'message.fail')
+ cquit
+endif
+
+let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files']
+
+try
+ source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim
+catch
+ call writefile(['Unexpected exception:', v:exception], 'message.fail')
+ cquit
+endtry
+set ls=2
+redrawstatus!
+redir => g:messages
+ messages
+redir END
+let mess=split(g:messages, "\n")
+if len(mess)>1
+ call writefile(['Unexpected message(s):']+mess, 'message.fail')
+ cquit
+endif
+qall!
diff --git a/tests/test_vim/tests/foreign_stl_override.vim b/tests/test_vim/tests/foreign_stl_override.vim
new file mode 100644
index 0000000..2a5b8c0
--- /dev/null
+++ b/tests/test_vim/tests/foreign_stl_override.vim
@@ -0,0 +1,22 @@
+scriptencoding utf-8
+set encoding=utf-8
+let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files']
+set laststatus=2
+redir => g:messages
+ try
+ source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim
+ redrawstatus!
+ vsplit
+ redrawstatus!
+ setlocal statusline=«»
+ redrawstatus!
+ catch
+ call writefile(['Unexpected exception', v:exception], 'message.fail')
+ cquit
+ endtry
+redir END
+if g:messages =~# '\v\S'
+ call writefile(['Unexpected messages'] + split(g:messages, "\n", 1), 'message.fail')
+ cquit
+endif
+qall!
diff --git a/tests/test_vim/tests/invalid_unicode.vim b/tests/test_vim/tests/invalid_unicode.vim
new file mode 100644
index 0000000..ac91f3c
--- /dev/null
+++ b/tests/test_vim/tests/invalid_unicode.vim
@@ -0,0 +1,19 @@
+set encoding=utf-8
+let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files']
+set laststatus=2
+set showtabline=2
+edit `="\xFF"`
+redir => g:messages
+ try
+ source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim
+ redrawstatus!
+ catch
+ call writefile(['Unexpected exception', v:exception], 'message.fail')
+ cquit
+ endtry
+redir END
+if g:messages =~# '\v\S'
+ call writefile(['Unexpected messages'] + split(g:messages, "\n", 1), 'message.fail')
+ cquit
+endif
+qall!
diff --git a/tests/test_vim/tests/local_overrides.vim b/tests/test_vim/tests/local_overrides.vim
new file mode 100755
index 0000000..aba14e2
--- /dev/null
+++ b/tests/test_vim/tests/local_overrides.vim
@@ -0,0 +1,48 @@
+#!/usr/bin/vim -S
+set encoding=utf-8
+let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files']
+let g:powerline_config_overrides = {'common': {'default_top_theme': 'ascii'}}
+let g:powerline_theme_overrides = {'default': {'segment_data': {'line_current_symbol': {'contents': 'LN '}, 'branch': {'before': 'B '}}}}
+
+redir => g:messages
+
+try
+ python import powerline.vim
+ let pycmd = 'python'
+catch
+ try
+ python3 import powerline.vim
+ let pycmd = 'python3'
+ catch
+ call writefile(['Unable to determine python version', v:exception], 'message.fail')
+ cquit
+ endtry
+endtry
+
+try
+ execute pycmd 'powerline.vim.setup()'
+catch
+ call writefile(['Failed to run setup function', v:exception], 'message.fail')
+ cquit
+endtry
+
+try
+ let &columns = 80
+ let result = eval(&statusline[2:])
+catch
+ call writefile(['Exception while evaluating &stl', v:exception], 'message.fail')
+ cquit
+endtry
+
+if result isnot# '%#Pl_22_24320_148_11523840_bold# NORMAL %#Pl_148_11523840_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#unix%#Pl_240_5789784_236_3158064_NONE# %#Pl_247_10329757_240_5789784_NONE# 100%%%#Pl_252_13684944_240_5789784_NONE# %#Pl_235_2500134_252_13684944_NONE# LN %#Pl_235_2500134_252_13684944_bold# 1%#Pl_22_24576_252_13684944_NONE#:1 '
+ call writefile(['Unexpected result', result], 'message.fail')
+ cquit
+endif
+
+redir END
+if g:messages =~ '\S'
+ call writefile(['Non-empty messages:', g:messages], 'message.fail')
+ cquit
+endif
+
+qall!
diff --git a/tests/test_vim/tests/nerdtree_plugin.vim b/tests/test_vim/tests/nerdtree_plugin.vim
new file mode 100755
index 0000000..761cb5f
--- /dev/null
+++ b/tests/test_vim/tests/nerdtree_plugin.vim
@@ -0,0 +1,11 @@
+#!/usr/bin/vim -S
+set nocompatible
+set columns=80
+execute 'source' fnameescape(expand('<sfile>:p:h:h').'/vim_utils.vim')
+call EnablePlugins('nerdtree')
+call SourcePowerline()
+NERDTree /home
+redrawstatus
+call CheckCurrentStatusline('%#Pl_231_16777215_240_5789784_bold# /home %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                      ')
+call CheckMessages()
+qall
diff --git a/tests/test_vim/tests/plugin_file.vim b/tests/test_vim/tests/plugin_file.vim
new file mode 100755
index 0000000..1848933
--- /dev/null
+++ b/tests/test_vim/tests/plugin_file.vim
@@ -0,0 +1,22 @@
+#!/usr/bin/vim -S
+set encoding=utf-8
+let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files']
+tabedit abc
+tabedit def
+try
+ source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim
+catch
+ call writefile([v:exception], 'message.fail')
+ cquit
+endtry
+set ls=2
+redrawstatus!
+redir =>mes
+ messages
+redir END
+let mess=split(mes, "\n")
+if len(mess)>1
+ call writefile(mess, 'message.fail')
+ cquit
+endif
+qall!
diff --git a/tests/test_vim/tests/tabline.vim b/tests/test_vim/tests/tabline.vim
new file mode 100755
index 0000000..ff76dc0
--- /dev/null
+++ b/tests/test_vim/tests/tabline.vim
@@ -0,0 +1,56 @@
+#!/usr/bin/vim -S
+set encoding=utf-8
+let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files']
+source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim
+edit abc
+tabedit def
+tabedit ghi
+
+redir => g:messages
+
+try
+ let &columns = 80
+ let result = eval(&tabline[2:])
+catch
+ call writefile(['Exception while evaluating &tabline', v:exception], 'message.fail')
+ cquit
+endtry
+
+if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %2T%#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs '
+ call writefile(['Unexpected tabline', result], 'message.fail')
+ cquit
+endif
+
+tabonly!
+
+try
+ let result = eval(&tabline[2:])
+catch
+ call writefile(['Exception while evaluating &tabline (2)', v:exception], 'message.fail')
+ cquit
+endtry
+
+if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
+ call writefile(['Unexpected tabline (2)', result], 'message.fail')
+ cquit
+endif
+
+try
+ vsplit
+ let result = eval(&tabline[2:])
+catch
+ call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail')
+endtry
+
+if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
+ call writefile(['Unexpected tabline (3)', result], 'message.fail')
+ cquit
+endif
+
+redir END
+if g:messages =~ '\S'
+ call writefile(['Non-empty messages:', g:messages], 'message.fail')
+ cquit
+endif
+
+qall!
diff --git a/tests/test_vim/vim_utils.vim b/tests/test_vim/vim_utils.vim
new file mode 100644
index 0000000..6219ec4
--- /dev/null
+++ b/tests/test_vim/vim_utils.vim
@@ -0,0 +1,88 @@
+let g:powerline_use_var_handler = 1
+
+let g:pyfiles_root=expand('<sfile>:p:h').'/pyfiles'
+let g:root=expand('<sfile>:p:h:h:h')
+let g:mf=fnamemodify('message.fail', ':p')
+
+command -nargs=1 LST :call writefile(<args>, g:mf, 'a') | cquit
+command -nargs=1 ERR :LST [<args>]
+command -nargs=1 EXC :ERR 'Unexpected exception', <q-args>, v:exception, v:throwpoint
+
+function EnablePlugins(...)
+ let &runtimepath = join(map(copy(a:000), 'escape(g:root."/tests/vim-plugins/".v:val, "\\,")'), ',')
+ try
+ runtime! plugin/*.vim
+ silent doautocmd BufWinEnter
+ silent doautocmd BufEnter
+ silent doautocmd VimEnter
+ catch
+ EXC EnablePlugins
+ endtry
+endfunction
+function RecordStatusline()
+ let g:statusline = &l:statusline
+ if g:statusline[:1] is# '%!'
+ let g:statusline_value=eval(g:statusline[2:])
+ else
+ ERR 'Statusline does not start with %!', g:statusline
+ endif
+ return ''
+endfunction
+function SourcePowerline()
+ let g:powerline_config_paths = [g:root . '/powerline/config_files']
+ try
+ execute 'source' fnameescape(g:root . '/powerline/bindings/vim/plugin/powerline.vim')
+ catch
+ EXC SourcePowerline
+ endtry
+endfunction
+function NDiff(actual, expected)
+ return systemlist(shellescape(g:root.'/tests/bot-ci/scripts/ndiff-strings.py').' '.shellescape(a:actual).' '.shellescape(a:expected))
+endfunction
+function CheckStatuslineValue(actual, expected)
+ if a:actual isnot# a:expected
+ LST ['Expected different statusline value', a:actual, a:expected] + NDiff(a:actual, a:expected)
+ endif
+endfunction
+function CheckRecordedStatuslineValue(expected)
+ return CheckStatuslineValue(g:statusline_value, a:expected)
+endfunction
+function GetCurrentStatusline()
+ if &l:statusline[:1] isnot# '%!'
+ ERR 'Statusline does not start with %!', &l:statusline
+ endif
+ return eval(&l:statusline[2:])
+endfunction
+function CheckCurrentStatusline(expected)
+ return CheckStatuslineValue(GetCurrentStatusline(), a:expected)
+endfunction
+function CheckMessages()
+ if !empty(g:powerline_log_messages)
+ LST ['Unexpected messages in log'] + g:powerline_log_messages
+ endif
+ redir => mes
+ messages
+ redir END
+ let mesl = split(mes, "\n")[1:]
+ if !empty(mesl)
+ LST ['Unexpected messages'] + split(mes, "\n", 1)
+ endif
+endfunction
+function RunPython(s)
+ if has('python')
+ execute 'python' a:s
+ else
+ execute 'python3' a:s
+ endif
+endfunction
+function PyFile(f)
+ if has('python')
+ execute 'pyfile' fnameescape(g:pyfiles_root.'/'.a:f.'.py')
+ else
+ execute 'py3file' fnameescape(g:pyfiles_root.'/'.a:f.'.py')
+ endif
+endfunction
+
+for s:c in ['noremap', 'noremap!']
+ execute s:c '<special><expr>' '<Plug>(PowerlineTestRecordStatusline)' 'RecordStatusline()'
+endfor
diff --git a/tests/vim_sys_path/vim.py b/tests/vim_sys_path/vim.py
new file mode 100644
index 0000000..e9dba66
--- /dev/null
+++ b/tests/vim_sys_path/vim.py
@@ -0,0 +1,7 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import tests.modules.vim as vim
+
+
+globals().update(vim._init())