diff --git a/.buildbot/test/01-requirements.sh b/.buildbot/test/01-requirements.sh
index 0704da7..36000db 100644
--- a/.buildbot/test/01-requirements.sh
+++ b/.buildbot/test/01-requirements.sh
@@ -1,5 +1,6 @@
python_major_version=`python -c 'import sys; print sys.version_info.major'`
if test "$python_major_version" = "2" ; then
pip install 'django<2'
+ pip install 'pylint-django<2'
fi
-pip install -r requirements.txt
+pip install -r requirements-test.txt
diff --git a/.buildbot/test/50-django_testrunner.sh b/.buildbot/test/50-django_testrunner.sh
index 7cd7b17..9ede9ff 100644
--- a/.buildbot/test/50-django_testrunner.sh
+++ b/.buildbot/test/50-django_testrunner.sh
@@ -1 +1 @@
-python manage.py test
+coverage run manage.py test
diff --git a/.buildbot/test/60-coverage_report.sh b/.buildbot/test/60-coverage_report.sh
new file mode 100644
index 0000000..3afc826
--- /dev/null
+++ b/.buildbot/test/60-coverage_report.sh
@@ -0,0 +1 @@
+coverage report --skip-covered --fail-under=99
diff --git a/.buildbot/test/70-pylint.sh b/.buildbot/test/70-pylint.sh
new file mode 100644
index 0000000..4d04efd
--- /dev/null
+++ b/.buildbot/test/70-pylint.sh
@@ -0,0 +1 @@
+pylint apps.base
diff --git a/.coveragerc b/.coveragerc
index 701fac8..48875c8 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,2 +1,3 @@
[run]
-source = base
+source = apps.base
+omit = apps/base/tests/utils.py
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..bfd1a4a
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,27 @@
+[MASTER]
+
+persistent=no
+#load-plugins=pylint_django
+
+
+[MESSAGES CONTROL]
+
+disable=missing-docstring,
+ missing-module-docstring,
+ missing-class-docstring,
+ missing-function-docstring,
+ useless-object-inheritance,
+
+[BASIC]
+
+good-names=_,
+ c,
+ d,
+ e,
+ i,
+ j,
+ k,
+
+[FORMAT]
+
+max-line-length=120
diff --git a/apps/base/tests/test_templates.py b/apps/base/tests/test_templates.py
index 6e38dbf..55c572f 100644
--- a/apps/base/tests/test_templates.py
+++ b/apps/base/tests/test_templates.py
@@ -6,7 +6,7 @@ from django.test import SimpleTestCase
class BaseTemplateTestCase(SimpleTestCase):
- def assertInHTML_multi(self, response, needles, format_kwargs=None):
+ def assertInHTMLMulti(self, response, needles, format_kwargs=None): # pylint: disable=invalid-name
content = response.content.decode('utf-8')
for needle in needles:
if format_kwargs is not None:
@@ -35,7 +35,7 @@ class BaseTemplateTestCase(SimpleTestCase):
'',
)
- self.assertInHTML_multi(response, needles, format_kwargs)
+ self.assertInHTMLMulti(response, needles, format_kwargs)
def test_bootstrap_css_links(self):
response = self.response
@@ -55,7 +55,7 @@ class BaseTemplateTestCase(SimpleTestCase):
'',
)
- self.assertInHTML_multi(response, needles, format_kwargs)
+ self.assertInHTMLMulti(response, needles, format_kwargs)
def test_page_footer(self):
response = self.response
diff --git a/apps/base/tests/test_urls.py b/apps/base/tests/test_urls.py
new file mode 100644
index 0000000..a8839ae
--- /dev/null
+++ b/apps/base/tests/test_urls.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from django.test import SimpleTestCase
+from django.urls import reverse
+
+
+class UrlsTestCase(SimpleTestCase):
+ def test_root_reverse(self):
+ response = self.client.get(reverse('root'))
+ self.assertEqual(response.status_code, 200)
diff --git a/apps/base/tests/test_views.py b/apps/base/tests/test_views.py
index 6d42e77..ed5a682 100644
--- a/apps/base/tests/test_views.py
+++ b/apps/base/tests/test_views.py
@@ -1,31 +1,82 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import datetime
+import re
import socket
+import unittest
+import mock
from django.test import SimpleTestCase
+from ..views import RootView
-class DjangoAdminTestCase(SimpleTestCase):
+
+class RootUnitTestCase(unittest.TestCase):
+ def test_get_context_data(self):
+ default_color_hex = '47825b'
+
+ test_data_sets = [
+ {},
+ {'hostname': 'localhost'},
+ {'hostname': 'abcde'},
+ {'hostname': 'frodo.abcdef.example.com'},
+ {'hostname': 'aBc-dEf', 'expected_color_hex': 'abcdef'},
+ {'hostname': 'ab34EF12cd56ZZ', 'expected_color_hex': 'ab34ef'},
+ {'hostname': 'abcde.example.com', 'expected_color_hex': 'abcdee'},
+ {'hostname': 'abcdef', 'kwargs': {'color_hex': '123456'}, 'expected_color_hex': '123456'},
+ ]
+
+ real_hostname = socket.gethostname()
+
+ for test_data in test_data_sets:
+ if 'hostname' in test_data:
+ hostname = test_data['hostname']
+ else:
+ hostname = real_hostname
+
+ if 'expected_color_hex' in test_data:
+ expected_color_hex = test_data['expected_color_hex']
+ else:
+ expected_color_hex = default_color_hex
+
+ with mock.patch('socket.gethostname', return_value=hostname):
+ view = RootView()
+ if 'kwargs' in test_data:
+ ctx = view.get_context_data(**test_data['kwargs'])
+ else:
+ ctx = view.get_context_data()
+
+ self.assertIsInstance(ctx, dict)
+ self.assertIn('hostname', ctx)
+ self.assertEqual(ctx['hostname'], hostname)
+ self.assertIn('color_hex', ctx)
+ self.assertTrue(re.match('[0-9a-f]{6}', ctx['color_hex']))
+ self.assertEqual(ctx['color_hex'], expected_color_hex)
+ self.assertIn('time', ctx)
+ self.assertIsInstance(ctx['time'], datetime.datetime)
+
+
+class DjangoAdminDjangoTestCase(SimpleTestCase):
def test_djangoadmin(self):
response = self.client.get('/djangoadmin', follow=True)
self.assertContains(response, 'Django administration')
-class RootTestCase(SimpleTestCase):
- def setUp(self):
- super(RootTestCase, self).setUp()
- self.response = self.client.get('/')
-
+class RootDjangoTestCase(SimpleTestCase):
def test_root_template(self):
- response = self.response
+ response = self.client.get('/')
self.assertTemplateUsed(response, 'base/root.html')
def test_root_context(self):
- response = self.response
+ response = self.client.get('/')
self.assertIn('hostname', response.context)
hostname = socket.gethostname()
self.assertEqual(response.context['hostname'], hostname)
+ self.assertIn('color_hex', response.context)
+ self.assertTrue(re.match('[0-9a-f]{6}', response.context['color_hex']))
+ self.assertIn('time', response.context)
+ self.assertIsInstance(response.context['time'], datetime.datetime)
def test_root_content(self):
- response = self.response
+ response = self.client.get('/')
hostname = socket.gethostname().capitalize()
self.assertContains(response, hostname)
diff --git a/apps/base/tests/utils.py b/apps/base/tests/utils.py
index cd8727b..7464a0f 100644
--- a/apps/base/tests/utils.py
+++ b/apps/base/tests/utils.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+# pylint: skip-file
from __future__ import unicode_literals
import os
from tempfile import mkdtemp as _mkdtmp
diff --git a/apps/base/urls.py b/apps/base/urls.py
index 4a69660..9276107 100644
--- a/apps/base/urls.py
+++ b/apps/base/urls.py
@@ -5,7 +5,7 @@ from django.conf.urls import url
from . import views
-urlpatterns = [
+urlpatterns = [ # pylint: disable=invalid-name
url(r'^$', views.RootView.as_view(), name='root'),
url(r'^djangoadmin/', admin.site.urls),
]
diff --git a/apps/base/views.py b/apps/base/views.py
index 3b0c7a3..ae9f4cc 100644
--- a/apps/base/views.py
+++ b/apps/base/views.py
@@ -8,16 +8,23 @@ from django.views import generic
class RootView(generic.TemplateView):
template_name = 'base/root.html'
+ _default_color_hex = '47825b'
+
+ def _get_color_hex(self, hostname=None):
+ color_hex = self._default_color_hex
+ if hostname:
+ buf = hostname
+ buf = re.sub('[-.]', '', buf)
+ buf = buf[:6].lower()
+ if re.match('[0-9a-f]{6}', buf):
+ color_hex = buf
+ return color_hex
def get_context_data(self, **kwargs):
if 'hostname' not in kwargs:
kwargs['hostname'] = socket.gethostname()
if 'color_hex' not in kwargs:
- buf = kwargs['hostname']
- buf = re.sub('[-.]', '', buf)
- buf = buf[:6].lower()
- if re.match('[0-9a-f]{6}', buf):
- kwargs['color_hex'] = buf
+ kwargs['color_hex'] = self._get_color_hex(kwargs['hostname'])
if 'time' not in kwargs:
kwargs['time'] = timezone.now()
return super(RootView, self).get_context_data(**kwargs)
diff --git a/bin/coverage-html.py b/bin/coverage-html.py
index 2750291..e72fb19 100755
--- a/bin/coverage-html.py
+++ b/bin/coverage-html.py
@@ -2,22 +2,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import argparse
-import coverage
import datetime
import os
import shutil
import sys
+import time
+import coverage
+from selenium import webdriver
+from selenium.common.exceptions import WebDriverException
-class Command(object):
+class Command(object): # pylint: disable=too-few-public-methods
default_browser = 'firefox'
@staticmethod
def _setup_argparser():
kwargs = {
- 'description': 'Tool to create html coverage report.',
+ 'description': 'Create a coverage report from a previous coverage run and show it within a browser window.',
}
parser = argparse.ArgumentParser(**kwargs)
+ parser.add_argument('-k', '--keep', action='store_true', dest='keep_report',
+ help='keep the report after closing the browser')
return parser
def _parse_args(self, argv=None):
@@ -35,41 +40,45 @@ class Command(object):
os.makedirs(path)
return path
- def _remove_report_directory(self, path=None):
- if path is None:
- path = self._report_directory
- if path is not None:
- if os.path.isdir(path):
- sys.stdout.write('Removing report directory {}\n'.format(path))
- shutil.rmtree(path)
+ @staticmethod
+ def _remove_report_directory(path):
+ if os.path.isdir(path):
+ sys.stdout.write('Removing report directory {}\n'.format(path))
+ shutil.rmtree(path)
def _create_report(self, path):
return self._coverage.html_report(directory=path, skip_covered=True)
- def _show_report(self, path):
- start_file = os.path.join(path, 'index.html')
- browser = os.environ.get('BROWSER', self.default_browser)
- cmd = '{browser} --new-window "{file}"'.format(browser=browser, file=start_file)
- return os.system(cmd)
+ @staticmethod
+ def _show_report(path):
+ browser = webdriver.Firefox()
+ start_file = os.path.abspath(os.path.join(path, 'index.html'))
+ browser.get('file://{}'.format(start_file))
+ while True:
+ time.sleep(1)
+ try:
+ _ = browser.window_handles
+ except WebDriverException:
+ break
+ return True
def __init__(self):
self._argparser = self._setup_argparser()
- self._report_directory = self._create_report_directory()
self._coverage = coverage.Coverage()
self._coverage.load()
def __call__(self, argv=None):
- self._parse_args(argv)
- report_dir = self._report_directory
+ cmd_args = self._parse_args(argv)
+ report_dir = self._create_report_directory()
sys.stdout.write('Report directory: {}\n'.format(report_dir))
try:
self._create_report(report_dir)
- except Exception as e1:
- self._remove_report_directory(report_dir)
- raise e1
+ self._show_report(report_dir)
+ finally:
+ if not cmd_args.keep_report:
+ self._remove_report_directory(report_dir)
- exitval = self._show_report(report_dir)
- return exitval
+ return os.EX_OK
def main():
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644
index 0000000..79e0c87
--- /dev/null
+++ b/requirements-test.txt
@@ -0,0 +1,5 @@
+-r requirements.txt
+coverage
+mock
+pylint-django
+selenium
diff --git a/requirements.txt b/requirements.txt
index a4d9b9a..8a964ed 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,2 @@
-coverage
django
django-extensions
diff --git a/setup.py b/setup.py
index 878f559..3c0c190 100644
--- a/setup.py
+++ b/setup.py
@@ -110,6 +110,8 @@ class SetupDjangoProject(MyCommand):
if not os.path.exists(d):
os.makedirs(d)
+ return os.EX_OK
+
setup(
name='django-test',