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

View File

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

View File

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

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