[asterisk-commits] sgriepentrog: branch sgriepentrog/testsuite-vg2 r5004 - in /asterisk/team/sgr...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Mon Apr 28 14:17:08 CDT 2014


Author: sgriepentrog
Date: Mon Apr 28 14:17:02 2014
New Revision: 5004

URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=5004
Log:
testsuite: valgrind changes v2

Added:
    asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py   (with props)
Modified:
    asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/asterisk.py
    asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_case.py
    asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_config.py
    asterisk/team/sgriepentrog/testsuite-vg2/logger.conf
    asterisk/team/sgriepentrog/testsuite-vg2/runtests.py

Modified: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/asterisk.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/asterisk.py?view=diff&rev=5004&r1=5003&r2=5004
==============================================================================
--- asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/asterisk.py (original)
+++ asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/asterisk.py Mon Apr 28 14:17:02 2014
@@ -317,7 +317,8 @@
         def __wait_fully_booted_error(cli_command):
             """Errback for CLI command waitfullybooted"""
 
-            if time.time() - self.__start_asterisk_time > 5:
+            wait_time = 25 if self.valgrind.enable else 5
+            if time.time() - self.__start_asterisk_time > wait_time:
                 msg = "Asterisk core waitfullybooted for %s failed" % self.host 
                 LOGGER.error(msg)
                 self._start_deferred.errback(Exception(msg))
@@ -335,6 +336,11 @@
             "-f", "-g", "-q", "-m", "-n",
             "-C", "%s" % os.path.join(self.astetcdir, "asterisk.conf")
         ]
+
+        if self.valgrind.enable:
+            cmd = self.valgrind.insert_command(cmd, self.base)
+
+        LOGGER.debug("Executing %s ..." % ' '.join(cmd))
 
         # Make the start/stop deferreds - this method will return
         # the start deferred, and pass the stop deferred to the AsteriskProtocol
@@ -440,7 +446,8 @@
         else:
             # Schedule a kill. If we don't gracefully shut down Asterisk, this
             # will ensure that the test is stopped.
-            self._stop_cancel_tokens.append(reactor.callLater(10, __send_kill))
+            secs = 90 if self.valgrind.enable else 10
+            self._stop_cancel_tokens.append(reactor.callLater(secs, __send_kill))
 
             # Start by asking to stop gracefully.
             __send_stop_gracefully()

Modified: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_case.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_case.py?view=diff&rev=5004&r1=5003&r2=5004
==============================================================================
--- asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_case.py (original)
+++ asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_case.py Mon Apr 28 14:17:02 2014
@@ -13,6 +13,7 @@
 import os
 import traceback
 import uuid
+import copy
 from datetime import datetime
 from hashlib import md5
 from twisted.internet import reactor, defer
@@ -23,7 +24,7 @@
 from test_config import TestConfig
 from test_conditions import TestConditionController
 from version import AsteriskVersion
-
+from valgrind import ValgrindConfig
 
 try:
     from pcap_listener import PcapListener
@@ -118,6 +119,7 @@
         self._ami_callbacks = []
         self._pcap_callbacks = []
         self._stop_deferred = None
+        self.valgrind = ValgrindConfig(self.test_config)
 
         # Pull additional configuration from YAML config if possible
         if test_config:
@@ -139,6 +141,11 @@
         self._setup_conditions()
 
         LOGGER.info("Executing " + self.test_name)
+
+        if self.valgrind.enable:
+            self.reactor_timeout *= 5
+            LOGGER.info("Adjusting reactor timeout to %d for valgrind" %
+                        self.reactor_timeout)
 
         # Enable twisted logging
         observer = log.PythonLoggingObserver()
@@ -211,6 +218,8 @@
             self.ast.append(Asterisk(base=self.base, host=host,
                                      ast_conf_options=self.ast_conf_options))
             self.condition_controller.register_asterisk_instance(self.ast[i])
+            self.ast[i].valgrind = copy.deepcopy(self.valgrind)
+
             # If a base configuration for this Asterisk instance has been
             # provided, install it first
             if base_configs_path:
@@ -867,3 +876,4 @@
         if self.connect_agi:
             self.create_fastagi_factory(count=self.asterisk_instances)
 
+# vim: set ts=8 sw=4 sts=4 et ai tw=79:

Modified: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_config.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_config.py?view=diff&rev=5004&r1=5003&r2=5004
==============================================================================
--- asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_config.py (original)
+++ asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/test_config.py Mon Apr 28 14:17:02 2014
@@ -251,6 +251,9 @@
         self.test_configuration = "(none)"
         self.condition_definitions = []
         self.global_test_config = global_test_config
+        self.valgrind = None
+        self.valgrind_leak = None
+        self.valgrind_undef = None
 
         try:
             self._parse_config()
@@ -332,6 +335,10 @@
         if "forced-version" in properties:
             self.forced_version = AsteriskVersion(properties["forced-version"])
 
+        self.valgrind = properties.get("valgrind") or False
+        self.valgrind_leak = properties.get("valgrind-leak") or False
+        self.valgrind_undef = properties.get("valgrind-undef") or False
+
     def _parse_config(self):
         """Parse the test-config YAML file."""
 
@@ -468,3 +475,4 @@
 
         # all tags matched successfully
         return self.can_run
+# vim:sw=4:ts=4:expandtab:textwidth=79

Added: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py?view=auto&rev=5004
==============================================================================
--- asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py (added)
+++ asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py Mon Apr 28 14:17:02 2014
@@ -1,0 +1,333 @@
+#! /usr/bin/env python
+""" Asterisk testsuite support for Valgrind
+
+This module provides classes to enable running Asterisk under Valgrind
+
+Copyright (C) 2013, Digium, Inc.
+Scott Griepentrog <sgriepentrog at digium.com>
+Nitesh Bansal <nitesh.bansal at gmail.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+import os
+import logging
+import test_suite_utils
+import xml.dom
+import xml.dom.minidom
+
+LOGGER = logging.getLogger(__name__)
+
+
+class ValgrindConfig(object):
+    """
+    Class that contains valgrind options processing
+    and argument list additions used to start valgrind
+    Instantiated by TestCase
+
+    This class knows how to assemble the options
+    to Valgrind to meet requested configuration and
+    insert it into the argument list for running
+    Asterisk
+
+    """
+
+    def __init__(self, test_config):
+        """ Create a new Valgrind Configuration instance
+
+        :test_config: TestConfig object
+
+        """
+        self.enable = False
+        self._gensupp = False
+        self._leak = "full"
+        self._undef = "yes"
+        self._gdb = False
+        self._supp_filename = ""
+        self._log_filename = ""
+        self._xml_filename = ""
+        self._use_xml_log = True
+
+        self.enable = test_config.valgrind or os.getenv("VALGRIND") == "true"
+
+        if self.enable and test_suite_utils.which('valgrind') is None:
+            LOGGER.error("VALGRIND IS NOT INSTALLED - disabling")
+            self.enable = False
+
+        if self.enable:
+            self._gensupp = os.getenv("VALGRINDGENSUPP") == "true"
+            self._leak = test_config.valgrind_leak or os.getenv("VALGRINDLEAK")
+            self._undef = test_config.valgrind_undef or \
+                os.getenv("VALGRINDUNDEF")
+
+    def insert_command(self, cmd, base):
+        """ insert the valgrind command and options into asterisk command
+
+            :cmd: List of cli arguments to start asterisk with
+            :base: Base directory of Asterisk instance to be started
+            :returns: Modified copy of cmd with additional entries inserted
+
+        """
+        if not self.enable:
+            return cmd
+
+        self._log_filename = os.path.join(base, "valgrind.txt")
+        self._xml_filename = os.path.join(base, "valgrind.xml")
+        self._supp_filename = os.path.join(base, "etc/asterisk/valgrind.supp")
+
+        #if self.gdb and not self.index:
+        #    cmd = ["--vgdb=yes", "--vgdb-error=0"] + cmd
+        #    reactor.callLater(2, self.StartGdb)
+
+        # cannot both generate suppresion file and use suppressions
+        if self._gensupp:
+            LOGGER.info("Generating suppresion file")
+            cmd = ["--gen-suppressions=all"] + cmd
+        elif os.path.exists(self._supp_filename):
+            LOGGER.info("Using valgrind suppression file %s" %
+                        (self._supp_filename))
+            cmd = ["--suppressions=%s" % self._supp_filename] + cmd
+
+        if self._leak.lower() == "all":
+            cmd = ["--show-reachable=yes"] + cmd
+            self._leak = "full"
+
+        if self._leak.lower() == "no":
+            cmd = ["--show-possibly-lost=no"] + cmd
+
+        if self._undef.lower() == "no":
+            cmd = ["--undef-value-errors=no"] + cmd
+
+        if self._use_xml_log:
+            cmd = [
+                "--xml=yes",
+                "--xml-file=%s" % self._xml_filename,
+            ] + cmd
+
+        cmd = [
+            "valgrind",
+            "--log-file=%s" % self._log_filename,
+            "--num-callers=50",
+            "--error-limit=no",
+            "--leak-check=%s" % self._leak,
+        ] + cmd
+
+        return cmd
+
+
+class ValgrindPostTest(object):
+    """
+    Class that contains valgrind post-test xml parsing,
+    Instantiated by TestRun in runtests.py
+    """
+    def __init__(self):
+        """ initialize the post test processing
+
+            :returns: nothing
+
+        """
+        self.passed = True
+        self.stdout = ''
+        self._ast_directories = ''
+
+    def _output(self, text):
+        """ output a message via stdout
+
+            :text: the text string to output
+            :returns: nothing, but updates self.stdout
+
+        """
+        self.stdout += text + "\n"
+
+    def parse_xml(self, xmldoc):
+        """ parse the xml error output from valgrind
+
+            :xmldoc: the xml document tree
+            :returns: nothing, but self.passed is updated
+
+        """
+        count_bytes_leaked = 0
+        count_errors_found = 0
+        count_error_kinds = {}
+        code_error_tree = {}
+        generated_suppressions = []
+
+        error_list = xmldoc.getElementsByTagName('error')
+        count_errors_found = len(error_list)
+
+        def get_text(nodelist):
+            """ recursively scan a list of xml nodes and extract text
+
+                :nodelist: a list of nodes, or a single node
+                :returns: string concatenation of text elements
+            """
+            text = []
+            if not isinstance(nodelist, list):
+                return get_text(nodelist.childNodes)
+            for node in nodelist:
+                if node.nodeType == node.ELEMENT_NODE:
+                    text.append(get_text(node.childNodes))
+                if node.nodeType == node.TEXT_NODE:
+                    text.append(str(node.data))
+            return ''.join(text)
+
+        def get_data(nodelist):
+            """ scan a list of xml nodes and extract CDATA
+
+                :nodelist: a list of nodes to recursively examine
+                :returns: string concatenation of CDATA elements
+            """
+            data = []
+            for node in nodelist:
+                if node.nodeType == node.ELEMENT_NODE:
+                    data.append(get_data(node.childNodes))
+                if node.nodeType == node.CDATA_SECTION_NODE:
+                    data.append(str(node.data))
+            return ''.join(data)
+
+        def arrange_frames_by_code(tree, frames, error):
+            """ recursively build tree of backtrace and errors
+
+                :tree: current position in the tree
+                :frames: array of backtrace stack frames at point of error
+                :error: error to insert at end of frame list
+                :returns: nothing, but modifies tree
+
+            """
+            frame = frames[0]
+            func_name = get_text(frame.getElementsByTagName('fn'))
+            file_path = get_text(frame.getElementsByTagName('dir'))
+            file_name = get_text(frame.getElementsByTagName('file'))
+            line = get_text(frame.getElementsByTagName('line'))
+            obj = get_text(frame.getElementsByTagName('obj'))
+
+            if file_name:
+                codefile = file_path + "/" + file_name + ":" + line
+                codefile += " " + func_name + "()"
+            else:
+                codefile = "{" + obj + "}: " + func_name + "()"
+
+            if line:
+                errortag = error + " at line " + line
+            else:
+                errortag = error
+
+            if not codefile in tree:
+                tree[codefile] = {}
+
+            if 1 == len(frames):
+                tree[codefile][errortag] = {}
+            else:
+                arrange_frames_by_code(tree[codefile], frames[1:], error)
+
+        def dump_code_errors(tree, depth):
+            """ recurse the error tree and output the details
+
+                :tree: nested dictionaries containing backtrace and errors
+                :depth: indention level
+                :returns: nothing
+
+            """
+            for text, branch in tree.items():
+                self._output('  ' * depth + text)
+                if branch:
+                    dump_code_errors(branch, depth + 1)
+
+        for error in error_list:
+            kind = get_text(error.getElementsByTagName('kind'))
+            what = get_text(error.getElementsByTagName('what'))
+            auxwhat = get_text(error.getElementsByTagName('auxwhat'))
+
+            if kind == "Leak_DefinitelyLost":
+                leaked_bytes = error.getElementsByTagName('leakedbytes')
+                if len(leaked_bytes) == 1:
+                    dom_element = leaked_bytes[0]
+                    bytes_leaked = int(dom_element.childNodes[0].nodeValue)
+                    what += " %d bytes" % bytes_leaked
+                    count_bytes_leaked += bytes_leaked
+
+            if kind == "InvalidRead" or kind == "InvalidWrite":
+                what += " " + auxwhat
+
+            stacks = error.getElementsByTagName('stack')
+            stack = stacks[0]
+            frames = stack.getElementsByTagName('frame')
+
+            arrange_frames_by_code(code_error_tree, frames[::-1],
+                                   "#" + kind + "# " + what)
+
+            if len(stacks) > 1:
+                auxstack = stacks[1]
+                auxframes = auxstack.getElementsByTagName('frame')
+                arrange_frames_by_code(code_error_tree, auxframes[::-1],
+                                       "#" + kind + "#=INFO: " + auxwhat)
+
+            if not kind in count_error_kinds:
+                count_error_kinds[kind] = 0
+            count_error_kinds[kind] += 1
+
+            suppressions = error.getElementsByTagName('suppression')
+            if suppressions:
+                suppression = suppressions[0]
+                rawtext = get_data(suppression.getElementsByTagName('rawtext'))
+                if rawtext:
+                    if not rawtext in generated_suppressions:
+                        generated_suppressions.append(rawtext)
+
+        if code_error_tree:
+            self._output("Valgrind error trace:")
+            dump_code_errors(code_error_tree, 0)
+
+        for kind, count in count_error_kinds.iteritems():
+            self._output("Valgrind found %d errors of type %s" % (count, kind))
+        if count_bytes_leaked > 0:
+            self._output("Valgrind detected %s bytes definitely leaked" % (
+                count_bytes_leaked))
+            self.passed = False
+        if count_errors_found > 0:
+            self._output("Valgrind detected %s total errors" % (
+                         count_errors_found))
+            self.passed = False
+
+        if generated_suppressions:
+            supp_file = "%s/valgrind.supp" % self._ast_directories
+            LOGGER.error("Writing example suppression file to %s" % supp_file)
+            with open(supp_file, "w") as supp_file_handle:
+                supp_file_handle.write(''.join(generated_suppressions))
+
+    def check_results(self, existing_ast_instances, ast_directories):
+        """ check the results in valgrind.xml after the test
+
+            :existing_ast_instances: ast# directories ignored from previous
+            :ast_directories: Base directory of Asterisk instance
+            :returns: nothing, but sets self.passed and self.stdout
+
+        """
+        self.passed = True
+        self._ast_directories = ast_directories
+        i = existing_ast_instances + 1
+        while os.path.isdir("%s/ast%d" % (ast_directories, i)):
+            ast_dir = "%s/ast%d/" % (ast_directories, i)
+            xml_file = ast_dir + "/valgrind.xml"
+            if not os.path.exists(xml_file):
+                i += 1
+                continue
+            LOGGER.info("Parsing %s " % xml_file)
+            xmldoc = None
+            try:
+                xmldoc = xml.dom.minidom.parse(xml_file)
+            except xml.parsers.expat.ExpatError:
+                # valgrind may have left off the closing tag
+                with open(xml_file, "a") as xmlfile:
+                    xmlfile.write("</valgrindoutput>")
+                    self.passed = False
+                    self._output("Valgrind did not shutdown normally, " +
+                                 "leak info may be missing")
+
+            if not xmldoc:
+                xmldoc = xml.dom.minidom.parse(xml_file)
+
+            self.parse_xml(xmldoc)
+            i += 1
+
+# vim:sw=4:ts=4:expandtab:textwidth=79

Propchange: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/team/sgriepentrog/testsuite-vg2/lib/python/asterisk/valgrind.py
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: asterisk/team/sgriepentrog/testsuite-vg2/logger.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/sgriepentrog/testsuite-vg2/logger.conf?view=diff&rev=5004&r1=5003&r2=5004
==============================================================================
--- asterisk/team/sgriepentrog/testsuite-vg2/logger.conf (original)
+++ asterisk/team/sgriepentrog/testsuite-vg2/logger.conf Mon Apr 28 14:17:02 2014
@@ -35,6 +35,12 @@
 handlers=stdout,normalFile,verboseFile
 qualname=asterisk
 
+[logger_valgrind]
+level=NOTSET
+propagate=0
+handlers=stdout,normalFile,verboseFile
+qualname=valgrind
+
 [logger_test_case]
 level=NOTSET
 propagate=0

Modified: asterisk/team/sgriepentrog/testsuite-vg2/runtests.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/sgriepentrog/testsuite-vg2/runtests.py?view=diff&rev=5004&r1=5003&r2=5004
==============================================================================
--- asterisk/team/sgriepentrog/testsuite-vg2/runtests.py (original)
+++ asterisk/team/sgriepentrog/testsuite-vg2/runtests.py Mon Apr 28 14:17:02 2014
@@ -26,6 +26,7 @@
 from asterisk.asterisk import Asterisk
 from asterisk.test_config import Dependency, TestConfig
 from asterisk import test_suite_utils
+from asterisk.valgrind import ValgrindPostTest
 
 TESTS_CONFIG = "tests.yaml"
 TEST_RESULTS = "asterisk-test-suite-report.xml"
@@ -38,11 +39,13 @@
         self.time = 0.0
         self.test_name = test_name
         self.ast_version = ast_version
+        self.existing_ast_instances = 0
         self.options = options
         self.test_config = TestConfig(test_name, global_config)
         self.failure_message = ""
         self.__check_can_run(ast_version)
         self.stdout = ""
+        self.valgrind = ValgrindPostTest()
 
     def run(self):
         self.passed = False
@@ -60,6 +63,12 @@
             print msg
             self.stdout += msg + "\n"
             cmd.append(str(self.ast_version).rstrip())
+            # check for existing asterisk instances
+            ast_directories = "%s/%s" % (Asterisk.test_suite_root,
+                                         self.test_name.lstrip("tests/"))
+            while os.path.isdir("%s/ast%d" % (
+                    ast_directories, self.existing_ast_instances+1)):
+                self.existing_ast_instances += 1
             p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                  stderr=subprocess.STDOUT)
             self.pid = p.pid
@@ -71,6 +80,12 @@
                 pass
             p.wait()
 
+            self.valgrind.check_results(
+                self.existing_ast_instances,
+                "%s/%s" % (Asterisk.test_suite_root,
+                           self.test_name.lstrip("tests/")))
+            self.stdout += self.valgrind.stdout
+
             self.__parse_run_output(self.stdout)
 
             self.passed = (p.returncode == 0 and self.test_config.expect_pass) or (p.returncode and not self.test_config.expect_pass)
@@ -79,6 +94,10 @@
                 print "Core dumps detected; failing test"
                 self.passed = False
                 self.__archive_core_dumps(core_dumps)
+            if not self.valgrind.passed:
+                if self.passed:
+                    self.stdout += "Test failure marked by Valgrind results\n"
+                    self.passed = False
             if not self.passed:
                 self.__archive_ast_logs()
                 self.__archive_pcap_dump()
@@ -410,6 +429,19 @@
     parser.add_option("-L", "--list-tags", action="store_true",
             dest="list_tags", default=False,
             help="List available tags")
+    parser.add_option("-V", "--valgrind", action="store_true",
+                      dest="valgrind", default=False,
+                      help="Run Asterisk under Valgrind")
+    parser.add_option("--valgrind-gensupp", action="store_true",
+                      dest="valgrind_gensupp", default=False,
+                      help="Generate suppression file for valgrind")
+    parser.add_option("--valgrind-leak",
+                      dest="valgrind_leak", default="no",
+                      help="Set leak check to one of: no (default), summary, yes, all, full")
+    parser.add_option("--valgrind-undef",
+                      dest="valgrind_undef", default="yes",
+                      help="Set undefined check to one of: no, yes (default)")
+
     (options, args) = parser.parse_args(argv)
 
     ast_version = AsteriskVersion(options.version)
@@ -428,6 +460,15 @@
     if options.list_tags:
         test_suite.list_tags()
         return 0
+
+    if options.valgrind:
+        os.environ["VALGRIND"] = "true"
+    if options.valgrind_gensupp:
+        os.environ["VALGRINDGENSUPP"] = "true"
+    if options.valgrind_leak:
+        os.environ["VALGRINDLEAK"] = options.valgrind_leak
+    if options.valgrind_undef:
+        os.environ["VALGRINDUNDEF"] = options.valgrind_undef
 
     print "Running tests for Asterisk %s ...\n" % str(ast_version)
 
@@ -482,3 +523,4 @@
 
 if __name__ == "__main__":
     sys.exit(main() or 0)
+# vim: set ts=8 sw=4 sts=4 et ai tw=79:




More information about the asterisk-commits mailing list