mirror of https://github.com/fail2ban/fail2ban
Merge pull request #133 from grooverdan/development-code-coverage
coverage pragma comments, improved documentation for the developerspull/135/merge
commit
b03e046370
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
[run]
|
||||||
|
branch = True
|
||||||
|
omit = /usr*
|
|
@ -1,3 +1,6 @@
|
||||||
*~
|
*~
|
||||||
build
|
build
|
||||||
|
dist
|
||||||
*.pyc
|
*.pyc
|
||||||
|
htmlcov
|
||||||
|
.coverage
|
||||||
|
|
120
DEVELOP
120
DEVELOP
|
@ -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.
|
||||||
|
|
2
MANIFEST
2
MANIFEST
|
@ -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
|
||||||
|
|
4
README
4
README
|
@ -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…
Reference in New Issue