[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