Merge pull request #133 from grooverdan/development-code-coverage

coverage pragma comments, improved documentation for the developers
pull/135/merge
Yaroslav Halchenko 12 years ago
commit b03e046370

@ -0,0 +1,4 @@
[run]
branch = True
omit = /usr*

3
.gitignore vendored

@ -1,3 +1,6 @@
*~ *~
build build
dist
*.pyc *.pyc
htmlcov
.coverage

@ -24,14 +24,96 @@ Request feature. You can find more details on the Fail2Ban wiki
Testing Testing
======= =======
Existing tests can be run by executing `fail2ban-testcases`. Existing tests can be run by executing `fail2ban-testcases`. This has options
like --log-level that will probably be useful. `fail2ban-testcases --help` for
full options.
Test cases should cover all usual cases, all exception cases and all inside
/ outside boundary conditions.
Test cases should cover all branches. The coverage tool will help identify
missing branches. Also see http://nedbatchelder.com/code/coverage/branch.html
for more details.
Install the package python-coverage to visualise your test coverage. Run the
following:
coverage run fail2ban-testcases
coverage html
Then look at htmlcov/index.html and see how much coverage your test cases
exert over the codebase. Full coverage is a good thing however it may not be
complete. Try to ensure tests cover as many independant paths through the
code.
Manual Execution. To run in a development environment do:
./fail2ban-client -c config/ -s /tmp/f2b.sock -i start
some quick commands:
status
add test pyinotify
status test
set test addaction iptables
set test actionban iptables echo <ip> <cidr> >> /tmp/ban
set test actionunban iptables echo <ip> <cidr> >> /tmp/unban
get test actionban iptables
get test actionunban iptables
set test banip 192.168.2.2
status test
Documentation about creating tests (when tests are required and some guidelines
for creating good tests) will be added soon.
Coding Standards Coding Standards
================ ================
Coming Soon.
Style
-----
Please use tabs for now. Keep to 80 columns, at least for readable text.
Tests
-----
Add tests. They should test all the code you add in a meaning way.
Coverage
--------
Test coverage should always increase as you add code.
You may use "# pragma: no cover" in the code for branches of code that support
older versions on python. For all other uses of "pragma: no cover" or
"pragma: no branch" document the reason why its not covered. "I haven't written
a test case" isn't a sufficient reason.
Documentation
-------------
Ensure this documentation is up to date after changes. Also ensure that the man
pages still are accurage. Ensure that there is sufficient documentation for
your new features to be used.
Bugs
----
Remove them and don't add any more.
Git
---
Use the following tags in your commit messages:
'ENH:' for enhancements
'BF:' for bug fixes
'DOC:' for documenation fixes
Adding Actions
--------------
If you add an action.d/*.conf file also add a example in config/jail.conf
with enabled=false and maxretry=5 for ssh.
Design Design
@ -127,12 +209,14 @@ FileContainer
.__pos .__pos
Keeps the position pointer Keeps the position pointer
dnsutils.py
~~~~~~~~~~~
DNSUtils DNSUtils
Utility class for DNS and IP handling Utility class for DNS and IP handling
RF-Note: convert to functions within a separate submodule
filter*.py filter*.py
~~~~~~~~~~ ~~~~~~~~~~
@ -156,3 +240,27 @@ action.py
~~~~~~~~~ ~~~~~~~~~
Takes care about executing start/check/ban/unban/stop commands Takes care about executing start/check/ban/unban/stop commands
Releasing
=========
Ensure the version is correct in ./common/version.py
# update man pages
(cd man ; ./generate-man )
git commit -m 'update man pages for release' man/*
python setup.py check
python setup.py sdist
python setup.py bdist_rpm
python setup.py upload
Run the following and update the wiki with output:
python -c 'import common.protocol; common.protocol.printWiki()'
email users and development list of release
TODO notifing distributors etc.

@ -3,6 +3,8 @@ ChangeLog
TODO TODO
THANKS THANKS
COPYING COPYING
DEVELOP
doc/run-rootless.txt
fail2ban-client fail2ban-client
fail2ban-server fail2ban-server
fail2ban-testcases fail2ban-testcases

@ -91,5 +91,5 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License along with
Fail2Ban; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Fail2Ban; if not, write to the Free Software Foundation, Inc., 51 Franklin
Suite 330, Boston, MA 02111-1307 USA Street, Fifth Floor, Boston, MA 02110, USA

@ -418,7 +418,7 @@ class Fail2banClient:
class ServerExecutionException(Exception): class ServerExecutionException(Exception):
pass pass
if __name__ == "__main__": if __name__ == "__main__": # pragma: no cover - can't test main
client = Fail2banClient() client = Fail2banClient()
# Exit with correct return value # Exit with correct return value
if client.start(sys.argv): if client.start(sys.argv):

@ -77,10 +77,10 @@ verbosity = {'debug': 3,
'fatal': 0, 'fatal': 0,
None: 1}[opts.log_level] None: 1}[opts.log_level]
if opts.log_level is not None: if opts.log_level is not None: # pragma: no cover
# so we had explicit settings # so we had explicit settings
logSys.setLevel(getattr(logging, opts.log_level.upper())) logSys.setLevel(getattr(logging, opts.log_level.upper()))
else: else: # pragma: no cover
# suppress the logging but it would leave unittests' progress dots # suppress the logging but it would leave unittests' progress dots
# ticking, unless like with '-l fatal' which would be silent # ticking, unless like with '-l fatal' which would be silent
# unless error occurs # unless error occurs
@ -89,9 +89,9 @@ else:
# Add the default logging handler # Add the default logging handler
stdout = logging.StreamHandler(sys.stdout) stdout = logging.StreamHandler(sys.stdout)
# Custom log format for the verbose tests runs # Custom log format for the verbose tests runs
if verbosity > 1: if verbosity > 1: # pragma: no cover
stdout.setFormatter(logging.Formatter(' %(asctime)-15s %(thread)s %(message)s')) stdout.setFormatter(logging.Formatter(' %(asctime)-15s %(thread)s %(message)s'))
else: else: # pragma: no cover
# just prefix with the space # just prefix with the space
stdout.setFormatter(logging.Formatter(' %(message)s')) stdout.setFormatter(logging.Formatter(' %(message)s'))
logSys.addHandler(stdout) logSys.addHandler(stdout)
@ -99,7 +99,7 @@ logSys.addHandler(stdout)
# #
# Let know the version # Let know the version
# #
if not opts.log_level or opts.log_level != 'fatal': if not opts.log_level or opts.log_level != 'fatal': # pragma: no cover
print "Fail2ban %s test suite. Python %s. Please wait..." \ print "Fail2ban %s test suite. Python %s. Please wait..." \
% (version, str(sys.version).replace('\n', '')) % (version, str(sys.version).replace('\n', ''))
@ -107,9 +107,9 @@ if not opts.log_level or opts.log_level != 'fatal':
# #
# Gather the tests # Gather the tests
# #
if not len(regexps): if not len(regexps): # pragma: no cover
tests = unittest.TestSuite() tests = unittest.TestSuite()
else: else: # pragma: no cover
import re import re
class FilteredTestSuite(unittest.TestSuite): class FilteredTestSuite(unittest.TestSuite):
_regexps = [re.compile(r) for r in regexps] _regexps = [re.compile(r) for r in regexps]
@ -159,13 +159,13 @@ filters = [FilterPoll] # always available
try: try:
from server.filtergamin import FilterGamin from server.filtergamin import FilterGamin
filters.append(FilterGamin) filters.append(FilterGamin)
except Exception, e: except Exception, e: # pragma: no cover
print "I: Skipping gamin backend testing. Got exception '%s'" % e print "I: Skipping gamin backend testing. Got exception '%s'" % e
try: try:
from server.filterpyinotify import FilterPyinotify from server.filterpyinotify import FilterPyinotify
filters.append(FilterPyinotify) filters.append(FilterPyinotify)
except Exception, e: except Exception, e: # pragma: no cover
print "I: Skipping pyinotify backend testing. Got exception '%s'" % e print "I: Skipping pyinotify backend testing. Got exception '%s'" % e
for Filter_ in filters: for Filter_ in filters:
@ -189,7 +189,7 @@ try:
tests_results = testRunner.run(tests) tests_results = testRunner.run(tests)
finally: finally: # pragma: no cover
# Just for the sake of it reset the TZ # Just for the sake of it reset the TZ
# yoh: move all this into setup/teardown methods within tests # yoh: move all this into setup/teardown methods within tests
os.environ.pop('TZ') os.environ.pop('TZ')
@ -197,5 +197,5 @@ finally:
os.environ['TZ'] = old_TZ os.environ['TZ'] = old_TZ
time.tzset() time.tzset()
if not tests_results.wasSuccessful(): if not tests_results.wasSuccessful(): # pragma: no cover
sys.exit(1) sys.exit(1)

@ -49,5 +49,5 @@ details.
You should have received a copy of the GNU General Public You should have received a copy of the GNU General Public
License along with Fail2Ban; if not, write to the Free License along with Fail2Ban; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111-1307 USA Boston, MA 02110, USA

@ -142,7 +142,7 @@ class AsyncServer(asyncore.dispatcher):
if sys.version_info >= (2, 6): # if python 2.6 or greater... if sys.version_info >= (2, 6): # if python 2.6 or greater...
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll") logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0 asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
else: else: # pragma: no cover
logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll") logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll")
asyncore.loop(use_poll = True) asyncore.loop(use_poll = True)

@ -211,7 +211,7 @@ class Filter(JailThread):
# file has been modified and looks for failures. # file has been modified and looks for failures.
# @return True when the thread exits nicely # @return True when the thread exits nicely
def run(self): def run(self): # pragma: no cover
raise Exception("run() is abstract") raise Exception("run() is abstract")
## ##
@ -226,7 +226,7 @@ class Filter(JailThread):
self.failManager.addFailure(FailTicket(ip, unixTime)) self.failManager.addFailure(FailTicket(ip, unixTime))
# Perform the banning of the IP now. # Perform the banning of the IP now.
try: try: # pragma: no branch - exception is the only way out
while True: while True:
ticket = self.failManager.toBan() ticket = self.failManager.toBan()
self.jail.putFailTicket(ticket) self.jail.putFailTicket(ticket)
@ -373,7 +373,7 @@ class Filter(JailThread):
failList.append([ip, date]) failList.append([ip, date])
# We matched a regex, it is enough to stop. # We matched a regex, it is enough to stop.
break break
except RegexException, e: except RegexException, e: # pragma: no cover - unsure if reachable
logSys.error(e) logSys.error(e)
return failList return failList
@ -507,7 +507,7 @@ class FileFilter(Filter):
try: try:
import hashlib import hashlib
md5sum = hashlib.md5 md5sum = hashlib.md5
except ImportError: except ImportError: # pragma: no cover
# hashlib was introduced in Python 2.5. For compatibility with those # hashlib was introduced in Python 2.5. For compatibility with those
# elderly Pythons, import from md5 # elderly Pythons, import from md5
import md5 import md5

@ -99,11 +99,11 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line
Returns open fout Returns open fout
""" """
if sys.version_info[:2] <= (2,4): if sys.version_info[:2] <= (2,4): # pragma: no cover
# on old Python st_mtime is int, so we should give at least 1 sec so # on old Python st_mtime is int, so we should give at least 1 sec so
# polling filter could detect the change # polling filter could detect the change
time.sleep(1) time.sleep(1)
if isinstance(fin, str): if isinstance(fin, str): # pragma: no branch - only used with str in test cases
fin = open(fin, 'r') fin = open(fin, 'r')
if isinstance(fout, str): if isinstance(fout, str):
fout = open(fout, mode) fout = open(fout, mode)
@ -353,7 +353,7 @@ def get_monitor_failures_testcase(Filter_):
_killfile(self.file, self.name) _killfile(self.file, self.name)
pass pass
def __str__(self): def __str__(self): # pragma: no cover - will only show up if unexpected exception is thrown
return "MonitorFailures%s(%s)" \ return "MonitorFailures%s(%s)" \
% (Filter_, hasattr(self, 'name') and self.name or 'tempfile') % (Filter_, hasattr(self, 'name') and self.name or 'tempfile')

Loading…
Cancel
Save