casa  $Rev:20696$
 All Classes Namespaces Files Functions Variables
memTest.py
Go to the documentation of this file.
00001 #
00002 # This file implements a plugin for nose, which monitors the net memory leaked
00003 # and opened file descriptors from a test run. The result are written to
00004 # an XML file. This plugin is just an adaptation of nose's built-in
00005 # XUnit plugin.
00006 #
00007 
00008 import nose.plugins.xunit
00009 import traceback
00010 import inspect
00011 import time
00012 import commands
00013 import os
00014 
00015 def nice_classname(obj):
00016     """Returns a nice name for class object or class instance.
00017     
00018         >>> nice_classname(Exception()) # doctest: +ELLIPSIS
00019         '...Exception'
00020         >>> nice_classname(Exception)
00021         'exceptions.Exception'
00022     
00023     """
00024     if inspect.isclass(obj):
00025         cls_name = obj.__name__
00026     else:
00027         cls_name = obj.__class__.__name__
00028     mod = inspect.getmodule(obj)
00029     if mod:
00030         name = mod.__name__
00031         # jython
00032         if name.startswith('org.python.core.'):
00033             name = name[len('org.python.core.'):]
00034         return "%s.%s" % (name, cls_name)
00035     else:
00036         return cls_name
00037 
00038 def write_message(fileleak, memoryleak):
00039 
00040     if fileleak != 0:
00041         print "Net file descriptors opened:", fileleak
00042     if memoryleak != 0:
00043         print "Net memory allocated:", memoryleak, "kB"
00044     return "<system-out>\
00045 &lt;measurement&gt;&lt;name&gt;Files leaked&lt;/name&gt;&lt;value&gt;" + str(fileleak) + "&lt;/value&gt;&lt;/measurement&gt;\
00046 &lt;measurement&gt;&lt;name&gt;Memory leaked (kB)&lt;/name&gt;&lt;value&gt;" + str(memoryleak) + "&lt;/value&gt;&lt;/measurement&gt;\
00047 </system-out>"
00048 
00049 
00050 class MemTest(nose.plugins.xunit.Xunit):
00051 
00052     name = "memtest"
00053 
00054     def options(self, parser, env):
00055         # Identical to the base class method, except that the command line
00056         # option needs to be called something different
00057         """Sets additional command line options."""
00058         nose.plugins.base.Plugin.options(self, parser, env)
00059         parser.add_option(
00060             '--memtest-file', action='store',
00061             dest='xunit_file', metavar="FILE",
00062             default=env.get('NOSE_XUNIT_FILE', 'nosetests.xml'),
00063             help=("Path to xml file to store the xunit report in. "
00064                   "Default is nosetests.xml in the working directory "
00065                   "[NOSE_XUNIT_FILE]"))
00066 
00067         # Find the lsof executable
00068         self.lsof = "/usr/sbin/lsof"
00069         if not os.path.exists(self.lsof):
00070             self.lsof = "/usr/bin/lsof"
00071         if not os.path.exists(self.lsof):
00072             print "Warning: Could not find lsof at /usr/sbin/lsof or /usr/bin/lsof"
00073 
00074     def report(self, stream):
00075         """Writes an Xunit-formatted XML file
00076 
00077         The file includes a report of test errors and failures.
00078 
00079         """
00080         self.stats['encoding'] = self.encoding
00081         self.stats['total'] = (self.stats['errors'] + self.stats['failures']
00082                                + self.stats['passes'] + self.stats['skipped'])
00083         self.error_report_file.write(
00084             '<?xml version="1.0" encoding="%s"?>'
00085             '<testsuites>' % self.encoding)
00086         
00087         self.error_report_file.write(''.join(self.errorlist))
00088         self.error_report_file.write('</testsuites>')
00089         self.error_report_file.close()
00090         if self.config.verbosity > 1:
00091             stream.writeln("-" * 70)
00092             stream.writeln("XML: %s" % self.error_report_file.name)
00093 
00094     def startTest(self, test):
00095         """Initializes a timer before starting a test."""
00096         self._timer = time.time()
00097         self._pid = os.getpid()
00098         msg = '***** List of open files after running %s\n'%test
00099         infile = open('ListOpenFiles','a')
00100         infile.write(msg)
00101         infile.close()
00102 
00103         (errorcode, n) = commands.getstatusoutput(self.lsof + ' -p ' + str(self._pid) + ' | wc -l')
00104         if errorcode == 0:
00105             self._openfiles = n
00106         else:
00107             self._openfiles = 0
00108 
00109         (errorcode, n) = commands.getstatusoutput('ps -p ' + str(self._pid) + ' -o rss | tail -1')
00110         if errorcode == 0:
00111             self._resident_memory = n
00112         else:
00113             self._resident_memory = 0           
00114 
00115     def stopContext(self, context):
00116         out = commands.getoutput("du -h")
00117         print "Directory contents after", context
00118         print out
00119 
00120     def _update_after_test(self):
00121         # The predefined hooks stopTest() and afterTest() cannot be used
00122         # because they get called after addError/addFailure/addSuccess
00123         os.system(self.lsof + ' -p ' + str(self._pid) + ' | grep -i nosedir >> ListOpenFiles')
00124 
00125         (errorcode, n) = commands.getstatusoutput(self.lsof + ' -p ' + str(self._pid) + ' | wc -l')
00126 
00127         if errorcode == 0:
00128             self._fileleak = int(n) - int(self._openfiles)
00129         else:
00130             self._fileleak = -1
00131 
00132         (errorcode, n) = commands.getstatusoutput('ps -p ' + str(self._pid) + ' -o rss | tail -1')
00133         if errorcode == 0:
00134             self._memoryleak = int(n) - int(self._resident_memory)
00135         else:
00136             self._memoryleak = 0
00137 
00138     def addError(self, test, err, capt=None):
00139         """Add error output to Xunit report.
00140         """
00141         self._update_after_test()
00142         taken = time.time() - self._timer
00143         if issubclass(err[0], nose.exc.SkipTest):
00144             self.stats['skipped'] +=1
00145             return
00146         tb = ''.join(traceback.format_exception(*err))
00147         self.stats['errors'] += 1
00148         id = test.id()
00149         name = id[-id[::-1].find("."):]
00150         self.errorlist.append(
00151             '<testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">'
00152             '<testcase classname="%(cls)s" name="%(name)s" time="%(taken)d">'
00153             '<error type="%(errtype)s">%(tb)s</error></testcase>' %
00154             {'cls': '.'.join(id.split('.')[:-1]),
00155              'name': name,
00156              'errtype': nice_classname(err[0]),
00157              'tb': tb,
00158              'taken': taken,
00159              })
00160         self.errorlist.append(write_message(self._fileleak, self._memoryleak))
00161         self.errorlist.append('</testsuite>')
00162 
00163     def addFailure(self, test, err, capt=None, tb_info=None):
00164         """Add failure output to Xunit report.
00165         """
00166         self._update_after_test()
00167         taken = time.time() - self._timer
00168         tb = ''.join(traceback.format_exception(*err))
00169         self.stats['failures'] += 1
00170         id = test.id()
00171         name = id[-id[::-1].find("."):]
00172         self.errorlist.append(
00173             '<testsuite name="nosetests" tests="1" errors="0" failures="1" skip="0">'
00174             '<testcase classname="%(cls)s" name="%(name)s" time="%(taken)d">'
00175             '<failure type="%(errtype)s">%(tb)s</failure></testcase>' %
00176             {'cls': '.'.join(id.split('.')[:-1]),
00177              'name': name,
00178              'errtype': nice_classname(err[0]),
00179              'tb': tb,
00180              'taken': taken,
00181              })
00182         self.errorlist.append(write_message(self._fileleak, self._memoryleak))
00183         self.errorlist.append('</testsuite>')
00184         
00185     def addSuccess(self, test, capt=None):
00186         """Add success output to Xunit report.
00187         """
00188         self._update_after_test()
00189         taken = time.time() - self._timer
00190         self.stats['passes'] += 1
00191         id = test.id()
00192         name = id[-id[::-1].find("."):]
00193         self.errorlist.append(
00194             '<testsuite name="nosetests" tests="1" errors="0" failures="0" skip="0">'
00195             '<testcase classname="%(cls)s" name="%(name)s" '
00196             'time="%(taken)d" />' %
00197             {'cls': '.'.join(id.split('.')[:-1]),
00198              'name': name,
00199              'taken': taken,
00200              })
00201         self.errorlist.append(write_message(self._fileleak, self._memoryleak))
00202         self.errorlist.append('</testsuite>')