From 00ad4d56a775ed802588bf08efe5c0ec2d824a0f Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 10 Mar 2013 15:18:09 +1100 Subject: [PATCH 1/2] FSF address changes missing from previous --- README | 4 ++-- files/cacti/README | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README b/README index db97aa8b..e926509e 100644 --- a/README +++ b/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. 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, -Suite 330, Boston, MA 02111-1307 USA +Fail2Ban; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110, USA diff --git a/files/cacti/README b/files/cacti/README index 80c97ce9..73ceb3bd 100644 --- a/files/cacti/README +++ b/files/cacti/README @@ -49,5 +49,5 @@ details. 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, Suite 330, -Boston, MA 02111-1307 USA +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110, USA From 3665e6dc445bb8108b1a3f4ed18113a7729459e3 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 10 Mar 2013 15:18:42 +1100 Subject: [PATCH 2/2] Add development documentation and framework for code coverage measurement --- .coveragerc | 4 ++ .gitignore | 3 + DEVELOP | 122 +++++++++++++++++++++++++++++++++--- MANIFEST | 2 + fail2ban-client | 2 +- fail2ban-testcases | 22 +++---- server/asyncserver.py | 2 +- server/filter.py | 8 +-- testcases/filtertestcase.py | 6 +- 9 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..3bffd79a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ + +[run] +branch = True +omit = /usr* diff --git a/.gitignore b/.gitignore index 6a0d5e64..1e3b2ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *~ build +dist *.pyc +htmlcov +.coverage diff --git a/DEVELOP b/DEVELOP index 7a6a7b9b..bce9e64e 100644 --- a/DEVELOP +++ b/DEVELOP @@ -24,14 +24,96 @@ Request feature. You can find more details on the Fail2Ban wiki 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 >> /tmp/ban +set test actionunban iptables echo >> /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 -================ -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 @@ -127,12 +209,14 @@ FileContainer .__pos Keeps the position pointer + +dnsutils.py +~~~~~~~~~~~ + DNSUtils Utility class for DNS and IP handling - RF-Note: convert to functions within a separate submodule - filter*.py ~~~~~~~~~~ @@ -156,3 +240,27 @@ action.py ~~~~~~~~~ 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. diff --git a/MANIFEST b/MANIFEST index eef145b6..bedfe560 100644 --- a/MANIFEST +++ b/MANIFEST @@ -3,6 +3,8 @@ ChangeLog TODO THANKS COPYING +DEVELOP +doc/run-rootless.txt fail2ban-client fail2ban-server fail2ban-testcases diff --git a/fail2ban-client b/fail2ban-client index 7ee4a47c..ed211e62 100755 --- a/fail2ban-client +++ b/fail2ban-client @@ -418,7 +418,7 @@ class Fail2banClient: class ServerExecutionException(Exception): pass -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover - can't test main client = Fail2banClient() # Exit with correct return value if client.start(sys.argv): diff --git a/fail2ban-testcases b/fail2ban-testcases index d2bbfed1..ed341385 100755 --- a/fail2ban-testcases +++ b/fail2ban-testcases @@ -77,10 +77,10 @@ verbosity = {'debug': 3, 'fatal': 0, 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 logSys.setLevel(getattr(logging, opts.log_level.upper())) -else: +else: # pragma: no cover # suppress the logging but it would leave unittests' progress dots # ticking, unless like with '-l fatal' which would be silent # unless error occurs @@ -89,9 +89,9 @@ else: # Add the default logging handler stdout = logging.StreamHandler(sys.stdout) # 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')) -else: +else: # pragma: no cover # just prefix with the space stdout.setFormatter(logging.Formatter(' %(message)s')) logSys.addHandler(stdout) @@ -99,7 +99,7 @@ logSys.addHandler(stdout) # # 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..." \ % (version, str(sys.version).replace('\n', '')) @@ -107,9 +107,9 @@ if not opts.log_level or opts.log_level != 'fatal': # # Gather the tests # -if not len(regexps): +if not len(regexps): # pragma: no cover tests = unittest.TestSuite() -else: +else: # pragma: no cover import re class FilteredTestSuite(unittest.TestSuite): _regexps = [re.compile(r) for r in regexps] @@ -159,13 +159,13 @@ filters = [FilterPoll] # always available try: from server.filtergamin import FilterGamin filters.append(FilterGamin) -except Exception, e: +except Exception, e: # pragma: no cover print "I: Skipping gamin backend testing. Got exception '%s'" % e try: from server.filterpyinotify import FilterPyinotify filters.append(FilterPyinotify) -except Exception, e: +except Exception, e: # pragma: no cover print "I: Skipping pyinotify backend testing. Got exception '%s'" % e for Filter_ in filters: @@ -189,7 +189,7 @@ try: tests_results = testRunner.run(tests) -finally: +finally: # pragma: no cover # Just for the sake of it reset the TZ # yoh: move all this into setup/teardown methods within tests os.environ.pop('TZ') @@ -197,5 +197,5 @@ finally: os.environ['TZ'] = old_TZ time.tzset() -if not tests_results.wasSuccessful(): +if not tests_results.wasSuccessful(): # pragma: no cover sys.exit(1) diff --git a/server/asyncserver.py b/server/asyncserver.py index 64ec8f39..66b2b53f 100644 --- a/server/asyncserver.py +++ b/server/asyncserver.py @@ -142,7 +142,7 @@ class AsyncServer(asyncore.dispatcher): 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") 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") asyncore.loop(use_poll = True) diff --git a/server/filter.py b/server/filter.py index 5b2b85e0..fb292573 100644 --- a/server/filter.py +++ b/server/filter.py @@ -211,7 +211,7 @@ class Filter(JailThread): # file has been modified and looks for failures. # @return True when the thread exits nicely - def run(self): + def run(self): # pragma: no cover raise Exception("run() is abstract") ## @@ -226,7 +226,7 @@ class Filter(JailThread): self.failManager.addFailure(FailTicket(ip, unixTime)) # Perform the banning of the IP now. - try: + try: # pragma: no branch - exception is the only way out while True: ticket = self.failManager.toBan() self.jail.putFailTicket(ticket) @@ -373,7 +373,7 @@ class Filter(JailThread): failList.append([ip, date]) # We matched a regex, it is enough to stop. break - except RegexException, e: + except RegexException, e: # pragma: no cover - unsure if reachable logSys.error(e) return failList @@ -507,7 +507,7 @@ class FileFilter(Filter): try: import hashlib md5sum = hashlib.md5 -except ImportError: +except ImportError: # pragma: no cover # hashlib was introduced in Python 2.5. For compatibility with those # elderly Pythons, import from md5 import md5 diff --git a/testcases/filtertestcase.py b/testcases/filtertestcase.py index c10fa78d..a036f804 100644 --- a/testcases/filtertestcase.py +++ b/testcases/filtertestcase.py @@ -99,11 +99,11 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line 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 # polling filter could detect the change 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') if isinstance(fout, str): fout = open(fout, mode) @@ -353,7 +353,7 @@ def get_monitor_failures_testcase(Filter_): _killfile(self.file, self.name) pass - def __str__(self): + def __str__(self): # pragma: no cover - will only show up if unexpected exception is thrown return "MonitorFailures%s(%s)" \ % (Filter_, hasattr(self, 'name') and self.name or 'tempfile')