diff options
Diffstat (limited to 'distbuild/crashpoint.py')
-rw-r--r-- | distbuild/crashpoint.py | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/distbuild/crashpoint.py b/distbuild/crashpoint.py new file mode 100644 index 00000000..01bfbd6b --- /dev/null +++ b/distbuild/crashpoint.py @@ -0,0 +1,126 @@ +# distbuild/crashpoint.py -- user-controlled crashing +# +# Copyright (C) 2014 Codethink Limited +# +# This program 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; version 2 of the License. +# +# This program 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.. + + +'''Crash the application. + +For crash testing, it's useful to easily induce crashes, to see how the +rest of the system manages. This module implements user-controllable +crashes. The code will be sprinkled with calls to the ``crash_point`` +function, which crashes the process if call matches a set of user-defined +criteria. + +The criteria consist of: + +* a filename +* a function name +* a maximum call count + +The criterion is fullfilled if ``crash_point`` is called from the named +function defined in the named file more than the given number of times. +Filename matching is using substrings (a filename pattern ``foo.py`` +matches an actual source file path of +``/usr/lib/python2.7/site-packages/distbuild/foo.py``), but function +names must match exactly. It is not possible to match on class names +(since that information is not available from a traceback). + +''' + + +import logging +import os +import sys +import traceback + + +detailed_logging = False + + +def debug(msg): # pragma: no cover + if detailed_logging: + logging.debug(msg) + + +class CrashCondition(object): + + def __init__(self, filename, funcname, max_calls): + self.filename = filename + self.funcname = funcname + self.max_calls = max_calls + self.called = 0 + + def matches(self, filename, funcname): + if self.filename not in filename: + debug( + 'crashpoint: filename mismatch: %s not in %s' % + (repr(self.filename), repr(filename))) + return False + + if self.funcname != funcname: + debug( + 'crashpoint: funcname mismatch: %s != %s' % + (self.funcname, funcname)) + return False + + debug('crashpoint: matches: %s %s' % (filename, funcname)) + return True + + def triggered(self, filename, funcname): + if self.matches(filename, funcname): + self.called += 1 + return self.called >= self.max_calls + else: + return False + + +crash_conditions = [] + + +def add_crash_condition(filename, funcname, max_calls): + crash_conditions.append(CrashCondition(filename, funcname, max_calls)) + + +def add_crash_conditions(strings): + for s in strings: + words = s.split(':') + if len(words) != 3: # pragma: no cover + logging.error('Ignoring malformed crash condition: %s' % repr(s)) + else: + add_crash_condition(words[0], words[1], int(words[2])) + + +def clear_crash_conditions(): + del crash_conditions[:] + + +def crash_point(frame=None): + if frame is None: + frames = traceback.extract_stack(limit=2) + frame = frames[0] + + filename, lineno, funcname, text = frame + + for condition in crash_conditions: + if condition.triggered(filename, funcname): + logging.critical( + 'Crash triggered from %s:%s:%s' % (filename, lineno, funcname)) + sys.exit(255) + else: + debug( + 'Crash not triggered by %s:%s:%s' % + (filename, lineno, funcname)) + |