add feedcore unittests

This commit is contained in:
Eric Le Lay 2020-07-23 22:31:55 +02:00
parent 831186b0c7
commit 7394ca4779
8 changed files with 123 additions and 114 deletions

View file

@ -6,7 +6,7 @@ python:
install:
- sudo apt-get update -q
- sudo apt-get install intltool desktop-file-utils
- pip3 install coverage==4.5.4 minimock pycodestyle isort requests
- pip3 install pytest-cov minimock pycodestyle isort requests pytest pytest-httpserver
- python3 tools/localdepends.py
script:
- make lint

View file

@ -67,7 +67,9 @@ PyPI. With this, you get a self-contained gPodder CLI codebase.
### Test Dependencies
- python-minimock
- python-coverage
- pytest
- pytest-httpserver
- pytest-cov
- desktop-file-utils
## Testing
@ -86,9 +88,8 @@ Tests in gPodder are written in two different ways:
- [unittests](http://docs.python.org/3/library/unittest.html)
If you want to add doctests, simply write the doctest and make sure that
the module appears in "doctest_modules" in src/gpodder/unittests.py. For
example, the doctests in src/gpodder/util.py are added as 'util' (the
"gpodder" prefix must not be specified there).
the module appears after `--doctest-modules` in `pytest.ini`. If you
add tests to any module in `src/gpodder` you have nothing to do.
If you want to add unit tests for a specific module (ex: gpodder.model),
you should add the tests as gpodder.test.model, or in other words:

View file

@ -61,7 +61,8 @@ help:
##########################################################################
unittest:
LC_ALL=C PYTHONPATH=src/ $(PYTHON) -m gpodder.unittests
LC_ALL=C PYTHONPATH=src/ pytest --ignore=tests --ignore=src/gpodder/utilwin32ctypes.py --doctest-modules src/gpodder/util.py src/gpodder/jsonconfig.py
LC_ALL=C PYTHONPATH=src/ pytest tests --ignore=src/gpodder/utilwin32ctypes.py --ignore=src/mygpoclient --cov=gpodder
ISORTOPTS := -rc -c share src/gpodder tools bin/* *.py
lint:

View file

@ -1,2 +0,0 @@
[pytest]
addopts = --doctest-modules src/gpodder

View file

@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2018 The gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Run Doctests and Unittests for gPodder modules
# 2009-02-25 Thomas Perl <thp@gpodder.org>
import doctest
import sys
import unittest
try:
# Unused here locally, but we import it to be able to give an early
# warning about this missing dependency in order to avoid bogus errors.
import minimock
except ImportError as e:
print("""
Error: Unit tests require the "minimock" module (python-minimock).
Please install it before running the unit tests.
""", file=sys.stderr)
sys.exit(2)
# Main package and test package (for modules in main package)
package = 'gpodder'
test_package = '.'.join((package, 'test'))
suite = unittest.TestSuite()
coverage_modules = []
# Modules (in gpodder) for which doctests exist
# ex: Doctests embedded in "gpodder.util", coverage reported for "gpodder.util"
doctest_modules = ['util', 'jsonconfig']
for module in doctest_modules:
doctest_mod = __import__('.'.join((package, module)), fromlist=[module])
suite.addTest(doctest.DocTestSuite(doctest_mod))
coverage_modules.append(doctest_mod)
# Modules (in gpodder) for which unit tests (in gpodder.test) exist
# ex: Tests are in "gpodder.test.model", coverage reported for "gpodder.model"
test_modules = ['model']
for module in test_modules:
test_mod = __import__('.'.join((test_package, module)), fromlist=[module])
coverage_mod = __import__('.'.join((package, module)), fromlist=[module])
suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_mod))
coverage_modules.append(coverage_mod)
try:
# If you want a HTML-based test report, install HTMLTestRunner from:
# http://tungwaiyip.info/software/HTMLTestRunner.html
import HTMLTestRunner
REPORT_FILENAME = 'test_report.html'
runner = HTMLTestRunner.HTMLTestRunner(stream=open(REPORT_FILENAME, 'w'))
print("""
HTML Test Report will be written to %s
""" % REPORT_FILENAME)
except ImportError:
runner = unittest.TextTestRunner(verbosity=2)
try:
import coverage
except ImportError:
coverage = None
if __name__ == '__main__':
if coverage is not None:
cov = coverage.Coverage()
cov.erase()
cov.start()
result = runner.run(suite)
if not result.wasSuccessful():
sys.exit(1)
if coverage is not None:
cov.stop()
cov.report(coverage_modules)
cov.erase()
else:
print("""
No coverage reporting done (Python module "coverage" is missing)
Please install the python-coverage package to get coverage reporting.
""", file=sys.stderr)

115
tests/test_feedcore.py Normal file
View file

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2023 The gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import io
import pytest
import requests.exceptions
from gpodder.feedcore import Fetcher, Result, NEW_LOCATION, NOT_MODIFIED, UPDATED_FEED
class MyFetcher(Fetcher):
def parse_feed(self, url, data_stream, headers, status, **kwargs):
return Result(status, {
'parse_feed': {
'url': url,
'data_stream': data_stream,
'headers': headers,
'extra_args': dict(**kwargs),
},
})
SIMPLE_RSS = """
<rss>
<channel>
<title>Feed Name</title>
<item>
<title>Some Episode Title</title>
<guid>urn:test/ep1</guid>
<pubDate>Sun, 25 Nov 2018 17:28:03 +0000</pubDate>
<enclosure
url="/ep1.ogg"
type="audio/ogg"
length="100000"/>
</item>
</channel>
</rss>
"""
def test_easy(httpserver):
res_data = SIMPLE_RSS
httpserver.expect_request('/feed').respond_with_data(SIMPLE_RSS, content_type='text/xml')
res = MyFetcher().fetch(httpserver.url_for('/feed'), custom_key='value')
assert res.status == UPDATED_FEED
args = res.feed['parse_feed']
assert args['headers']['content-type'] == 'text/xml'
assert isinstance(args['data_stream'], io.BytesIO)
assert args['data_stream'].getvalue().decode('utf-8') == SIMPLE_RSS
assert args['url'] == httpserver.url_for('/feed')
assert args['extra_args']['custom_key'] == 'value'
def test_redirect(httpserver):
res_data = SIMPLE_RSS
httpserver.expect_request('/endfeed').respond_with_data(SIMPLE_RSS, content_type='text/xml')
redir_headers = {
'Location': '/endfeed',
}
# temporary redirect
httpserver.expect_request('/feed').respond_with_data(status=302, headers=redir_headers)
httpserver.expect_request('/permanentfeed').respond_with_data(status=301, headers=redir_headers)
res = MyFetcher().fetch(httpserver.url_for('/feed'))
assert res.status == UPDATED_FEED
args = res.feed['parse_feed']
assert args['headers']['content-type'] == 'text/xml'
assert isinstance(args['data_stream'], io.BytesIO)
assert args['data_stream'].getvalue().decode('utf-8') == SIMPLE_RSS
assert args['url'] == httpserver.url_for('/feed')
res = MyFetcher().fetch(httpserver.url_for('/permanentfeed'))
assert res.status == NEW_LOCATION
assert res.feed == httpserver.url_for('/endfeed')
def test_redirect_loop(httpserver):
""" verify that feedcore fetching will not loop indefinitely on redirects """
redir_headers = {
'Location': '/feed', # it loops
}
httpserver.expect_request('/feed').respond_with_data(status=302, headers=redir_headers)
with pytest.raises(requests.exceptions.TooManyRedirects):
res = MyFetcher().fetch(httpserver.url_for('/feed'))
assert res.status == UPDATED_FEED
args = res.feed['parse_feed']
assert args['headers']['content-type'] == 'text/xml'
assert isinstance(args['data_stream'], io.BytesIO)
assert args['data_stream'].getvalue().decode('utf-8') == SIMPLE_RSS
assert args['url'] == httpserver.url_for('/feed')
def test_temporary_error_retry(httpserver):
httpserver.expect_ordered_request('/feed').respond_with_data(status=503)
res_data = SIMPLE_RSS
httpserver.expect_ordered_request('/feed').respond_with_data(SIMPLE_RSS, content_type='text/xml')
res = MyFetcher().fetch(httpserver.url_for('/feed'))
assert res.status == UPDATED_FEED
args = res.feed['parse_feed']
assert args['headers']['content-type'] == 'text/xml'
assert args['url'] == httpserver.url_for('/feed')