Merge pull request #2747 from avaris/github-actions

Add GitHub Actions workflow
This commit is contained in:
Justin Mayer 2020-05-10 07:16:47 +02:00 committed by GitHub
commit 177bc2262c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 236 additions and 102 deletions

159
.github/workflows/main.yml vendored Normal file
View file

@ -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

View file

@ -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

View file

@ -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 -------------------------------------------------

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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 = ['<img alt="" src="test-image.png" width="300px" />',

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")))
)

View file

@ -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)

View file

@ -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):

View file

@ -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)

View file

@ -1,3 +1,3 @@
sphinx==1.4.9
sphinx
sphinx_rtd_theme
livereload

View file

@ -1,6 +1,5 @@
# Tests
Pygments==2.6.1
mock
pytest==5.3.5
pytest-cov
pytest-xdist

View file

@ -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)

13
tox.ini
View file

@ -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