diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..e446ad8 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,48 @@ +name: collective.handlebars CI +on: [push] +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.8, 3.7, 2.7] + plone-version: [5.2, 5.1] + exclude: + - python-version: 3.8 + plone-version: 5.1 + - python-version: 3.7 + plone-version: 5.1 + steps: + # git checkout + - uses: actions/checkout@v2 + + # python setup + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + # python cache + - uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + # python install + - name: pip install + run: pip install -r requirements.txt + + # buildout + - name: buildout + run: buildout -t 10 -c plone-${{ matrix.plone-version }}.x.cfg code-analysis:return-status-codes=True + + # code analysis + - name: code analysis + run: bin/code-analysis + + # test + - name: test + run: bin/coverage run bin/test diff --git a/.gitignore b/.gitignore index a7f46ca..18115ae 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ report.html .*.cfg .Python Pipfile* +.idea +pyvenv.cfg diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index abf4678..0000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: python -sudo: false -cache: - directories: - - eggs -python: - - 2.7.13 -env: - - PLONE_VERSION=5.0 - - PLONE_VERSION=5.1 -before_install: - - pip install -r requirements.txt -install: - - sed -ie "s#test-5.1#test-$PLONE_VERSION#" buildout.cfg - - buildout annotate - - buildout -script: - - bin/code-analysis - - bin/coverage run bin/test -after_success: - - bin/coveralls -notifications: - email: - - itconsense@gmail.com diff --git a/CHANGES.rst b/CHANGES.rst index c9e7006..1b221bb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,14 +5,16 @@ Changelog 1.5 (unreleased) ---------------- -- Nothing changed yet. +- Python 3 / Plone 5.2 compatibility +- Update dependencies +- Move to Github CI 1.4.1 (2018-10-22) ------------------ - Add div-element to wrapper to support cases where only text is provided - (otherwise plone.protect can fail) + (otherwise plone.protect can fail) [tomgross] 1.4 (2018-09-10) diff --git a/buildout.cfg b/buildout.cfg index 66b232f..cc47c72 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,5 +1,7 @@ [buildout] -extends = https://raw.github.com/collective/buildout.plonetest/master/test-5.1.x.cfg +extends = + https://raw.github.com/collective/buildout.plonetest/master/test-5.2.x.cfg + versions.cfg package-name = collective.handlebars package-extras = [test] parts += @@ -26,29 +28,4 @@ flake8-extensions = flake8-coding -[versions] -# Don't use a released version of collective.handlebars -collective.handlebars = -PyMeta3 = 0.5.1 -PyYAML = 3.13 -chardet = 3.0.4 -check-manifest = 0.37 -configparser = 3.5.0 -coverage = 4.0.3 -flake8 = 3.0.4 -flake8-blind-except = 0.1.1 -flake8-coding = 1.3.1 -flake8-debugger = 1.4.0 -idna = 2.6 -mccabe = 0.5.3 -plone.recipe.codeanalysis = 3.0.1 -plone.recipe.zope2instance = 4.4.0 -plone.testing = 5.0.0 -pybars3 = 0.9.6 -pycodestyle = 2.0.0 -pyflakes = 1.2.3 -python-coveralls = 2.7.0 -setuptools = 33.1.1 -sh = 1.12.14 -zc.buildout = 2.12.2 -zc.recipe.egg = 2.0.6 + diff --git a/plone-5.1.x.cfg b/plone-5.1.x.cfg new file mode 100644 index 0000000..af492e8 --- /dev/null +++ b/plone-5.1.x.cfg @@ -0,0 +1,32 @@ +[buildout] +extends = + https://raw.github.com/collective/buildout.plonetest/master/test-5.1.x.cfg + versions.cfg +package-name = collective.handlebars +package-extras = [test] +parts += + coverage + code-analysis + + +[coverage] +recipe = zc.recipe.egg +eggs = + ${test:eggs} + coverage + python-coveralls + + +[code-analysis] +recipe = plone.recipe.codeanalysis +directory = ${buildout:directory}/src/collective +flake8-exclude = bootstrap.py,bootstrap-buildout.py,docs,*.egg.,omelette +flake8-max-complexity = 15 +flake8-extensions = + flake8-blind-except + flake8-debugger + flake8-coding + +[versions] +plone.testing = 5.0.0 +six = 1.16.0 diff --git a/plone-5.2.x.cfg b/plone-5.2.x.cfg new file mode 100644 index 0000000..0628f9b --- /dev/null +++ b/plone-5.2.x.cfg @@ -0,0 +1,28 @@ +[buildout] +extends = + https://raw.github.com/collective/buildout.plonetest/master/test-5.2.x.cfg + versions.cfg +package-name = collective.handlebars +package-extras = [test] +parts += + coverage + code-analysis + + +[coverage] +recipe = zc.recipe.egg +eggs = + ${test:eggs} + coverage + python-coveralls + + +[code-analysis] +recipe = plone.recipe.codeanalysis +directory = ${buildout:directory}/src/collective +flake8-exclude = bootstrap.py,bootstrap-buildout.py,docs,*.egg.,omelette +flake8-max-complexity = 15 +flake8-extensions = + flake8-blind-except + flake8-debugger + flake8-coding diff --git a/requirements.txt b/requirements.txt index c16b9b0..760259f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -setuptools==33.1.1 -zc.buildout==2.12.2 +setuptools==42.0.2 +zc.buildout==2.13.4 +wheel diff --git a/setup.py b/setup.py index 455e5bf..483e5a0 100644 --- a/setup.py +++ b/setup.py @@ -23,10 +23,14 @@ "Framework :: Plone", "Framework :: Plone :: 5.0", "Framework :: Plone :: 5.1", + "Framework :: Plone :: 5.2", "Programming Language :: Python", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Operating System :: OS Independent", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + "Development Status :: 5 - Production/Stable" ], keywords='Python Plone', author='Tom Gross', @@ -43,6 +47,7 @@ 'Products.GenericSetup>=1.8.2', 'setuptools', 'pybars3', + 'six' ], extras_require={ 'test': [ diff --git a/src/collective/handlebars/browser/views.py b/src/collective/handlebars/browser/views.py index ccb0622..1c1a3e2 100644 --- a/src/collective/handlebars/browser/views.py +++ b/src/collective/handlebars/browser/views.py @@ -9,6 +9,7 @@ from zope.i18n import translate import os.path +import six import sys try: @@ -63,9 +64,9 @@ def _get_hbs_template(self, hbs_filename): compiled_template = HBS_REGISTRY[hbs_filename] else: with open(hbs_filename) as f: - hbs_template = unicode(f.read(), 'utf-8') - compiled_template = compiler.compile(hbs_template) - HBS_REGISTRY[hbs_filename] = compiled_template + hbs_template = six.ensure_text(f.read()) + compiled_template = compiler.compile(hbs_template) + HBS_REGISTRY[hbs_filename] = compiled_template return compiled_template def get_partials(self, hbs_dir): diff --git a/src/collective/handlebars/tests/robot/test_example.robot b/src/collective/handlebars/tests/robot/test_example.robot deleted file mode 100644 index dde1662..0000000 --- a/src/collective/handlebars/tests/robot/test_example.robot +++ /dev/null @@ -1,66 +0,0 @@ -# ============================================================================ -# EXAMPLE ROBOT TESTS -# ============================================================================ -# -# Run this robot test stand-alone: -# -# $ bin/test -s collective.handlebars -t test_example.robot --all -# -# Run this robot test with robot server (which is faster): -# -# 1) Start robot server: -# -# $ bin/robot-server --reload-path src collective.handlebars.testing.COLLECTIVE_HANDLEBARS_ACCEPTANCE_TESTING -# -# 2) Run robot tests: -# -# $ bin/robot src/collective/handlebars/tests/robot/test_example.robot -# -# See the http://docs.plone.org for further details (search for robot -# framework). -# -# ============================================================================ - -*** Settings ***************************************************************** - -Resource plone/app/robotframework/selenium.robot -Resource plone/app/robotframework/keywords.robot - -Library Remote ${PLONE_URL}/RobotRemote - -Test Setup Open test browser -Test Teardown Close all browsers - - -*** Test Cases *************************************************************** - -Scenario: As a member I want to be able to log into the website - [Documentation] Example of a BDD-style (Behavior-driven development) test. - Given a login form - When I enter valid credentials - Then I am logged in - - -*** Keywords ***************************************************************** - -# --- Given ------------------------------------------------------------------ - -a login form - Go To ${PLONE_URL}/login_form - Wait until page contains Login Name - Wait until page contains Password - - -# --- WHEN ------------------------------------------------------------------- - -I enter valid credentials - Input Text __ac_name admin - Input Text __ac_password secret - Click Button Log in - - -# --- THEN ------------------------------------------------------------------- - -I am logged in - Wait until page contains You are now logged in - Page should contain You are now logged in diff --git a/src/collective/handlebars/tests/test_robot.py b/src/collective/handlebars/tests/test_robot.py deleted file mode 100644 index f83b9fc..0000000 --- a/src/collective/handlebars/tests/test_robot.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from collective.handlebars.testing import COLLECTIVE_HANDLEBARS_ACCEPTANCE_TESTING # noqa -from plone.app.testing import ROBOT_TEST_LEVEL -from plone.testing import layered - -import os -import robotsuite -import unittest - - -def test_suite(): - suite = unittest.TestSuite() - current_dir = os.path.abspath(os.path.dirname(__file__)) - robot_dir = os.path.join(current_dir, 'robot') - robot_tests = [ - os.path.join('robot', doc) for doc in os.listdir(robot_dir) - if doc.endswith('.robot') and doc.startswith('test_') - ] - for robot_test in robot_tests: - robottestsuite = robotsuite.RobotTestSuite(robot_test) - robottestsuite.level = ROBOT_TEST_LEVEL - suite.addTests([ - layered( - robottestsuite, - layer=COLLECTIVE_HANDLEBARS_ACCEPTANCE_TESTING - ), - ]) - return suite diff --git a/src/collective/handlebars/tests/test_setup.py b/src/collective/handlebars/tests/test_setup.py index 195d137..65a3ad1 100644 --- a/src/collective/handlebars/tests/test_setup.py +++ b/src/collective/handlebars/tests/test_setup.py @@ -6,6 +6,22 @@ import unittest +try: + from Products.CMFPlone.utils import get_installer +except ImportError: # pragma: no cover + # Quick shim for 5.1 api change + + class get_installer(object): # noqa + def __init__(self, portal, request): # noqa + self.installer = api.portal.get_tool(name='portal_quickinstaller') + + def is_product_installed(self, name): + return self.installer.isProductInstalled(name) + + def uninstall_product(self, name): + return self.installer.uninstallProducts([name]) + + class TestSetup(unittest.TestCase): """Test that collective.handlebars is properly installed.""" @@ -14,11 +30,11 @@ class TestSetup(unittest.TestCase): def setUp(self): """Custom shared utility setup for tests.""" self.portal = self.layer['portal'] - self.installer = api.portal.get_tool('portal_quickinstaller') + self.installer = get_installer(self.portal, self.layer["request"]) def test_product_installed(self): """Test if collective.handlebars is installed.""" - self.assertTrue(self.installer.isProductInstalled( + self.assertTrue(self.installer.is_product_installed( 'collective.handlebars')) def test_browserlayer(self): @@ -35,12 +51,12 @@ class TestUninstall(unittest.TestCase): def setUp(self): self.portal = self.layer['portal'] - self.installer = api.portal.get_tool('portal_quickinstaller') - self.installer.uninstallProducts(['collective.handlebars']) + self.installer = get_installer(self.portal, self.layer["request"]) + self.installer.uninstall_product('collective.handlebars') def test_product_uninstalled(self): """Test if collective.handlebars is cleanly uninstalled.""" - self.assertFalse(self.installer.isProductInstalled( + self.assertFalse(self.installer.is_product_installed( 'collective.handlebars')) def test_browserlayer_removed(self): diff --git a/src/collective/handlebars/tests/test_views.py b/src/collective/handlebars/tests/test_views.py index 3b5e41d..29021b7 100644 --- a/src/collective/handlebars/tests/test_views.py +++ b/src/collective/handlebars/tests/test_views.py @@ -9,8 +9,9 @@ from zope.i18nmessageid import MessageFactory from zope import component, interface from zope.i18n.interfaces import ITranslationDomain -import os.path +import os.path +import six import unittest @@ -104,11 +105,12 @@ def test_example_view(self): """Test that a handlebars template is rendered.""" view = self.portal.restrictedTraverse('@@hbs_test_view') result_file = open(os.path.join(TEST_DATA__DIR, 'minimal.html')) - self.assertEqual(view(), unicode(result_file.read(), encoding='utf-8')) + self.assertEqual(view(), six.ensure_text(result_file.read())) def test_translate(self): view = HandlebarsBrowserView(self.portal, self.layer['request']) component.provideUtility(TestDomain(Allowed=_('Erlaubt')), + provides=ITranslationDomain, name='my.domain') self.assertEqual(view.translate(_('Allowed'), target_language='de'), (u'Erlaubt', 'de')) @@ -116,13 +118,14 @@ def test_translate(self): def test_translate_default_lang(self): view = HandlebarsBrowserView(self.portal, self.layer['request']) component.provideUtility(TestDomain(Allowed=_('Allowed')), + provides=ITranslationDomain, name='my.domain') self.assertEqual(view.translate(_('Allowed')), (u'Allowed', 'en')) def test_hbs_snippet(self): view = DummyHbsFile(self.portal, self.layer['request']) result_file = open(os.path.join(TEST_DATA__DIR, 'minimal_file.html')) - self.assertEqual(view(), unicode(result_file.read(), encoding='utf-8')) + self.assertEqual(view(), six.ensure_text(result_file.read())) def test_hbs_snippet_nofile(self): view = DummyHbsFile(self.portal, self.layer['request']) @@ -135,7 +138,7 @@ def test_get_path_from_prefix(self): def test_helpers(self): view = HelperHbsView(self.portal, self.layer['request']) result_file = open(os.path.join(TEST_DATA__DIR, 'helper.html')) - self.assertEqual(view(), unicode(result_file.read(), encoding='utf-8')) + self.assertEqual(view(), six.ensure_text(result_file.read())) def test_render_helper(self): view = HandlebarsBrowserView(self.portal, self.layer['request']) @@ -176,32 +179,11 @@ def test_example_view(self): filename = os.path.join(TEST_DATA__DIR, 'minimal_plone_51.html') with open(filename) as f: self.assertIn( - unicode(f.read().strip(), encoding='utf-8'), + six.ensure_text(f.read().strip()), normalize(view()) ) -class TestUninstall(unittest.TestCase): - - layer = COLLECTIVE_HANDLEBARS_INTEGRATION_TESTING - - def setUp(self): - self.portal = self.layer['portal'] - self.installer = api.portal.get_tool('portal_quickinstaller') - self.installer.uninstallProducts(['collective.handlebars']) - - def test_product_uninstalled(self): - """Test if collective.handlebars is cleanly uninstalled.""" - self.assertFalse(self.installer.isProductInstalled( - 'collective.handlebars')) - - def test_browserlayer_removed(self): - """Test that ICollectiveHandlebarsLayer is removed.""" - from collective.handlebars.interfaces import ICollectiveHandlebarsLayer - from plone.browserlayer import utils - self.assertNotIn(ICollectiveHandlebarsLayer, utils.registered_layers()) - - class TestHandlebarTile(unittest.TestCase): """Test that fhnw.web16theme viewlets.""" diff --git a/versions.cfg b/versions.cfg new file mode 100644 index 0000000..3412694 --- /dev/null +++ b/versions.cfg @@ -0,0 +1,24 @@ +[versions] +# Don't use a released version of collective.handlebars +collective.handlebars = +zc.buildout = +setuptools = +zc.recipe.egg = 2.0.7 +PyMeta3 = 0.5.1 +PyYAML = 5.4.1 +chardet = 3.0.4 +check-manifest = 0.37 +configparser = 4.0.2 +coverage = 5.5 +flake8 = 3.9.2 +flake8-blind-except = 0.2.0 +flake8-coding = 1.3.2 +flake8-debugger = 3.2.1 +idna = 2.10 +mccabe = 0.6.1 +plone.recipe.codeanalysis = 3.0.1 +pybars3 = 0.9.7 +pycodestyle = 2.7.0 +pyflakes = 2.3.1 +python-coveralls = 2.9.3 +sh = 1.14.2