add feedcore unittests
This commit is contained in:
parent
831186b0c7
commit
7394ca4779
8 changed files with 123 additions and 114 deletions
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
3
makefile
3
makefile
|
@ -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:
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[pytest]
|
||||
addopts = --doctest-modules src/gpodder
|
|
@ -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
115
tests/test_feedcore.py
Normal 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')
|
Loading…
Reference in a new issue