add feedcore unittests
This commit is contained in:
parent
831186b0c7
commit
7394ca4779
|
@ -6,7 +6,7 @@ python:
|
||||||
install:
|
install:
|
||||||
- sudo apt-get update -q
|
- sudo apt-get update -q
|
||||||
- sudo apt-get install intltool desktop-file-utils
|
- 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
|
- python3 tools/localdepends.py
|
||||||
script:
|
script:
|
||||||
- make lint
|
- make lint
|
||||||
|
|
|
@ -67,7 +67,9 @@ PyPI. With this, you get a self-contained gPodder CLI codebase.
|
||||||
### Test Dependencies
|
### Test Dependencies
|
||||||
|
|
||||||
- python-minimock
|
- python-minimock
|
||||||
- python-coverage
|
- pytest
|
||||||
|
- pytest-httpserver
|
||||||
|
- pytest-cov
|
||||||
- desktop-file-utils
|
- desktop-file-utils
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
@ -86,9 +88,8 @@ Tests in gPodder are written in two different ways:
|
||||||
- [unittests](http://docs.python.org/3/library/unittest.html)
|
- [unittests](http://docs.python.org/3/library/unittest.html)
|
||||||
|
|
||||||
If you want to add doctests, simply write the doctest and make sure that
|
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
|
the module appears after `--doctest-modules` in `pytest.ini`. If you
|
||||||
example, the doctests in src/gpodder/util.py are added as 'util' (the
|
add tests to any module in `src/gpodder` you have nothing to do.
|
||||||
"gpodder" prefix must not be specified there).
|
|
||||||
|
|
||||||
If you want to add unit tests for a specific module (ex: gpodder.model),
|
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:
|
you should add the tests as gpodder.test.model, or in other words:
|
||||||
|
|
3
makefile
3
makefile
|
@ -61,7 +61,8 @@ help:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
unittest:
|
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
|
ISORTOPTS := -rc -c share src/gpodder tools bin/* *.py
|
||||||
lint:
|
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)
|
|
|
@ -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 New Issue