casa
$Rev:20696$
|
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 <measurement><name>Files leaked</name><value>" + str(fileleak) + "</value></measurement>\ 00046 <measurement><name>Memory leaked (kB)</name><value>" + str(memoryleak) + "</value></measurement>\ 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>')