Merge pull request #212 from b-ryan/feature/test-framework

Test framework
This commit is contained in:
Buck Ryan 2015-12-26 16:48:29 -05:00
commit 71dd624c2c
26 changed files with 195 additions and 94 deletions

View file

@ -151,6 +151,10 @@ scenario.
Make sure you introduce new default colors in `themes/default.py` for every new
segment you create. Test your segment with this theme first.
You should add tests for your segment as best you are able. Unit and
integration tests are both welcome. Run your tests with the `nosetests` command
after install the requirements in `dev_requirements.txt`.
### Themes
The `themes` directory stores themes for your prompt, which are basically color

5
circle.yml Normal file
View file

@ -0,0 +1,5 @@
dependencies:
pre:
- sudo pip install -r dev_requirements.txt
- git config --global user.email "tester@example.com"
- git config --global user.name "Tester McGee"

View file

@ -1,2 +1,3 @@
nose
mock
nose>=1.3.7
mock>=1.3.0
sh>=1.11

View file

@ -33,6 +33,10 @@ if __name__ == "__main__":
for segment in config.SEGMENTS:
source += load_source(os.path.join(SEGMENTS_DIR, segment + '.py'))
# assumes each segment file will have a function called
# add_segment__[segment] that accepts the powerline object
source += 'add_{}_segment(powerline)\n'.format(segment)
source += 'sys.stdout.write(powerline.draw())\n'
try:

0
segments/__init__.py Normal file
View file

View file

@ -28,7 +28,7 @@ def requires_special_home_display(name):
return (name == '~' and Color.HOME_SPECIAL_DISPLAY)
def maybe_shorten_name(name):
def maybe_shorten_name(powerline, name):
"""If the user has asked for each directory name to be shortened, will
return the name up to their specified length. Otherwise returns the full
name."""
@ -45,7 +45,7 @@ def get_fg_bg(name):
return (Color.PATH_FG, Color.PATH_BG,)
def add_cwd_segment():
def add_cwd_segment(powerline):
cwd = powerline.cwd or os.getenv('PWD')
if not py3:
cwd = cwd.decode("utf-8")
@ -86,7 +86,5 @@ def add_cwd_segment():
separator = None
separator_fg = None
powerline.append(' %s ' % maybe_shorten_name(name), fg, bg,
powerline.append(' %s ' % maybe_shorten_name(powerline, name), fg, bg,
separator, separator_fg)
add_cwd_segment()

View file

@ -1,8 +1,6 @@
def add_exit_code_segment():
def add_exit_code_segment(powerline):
if powerline.args.prev_error == 0:
return
fg = Color.CMD_FAILED_FG
bg = Color.CMD_FAILED_BG
powerline.append(' %s ' % str(powerline.args.prev_error), fg, bg)
add_exit_code_segment()

View file

@ -12,7 +12,7 @@ def get_fossil_status():
return has_modified_files, has_untracked_files, has_missing_files
def add_fossil_segment():
def _add_fossil_segment(powerline):
subprocess.Popen(['fossil'], stdout=subprocess.PIPE).communicate()[0]
branch = ''.join([i.replace('*','').strip() for i in os.popen("fossil branch 2> /dev/null").read().strip().split("\n") if i.startswith('*')])
if len(branch) == 0:
@ -32,9 +32,19 @@ def add_fossil_segment():
branch += (' ' + extra if extra != '' else '')
powerline.append(' %s ' % branch, fg, bg)
try:
add_fossil_segment()
except OSError:
pass
except subprocess.CalledProcessError:
pass
def add_fossil_segment(powerline):
"""Wraps _add_fossil_segment in exception handling."""
# FIXME This function was added when introducing a testing framework,
# during which the 'powerline' object was passed into the
# `add_[segment]_segment` functions instead of being a global variable. At
# that time it was unclear whether the below exceptions could actually be
# thrown. It would be preferable to find out whether they ever will. If so,
# write a comment explaining when. Otherwise remove.
try:
_add_fossil_segment(powerline)
except OSError:
pass
except subprocess.CalledProcessError:
pass

View file

@ -1,5 +1,6 @@
import re
import subprocess
import os
GIT_SYMBOLS = {
'detached': u'\u2693',
@ -11,17 +12,24 @@ GIT_SYMBOLS = {
'conflicted': u'\u273C'
}
git_subprocess_env = {
# LANG is specified to ensure git always uses a language we are expecting.
# Otherwise we may be unable to parse the output.
"LANG": "C",
def get_PATH():
"""Normally gets the PATH from the OS. This function exists to enable
easily mocking the PATH in tests.
"""
return os.getenv("PATH")
# https://github.com/milkbikis/powerline-shell/pull/126
"HOME": os.getenv("HOME"),
def git_subprocess_env():
return {
# LANG is specified to ensure git always uses a language we are expecting.
# Otherwise we may be unable to parse the output.
"LANG": "C",
# https://github.com/milkbikis/powerline-shell/pull/153
"PATH": os.getenv("PATH"),
}
# https://github.com/milkbikis/powerline-shell/pull/126
"HOME": os.getenv("HOME"),
# https://github.com/milkbikis/powerline-shell/pull/153
"PATH": get_PATH(),
}
def parse_git_branch_info(status):
@ -32,7 +40,7 @@ def parse_git_branch_info(status):
def _get_git_detached_branch():
p = subprocess.Popen(['git', 'describe', '--tags', '--always'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=git_subprocess_env)
env=git_subprocess_env())
detached_ref = p.communicate()[0].decode("utf-8").rstrip('\n')
if p.returncode == 0:
branch = u'{} {}'.format(GIT_SYMBOLS['detached'], detached_ref)
@ -62,10 +70,15 @@ def _n_or_empty(_dict, _key):
return _dict[_key] if int(_dict[_key]) > 1 else u''
def add_git_segment():
p = subprocess.Popen(['git', 'status', '--porcelain', '-b'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=git_subprocess_env)
def add_git_segment(powerline):
try:
p = subprocess.Popen(['git', 'status', '--porcelain', '-b'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=git_subprocess_env())
except OSError:
# Popen will throw an OSError if git is not found
return
pdata = p.communicate()
if p.returncode != 0:
return
@ -101,8 +114,3 @@ def add_git_segment():
_add(stats, 'notstaged', Color.GIT_NOTSTAGED_FG, Color.GIT_NOTSTAGED_BG)
_add(stats, 'untracked', Color.GIT_UNTRACKED_FG, Color.GIT_UNTRACKED_BG)
_add(stats, 'conflicted', Color.GIT_CONFLICTED_FG, Color.GIT_CONFLICTED_BG)
try:
add_git_segment()
except (OSError, subprocess.CalledProcessError):
pass

View file

@ -20,7 +20,7 @@ def get_hg_status():
has_modified_files = True
return has_modified_files, has_untracked_files, has_missing_files
def add_hg_segment():
def add_hg_segment(powerline):
branch = os.popen('hg branch 2> /dev/null').read().rstrip()
if len(branch) == 0:
return False
@ -37,5 +37,3 @@ def add_hg_segment():
extra += '!'
branch += (' ' + extra if extra != '' else '')
return powerline.append(' %s ' % branch, fg, bg)
add_hg_segment()

View file

@ -1,4 +1,4 @@
def add_hostname_segment():
def add_hostname_segment(powerline):
if powerline.args.colorize_hostname:
from lib.color_compliment import stringToHashToColorAndOpposite
from lib.colortrans import rgb2short
@ -19,6 +19,3 @@ def add_hostname_segment():
host_prompt = ' %s ' % socket.gethostname().split('.')[0]
powerline.append(host_prompt, Color.HOSTNAME_FG, Color.HOSTNAME_BG)
add_hostname_segment()

View file

@ -2,7 +2,7 @@ import os
import re
import subprocess
def add_jobs_segment():
def add_jobs_segment(powerline):
pppid_proc = subprocess.Popen(['ps', '-p', str(os.getppid()), '-oppid='],
stdout=subprocess.PIPE)
pppid = pppid_proc.communicate()[0].decode("utf-8").strip()
@ -15,5 +15,3 @@ def add_jobs_segment():
if num_jobs > 0:
powerline.append(' %d ' % num_jobs, Color.JOBS_FG, Color.JOBS_BG)
add_jobs_segment()

View file

@ -1,7 +1,7 @@
import subprocess
def add_node_version_segment():
def add_node_version_segment(powerline):
try:
p1 = subprocess.Popen(["node", "--version"], stdout=subprocess.PIPE)
version = p1.communicate()[0].decode("utf-8").rstrip()
@ -9,5 +9,3 @@ def add_node_version_segment():
powerline.append(version, 15, 18)
except OSError:
return
add_node_version_segment()

View file

@ -1,7 +1,7 @@
import subprocess
def add_php_version_segment():
def add_php_version_segment(powerline):
try:
output = subprocess.check_output(['php', '-r', 'echo PHP_VERSION;'], stderr=subprocess.STDOUT)
if '-' in output:
@ -12,5 +12,3 @@ def add_php_version_segment():
powerline.append(version, 15, 4)
except OSError:
return
add_php_version_segment()

View file

@ -1,9 +1,7 @@
import os
def add_read_only_segment():
def add_read_only_segment(powerline):
cwd = powerline.cwd or os.getenv('PWD')
if not os.access(cwd, os.W_OK):
powerline.append(' %s ' % powerline.lock, Color.READONLY_FG, Color.READONLY_BG)
add_read_only_segment()

View file

@ -1,4 +1,4 @@
def add_root_indicator_segment():
def add_root_segment(powerline):
root_indicators = {
'bash': ' \\$ ',
'zsh': ' %# ',
@ -10,5 +10,3 @@ def add_root_indicator_segment():
fg = Color.CMD_FAILED_FG
bg = Color.CMD_FAILED_BG
powerline.append(root_indicators[powerline.args.shell], fg, bg)
add_root_indicator_segment()

View file

@ -1,7 +1,7 @@
import subprocess
def add_ruby_version_segment():
def add_ruby_version_segment(powerline):
try:
p1 = subprocess.Popen(["ruby", "-v"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["sed", "s/ (.*//"], stdin=p1.stdout, stdout=subprocess.PIPE)
@ -13,5 +13,3 @@ def add_ruby_version_segment():
powerline.append(version, 15, 1)
except OSError:
return
add_ruby_version_segment()

View file

@ -1,4 +1,4 @@
def add_term_title_segment():
def add_set_term_title_segment(powerline):
term = os.getenv('TERM')
if not (('xterm' in term) or ('rxvt' in term)):
return
@ -13,5 +13,3 @@ def add_term_title_segment():
powerline.append(set_title, None, None, '')
add_term_title_segment()

View file

@ -1,8 +1,6 @@
import os
def add_ssh_segment():
def add_ssh_segment(powerline):
if os.getenv('SSH_CLIENT'):
powerline.append(' %s ' % powerline.network, Color.SSH_FG, Color.SSH_BG)
add_ssh_segment()

View file

@ -1,6 +1,7 @@
import subprocess
def add_svn_segment():
def _add_svn_segment(powerline):
is_svn = subprocess.Popen(['svn', 'status'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
is_svn_output = is_svn.communicate()[1].decode("utf-8").strip()
@ -17,9 +18,20 @@ def add_svn_segment():
changes = output.strip()
powerline.append(' %s ' % changes, Color.SVN_CHANGES_FG, Color.SVN_CHANGES_BG)
try:
add_svn_segment()
except OSError:
pass
except subprocess.CalledProcessError:
pass
def add_svn_segment(powerline):
"""Wraps _add_svn_segment in exception handling."""
# FIXME This function was added when introducing a testing framework,
# during which the 'powerline' object was passed into the
# `add_[segment]_segment` functions instead of being a global variable. At
# that time it was unclear whether the below exceptions could actually be
# thrown. It would be preferable to find out whether they ever will. If so,
# write a comment explaining when. Otherwise remove.
try:
_add_svn_segment(powerline)
except OSError:
pass
except subprocess.CalledProcessError:
pass

View file

@ -1,4 +1,4 @@
def add_time_segment():
def add_time_segment(powerline):
if powerline.args.shell == 'bash':
time = ' \\t '
elif powerline.args.shell == 'zsh':
@ -8,5 +8,3 @@ def add_time_segment():
time = ' %s ' % time.strftime('%H:%M:%S')
powerline.append(time, Color.HOSTNAME_FG, Color.HOSTNAME_BG)
add_time_segment()

View file

@ -1,15 +1,7 @@
import subprocess
import re
# uptime output samples
# 1h: 00:00:00 up 1:00, 2 users, load average: 0,00, 0,00, 0,00
# 10+h: 00:00:00 up 10:00, 2 users, load average: 0,00, 0,00, 0,00
# 1+d: 00:00:00 up 1 days, 1:00, 2 users, load average: 0,00, 0,00, 0,00
# 9+d: 00:00:00 up 12 days, 1:00, 2 users, load average: 0,00, 0,00, 0,00
# -1h 00:00:00 up 120 days, 49 min, 2 users, load average: 0,00, 0,00, 0,00
# mac: 00:00:00 up 23 3 day(s), 10:00, 2 users, load average: 0,00, 0,00, 0,00
def add_uptime_segment():
def add_uptime_segment(powerline):
try:
output = subprocess.check_output(['uptime'], stderr=subprocess.STDOUT)
raw_uptime = re.search('(?<=up).+(?=,\s+\d+\s+user)', output).group(0)
@ -22,5 +14,3 @@ def add_uptime_segment():
powerline.append(uptime, Color.CWD_FG, Color.PATH_BG)
except OSError:
return
add_uptime_segment()

View file

@ -1,5 +1,5 @@
def add_username_segment():
def add_username_segment(powerline):
import os
if powerline.args.shell == 'bash':
user_prompt = ' \\u '
@ -14,5 +14,3 @@ def add_username_segment():
bgcolor = Color.USERNAME_BG
powerline.append(user_prompt, Color.USERNAME_FG, bgcolor)
add_username_segment()

View file

@ -1,6 +1,6 @@
import os
def add_virtual_env_segment():
def add_virtual_env_segment(powerline):
env = os.getenv('VIRTUAL_ENV') or os.getenv('CONDA_ENV_PATH')
if env is None:
return
@ -9,5 +9,3 @@ def add_virtual_env_segment():
bg = Color.VIRTUAL_ENV_BG
fg = Color.VIRTUAL_ENV_FG
powerline.append(' %s ' % env_name, fg, bg)
add_virtual_env_segment()

View file

@ -0,0 +1,69 @@
import unittest
import mock
import tempfile
import shutil
import sh
import segments.git as git
class GitTest(unittest.TestCase):
def setUp(self):
self.powerline = mock.MagicMock()
git.Color = mock.MagicMock()
self.dirname = tempfile.mkdtemp()
sh.cd(self.dirname)
sh.git("init", ".")
def tearDown(self):
shutil.rmtree(self.dirname)
def _add_and_commit(self, filename):
sh.touch(filename)
sh.git("add", filename)
sh.git("commit", "-m", "add file " + filename)
def _new_branch(self, branch):
sh.git("checkout", "-b", branch)
def _get_commit_hash(self):
return sh.git("rev-parse", "HEAD")
@mock.patch('segments.git.get_PATH')
def test_git_not_installed(self, get_PATH):
get_PATH.return_value = "" # so git can't be found
git.add_git_segment(self.powerline)
self.assertEqual(self.powerline.append.call_count, 0)
def test_non_git_directory(self):
shutil.rmtree(".git")
git.add_git_segment(self.powerline)
self.assertEqual(self.powerline.append.call_count, 0)
def test_big_bang(self):
git.add_git_segment(self.powerline)
self.assertEqual(self.powerline.append.call_args[0][0], ' Big Bang ')
def test_master_branch(self):
self._add_and_commit("foo")
git.add_git_segment(self.powerline)
self.assertEqual(self.powerline.append.call_args[0][0], ' master ')
def test_different_branch(self):
self._add_and_commit("foo")
self._new_branch("bar")
git.add_git_segment(self.powerline)
self.assertEqual(self.powerline.append.call_args[0][0], ' bar ')
def test_detached(self):
self._add_and_commit("foo")
commit_hash = self._get_commit_hash()
self._add_and_commit("bar")
sh.git("checkout", "HEAD^")
git.add_git_segment(self.powerline)
# In detached mode, we output a unicode symbol and then the shortened
# commit hash.
self.assertIn(self.powerline.append.call_args[0][0].split()[1],
commit_hash)

View file

@ -0,0 +1,29 @@
import unittest
import mock
import segments.uptime as uptime
test_cases = {
# linux test cases
"00:00:00 up 1:00, 2 users, load average: 0,00, 0,00, 0,00": "1h",
"00:00:00 up 10:00, 2 users, load average: 0,00, 0,00, 0,00": "10h",
"00:00:00 up 1 days, 1:00, 2 users, load average: 0,00, 0,00, 0,00": "1d",
"00:00:00 up 12 days, 1:00, 2 users, load average: 0,00, 0,00, 0,00": "12d",
"00:00:00 up 120 days, 49 min, 2 users, load average: 0,00, 0,00, 0,00": "120d",
# mac test cases
"00:00:00 up 23 3 day(s), 10:00, 2 users, load average: 0,00, 0,00, 0,00": "3d",
}
class UptimeTest(unittest.TestCase):
def setUp(self):
self.powerline = mock.MagicMock()
uptime.Color = mock.MagicMock()
@mock.patch('subprocess.check_output')
def test_all(self, check_output):
for stdout, result in test_cases.items():
check_output.return_value = stdout
uptime.add_uptime_segment(self.powerline)
self.assertEqual(self.powerline.append.call_args[0][0].split()[0], result)