diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..bcbd94d4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,159 @@ +name: build + +on: [push, pull_request] + +env: + # color output for pytest and tox + PYTEST_ADDOPTS: "--color=yes" + PY_COLORS: 1 + +jobs: + test: + name: Test - ${{ matrix.config.python }} - ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }}-latest + + strategy: + matrix: + config: + - os: ubuntu + python: 3.5 + - os: ubuntu + python: 3.6 + - os: ubuntu + python: 3.7 + - os: ubuntu + python: 3.8 + - os: macos + python: 3.7 + - os: windows + python: 3.7 + + steps: + - uses: actions/checkout@v2 + - name: Setup Python ${{ matrix.config.python }} + uses: actions/setup-python@v1.1.1 + with: + python-version: ${{ matrix.config.python }} + - name: Set pip cache (Linux) + uses: actions/cache@v1 + if: startsWith(runner.os, 'Linux') + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Setup pip cache (macOS) + uses: actions/cache@v1 + if: startsWith(runner.os, 'macOS') + with: + path: ~/Library/Caches/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Setup pip cache (Windows) + uses: actions/cache@v1 + if: startsWith(runner.os, 'Windows') + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install locale (Linux) + if: startsWith(runner.os, 'Linux') + run: sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 + - name: Install pandoc + uses: r-lib/actions/setup-pandoc@v1 + with: + pandoc-version: "2.9.2" + - name: Install tox + run: python -m pip install -U pip tox + - name: Info + run: | + echo "===== PYTHON =====" + python --version + echo "===== PANDOC =====" + pandoc --version | head -2 + - name: Run tests + run: tox -e py${{ matrix.config.python }} + + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1.1.1 + with: + python-version: 3.6 + - name: Set pip cache (Linux) + uses: actions/cache@v1 + if: startsWith(runner.os, 'Linux') + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install tox + run: python -m pip install -U pip tox + - name: Check + run: tox -e flake8 + + + docs: + name: Build docs + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1.1.1 + with: + python-version: 3.6 + - name: Set pip cache (Linux) + uses: actions/cache@v1 + if: startsWith(runner.os, 'Linux') + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install tox + run: python -m pip install -U pip tox + - name: Check + run: tox -e docs + + + deploy: + name: Deploy + needs: [test, lint, docs] + runs-on: ubuntu-latest + if: ${{ github.ref=='refs/heads/master' && github.event_name!='pull_request' }} + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1.1.1 + with: + python-version: 3.7 + - name: Check release + id: check_release + run: | + python -m pip install pip --upgrade + pip install poetry + pip install githubrelease + pip install --pre autopub + echo "##[set-output name=release;]$(autopub check)" + - name: Publish + if: ${{ steps.check_release.outputs.release=='' }} + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + git remote set-url origin https://$GITHUB_TOKEN@github.com/${{ github.repository }} + autopub prepare + poetry build + autopub commit + autopub githubrelease + poetry publish -u __token__ -p $PYPI_PASSWORD diff --git a/.travis.yml b/.travis.yml index af3a6ca8..c0637a7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,15 +11,15 @@ env: matrix: - TOX_ENV=docs - TOX_ENV=flake8 - - TOX_ENV=py35 - - TOX_ENV=py36 + - TOX_ENV=py3.5 + - TOX_ENV=py3.6 matrix: include: - python: 3.7 sudo: true dist: xenial env: - - TOX_ENV=py37 + - TOX_ENV=py3.7 addons: apt_packages: - pandoc diff --git a/docs/conf.py b/docs/conf.py index 53171646..fc49975a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ html_show_sourcelink = False def setup(app): # overrides for wide tables in RTD theme - app.add_stylesheet('theme_overrides.css') # path relative to _static + app.add_css_file('theme_overrides.css') # path relative to _static # -- Options for LaTeX output ------------------------------------------------- diff --git a/docs/themes.rst b/docs/themes.rst index a2332615..86a754bc 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -242,7 +242,7 @@ as the name of the metadata field, except in all-lowercase characters. For example, you could add a field called `FacebookImage` to your article metadata, as shown below: -.. code-block:: markdown +.. code-block:: md Title: I love Python more than music Date: 2013-11-06 10:06 diff --git a/pelican/readers.py b/pelican/readers.py index 8174aa79..8c108510 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -696,7 +696,7 @@ def path_metadata(full_path, source_path, settings=None): # Enforce a trailing slash when checking for parent directories. # This prevents false positives when one file or directory's name # is a prefix of another's. - dirpath = os.path.join(path, '') + dirpath = posixize_path(os.path.join(path, '')) if source_path == path or source_path.startswith(dirpath): metadata.update(meta) diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 8a22052e..e52bc0ae 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -160,6 +160,19 @@ def locale_available(locale_): return True +def can_symlink(): + res = True + try: + with temporary_folder() as f: + os.symlink( + f, + os.path.join(f, 'symlink') + ) + except OSError: + res = False + return res + + def get_settings(**kwargs): """Provide tweaked setting dictionaries for testing diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index cc5218b7..564f1d31 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -1,17 +1,11 @@ import os from shutil import rmtree from tempfile import mkdtemp +from unittest.mock import MagicMock from pelican.generators import ArticlesGenerator, PagesGenerator from pelican.tests.support import get_context, get_settings, unittest -try: - from unittest.mock import MagicMock -except ImportError: - try: - from mock import MagicMock - except ImportError: - MagicMock = False CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, 'content') @@ -131,7 +125,6 @@ class TestCache(unittest.TestCase): self.assertEqual(uncached_pages, cached_pages) self.assertEqual(uncached_hidden_pages, cached_hidden_pages) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_article_object_caching(self): """Test Article objects caching at the generator level""" settings = self._get_cache_enabled_settings() @@ -162,7 +155,6 @@ class TestCache(unittest.TestCase): """ self.assertEqual(generator.readers.read_file.call_count, 6) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_article_reader_content_caching(self): """Test raw article content caching at the reader level""" settings = self._get_cache_enabled_settings() @@ -185,7 +177,6 @@ class TestCache(unittest.TestCase): for reader in readers.values(): self.assertEqual(reader.read.call_count, 0) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_article_ignore_cache(self): """Test that all the articles are read again when not loading cache @@ -212,7 +203,6 @@ class TestCache(unittest.TestCase): generator.readers.read_file.call_count, orig_call_count) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_page_object_caching(self): """Test Page objects caching at the generator level""" settings = self._get_cache_enabled_settings() @@ -238,7 +228,6 @@ class TestCache(unittest.TestCase): """ self.assertEqual(generator.readers.read_file.call_count, 1) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_page_reader_content_caching(self): """Test raw page content caching at the reader level""" settings = self._get_cache_enabled_settings() @@ -262,7 +251,6 @@ class TestCache(unittest.TestCase): for reader in readers.values(): self.assertEqual(reader.read.call_count, 0) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_page_ignore_cache(self): """Test that all the pages are read again when not loading cache diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 5dd5c3ec..332d7e78 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,22 +1,17 @@ import locale import os +import sys from shutil import copy, rmtree from tempfile import mkdtemp +from unittest.mock import MagicMock from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, PelicanTemplateNotFound, StaticGenerator, TemplatePagesGenerator) -from pelican.tests.support import get_context, get_settings, unittest +from pelican.tests.support import (can_symlink, get_context, get_settings, + unittest) from pelican.writers import Writer -try: - from unittest.mock import MagicMock -except ImportError: - try: - from mock import MagicMock - except ImportError: - MagicMock = False - CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, 'content') @@ -198,7 +193,6 @@ class TestArticlesGenerator(unittest.TestCase): return [[article.title, article.status, article.category.name, article.template] for article in articles] - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_generate_feeds(self): settings = get_settings() settings['CACHE_PATH'] = self.temp_cache @@ -218,7 +212,6 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_feeds(writer) self.assertFalse(writer.write_feed.called) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_generate_feeds_override_url(self): settings = get_settings() settings['CACHE_PATH'] = self.temp_cache @@ -334,7 +327,6 @@ class TestArticlesGenerator(unittest.TestCase): categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu'] self.assertEqual(sorted(categories), sorted(categories_expected)) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_url_default(self): settings = get_settings() @@ -352,7 +344,6 @@ class TestArticlesGenerator(unittest.TestCase): template_name='archives', page_name='archives', url="archives.html") - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_url_modified(self): settings = get_settings() @@ -373,7 +364,6 @@ class TestArticlesGenerator(unittest.TestCase): page_name='archives/index', url="archives/") - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_false(self): settings = get_settings() @@ -398,7 +388,6 @@ class TestArticlesGenerator(unittest.TestCase): self.assertIn(custom_template, self.articles) self.assertIn(standard_template, self.articles) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_period_in_timeperiod_archive(self): """ Test that the context of a generated period_archive is passed @@ -1022,7 +1011,6 @@ class TestStaticGenerator(unittest.TestCase): with open(self.endfile) as f: self.assertEqual(f.read(), "staticcontent") - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_file_update_required_when_dest_does_not_exist(self): staticfile = MagicMock() staticfile.source_path = self.startfile @@ -1032,7 +1020,6 @@ class TestStaticGenerator(unittest.TestCase): update_required = self.generator._file_update_required(staticfile) self.assertTrue(update_required) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_dest_and_source_mtimes_are_equal(self): staticfile = MagicMock() staticfile.source_path = self.startfile @@ -1045,7 +1032,6 @@ class TestStaticGenerator(unittest.TestCase): isnewer = self.generator._source_is_newer(staticfile) self.assertFalse(isnewer) - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_source_is_newer(self): staticfile = MagicMock() staticfile.source_path = self.startfile @@ -1097,6 +1083,7 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_output(None) self.assertTrue(os.path.samefile(self.startfile, self.endfile)) + @unittest.skipUnless(can_symlink(), 'No symlink privilege') def test_can_symlink_when_hardlink_not_possible(self): self.settings['STATIC_CREATE_LINKS'] = True with open(self.startfile, "w") as f: @@ -1104,40 +1091,29 @@ class TestStaticGenerator(unittest.TestCase): os.mkdir(os.path.join(self.temp_output, "static")) self.generator.fallback_to_symlinks = True self.generator.generate_context() - try: - self.generator.generate_output(None) - except OSError as e: - # On Windows, possibly others, due to not holding symbolic link - # privilege - self.skipTest(e) + self.generator.generate_output(None) self.assertTrue(os.path.islink(self.endfile)) + @unittest.skipUnless(can_symlink(), 'No symlink privilege') def test_existing_symlink_is_considered_up_to_date(self): self.settings['STATIC_CREATE_LINKS'] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) - try: - os.symlink(self.startfile, self.endfile) - except OSError as e: - # On Windows, possibly others - self.skipTest(e) + os.symlink(self.startfile, self.endfile) staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile requires_update = self.generator._file_update_required(staticfile) self.assertFalse(requires_update) + @unittest.skipUnless(can_symlink(), 'No symlink privilege') def test_invalid_symlink_is_overwritten(self): self.settings['STATIC_CREATE_LINKS'] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) - try: - os.symlink("invalid", self.endfile) - except OSError as e: - # On Windows, possibly others - self.skipTest(e) + os.symlink("invalid", self.endfile) staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile @@ -1147,8 +1123,18 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_context() self.generator.generate_output(None) self.assertTrue(os.path.islink(self.endfile)) - self.assertEqual(os.path.realpath(self.endfile), - os.path.realpath(self.startfile)) + + # os.path.realpath is broken on Windows before python3.8 for symlinks. + # This is a (ugly) workaround. + # see: https://bugs.python.org/issue9949 + if os.name == 'nt' and sys.version_info < (3, 8): + def get_real_path(path): + return os.readlink(path) if os.path.islink(path) else path + else: + get_real_path = os.path.realpath + + self.assertEqual(get_real_path(self.endfile), + get_real_path(self.startfile)) def test_delete_existing_file_before_mkdir(self): with open(self.startfile, "w") as f: diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 709086c0..76feb9ce 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,6 +1,7 @@ import locale import os import re +from posixpath import join as posix_join from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, @@ -448,5 +449,5 @@ class TestWordpressXMLAttachements(unittest.TestCase): self.assertEqual(1, len(locations)) directory = locations[0] self.assertTrue( - directory.endswith(os.path.join('content', 'article.rst')), + directory.endswith(posix_join('content', 'article.rst')), directory) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index eb755338..de2a1b22 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -1,16 +1,10 @@ import os +from unittest.mock import patch from pelican import readers from pelican.tests.support import get_settings, unittest from pelican.utils import SafeDatetime -try: - from unittest.mock import patch -except ImportError: - try: - from mock import patch - except ImportError: - patch = False CUR_DIR = os.path.dirname(__file__) CONTENT_PATH = os.path.join(CUR_DIR, 'content') @@ -125,7 +119,6 @@ class DefaultReaderTest(ReaderTest): self.assertDictHasSubset(page.metadata, expected) - @unittest.skipUnless(patch, 'Needs Mock module') def test_find_empty_alt(self): with patch('pelican.readers.logger') as log_mock: content = ['', diff --git a/pelican/tests/test_rstdirectives.py b/pelican/tests/test_rstdirectives.py index 36623fc4..6b733971 100644 --- a/pelican/tests/test_rstdirectives.py +++ b/pelican/tests/test_rstdirectives.py @@ -1,15 +1,8 @@ +from unittest.mock import Mock + from pelican.tests.support import unittest -try: - from unittest.mock import Mock -except ImportError: - try: - from mock import Mock - except ImportError: - Mock = False - -@unittest.skipUnless(Mock, 'Needs Mock module') class Test_abbr_role(unittest.TestCase): def call_it(self, text): from pelican.rstdirectives import abbr_role diff --git a/pelican/tests/test_server.py b/pelican/tests/test_server.py index 68747001..307a3e10 100644 --- a/pelican/tests/test_server.py +++ b/pelican/tests/test_server.py @@ -25,11 +25,10 @@ class TestServer(unittest.TestCase): os.chdir(self.temp_output) def tearDown(self): - rmtree(self.temp_output) os.chdir(self.old_cwd) + rmtree(self.temp_output) def test_get_path_that_exists(self): - handler = ComplexHTTPRequestHandler(MockRequest(), ('0.0.0.0', 8888), self.server) handler.base_path = self.temp_output diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 8f12f3fd..708c0981 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -136,6 +136,8 @@ class TestSettingsConfiguration(unittest.TestCase): settings['ARTICLE_DIR'] settings['PAGE_DIR'] + # locale.getdefaultlocale() is broken on Windows + # See: https://bugs.python.org/issue37945 @unittest.skipIf(platform == 'win32', "Doesn't work on Windows") def test_default_encoding(self): # Test that the default locale is set if not specified in settings diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f0bc290d..e12c4378 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -757,35 +757,32 @@ class TestDateFormatter(unittest.TestCase): class TestSanitisedJoin(unittest.TestCase): - @unittest.skipIf(platform == 'win32', - "Different filesystem root on Windows") def test_detect_parent_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to /foo/test"): + "Attempted to break out of output directory to " + "(.*?:)?/foo/test"): # (.*?:)? accounts for Windows root utils.sanitised_join( "/foo/bar", "../test" ) - @unittest.skipIf(platform == 'win32', - "Different filesystem root on Windows") def test_detect_root_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to /test"): + "Attempted to break out of output directory to " + "(.*?:)?/test"): # (.*?:)? accounts for Windows root utils.sanitised_join( "/foo/bar", "/test" ) - @unittest.skipIf(platform == 'win32', - "Different filesystem root on Windows") def test_pass_deep_subpaths(self): self.assertEqual( utils.sanitised_join( "/foo/bar", "test" ), - os.path.join("/foo/bar", "test") + utils.posixize_path( + os.path.abspath(os.path.join("/foo/bar", "test"))) ) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index e288711e..b74da750 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -728,8 +728,9 @@ def download_attachments(output_path, urls): # Generate percent-encoded URL scheme, netloc, path, query, fragment = urlsplit(url) - path = quote(path) - url = urlunsplit((scheme, netloc, path, query, fragment)) + if scheme != 'file': + path = quote(path) + url = urlunsplit((scheme, netloc, path, query, fragment)) if not os.path.exists(full_path): os.makedirs(full_path) diff --git a/pelican/utils.py b/pelican/utils.py index 1f3d502c..64a308c1 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -27,8 +27,10 @@ logger = logging.getLogger(__name__) def sanitised_join(base_directory, *parts): - joined = os.path.abspath(os.path.join(base_directory, *parts)) - if not joined.startswith(os.path.abspath(base_directory)): + joined = posixize_path( + os.path.abspath(os.path.join(base_directory, *parts))) + base = posixize_path(os.path.abspath(base_directory)) + if not joined.startswith(base): raise RuntimeError( "Attempted to break out of output directory to {}".format( joined @@ -391,10 +393,9 @@ def get_relative_path(path): def path_to_url(path): """Return the URL corresponding to a given path.""" - if os.sep == '/': - return path - else: - return '/'.join(split_all(path)) + if path is not None: + path = posixize_path(path) + return path def posixize_path(rel_path): diff --git a/pelican/writers.py b/pelican/writers.py index 7b14714a..9b27a748 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -1,5 +1,6 @@ import logging import os +from posixpath import join as posix_join from urllib.parse import urljoin from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri @@ -25,7 +26,7 @@ class Writer: # See Content._link_replacer for details if self.settings['RELATIVE_URLS']: - self.urljoiner = os.path.join + self.urljoiner = posix_join else: self.urljoiner = lambda base, url: urljoin( base if base.endswith('/') else base + '/', url) diff --git a/requirements/docs.pip b/requirements/docs.pip index acc5d5f5..bd25c939 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -1,3 +1,3 @@ -sphinx==1.4.9 +sphinx sphinx_rtd_theme livereload diff --git a/requirements/test.pip b/requirements/test.pip index 81d74c4e..973b27ca 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -1,6 +1,5 @@ # Tests Pygments==2.6.1 -mock pytest==5.3.5 pytest-cov pytest-xdist diff --git a/tasks.py b/tasks.py index 495194ab..52bfe6a3 100644 --- a/tasks.py +++ b/tasks.py @@ -24,7 +24,7 @@ PRECOMMIT = ( @task def docbuild(c): """Build documentation""" - c.run(f"{VENV_BIN}/sphinx-build docs docs/_build") + c.run(f"{VENV_BIN}/sphinx-build -W docs docs/_build") @task(docbuild) diff --git a/tox.ini b/tox.ini index 23bb518f..18b0e7a4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -envlist = py{35,36,37},docs,flake8 +envlist = py{3.5,3.6,3.7,3.8},docs,flake8 [testenv] basepython = - py35: python3.5 - py36: python3.6 - py37: python3.7 + py3.5: python3.5 + py3.6: python3.6 + py3.7: python3.7 + py3.8: python3.8 passenv = * usedevelop=True deps = @@ -13,7 +14,7 @@ deps = commands = {envpython} --version - pytest -sv --cov=pelican pelican + pytest -s --cov=pelican pelican [testenv:docs] basepython = python3.6 @@ -27,7 +28,7 @@ commands = filterwarnings = default::DeprecationWarning error:.*:Warning:pelican -addopts = -n 2 +addopts = -n 2 -r a [flake8] application-import-names = pelican