diff --git a/.gitignore b/.gitignore index 925680bb..0e0a592c 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,8 @@ my_test_project/* # Generated when running py.test for the cookiecutter-django generation tests .cache/ + +# Unit test / coverage reports +.coverage +.tox +.cache diff --git a/.travis.yml b/.travis.yml index 980294d5..4608b31c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,17 @@ -language: python -python: - - 3.5 - - 3.4 - - 2.7 -sudo: false -install: -- pip install -r requirements.txt +# Config file for automatic testing at travis-ci.org -script: - - py.test +sudo: false +language: python +python: 3.5 +env: + - TOX_ENV=py27 + - TOX_ENV=py34 + - TOX_ENV=py35 + +script: tox -e $TOX_ENV + +install: + - pip install tox notifications: email: diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a6b73259..d05ea0db 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -9,3 +9,48 @@ Getting your pull request merged in #. Keep it small. The smaller the pull request the more likely I'll pull it in. #. Pull requests that fix a current issue get priority for review. #. If you're not already in the `CONTRIBUTORS.rst` file, add yourself! + +Testing +------- + +Installation +~~~~~~~~~~~~ + +Please install `tox`_, which is a generic virtualenv management and test command line tool. + +`tox`_ is available for download from `PyPI`_ via `pip`_:: + + $ pip install tox + +It will automatically create a fresh virtual environment and install our test dependencies, +such as `pytest-cookies`_ and `flake8`_. + +Run the Tests +~~~~~~~~~~~~~ + +Tox uses py.test under the hood, hence it supports the same syntax for selecting tests. + +For further information please consult the `pytest usage docs`_. + +To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.:: + + $ tox + +It is possible to tests with some versions of python, to do this the command +is:: + + $ tox -e py27,py34 + +Will run py.test with the python2.7, and python3.4 interpreters, for +example. + +To run a particular test with tox for against your current Python version:: + + $ tox -e py -- -k test_default_configuration + +.. _`pytest usage docs`: https://pytest.org/latest/usage.html#specifying-tests-selecting-tests +.. _`tox`: https://tox.readthedocs.org/en/latest/ +.. _`pip`: https://pypi.python.org/pypi/pip/ +.. _`pytest-cookies`: https://pypi.python.org/pypi/pytest-cookies/ +.. _`flake8`: https://pypi.python.org/pypi/flake8/ +.. _`PyPI`: https://pypi.python.org/pypi diff --git a/requirements.txt b/requirements.txt index 9cb44207..aadbfa9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pytest==2.8.2 git+git://github.com/mverteuil/pytest-ipdb.git pep8==1.6.2 pyflakes==1.0.0 +tox==2.1.1 diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/base.py b/tests/base.py deleted file mode 100644 index 9b15198c..00000000 --- a/tests/base.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -import re -import shutil -import unittest -from os.path import exists, dirname, join - -from binaryornot.check import is_binary -import sh - -from cookiecutter.main import cookiecutter - - -class DjangoCookieTestCase(unittest.TestCase): - - root_dir = dirname(dirname(__file__)) - ctx = {} - destpath = None - - def check_paths(self, paths): - """ - Method to check all paths have correct substitutions, - used by other tests cases - """ - # Construct the cookiecutter search pattern - pattern = "{{(\s?cookiecutter)[.](.*?)}}" - re_obj = re.compile(pattern) - - # Assert that no match is found in any of the files - for path in paths: - if not is_binary(path): - for line in open(path, 'r'): - match = re_obj.search(line) - self.assertIsNone( - match, - "cookiecutter variable not replaced in {}".format(path)) - - def generate_project(self, extra_context=None): - ctx = { - "project_name": "My Test Project", - "repo_name": "my_test_project", - "author_name": "Test Author", - "email": "test@example.com", - "description": "A short description of the project.", - "domain_name": "example.com", - "version": "0.1.0", - "timezone": "UTC", - "now": "2015/01/13", - "year": "2015" - } - if extra_context: - assert isinstance(extra_context, dict) - ctx.update(extra_context) - - self.ctx = ctx - self.destpath = join(self.root_dir, self.ctx['repo_name']) - - cookiecutter(template='./', checkout=None, no_input=True, extra_context=ctx) - - # Build a list containing absolute paths to the generated files - paths = [os.path.join(dirpath, file_path) - for dirpath, subdirs, files in os.walk(self.destpath) - for file_path in files] - return paths - - def clean(self): - if exists(self.destpath): - shutil.rmtree(self.destpath) - sh.cd(self.root_dir) - - def tearDown(self): - self.clean() diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py new file mode 100644 index 00000000..74f8947c --- /dev/null +++ b/tests/test_cookiecutter_generation.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +import os +import re +import sh + +import pytest +from binaryornot.check import is_binary + +PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" +RE_OBJ = re.compile(PATTERN) + + +@pytest.fixture +def context(): + return { + "project_name": "My Test Project", + "repo_name": "my_test_project", + "author_name": "Test Author", + "email": "test@example.com", + "description": "A short description of the project.", + "domain_name": "example.com", + "version": "0.1.0", + "timezone": "UTC", + "now": "2015/01/13", + "year": "2015" + } + + +def build_files_list(root_dir): + """Build a list containing absolute paths to the generated files.""" + return [ + os.path.join(dirpath, file_path) + for dirpath, subdirs, files in os.walk(root_dir) + for file_path in files + ] + + +def check_paths(paths): + """Method to check all paths have correct substitutions, + used by other tests cases + """ + # Assert that no match is found in any of the files + for path in paths: + if is_binary(path): + continue + for line in open(path, 'r'): + match = RE_OBJ.search(line) + msg = "cookiecutter variable not replaced in {}" + assert match is None, msg.format(path) + + +def test_default_configuration(cookies, context): + result = cookies.bake(extra_context=context) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == context['repo_name'] + assert result.project.isdir() + + paths = build_files_list(str(result.project)) + assert paths + check_paths(paths) + + +@pytest.fixture(params=['use_maildump', 'use_celery', 'windows']) +def feature_context(request, context): + context.update({request.param: 'y'}) + return context + + +def test_enabled_features(cookies, feature_context): + result = cookies.bake(extra_context=feature_context) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == feature_context['repo_name'] + assert result.project.isdir() + + paths = build_files_list(str(result.project)) + assert paths + check_paths(paths) + + +def test_flake8_compliance(cookies): + """generated project should pass flake8""" + result = cookies.bake() + + try: + sh.flake8(str(result.project)) + except sh.ErrorReturnCode as e: + pytest.fail(e) diff --git a/tests/test_cookiecutter_substitution.py b/tests/test_cookiecutter_substitution.py deleted file mode 100644 index 5ac07406..00000000 --- a/tests/test_cookiecutter_substitution.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import absolute_import -import sh - -from .base import DjangoCookieTestCase - - -class TestCookiecutterSubstitution(DjangoCookieTestCase): - """Test that all cookiecutter instances are substituted""" - - def test_default_configuration(self): - # Build a list containing absolute paths to the generated files - paths = self.generate_project() - self.check_paths(paths) - - def test_maildump_enabled(self): - paths = self.generate_project(extra_context={'use_maildump': 'y'}) - self.check_paths(paths) - - def test_celery_enabled(self): - paths = self.generate_project(extra_context={'use_celery': 'y'}) - self.check_paths(paths) - - def test_windows_enabled(self): - paths = self.generate_project(extra_context={'windows': 'y'}) - self.check_paths(paths) - - def test_flake8_compliance(self): - """generated project should pass flake8""" - self.generate_project() - try: - sh.flake8(self.destpath) - except sh.ErrorReturnCode as e: - raise AssertionError(e) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..deb1dd50 --- /dev/null +++ b/tox.ini @@ -0,0 +1,12 @@ +[tox] +skipsdist = true +envlist = py27,py34,py35 + +[testenv] +passenv = LC_ALL, LANG, HOME +deps = + binaryornot + flake8 + pytest-cookies + sh +commands = py.test {posargs:tests}