<p>Corey Farrell has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/9314">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Begin work on python3 compatability.<br><br>* Use explicit relative import syntax.<br>* Correct syntax for print and except.<br>* Fix incorrect usage of IOError in astcdr.py - IOError is not a tuple.<br>* Use items, values instead of iteritems, itervalues.<br>* Use OrderedDict from collections if available (python 2.7+).<br>* Do not unpack tuples from argument list, save tuple to variable then<br>  unpack within code.<br>* Use exception handler to import Python3 version of urlencode if the<br>  Python2 version fails to import.<br>* Remove unused import unittest from opensslversion.<br>* Move executable tests to separate files to resolve issues with<br>  relative imports:<br>  - buildoptions<br>  - cdr<br>  - cel<br>  - channel_test_condition<br>  - config<br>  - lock_test_condition<br>  - sip_dialog_test_condition (disabled)<br>  - sippversion<br>* Modify utils_socket test to use new harness_shared module.<br>* Remove unused executable flag and shebang from phones.py.<br>* Update self_test script to run tests from new location.<br>* Remove ability to use config.py to display a config file.<br>* Allow PYTHON environmental variable to tell run-local to use a<br>  specific python binary.<br>* Execute test_runner.py using python -m syntax.  This avoids conflicts<br>  between needing to use relative imports for modules but not being<br>  allowed to use them in executable scripts.<br>* Encode self.realbase for use with md5 function.<br>* Remove executable flag and shebang from test_runner.py.  Remove<br>  redundant appending of sys.path.<br>* Modify output for running test and status to print the name of the<br>  test instead of the command used to run the test.<br>* Use key= sorter argument to compare sizes of tmp filesystems.  This<br>  may cause us to use /var/tmp instead of /tmp even if /var/tmp has only<br>  a few kilobytes more space but python3 does not support cmp= argument<br>  to sorter.<br>* Decode binary to utf-8<br>  - CLI output<br>  - runtest.py subprocess output<br>  - XML printed to stdout<br>  - SyncAMI data<br>  - SIPp runtime and version output<br>* Update test_runner.load_and_parse_module to check for the module in<br>  lib/python/asterisk, make required adjustments if found.<br>* Update __strip_illegal_xml_chars to work with unicode version of<br>  str.translate which does not support second argument.<br>* Update import statements from all *.py modules within tests/ to use<br>  absolute names for asterisk modules.  This is required for python2 to<br>  continue functioning with the new way that test_runner must be executed.<br>  No attempt has been made to fix python3 compatibility issues in these<br>  modules.<br><br>Some tests may pass using python3.  Doing so requires using an updated<br>starpy and no python sources within tests/ have been updated.  Unit<br>tests from lib/python can be run against python2 and python3 using<br>./self_test.<br><br>The goal of this is for all tests to continue functioning using python2.<br>Some tests may work under python3 but individual tests will need to be<br>addressed separately.  For tests which do not contain a run-test script<br>the version of python used to execute ./runtests.py will be used:<br>./runtests.py -t tests/manager/originate<br>python2 ./runtests.py -t tests/manager/originate<br>python3 ./runtests.py -t tests/manager/originate<br><br>These commands will be python, python2 and python3 respectively.  The<br>first example where no interpreter is specified uses the shebang from<br>./runtests.py.  The interpreter used by ./run-local can be controlled<br>by setting PYTHON environmental variable:<br>PYTHON=python3 ./run-local run -t tests/manager/originate<br><br>Change-Id: If76c2d3e11e4ab4552d0df7841287c8bb2de7918<br>---<br>M lib/python/asterisk/ami.py<br>M lib/python/asterisk/apptest.py<br>M lib/python/asterisk/ari.py<br>M lib/python/asterisk/astconfigparser.py<br>M lib/python/asterisk/astcsv.py<br>M lib/python/asterisk/astdicts.py<br>M lib/python/asterisk/asterisk.py<br>M lib/python/asterisk/bridge_test_case.py<br>M lib/python/asterisk/buildoptions.py<br>M lib/python/asterisk/cdr.py<br>M lib/python/asterisk/cel.py<br>M lib/python/asterisk/channel_test_condition.py<br>M lib/python/asterisk/confbridge.py<br>M lib/python/asterisk/config.py<br>M lib/python/asterisk/fd_test_condition.py<br>M lib/python/asterisk/lock_test_condition.py<br>M lib/python/asterisk/matcher.py<br>M lib/python/asterisk/matcher_listener.py<br>M lib/python/asterisk/opensslversion.py<br>M lib/python/asterisk/originate.py<br>M lib/python/asterisk/pcap.py<br>M lib/python/asterisk/phones.py<br>M lib/python/asterisk/pjsua_mod.py<br>M lib/python/asterisk/pluggable_modules.py<br>M lib/python/asterisk/pluggable_registry.py<br>M lib/python/asterisk/realtime_converter.py<br>M lib/python/asterisk/realtime_odbc_module.py<br>M lib/python/asterisk/realtime_test_module.py<br>A lib/python/asterisk/self_test/harness_shared.py<br>A lib/python/asterisk/self_test/locks-backtrace.txt<br>A lib/python/asterisk/self_test/locks-fail.txt<br>A lib/python/asterisk/self_test/locks-large-multiple-object.txt<br>A lib/python/asterisk/self_test/locks-multiple-objects-no-backtrace.txt<br>A lib/python/asterisk/self_test/locks-pass.txt<br>A lib/python/asterisk/self_test/locks-single-object-no-held-info.txt<br>A lib/python/asterisk/self_test/locks-single-object.txt<br>A lib/python/asterisk/self_test/test_buildoptions.py<br>A lib/python/asterisk/self_test/test_cdr.py<br>A lib/python/asterisk/self_test/test_cel.py<br>A lib/python/asterisk/self_test/test_channel_test_condition.py<br>A lib/python/asterisk/self_test/test_config.py<br>A lib/python/asterisk/self_test/test_lock_test_condition.py<br>R lib/python/asterisk/self_test/test_matcher.py<br>A lib/python/asterisk/self_test/test_sip_dialog_test_condition.py.txt<br>A lib/python/asterisk/self_test/test_sippversion.py<br>M lib/python/asterisk/self_test/test_utils_socket.py<br>M lib/python/asterisk/sip_channel_test_condition.py<br>M lib/python/asterisk/sip_dialog_test_condition.py<br>M lib/python/asterisk/sipp.py<br>M lib/python/asterisk/sippversion.py<br>M lib/python/asterisk/syncami.py<br>M lib/python/asterisk/test_case.py<br>M lib/python/asterisk/test_conditions.py<br>M lib/python/asterisk/test_config.py<br>M lib/python/asterisk/test_runner.py<br>M lib/python/asterisk/test_suite_utils.py<br>M lib/python/asterisk/thread_test_condition.py<br>M lib/python/asterisk/voicemail.py<br>M lib/python/rlmi.py<br>M lib/python/sip_message.py<br>M run-local<br>M runtests.py<br>M self_test<br>M tests/cdr/cdr-tests.py<br>M tests/cdr/sqlite3/cdr_sqlite3.py<br>M tests/channels/SIP/tcpauthlimit/sipp_scenario.py<br>M tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv4/scenario_generator.py<br>M tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv6/scenario_generator.py<br>M tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py<br>M tests/channels/pjsip/subscriptions/rls/rls_test.py<br>M tests/channels/pjsip/transfers/attended_transfer/nominal/packet_sniffer.py<br>M tests/codecs/audio_analyzer.py<br>M tests/codecs/rtp_analyzer.py<br>M tests/hep/hep_capture_node.py<br>M tests/manager/config/ManagerConfigTest.py<br>M tests/manager/device_state_changed/ami_device_state.py<br>M tests/manager/device_state_list/ami_device_state_list.py<br>M tests/manager/exten_state_list/ami_exten_state_list.py<br>M tests/manager/presence_state_changed/ami_presence_state.py<br>M tests/manager/presence_state_list/ami_presence_state_list.py<br>M tests/pbx/manager_extensions/ami_extension_control.py<br>M tests/rest_api/external_interaction/attended_transfer/attended_transfer.py<br>M tests/rest_api/external_interaction/blind_transfer/call_transfer.py<br>M tests/rest_api/message/message_modules.py<br>M usage.py<br>85 files changed, 1,910 insertions(+), 1,841 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/testsuite refs/changes/14/9314/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/lib/python/asterisk/ami.py b/lib/python/asterisk/ami.py<br>index fb52d1c..2e0fe4e 100644<br>--- a/lib/python/asterisk/ami.py<br>+++ b/lib/python/asterisk/ami.py<br>@@ -13,7 +13,8 @@<br> import logging<br> import re<br> import json<br>-from pluggable_registry import PLUGGABLE_EVENT_REGISTRY,\<br>+from .test_runner import load_and_parse_module<br>+from .pluggable_registry import PLUGGABLE_EVENT_REGISTRY,\<br>     PLUGGABLE_ACTION_REGISTRY, var_replace<br> <br> LOGGER = logging.getLogger(__name__)<br>@@ -361,7 +362,7 @@<br>             lower_key = key.lower()<br>             if lower_key == 'extra':<br>                 value = dict((key.lower(), value)<br>-                             for key, value in value.iteritems())<br>+                             for key, value in value.items())<br>             self.requirements[lower_key] = value<br>         self.orderings = requirements.get('partialorder') or []<br>         self.named_id = requirements.get('id')<br>@@ -529,8 +530,7 @@<br> <br>     def event_callback(self, ami, event):<br>         """Callback called when an event is received from AMI"""<br>-        callback_module = __import__(self.callback_module)<br>-        method = getattr(callback_module, self.callback_method)<br>+        method = load_and_parse_module(self.callback_module + '.' + self.callback_method)<br>         self.passed = method(ami, event)<br>         if self.passed is None:<br>             LOGGER.error("Callback %s.%s returned None instead of a boolean",<br>@@ -828,7 +828,7 @@<br> <br> def replace_ami_vars(mydict, values):<br>     outdict = {}<br>-    for key, value in mydict.iteritems():<br>+    for key, value in mydict.items():<br>         outdict[key] = var_replace(value, values)<br> <br>     return outdict<br>diff --git a/lib/python/asterisk/apptest.py b/lib/python/asterisk/apptest.py<br>index 1fc8496..2c33ad9 100644<br>--- a/lib/python/asterisk/apptest.py<br>+++ b/lib/python/asterisk/apptest.py<br>@@ -18,8 +18,8 @@<br> from twisted.internet import reactor, defer<br> <br> sys.path.append("lib/python")<br>-from test_case import TestCase<br>-from ami import AMIEventInstance<br>+from .test_case import TestCase<br>+from .ami import AMIEventInstance<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>@@ -165,7 +165,7 @@<br>         A Scenario is considered done if all results have been met and<br>         all expected actions have been executed.<br>         """<br>-        return (all(self._expected_results.itervalues()) and<br>+        return (all(self._expected_results.values()) and<br>                 all(i.ran_actions or i.unexpected<br>                     for i in self._event_instances))<br> <br>diff --git a/lib/python/asterisk/ari.py b/lib/python/asterisk/ari.py<br>index da19cee..23672dd 100644<br>--- a/lib/python/asterisk/ari.py<br>+++ b/lib/python/asterisk/ari.py<br>@@ -12,12 +12,16 @@<br> import re<br> import requests<br> import traceback<br>-import urllib<br>+try:<br>+    from urllib.parse import urlencode<br>+except:<br>+    from urllib import urlencode<br> <br>-from test_case import TestCase<br>-from pluggable_registry import PLUGGABLE_EVENT_REGISTRY,\<br>+from .test_case import TestCase<br>+from .test_runner import load_and_parse_module<br>+from .pluggable_registry import PLUGGABLE_EVENT_REGISTRY,\<br>     PLUGGABLE_ACTION_REGISTRY, var_replace<br>-from test_suite_utils import all_match<br>+from .test_suite_utils import all_match<br> from twisted.internet import reactor<br> try:<br>     from autobahn.websocket import WebSocketClientFactory, \<br>@@ -332,7 +336,7 @@<br>         """<br>         url = "ws://%s:%d/ari/events?%s" % \<br>               (host, port,<br>-               urllib.urlencode({'app': apps, 'api_key': '%s:%s' % userpass}))<br>+               urlencode({'app': apps, 'api_key': '%s:%s' % userpass}))<br>         if subscribe_all:<br>             url += '&subscribeAll=true'<br>         LOGGER.info("WebSocketClientFactory(url=%s)", url)<br>@@ -560,7 +564,7 @@<br>         url = self.ari.build_url(uri)<br>         requests_method = getattr(requests, self.method)<br>         params = dict((key, var_replace(val, values))<br>-                      for key, val in self.params.iteritems())<br>+                      for key, val in self.params.items())<br> <br>         response = requests_method(<br>             url,<br>@@ -580,7 +584,7 @@<br>                              response.status_code, response.text)<br>                 return False<br>         else:<br>-            if response.status_code / 100 != 2:<br>+            if response.status_code // 100 != 2:<br>                 LOGGER.error('sent %s %s %s response %d %s',<br>                              self.method, self.uri, self.params,<br>                              response.status_code, response.text)<br>@@ -606,8 +610,7 @@<br> <br>         callback = self.instance_config.get('callback')<br>         if callback:<br>-            module = __import__(callback['module'])<br>-            self.callback = getattr(module, callback['method'])<br>+            self.callback = load_and_parse_module(callback['module'] + '.' + callback['method'])<br>         else:<br>             # No callback; just use a no-op<br>             self.callback = lambda *args, **kwargs: True<br>diff --git a/lib/python/asterisk/astconfigparser.py b/lib/python/asterisk/astconfigparser.py<br>index dc79e80..e8da255 100644<br>--- a/lib/python/asterisk/astconfigparser.py<br>+++ b/lib/python/asterisk/astconfigparser.py<br>@@ -8,8 +8,7 @@<br> import re<br> import itertools<br> <br>-from astdicts import OrderedDict<br>-from astdicts import MultiOrderedDict<br>+from .astdicts import OrderedDict, MultiOrderedDict<br> <br> <br> def merge_values(left, right, key):<br>@@ -270,11 +269,11 @@<br> <br> def write_dicts(config_file, mdicts):<br>     """Write the contents of the mdicts to the specified config file"""<br>-    for section, sect_list in mdicts.iteritems():<br>+    for section, sect_list in mdicts.items():<br>         # every section contains a list of dictionaries<br>         for sect in sect_list:<br>             config_file.write("[%s]\n" % section)<br>-            for key, val_list in sect.iteritems():<br>+            for key, val_list in sect.items():<br>                 # every value is also a list<br>                 for v in val_list:<br>                     key_val = key<br>@@ -355,7 +354,7 @@<br>         if self._includes:<br>             res.extend(list(itertools.chain(*[<br>                 incl.get_sections(key, attr, searched)<br>-                for incl in self._includes.itervalues()])))<br>+                for incl in self._includes.values()])))<br>         if self._parent:<br>             res += self._parent.get_sections(key, attr, searched)<br>         return res<br>@@ -445,7 +444,7 @@<br>             with open(filename, 'rt') as config_file:<br>                 self._read(config_file, sect)<br>         except IOError:<br>-            print "Could not open file ", filename, " for reading"<br>+            print("Could not open file ", filename, " for reading")<br> <br>     def _read(self, config_file, sect):<br>         """Parse configuration information from the config_file"""<br>@@ -478,7 +477,7 @@<br>     def write(self, config_file):<br>         """Write configuration information out to a file"""<br>         try:<br>-            for key, val in self._includes.iteritems():<br>+            for key, val in self._includes.items():<br>                 val.write(key)<br>                 config_file.write('#include "%s"\n' % key)<br> <br>@@ -490,4 +489,4 @@<br>                 with open(config_file, 'wt') as fp:<br>                     self.write(fp)<br>             except IOError:<br>-                print "Could not open file ", config_file, " for writing"<br>+                print("Could not open file ", config_file, " for writing")<br>diff --git a/lib/python/asterisk/astcsv.py b/lib/python/asterisk/astcsv.py<br>index 67d25d3..7619bf5 100644<br>--- a/lib/python/asterisk/astcsv.py<br>+++ b/lib/python/asterisk/astcsv.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Asterisk CSV-based testing<br> <br> This module implements the basic CSV testing backend for things like<br>@@ -11,7 +10,6 @@<br> the GNU General Public License Version 2.<br> """<br> <br>-import unittest<br> import sys<br> import csv<br> import re<br>@@ -57,10 +55,10 @@<br>         else:<br>             cmp_fn = (lambda x, y: str(x).lower() == str(y).lower())<br> <br>-        for key, value in self.iteritems():<br>+        for key, value in self.items():<br>             if None not in (value, other.get(key)) and not cmp_fn(value, other.get(key)):<br>                 if not silent:<br>-                    LOGGER.warn("CSV MATCH FAILED, Expected: %s: '%s' "<br>+                    LOGGER.warning("CSV MATCH FAILED, Expected: %s: '%s' "<br>                                 "Got: %s: '%s'" % (key, value, key,<br>                                                    other.get(key)))<br>                 return False<br>@@ -70,9 +68,9 @@<br>         """Retrieve a value from the specified column key"""<br>         return self.__columns.get(key)<br> <br>-    def iteritems(self):<br>+    def items(self):<br>         """Iterate over the values in the columns"""<br>-        return self.__columns.iteritems()<br>+        return self.__columns.items()<br> <br>     def __str__(self):<br>         return ",".join(["\"%s\"" % (self.__dict__[x]) for x in self.__fields])<br>@@ -94,12 +92,14 @@<br>         self.__records = []<br> <br>         csvreader = None<br>+        csvfile = None<br> <br>         try:<br>-            csvreader = csv.DictReader(open(self.filename, "r"), fields, ",")<br>-        except IOError as (errno, strerror):<br>+            csvfile = open(self.filename, "r")<br>+            csvreader = csv.DictReader(csvfile, fields, ",")<br>+        except IOError as e:<br>             LOGGER.error("IOError %d[%s] while opening file '%s'" %<br>-                         (errno, strerror, self.filename))<br>+                         (e.errno, e.strerror, self.filename))<br>         except:<br>             LOGGER.error("Unexpected error: %s" % (sys.exc_info()[0]))<br> <br>@@ -110,6 +110,8 @@<br>         for row in csvreader:<br>             record = self.row_factory(**row)<br>             self.__records.append(record)<br>+<br>+        csvfile.close()<br> <br>     def __len__(self):<br>         return len(self.__records)<br>@@ -125,7 +127,7 @@<br>         each record"""<br> <br>         if not partial and (len(self) != len(other)):<br>-            LOGGER.warn("CSV MATCH FAILED, different number of records, "<br>+            LOGGER.warning("CSV MATCH FAILED, different number of records, "<br>                         "self=%d and other=%d" % (len(self), len(other)))<br>             return False<br> <br>@@ -147,7 +149,7 @@<br>             size = len(list_a)<br> <br>             # attempt two orderings: forward and reversed<br>-            guess_orders = (range(size), list(reversed(range(size))))<br>+            guess_orders = (list(range(size)), list(reversed(range(size))))<br>             found_orders = []<br> <br>             for guess_order in guess_orders:<br>@@ -185,7 +187,7 @@<br>             # have it complain immediately.<br>             for i, item in enumerate(self):<br>                 if not item.match(other[i], exact=exactness):<br>-                    LOGGER.warn("Failed to match entry %d" % (i,))<br>+                    LOGGER.warning("Failed to match entry %d" % (i,))<br>                     return False<br>             assert False<br> <br>@@ -193,7 +195,7 @@<br>             pass  # joy!<br> <br>         elif len(matches) > 1:<br>-            LOGGER.warn("More than one CSV permutation results in success")<br>+            LOGGER.warning("More than one CSV permutation results in success")<br> <br>         return True<br> <br>@@ -205,9 +207,6 @@<br>         try:<br>             open(self.filename, "w").close()<br>         except:<br>-            LOGGER.warn("Unable to empty CSV file %s" % (self.filename))<br>-<br>-if __name__ == '__main__':<br>-    unittest.main()<br>+            LOGGER.warning("Unable to empty CSV file %s" % (self.filename))<br> <br> # vim:sw=4:ts=4:expandtab:textwidth=79<br>diff --git a/lib/python/asterisk/astdicts.py b/lib/python/asterisk/astdicts.py<br>index ae63075..4e70eec 100644<br>--- a/lib/python/asterisk/astdicts.py<br>+++ b/lib/python/asterisk/astdicts.py<br>@@ -3,260 +3,264 @@<br> # copied from http://code.activestate.com/recipes/576693/<br> <br> try:<br>-    from thread import get_ident as _get_ident<br>+    # Use builtin OrderedDict() from Python2.7.<br>+    from collections import OrderedDict<br> except ImportError:<br>-    from dummy_thread import get_ident as _get_ident<br>+    try:<br>+        from thread import get_ident as _get_ident<br>+    except ImportError:<br>+        from dummy_thread import get_ident as _get_ident<br> <br>-try:<br>-    from _abcoll import KeysView, ValuesView, ItemsView<br>-except ImportError:<br>-    pass<br>+    try:<br>+        from _abcoll import KeysView, ValuesView, ItemsView<br>+    except ImportError:<br>+        pass<br> <br> <br>-class OrderedDict(dict):<br>-    'Dictionary that remembers insertion order'<br>-    # An inherited dict maps keys to values.<br>-    # The inherited dict provides __getitem__, __len__, __contains__, and get.<br>-    # The remaining methods are order-aware.<br>-    # Big-O running times for all methods are the same as for regular dictionaries.<br>+    class OrderedDict(dict):<br>+        'Dictionary that remembers insertion order'<br>+        # An inherited dict maps keys to values.<br>+        # The inherited dict provides __getitem__, __len__, __contains__, and get.<br>+        # The remaining methods are order-aware.<br>+        # Big-O running times for all methods are the same as for regular dictionaries.<br> <br>-    # The internal self.__map dictionary maps keys to links in a doubly linked list.<br>-    # The circular doubly linked list starts and ends with a sentinel element.<br>-    # The sentinel element never gets deleted (this simplifies the algorithm).<br>-    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].<br>+        # The internal self.__map dictionary maps keys to links in a doubly linked list.<br>+        # The circular doubly linked list starts and ends with a sentinel element.<br>+        # The sentinel element never gets deleted (this simplifies the algorithm).<br>+        # Each link is stored as a list of length three:  [PREV, NEXT, KEY].<br> <br>-    def __init__(self, *args, **kwds):<br>-        '''Initialize an ordered dictionary.  Signature is the same as for<br>-        regular dictionaries, but keyword arguments are not recommended<br>-        because their insertion order is arbitrary.<br>+        def __init__(self, *args, **kwds):<br>+            '''Initialize an ordered dictionary.  Signature is the same as for<br>+            regular dictionaries, but keyword arguments are not recommended<br>+            because their insertion order is arbitrary.<br> <br>-        '''<br>-        if len(args) > 1:<br>-            raise TypeError('expected at most 1 arguments, got %d' % len(args))<br>-        try:<br>-            self.__root<br>-        except AttributeError:<br>-            self.__root = root = []                     # sentinel node<br>-            root[:] = [root, root, None]<br>-            self.__map = {}<br>-        self.__update(*args, **kwds)<br>+            '''<br>+            if len(args) > 1:<br>+                raise TypeError('expected at most 1 arguments, got %d' % len(args))<br>+            try:<br>+                self.__root<br>+            except AttributeError:<br>+                self.__root = root = []                     # sentinel node<br>+                root[:] = [root, root, None]<br>+                self.__map = {}<br>+            self.__update(*args, **kwds)<br> <br>-    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):<br>-        'od.__setitem__(i, y) <==> od[i]=y'<br>-        # Setting a new item creates a new link which goes at the end of the linked<br>-        # list, and the inherited dictionary is updated with the new key/value pair.<br>-        if key not in self:<br>+        def __setitem__(self, key, value, dict_setitem=dict.__setitem__):<br>+            'od.__setitem__(i, y) <==> od[i]=y'<br>+            # Setting a new item creates a new link which goes at the end of the linked<br>+            # list, and the inherited dictionary is updated with the new key/value pair.<br>+            if key not in self:<br>+                root = self.__root<br>+                last = root[0]<br>+                last[1] = root[0] = self.__map[key] = [last, root, key]<br>+            dict_setitem(self, key, value)<br>+<br>+        def __delitem__(self, key, dict_delitem=dict.__delitem__):<br>+            'od.__delitem__(y) <==> del od[y]'<br>+            # Deleting an existing item uses self.__map to find the link which is<br>+            # then removed by updating the links in the predecessor and successor nodes.<br>+            dict_delitem(self, key)<br>+            link_prev, link_next, key = self.__map.pop(key)<br>+            link_prev[1] = link_next<br>+            link_next[0] = link_prev<br>+<br>+        def __iter__(self):<br>+            'od.__iter__() <==> iter(od)'<br>             root = self.__root<br>-            last = root[0]<br>-            last[1] = root[0] = self.__map[key] = [last, root, key]<br>-        dict_setitem(self, key, value)<br>+            curr = root[1]<br>+            while curr is not root:<br>+                yield curr[2]<br>+                curr = curr[1]<br> <br>-    def __delitem__(self, key, dict_delitem=dict.__delitem__):<br>-        'od.__delitem__(y) <==> del od[y]'<br>-        # Deleting an existing item uses self.__map to find the link which is<br>-        # then removed by updating the links in the predecessor and successor nodes.<br>-        dict_delitem(self, key)<br>-        link_prev, link_next, key = self.__map.pop(key)<br>-        link_prev[1] = link_next<br>-        link_next[0] = link_prev<br>-<br>-    def __iter__(self):<br>-        'od.__iter__() <==> iter(od)'<br>-        root = self.__root<br>-        curr = root[1]<br>-        while curr is not root:<br>-            yield curr[2]<br>-            curr = curr[1]<br>-<br>-    def __reversed__(self):<br>-        'od.__reversed__() <==> reversed(od)'<br>-        root = self.__root<br>-        curr = root[0]<br>-        while curr is not root:<br>-            yield curr[2]<br>-            curr = curr[0]<br>-<br>-    def clear(self):<br>-        'od.clear() -> None.  Remove all items from od.'<br>-        try:<br>-            for node in self.__map.itervalues():<br>-                del node[:]<br>+        def __reversed__(self):<br>+            'od.__reversed__() <==> reversed(od)'<br>             root = self.__root<br>-            root[:] = [root, root, None]<br>-            self.__map.clear()<br>-        except AttributeError:<br>-            pass<br>-        dict.clear(self)<br>+            curr = root[0]<br>+            while curr is not root:<br>+                yield curr[2]<br>+                curr = curr[0]<br> <br>-    def popitem(self, last=True):<br>-        '''od.popitem() -> (k, v), return and remove a (key, value) pair.<br>-        Pairs are returned in LIFO order if last is true or FIFO order if false.<br>+        def clear(self):<br>+            'od.clear() -> None.  Remove all items from od.'<br>+            try:<br>+                for node in self.__map.itervalues():<br>+                    del node[:]<br>+                root = self.__root<br>+                root[:] = [root, root, None]<br>+                self.__map.clear()<br>+            except AttributeError:<br>+                pass<br>+            dict.clear(self)<br> <br>-        '''<br>-        if not self:<br>-            raise KeyError('dictionary is empty')<br>-        root = self.__root<br>-        if last:<br>-            link = root[0]<br>-            link_prev = link[0]<br>-            link_prev[1] = root<br>-            root[0] = link_prev<br>-        else:<br>-            link = root[1]<br>-            link_next = link[1]<br>-            root[1] = link_next<br>-            link_next[0] = root<br>-        key = link[2]<br>-        del self.__map[key]<br>-        value = dict.pop(self, key)<br>-        return key, value<br>+        def popitem(self, last=True):<br>+            '''od.popitem() -> (k, v), return and remove a (key, value) pair.<br>+            Pairs are returned in LIFO order if last is true or FIFO order if false.<br> <br>-    # -- the following methods do not depend on the internal structure --<br>-<br>-    def keys(self):<br>-        'od.keys() -> list of keys in od'<br>-        return list(self)<br>-<br>-    def values(self):<br>-        'od.values() -> list of values in od'<br>-        return [self[key] for key in self]<br>-<br>-    def items(self):<br>-        'od.items() -> list of (key, value) pairs in od'<br>-        return [(key, self[key]) for key in self]<br>-<br>-    def iterkeys(self):<br>-        'od.iterkeys() -> an iterator over the keys in od'<br>-        return iter(self)<br>-<br>-    def itervalues(self):<br>-        'od.itervalues -> an iterator over the values in od'<br>-        for k in self:<br>-            yield self[k]<br>-<br>-    def iteritems(self):<br>-        'od.iteritems -> an iterator over the (key, value) items in od'<br>-        for k in self:<br>-            yield (k, self[k])<br>-<br>-    def update(*args, **kwds):<br>-        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.<br>-<br>-        If E is a dict instance, does:           for k in E: od[k] = E[k]<br>-        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]<br>-        Or if E is an iterable of items, does:   for k, v in E: od[k] = v<br>-        In either case, this is followed by:     for k, v in F.items(): od[k] = v<br>-<br>-        '''<br>-        if len(args) > 2:<br>-            raise TypeError('update() takes at most 2 positional '<br>-                            'arguments (%d given)' % (len(args),))<br>-        elif not args:<br>-            raise TypeError('update() takes at least 1 argument (0 given)')<br>-        self = args[0]<br>-        # Make progressively weaker assumptions about "other"<br>-        other = ()<br>-        if len(args) == 2:<br>-            other = args[1]<br>-        if isinstance(other, dict):<br>-            for key in other:<br>-                self[key] = other[key]<br>-        elif hasattr(other, 'keys'):<br>-            for key in other.keys():<br>-                self[key] = other[key]<br>-        else:<br>-            for key, value in other:<br>-                self[key] = value<br>-        for key, value in kwds.items():<br>-            self[key] = value<br>-<br>-    __update = update  # let subclasses override update without breaking __init__<br>-<br>-    __marker = object()<br>-<br>-    def pop(self, key, default=__marker):<br>-        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.<br>-        If key is not found, d is returned if given, otherwise KeyError is raised.<br>-<br>-        '''<br>-        if key in self:<br>-            result = self[key]<br>-            del self[key]<br>-            return result<br>-        if default is self.__marker:<br>-            raise KeyError(key)<br>-        return default<br>-<br>-    def setdefault(self, key, default=None):<br>-        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'<br>-        if key in self:<br>-            return self[key]<br>-        self[key] = default<br>-        return default<br>-<br>-    def __repr__(self, _repr_running={}):<br>-        'od.__repr__() <==> repr(od)'<br>-        call_key = id(self), _get_ident()<br>-        if call_key in _repr_running:<br>-            return '...'<br>-        _repr_running[call_key] = 1<br>-        try:<br>+            '''<br>             if not self:<br>-                return '%s()' % (self.__class__.__name__,)<br>-            return '%s(%r)' % (self.__class__.__name__, self.items())<br>-        finally:<br>-            del _repr_running[call_key]<br>+                raise KeyError('dictionary is empty')<br>+            root = self.__root<br>+            if last:<br>+                link = root[0]<br>+                link_prev = link[0]<br>+                link_prev[1] = root<br>+                root[0] = link_prev<br>+            else:<br>+                link = root[1]<br>+                link_next = link[1]<br>+                root[1] = link_next<br>+                link_next[0] = root<br>+            key = link[2]<br>+            del self.__map[key]<br>+            value = dict.pop(self, key)<br>+            return key, value<br> <br>-    def __reduce__(self):<br>-        'Return state information for pickling'<br>-        items = [[k, self[k]] for k in self]<br>-        inst_dict = vars(self).copy()<br>-        for k in vars(OrderedDict()):<br>-            inst_dict.pop(k, None)<br>-        if inst_dict:<br>-            return (self.__class__, (items,), inst_dict)<br>-        return self.__class__, (items,)<br>+        # -- the following methods do not depend on the internal structure --<br> <br>-    def copy(self):<br>-        'od.copy() -> a shallow copy of od'<br>-        return self.__class__(self)<br>+        def keys(self):<br>+            'od.keys() -> list of keys in od'<br>+            return list(self)<br> <br>-    @classmethod<br>-    def fromkeys(cls, iterable, value=None):<br>-        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S<br>-        and values equal to v (which defaults to None).<br>+        def values(self):<br>+            'od.values() -> list of values in od'<br>+            return [self[key] for key in self]<br> <br>-        '''<br>-        d = cls()<br>-        for key in iterable:<br>-            d[key] = value<br>-        return d<br>+        def items(self):<br>+            'od.items() -> list of (key, value) pairs in od'<br>+            return [(key, self[key]) for key in self]<br> <br>-    def __eq__(self, other):<br>-        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive<br>-        while comparison to a regular mapping is order-insensitive.<br>+        def iterkeys(self):<br>+            'od.iterkeys() -> an iterator over the keys in od'<br>+            return iter(self)<br> <br>-        '''<br>-        if isinstance(other, OrderedDict):<br>-            return len(self)==len(other) and self.items() == other.items()<br>-        return dict.__eq__(self, other)<br>+        def itervalues(self):<br>+            'od.itervalues -> an iterator over the values in od'<br>+            for k in self:<br>+                yield self[k]<br> <br>-    def __ne__(self, other):<br>-        return not self == other<br>+        def iteritems(self):<br>+            'od.iteritems -> an iterator over the (key, value) items in od'<br>+            for k in self:<br>+                yield (k, self[k])<br> <br>-    # -- the following methods are only used in Python 2.7 --<br>+        def update(*args, **kwds):<br>+            '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.<br> <br>-    def viewkeys(self):<br>-        "od.viewkeys() -> a set-like object providing a view on od's keys"<br>-        return KeysView(self)<br>+            If E is a dict instance, does:           for k in E: od[k] = E[k]<br>+            If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]<br>+            Or if E is an iterable of items, does:   for k, v in E: od[k] = v<br>+            In either case, this is followed by:     for k, v in F.items(): od[k] = v<br> <br>-    def viewvalues(self):<br>-        "od.viewvalues() -> an object providing a view on od's values"<br>-        return ValuesView(self)<br>+            '''<br>+            if len(args) > 2:<br>+                raise TypeError('update() takes at most 2 positional '<br>+                                'arguments (%d given)' % (len(args),))<br>+            elif not args:<br>+                raise TypeError('update() takes at least 1 argument (0 given)')<br>+            self = args[0]<br>+            # Make progressively weaker assumptions about "other"<br>+            other = ()<br>+            if len(args) == 2:<br>+                other = args[1]<br>+            if isinstance(other, dict):<br>+                for key in other:<br>+                    self[key] = other[key]<br>+            elif hasattr(other, 'keys'):<br>+                for key in other.keys():<br>+                    self[key] = other[key]<br>+            else:<br>+                for key, value in other:<br>+                    self[key] = value<br>+            for key, value in kwds.items():<br>+                self[key] = value<br> <br>-    def viewitems(self):<br>-        "od.viewitems() -> a set-like object providing a view on od's items"<br>-        return ItemsView(self)<br>+        __update = update  # let subclasses override update without breaking __init__<br>+<br>+        __marker = object()<br>+<br>+        def pop(self, key, default=__marker):<br>+            '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.<br>+            If key is not found, d is returned if given, otherwise KeyError is raised.<br>+<br>+            '''<br>+            if key in self:<br>+                result = self[key]<br>+                del self[key]<br>+                return result<br>+            if default is self.__marker:<br>+                raise KeyError(key)<br>+            return default<br>+<br>+        def setdefault(self, key, default=None):<br>+            'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'<br>+            if key in self:<br>+                return self[key]<br>+            self[key] = default<br>+            return default<br>+<br>+        def __repr__(self, _repr_running={}):<br>+            'od.__repr__() <==> repr(od)'<br>+            call_key = id(self), _get_ident()<br>+            if call_key in _repr_running:<br>+                return '...'<br>+            _repr_running[call_key] = 1<br>+            try:<br>+                if not self:<br>+                    return '%s()' % (self.__class__.__name__,)<br>+                return '%s(%r)' % (self.__class__.__name__, self.items())<br>+            finally:<br>+                del _repr_running[call_key]<br>+<br>+        def __reduce__(self):<br>+            'Return state information for pickling'<br>+            items = [[k, self[k]] for k in self]<br>+            inst_dict = vars(self).copy()<br>+            for k in vars(OrderedDict()):<br>+                inst_dict.pop(k, None)<br>+            if inst_dict:<br>+                return (self.__class__, (items,), inst_dict)<br>+            return self.__class__, (items,)<br>+<br>+        def copy(self):<br>+            'od.copy() -> a shallow copy of od'<br>+            return self.__class__(self)<br>+<br>+        @classmethod<br>+        def fromkeys(cls, iterable, value=None):<br>+            '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S<br>+            and values equal to v (which defaults to None).<br>+<br>+            '''<br>+            d = cls()<br>+            for key in iterable:<br>+                d[key] = value<br>+            return d<br>+<br>+        def __eq__(self, other):<br>+            '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive<br>+            while comparison to a regular mapping is order-insensitive.<br>+<br>+            '''<br>+            if isinstance(other, OrderedDict):<br>+                return len(self)==len(other) and self.items() == other.items()<br>+            return dict.__eq__(self, other)<br>+<br>+        def __ne__(self, other):<br>+            return not self == other<br>+<br>+        # -- the following methods are only used in Python 2.7 --<br>+<br>+        def viewkeys(self):<br>+            "od.viewkeys() -> a set-like object providing a view on od's keys"<br>+            return KeysView(self)<br>+<br>+        def viewvalues(self):<br>+            "od.viewvalues() -> an object providing a view on od's values"<br>+            return ValuesView(self)<br>+<br>+        def viewitems(self):<br>+            "od.viewitems() -> a set-like object providing a view on od's items"<br>+            return ItemsView(self)<br> <br> ###############################################################################<br> ### MultiOrderedDict<br>@@ -292,7 +296,7 @@<br>         # TODO - find out why for some reason copies<br>         #        the [] as an [[]], so do manually<br>         c = MultiOrderedDict() #self.__class__(self)<br>-        for key, val in self.iteritems():<br>+        for key, val in self.items():<br>             for v in val:<br>                 c[key] = v<br>         return c<br>diff --git a/lib/python/asterisk/asterisk.py b/lib/python/asterisk/asterisk.py<br>old mode 100755<br>new mode 100644<br>index 985afea..c3996f6<br>--- a/lib/python/asterisk/asterisk.py<br>+++ b/lib/python/asterisk/asterisk.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Asterisk Instances in Python.<br> <br> This module provides an interface for creating instances of Asterisk<br>@@ -18,9 +17,9 @@<br> import logging<br> import fileinput<br> <br>-import test_suite_utils<br>+from . import test_suite_utils<br> <br>-from config import ConfigFile<br>+from .config import ConfigFile<br> <br> from twisted.internet import reactor, protocol, defer, utils, error<br> from twisted.python.failure import Failure<br>@@ -54,7 +53,7 @@<br>     def dataReceived(self, data):<br>         """Called when we receive data back"""<br>         LOGGER.debug(data)<br>-        self.output += data<br>+        self.output += data.decode('utf-8', 'ignore')<br> <br>     def connectionLost(self, reason):<br>         """Called when the connect is lost"""<br>@@ -206,7 +205,8 @@<br>     def _set_properties(self, result):<br>         """Set the properties based on the result of the<br>         getProcessOutputAndValue call"""<br>-        self.output, self.err, self.exitcode = result<br>+        bintxt, self.err, self.exitcode = result<br>+        self.output = bintxt.decode('utf-8', 'ignore')<br> <br> <br> class AsteriskProtocol(protocol.ProcessProtocol):<br>@@ -232,7 +232,7 @@<br> <br>     def outReceived(self, data):<br>         """Override of ProcessProtocol.outReceived"""<br>-        self.output += data<br>+        self.output += data.decode('utf-8', 'ignore')<br> <br>     def connectionMade(self):<br>         """Override of ProcessProtocol.connectionMade"""<br>@@ -311,20 +311,6 @@<br>     we're not running into the asterisk.ctl AF_UNIX limit.<br>     """<br> <br>-    def compare_free_space(x, y):<br>-        if os.stat(y).st_dev == os.stat(x).st_dev:<br>-            return 0<br>-        # statvfs can return a long; comparison functions must return an<br>-        # int.  Where both are same filesystem, bavail might change from<br>-        # one call to the next, but hopefully it is not more than 1000.<br>-        difference = os.statvfs(y).f_bavail - os.statvfs(x).f_bavail<br>-        if (difference > 1000):<br>-            return 1<br>-        elif (difference < 1000):<br>-            return -1<br>-        else:<br>-            return 0<br>-<br>     localtest_root = os.getenv("AST_TEST_ROOT")<br>     if localtest_root:<br>         # Base location of the temporary files created by the testsuite<br>@@ -333,7 +319,7 @@<br>         default_etc_directory = os.path.join(localtest_root, "etc/asterisk")<br>     else:<br>         # select tmp path with most available space<br>-        best_tmp = sorted(['/tmp', '/var/tmp'], cmp=compare_free_space)[0]<br>+        best_tmp = sorted(['/tmp', '/var/tmp'], key=lambda path: os.statvfs(path).f_bavail)[0]<br>         # Base location of the temporary files created by the testsuite<br>         test_suite_root = best_tmp + "/asterisk-testsuite"<br>         # The default etc directory for Asterisk<br>@@ -955,7 +941,7 @@<br>                 ast_file.write("#include \"%s/asterisk.options.conf.inc\"\n" %<br>                                (self.astetcdir))<br>                 if ast_conf_options:<br>-                    for (var, val) in ast_conf_options.iteritems():<br>+                    for (var, val) in ast_conf_options.items():<br>                         ast_file.write("%s = %s\n" % (var, val))<br>                 for (var, val) in cat.options:<br>                     if not ast_conf_options or var not in ast_conf_options:<br>diff --git a/lib/python/asterisk/bridge_test_case.py b/lib/python/asterisk/bridge_test_case.py<br>index ea1767e..155b3a1 100644<br>--- a/lib/python/asterisk/bridge_test_case.py<br>+++ b/lib/python/asterisk/bridge_test_case.py<br>@@ -13,7 +13,7 @@<br> from time import sleep<br> <br> sys.path.append("lib/python")<br>-from test_case import TestCase<br>+from .test_case import TestCase<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/lib/python/asterisk/buildoptions.py b/lib/python/asterisk/buildoptions.py<br>index 3b0b4b9..833053a 100644<br>--- a/lib/python/asterisk/buildoptions.py<br>+++ b/lib/python/asterisk/buildoptions.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Asterisk Build Options Handling<br> <br> This module implements an Asterisk build options parser.  It<br>@@ -13,7 +12,6 @@<br> """<br> <br> import sys<br>-import unittest<br> <br> <br> class AsteriskBuildOptions(object):<br>@@ -51,7 +49,7 @@<br>         except IOError:<br>             return ret_val<br>         except:<br>-            print "Unexpected error: %s" % sys.exc_info()[0]<br>+            print("Unexpected error: %s" % sys.exc_info()[0])<br>             return ret_val<br>         for line in file_lines:<br>             if "#define" in line:<br>@@ -59,7 +57,7 @@<br>                 if (define_tuple[0] == "" or define_tuple[2] == ""):<br>                     msg = ("Unable to parse build option line [%s] into "<br>                            "compiler flag token and value" % line)<br>-                    print msg<br>+                    print(msg)<br>                 else:<br>                     flag = define_tuple[0].strip()<br>                     allowed = define_tuple[2].strip()<br>@@ -89,22 +87,3 @@<br>         elif expected_value == "0":<br>             return True<br>         return False<br>-<br>-<br>-class AsteriskBuildOptionsTests(unittest.TestCase):<br>-    """Unit tests for AsteriskBuildOptions"""<br>-<br>-    def test_1(self):<br>-        """Test the defaults paths"""<br>-        build_options = AsteriskBuildOptions()<br>-        self.assertTrue(1)<br>-<br>-<br>-def main():<br>-    """Main entry point for unit tests"""<br>-<br>-    unittest.main()<br>-<br>-<br>-if __name__ == "__main__":<br>-    main()<br>diff --git a/lib/python/asterisk/cdr.py b/lib/python/asterisk/cdr.py<br>index cc5226b..c5d62db 100644<br>--- a/lib/python/asterisk/cdr.py<br>+++ b/lib/python/asterisk/cdr.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Asterisk call detail record testing<br> <br> This module implements an Asterisk CDR parser.<br>@@ -10,9 +9,8 @@<br> the GNU General Public License Version 2.<br> """<br> <br>-import unittest<br> import sys<br>-import astcsv<br>+from . import astcsv<br> import logging<br> <br> LOGGER = logging.getLogger(__name__)<br>@@ -137,33 +135,5 @@<br>             self, filename, records,<br>             AsteriskCSVCDRLine.fields, AsteriskCSVCDRLine)<br> <br>-<br>-class AsteriskCSVCDRTests(unittest.TestCase):<br>-    """Unit tests for AsteriskCSVCDR"""<br>-<br>-    def test_cdr(self):<br>-        """Test the self_test/Master.csv record"""<br>-<br>-        cdr = AsteriskCSVCDR("self_test/Master.csv")<br>-        self.assertEqual(len(cdr), 2)<br>-        self.assertTrue(<br>-            AsteriskCSVCDRLine(duration=7, lastapp="hangup").match(<br>-                cdr[0],<br>-                exact=(True, True)))<br>-        self.assertTrue(cdr[0].match(<br>-            AsteriskCSVCDRLine(duration=7, lastapp="hangup"),<br>-            exact=(True, True)))<br>-<br>-        self.assertFalse(cdr[1].match(cdr[0]))<br>-        self.assertFalse(cdr[0].match(cdr[1]))<br>-        self.assertEqual(cdr[0].billsec, "7")<br>-<br>-        self.assertTrue(cdr.match(cdr))<br>-        cdr2 = AsteriskCSVCDR("self_test/Master2.csv")<br>-        self.assertFalse(cdr.match(cdr2))<br>-<br>-<br>-if __name__ == '__main__':<br>-    unittest.main()<br> <br> # vim:sw=4:ts=4:expandtab:textwidth=79<br>diff --git a/lib/python/asterisk/cel.py b/lib/python/asterisk/cel.py<br>index d22d67f..9965270 100644<br>--- a/lib/python/asterisk/cel.py<br>+++ b/lib/python/asterisk/cel.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Asterisk call detail record testing<br> <br> This module implements an Asterisk CEL parser.<br>@@ -11,8 +10,7 @@<br> """<br> <br> import yaml<br>-import unittest<br>-import astcsv<br>+from . import astcsv<br> import re<br> import logging<br> <br>@@ -176,37 +174,5 @@<br>             self, filename, records,<br>             AsteriskCSVCELLine.fields, AsteriskCSVCELLine)<br> <br>-<br>-class AsteriskCSVCELTests(unittest.TestCase):<br>-    """Unit tests for AsteriskCSVCEL"""<br>-<br>-    def test_cel(self):<br>-        """Test CEL using self_test/CELMaster1.csv"""<br>-<br>-        cel = AsteriskCSVCEL("self_test/CELMaster1.csv")<br>-        self.assertEqual(len(cel), 16)<br>-        self.assertTrue(AsteriskCSVCELLine(<br>-            eventtype="LINKEDID_END",<br>-            channel="TinCan/string").match(cel[-1],<br>-                                           silent=True,<br>-                                           exact=(True, True)))<br>-        self.assertTrue(cel[-1].match(<br>-            AsteriskCSVCELLine(eventtype="LINKEDID_END",<br>-                               channel="TinCan/string"),<br>-            silent=True,<br>-            exact=(True, True)))<br>-<br>-        self.assertFalse(cel[1].match(cel[0], silent=True))<br>-        self.assertFalse(cel[0].match(cel[1], silent=True))<br>-        self.assertEqual(cel[-1].channel, "TinCan/string")<br>-<br>-        self.assertTrue(cel.match(cel))<br>-        cel2 = AsteriskCSVCEL("self_test/CELMaster2.csv")<br>-        self.assertFalse(cel.match(cel2))<br>-<br>-<br>-if __name__ == '__main__':<br>-    logging.basicConfig()<br>-    unittest.main()<br> <br> # vim:sw=4:ts=4:expandtab:textwidth=79<br>diff --git a/lib/python/asterisk/channel_test_condition.py b/lib/python/asterisk/channel_test_condition.py<br>index ec1d774..36421a4 100644<br>--- a/lib/python/asterisk/channel_test_condition.py<br>+++ b/lib/python/asterisk/channel_test_condition.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Test condition for channels<br> <br> Copyright (C) 2011-2012, Digium, Inc.<br>@@ -9,9 +8,7 @@<br> """<br> <br> from twisted.internet import defer<br>-from test_conditions import TestCondition<br>-import logging<br>-import unittest<br>+from .test_conditions import TestCondition<br> import re<br> <br> <br>@@ -86,155 +83,3 @@<br>         defer.DeferredList(exec_list).addCallback(_raise_finished,<br>                                                   finish_deferred)<br>         return finish_deferred<br>-<br>-<br>-class AstMockOutput(object):<br>-    """mock cli output base class"""<br>-<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.1"<br>-<br>-    def MockDefer(self, output):<br>-        """use real defer to mock deferred output"""<br>-        self.output = output<br>-        deferred = defer.Deferred()<br>-        deferred.callback(self)<br>-        return deferred<br>-<br>-<br>-class AstMockObjectInactive(AstMockOutput):<br>-    """mock cli output showing no active channels"""<br>-<br>-    def cli_exec(self, command):<br>-        """presume command is core show channels and generate output"""<br>-        output = ""<br>-        output += "Channel              Location             State   Application(Data)\n"<br>-        output += "0 active channels\n"<br>-        output += "0 active calls\n"<br>-        output += "2 calls processed\n"<br>-        output += "Asterisk ending (0).\n"<br>-        return self.MockDefer(output)<br>-<br>-<br>-class AstMockObjectSingle(AstMockOutput):<br>-    """mock cli output showing single active channel"""<br>-<br>-    def cli_exec(self, command):<br>-        """presume command is core show channels and generate output"""<br>-        output = ""<br>-        output += "Channel              Location             State   Application(Data)\n"<br>-        output += "Local/123@default-00 (None)               Down    ()\n"<br>-        output += "1 active channels\n"<br>-        output += "0 active calls\n"<br>-        output += "2 calls processed\n"<br>-        output += "Asterisk ending (0).\n"<br>-        return self.MockDefer(output)<br>-<br>-<br>-class AstMockObjectMultiple(AstMockOutput):<br>-    """mock cli output showing multiple active channels"""<br>-<br>-    def cli_exec(self, command):<br>-        """presume command is core show channels and generate output"""<br>-        output = ""<br>-        output += "Channel              Location             State   Application(Data)\n"<br>-        output += "PJSIP/123@default-00 (None)               Down    ()\n"<br>-        output += "Local/123@default-00 (None)               Down    ()\n"<br>-        output += "SIP/alice@default-00 (None)               Down    ()\n"<br>-        output += "3 active channels\n"<br>-        output += "0 active calls\n"<br>-        output += "2 calls processed\n"<br>-        output += "Asterisk ending (0).\n"<br>-        return self.MockDefer(output)<br>-<br>-<br>-class AstMockObjectLeaked(AstMockOutput):<br>-    """mock cli output showing leaked channel"""<br>-<br>-    def cli_exec(self, command):<br>-        """presume command is core show channels and generate output"""<br>-        output = ""<br>-        output += "Channel              Location             State   Application(Data)\n"<br>-        output += "Local/123@default-00 (None)               Down    ()\n"<br>-        output += "0 active channels\n"<br>-        output += "0 active calls\n"<br>-        output += "2 calls processed\n"<br>-        output += "Asterisk ending (0).\n"<br>-        return self.MockDefer(output)<br>-<br>-<br>-class TestConfig(object):<br>-    """Fake TestConfig object for unittest"""<br>-<br>-    def __init__(self):<br>-        self.class_type_name = "bogus"<br>-        self.config = {}<br>-        self.enabled = True<br>-        self.pass_expected = True<br>-<br>-<br>-class ChannelTestConditionUnitTest(unittest.TestCase):<br>-    """Unit Tests for ChannelTestCondition"""<br>-<br>-    def test_evaluate_inactive(self):<br>-        """test inactive channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(AstMockObjectInactive())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_evaluate_multiple_fail(self):<br>-        """test multiple channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(AstMockObjectMultiple())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_evaluate_multiple_fail2(self):<br>-        """test multiple channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.allowed_channels = 2<br>-        obj.register_asterisk_instance(AstMockObjectMultiple())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_evaluate_multiple_pass(self):<br>-        """test multiple channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.allowed_channels = 3<br>-        obj.register_asterisk_instance(AstMockObjectMultiple())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_evaluate_single_fail(self):<br>-        """test single channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(AstMockObjectSingle())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_evaluate_single_pass(self):<br>-        """test single channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.allowed_channels = 1<br>-        obj.register_asterisk_instance(AstMockObjectSingle())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_evaluate_leaked(self):<br>-        """test leaked channel condition"""<br>-        obj = ChannelTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(AstMockObjectLeaked())<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-<br>-def main():<br>-    """Run the unit tests"""<br>-<br>-    logging.basicConfig(level=logging.DEBUG)<br>-    unittest.main()<br>-<br>-if __name__ == "__main__":<br>-    main()<br>diff --git a/lib/python/asterisk/confbridge.py b/lib/python/asterisk/confbridge.py<br>index acca1e7..0ca364d 100644<br>--- a/lib/python/asterisk/confbridge.py<br>+++ b/lib/python/asterisk/confbridge.py<br>@@ -14,7 +14,7 @@<br> import sys<br> import logging<br> <br>-from test_state import TestState<br>+from .test_state import TestState<br> from twisted.internet import reactor<br> <br> sys.path.append("lib/python")<br>diff --git a/lib/python/asterisk/config.py b/lib/python/asterisk/config.py<br>old mode 100755<br>new mode 100644<br>index e26b156..c5662d2<br>--- a/lib/python/asterisk/config.py<br>+++ b/lib/python/asterisk/config.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Asterisk Configuration File Handling.<br> <br> This module implements interfaces for dealing with Asterisk configuration<br>@@ -18,7 +17,6 @@<br> <br> import sys<br> import re<br>-import unittest<br> import logging<br> <br> LOGGER = logging.getLogger(__name__)<br>@@ -54,7 +52,7 @@<br>         match = self.varval_re.match(line)<br>         if match is None:<br>             if not is_blank_line(line):<br>-                LOGGER.warn("Invalid line: '%s'" % line.strip())<br>+                LOGGER.warning("Invalid line: '%s'" % line.strip())<br>             return<br>         self.options.append((match.group("name"), match.group("value").strip()))<br> <br>@@ -109,94 +107,6 @@<br>             )<br>         elif len(self.categories) == 0:<br>             if not is_blank_line(line):<br>-                LOGGER.warn("Invalid line: '%s'" % line.strip())<br>+                LOGGER.warning("Invalid line: '%s'" % line.strip())<br>         else:<br>             self.categories[-1].parse_line(line)<br>-<br>-<br>-class ConfigFileTests(unittest.TestCase):<br>-    """Unit tests for ConfigFile"""<br>-<br>-    def test_conf(self):<br>-        """Test parsing a blob of config data"""<br>-        test = \<br>-            "; stuff\n" \<br>-            "this line is invalid on purpose\n" \<br>-            "[this is] also invalid]\n" \<br>-            ";-- comment --;\n" \<br>-            ";--   \n" \<br>-            "[this is commented out]\n" \<br>-            "         --;\n" \<br>-            "[foo]\n" \<br>-            "a = b\n" \<br>-            "  b =   a  \n" \<br>-            "this line is invalid on purpose\n" \<br>-            ";moo\n" \<br>-            "c = d;asdadf;adfads;adsfasdf\n" \<br>-            "  [bar]   ;asdfasdf\n" \<br>-            "a-b=c-d\n" \<br>-            "xyz=x|y|z\n" \<br>-            "1234 => 4242,Example Mailbox,root@localhost,,var=val\n" \<br>-            "\n" \<br>-            "[template](!)\n" \<br>-            "foo=bar\n" \<br>-            "exten => _NXX.,n,Wait(1)\n" \<br>-            "astetcdir => /etc/asterisk\n"<br>-<br>-        conf = ConfigFile(fn=None, config_str=test)<br>-<br>-        self.assertEqual(len(conf.categories), 3)<br>-<br>-        self.assertEqual(conf.categories[0].name, "foo")<br>-        self.assertFalse(conf.categories[0].template)<br>-        self.assertEqual(len(conf.categories[0].options), 3)<br>-        self.assertEqual(conf.categories[0].options[0][0], "a")<br>-        self.assertEqual(conf.categories[0].options[0][1], "b")<br>-        self.assertEqual(conf.categories[0].options[1][0], "b")<br>-        self.assertEqual(conf.categories[0].options[1][1], "a")<br>-        self.assertEqual(conf.categories[0].options[2][0], "c")<br>-        self.assertEqual(conf.categories[0].options[2][1], "d")<br>-<br>-        self.assertEqual(conf.categories[1].name, "bar")<br>-        self.assertFalse(conf.categories[1].template)<br>-        self.assertEqual(len(conf.categories[1].options), 3)<br>-        self.assertEqual(conf.categories[1].options[0][0], "a-b")<br>-        self.assertEqual(conf.categories[1].options[0][1], "c-d")<br>-        self.assertEqual(conf.categories[1].options[1][0], "xyz")<br>-        self.assertEqual(conf.categories[1].options[1][1], "x|y|z")<br>-        self.assertEqual(conf.categories[1].options[2][0], "1234")<br>-        self.assertEqual(conf.categories[1].options[2][1],<br>-                         "4242,Example Mailbox,root@localhost,,var=val")<br>-<br>-        self.assertEqual(conf.categories[2].name, "template")<br>-        self.assertTrue(conf.categories[2].template)<br>-        self.assertEqual(len(conf.categories[2].options), 3)<br>-        self.assertEqual(conf.categories[2].options[0][0], "foo")<br>-        self.assertEqual(conf.categories[2].options[0][1], "bar")<br>-        self.assertEqual(conf.categories[2].options[1][0], "exten")<br>-        self.assertEqual(conf.categories[2].options[1][1],<br>-                         "_NXX.,n,Wait(1)")<br>-        self.assertEqual(conf.categories[2].options[2][0], "astetcdir")<br>-        self.assertEqual(conf.categories[2].options[2][1], "/etc/asterisk")<br>-<br>-<br>-def main(argv=None):<br>-    """Read in and show a config file, or run unit tests"""<br>-<br>-    if argv is None:<br>-        argv = sys.argv<br>-<br>-    if len(argv) == 2:<br>-        conf = ConfigFile(argv[1])<br>-        for cat in conf.categories:<br>-            LOGGER.debug("[%s]" % cat.name)<br>-            for (var, val) in cat.options:<br>-                LOGGER.debug("%s = %s" % (var, val))<br>-    else:<br>-        return unittest.main()<br>-<br>-    return 0<br>-<br>-<br>-if __name__ == "__main__":<br>-    sys.exit(main() or 0)<br>diff --git a/lib/python/asterisk/fd_test_condition.py b/lib/python/asterisk/fd_test_condition.py<br>index 4a8f869..234af28 100644<br>--- a/lib/python/asterisk/fd_test_condition.py<br>+++ b/lib/python/asterisk/fd_test_condition.py<br>@@ -11,7 +11,7 @@<br> import logging<br> <br> from twisted.internet import defer<br>-from test_conditions import TestCondition<br>+from .test_conditions import TestCondition<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/lib/python/asterisk/lock_test_condition.py b/lib/python/asterisk/lock_test_condition.py<br>index 0296693..805e2ff 100644<br>--- a/lib/python/asterisk/lock_test_condition.py<br>+++ b/lib/python/asterisk/lock_test_condition.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Held locks test condition<br> <br> Copyright (C) 2011-2012, Digium, Inc.<br>@@ -10,10 +9,9 @@<br> <br> import logging<br> import logging.config<br>-import unittest<br> <br> from twisted.internet import defer<br>-from test_conditions import TestCondition<br>+from .test_conditions import TestCondition<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>@@ -199,7 +197,7 @@<br>     def evaluate(self, related_test_condition=None):<br>         """Evaluate the condition"""<br> <br>-        def __lock_info_obtained(finished_deferred):<br>+        def __lock_info_obtained(lst, finished_deferred):<br>             """Callback when lock information has been obtained"""<br>             if (len(self.locks) > 0):<br>                 #Sometimes, a lock will be held at the end of a test run<br>@@ -229,314 +227,3 @@<br>                                          finished_deferred)<br>         return finished_deferred<br> <br>-<br>-class AstMockObjectPassed(object):<br>-    """A lock output that passed"""<br>-<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.2"<br>-<br>-    def cli_exec(self, command, sync):<br>-        """Fake out a CLI command execution"""<br>-        lock_lines = "=======================================================================\n"<br>-        lock_lines += "=== Currently Held Locks ==============================================\n"<br>-        lock_lines += "=======================================================================\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=== <pending> <lock#> (<file>): <lock type> <line num> <function> <lock name> <lock addr> (times locked)\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=======================================================================\n"<br>-        return lock_lines<br>-<br>-<br>-class AstMockObjectFailure(object):<br>-    """A lock object that failed"""<br>-<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.1"<br>-<br>-    def cli_exec(self, command, sync):<br>-        """Fake out a CLI command execution"""<br>-        lock_lines = "=======================================================================\n"<br>-        lock_lines += "=== Currently Held Locks ==============================================\n"<br>-        lock_lines += "=======================================================================\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=== <pending> <lock#> (<file>): <lock type> <line num> <function> <lock name> <lock addr> (times locked)\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=== Thread ID: 0x402c6940 (do_monitor           started at [25114] chan_sip.c restart_monitor())\n"<br>-        lock_lines += "=== ---> Lock #0 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe423ee8]\n"<br>-        lock_lines += "=== ---> Lock #1 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)\n"<br>-        lock_lines += "=== --- ---> Locked Here: channel.c line 4304 (ast_indicate_data)\n"<br>-        lock_lines += "=== -------------------------------------------------------------------\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=== Thread ID: 0x449ec940 (netconsole           started at [ 1351] asterisk.c listener())\n"<br>-        lock_lines += "=== ---> Waiting for Lock #0 (astobj2.c): MUTEX 842 internal_ao2_iterator_next a->c 0x2aaaac491f50 (1)\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x446cec]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_iterator_next+0x29) [0x447134]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_iterator_next+0x19) [0x46cf7d]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x489e43]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_full+0x222) [0x48eec4]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_multiple_full+0x92) [0x48f035]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x43d129]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]\n"<br>-        lock_lines += "/lib64/libpthread.so.0 [0x3d1d80673d]\n"<br>-        lock_lines += "/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]\n"<br>-        lock_lines += "=== --- ---> Locked Here: astobj2.c line 657 (internal_ao2_callback)\n"<br>-        lock_lines += "=== -------------------------------------------------------------------\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=== Thread ID: 0x44a68940 (netconsole           started at [ 1351] asterisk.c listener())\n"<br>-        lock_lines += "=== ---> Waiting for Lock #0 (astobj2.c): MUTEX 842 internal_ao2_iterator_next a->c 0x2aaaac491f50 (1)\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x446cec]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_iterator_next+0x29) [0x447134]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_iterator_next+0x19) [0x46cf7d]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x489e43]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_full+0x222) [0x48eec4]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_multiple_full+0x92) [0x48f035]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x43d129]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]\n"<br>-        lock_lines += "/lib64/libpthread.so.0 [0x3d1d80673d]\n"<br>-        lock_lines += "/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]\n"<br>-        lock_lines += "=== --- ---> Locked Here: astobj2.c line 657 (internal_ao2_callback)\n"<br>-        lock_lines += "=== -------------------------------------------------------------------\n"<br>-        lock_lines += "===\n"<br>-        lock_lines += "=======================================================================\n"<br>-        return lock_lines<br>-<br>-<br>-class TestConfig(object):<br>-    """Fake TestConfig object"""<br>-<br>-    def __init__(self):<br>-        """ Values here don't matter much - we just need to have something """<br>-        self.class_type_name = "asterisk.LockTestCondition.LockTestCondition"<br>-        self.pass_expected = True<br>-        self.type = "Post"<br>-        self.related_condition = ""<br>-        self.config = {}<br>-<br>-<br>-class LockTestConditionUnitTest(unittest.TestCase):<br>-    """Unit tests for LockTestCondition"""<br>-<br>-    def test_evaluate_failed(self):<br>-        """Test a failed locking condition"""<br>-        ast = AstMockObjectFailure()<br>-        obj = LockTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_evaluate_pass(self):<br>-        """Test a passed locking condition"""<br>-        ast = AstMockObjectPassed()<br>-        obj = LockTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_evaluate_multiple(self):<br>-        """Test multiple results"""<br>-        ast1 = AstMockObjectPassed()<br>-        ast2 = AstMockObjectFailure()<br>-        obj = LockTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast1)<br>-        obj.register_asterisk_instance(ast2)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-<br>-class LockSequenceUnitTest(unittest.TestCase):<br>-    """Tests for parsing a lock sequence"""<br>-<br>-    def test_single_object_no_held_info(self):<br>-        """Test a lock sequence with no waiting lock"""<br>-<br>-        lock_lines = "=== Thread ID: 0x7f668c142700 (do_monitor           started at [25915] chan_sip.c restart_monitor())\n"<br>-        lock_lines += "=== ---> Lock #0 (chan_sip.c): MUTEX 25390 handle_request_do &netlock 0x7f6652193900 (1)\n"<br>-        lock_lines += "main/logger.c:1302 ast_bt_get_addresses() (0x505e53+1D)\n"<br>-        lock_lines += "main/lock.c:193 __ast_pthread_mutex_lock() (0x4fe55c+D9)\n"<br>-        lock_lines += "channels/chan_sip.c:25393 handle_request_do()\n"<br>-        lock_lines += "channels/chan_sip.c:25352 sipsock_read()\n"<br>-        lock_lines += "main/io.c:288 ast_io_wait() (0x4f8228+19C)\n"<br>-        lock_lines += "channels/chan_sip.c:25882 do_monitor()\n"<br>-        lock_lines += "main/utils.c:1010 dummy_start()\n"<br>-        lock_lines += "libpthread.so.0 <unknown>()\n"<br>-        lock_lines += "libc.so.6 clone() (0x31be0e0bc0+6D)\n"<br>-<br>-        obj = LockSequence()<br>-        obj.parse_lock_sequence(lock_lines)<br>-<br>-    def test_large_multiple_object(self):<br>-        """Test with a waiting lock"""<br>-<br>-        lock_lines = "=== Thread ID: 0x449ec940 (netconsole           started at [ 1351] asterisk.c listener())\n"<br>-        lock_lines += "=== ---> Waiting for Lock #0 (astobj2.c): MUTEX 842 internal_ao2_iterator_next a->c 0x2aaaac491f50 (1)\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x446cec]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_iterator_next+0x29) [0x447134]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_iterator_next+0x19) [0x46cf7d]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x489e43]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_full+0x222) [0x48eec4]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_multiple_full+0x92) [0x48f035]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x43d129]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]\n"<br>-        lock_lines += "/lib64/libpthread.so.0 [0x3d1d80673d]\n"<br>-        lock_lines += "/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]\n"<br>-        lock_lines += "=== --- ---> Locked Here: astobj2.c line 657 (internal_ao2_callback)\n"<br>-<br>-        obj = LockSequence()<br>-        obj.parse_lock_sequence(lock_lines)<br>-        self.assertEqual(obj.thread_id, "0x449ec940")<br>-        self.assertEqual(obj.thread_name, "netconsole")<br>-        self.assertEqual(obj.thread_line, 1351)<br>-        self.assertEqual(obj.thread_file, "asterisk.c")<br>-        self.assertEqual(obj.thread_func, "listener")<br>-        self.assertTrue(len(obj.locks) == 1)<br>-        self.assertEqual(obj.locks[0].locked_file, "astobj2.c")<br>-        self.assertEqual(obj.locks[0].locked_line, 657)<br>-        self.assertEqual(obj.locks[0].locked_func, "internal_ao2_callback")<br>-<br>-    def test_single_object(self):<br>-        """Test a lock held somewhere else"""<br>-<br>-        lock_lines = "=== Thread ID: 0x402c6940 (do_monitor           started at [25114] chan_sip.c restart_monitor())\n"<br>-        lock_lines += "=== ---> Lock #0 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe423ee8]\n"<br>-        lock_lines += "=== --- ---> Locked Here: channel.c line 4304 (ast_indicate_data)\n"<br>-<br>-        obj = LockSequence()<br>-        obj.parse_lock_sequence(lock_lines)<br>-        self.assertEqual(obj.thread_id, "0x402c6940")<br>-        self.assertEqual(obj.thread_name, "do_monitor")<br>-        self.assertEqual(obj.thread_line, 25114)<br>-        self.assertEqual(obj.thread_file, "chan_sip.c")<br>-        self.assertEqual(obj.thread_func, "restart_monitor")<br>-        self.assertTrue(len(obj.locks) == 1)<br>-        self.assertEqual(obj.locks[0].locked_file, "channel.c")<br>-        self.assertEqual(obj.locks[0].locked_line, 4304)<br>-        self.assertEqual(obj.locks[0].locked_func, "ast_indicate_data")<br>-        self.assertEqual(obj.locks[0].id, 0)<br>-        self.assertEqual(obj.locks[0].type, "MUTEX")<br>-        self.assertEqual(obj.locks[0].file, "chan_sip.c")<br>-        self.assertEqual(obj.locks[0].line_number, 24629)<br>-        self.assertEqual(obj.locks[0].func, "handle_request_do")<br>-        self.assertEqual(obj.locks[0].name, "&netlock")<br>-        self.assertEqual(obj.locks[0].addr, "0x2aaabe671a40")<br>-        self.assertEqual(obj.locks[0].lock_count, 1)<br>-        self.assertTrue(obj.locks[0].held)<br>-        self.assertTrue(len(obj.locks[0].backtrace) == 3)<br>-<br>-    def test_multiple_objects_no_backtrace(self):<br>-        """Test multiple locks with no backtrace"""<br>-<br>-        lock_lines = "=== Thread ID: 0x402c6940 (do_monitor           started at [25114] chan_sip.c restart_monitor())\n"<br>-        lock_lines += "=== ---> Lock #0 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)\n"<br>-        lock_lines += "=== ---> Lock #1 (astobj2.c): MUTEX 657 internal_ao2_callback c 0x2aaaac491f50 (1)\n"<br>-        lock_lines += "=== ---> Waiting for Lock #2 (channel.c): MUTEX 1691 ast_channel_cmp_cb chan 0x2aaaacd3a4e0 (1)\n"<br>-        lock_lines += "=== --- ---> Locked Here: channel.c line 4304 (ast_indicate_data)\n"<br>-<br>-        obj = LockSequence()<br>-        obj.parse_lock_sequence(lock_lines)<br>-        self.assertEqual(obj.thread_id, "0x402c6940")<br>-        self.assertEqual(obj.thread_name, "do_monitor")<br>-        self.assertEqual(obj.thread_line, 25114)<br>-        self.assertEqual(obj.thread_file, "chan_sip.c")<br>-        self.assertEqual(obj.thread_func, "restart_monitor")<br>-        self.assertTrue(len(obj.locks) == 3)<br>-        self.assertEqual(obj.locks[2].locked_file, "channel.c")<br>-        self.assertEqual(obj.locks[2].locked_line, 4304)<br>-        self.assertEqual(obj.locks[2].locked_func, "ast_indicate_data")<br>-        self.assertEqual(obj.locks[0].id, 0)<br>-        self.assertEqual(obj.locks[0].type, "MUTEX")<br>-        self.assertEqual(obj.locks[0].file, "chan_sip.c")<br>-        self.assertEqual(obj.locks[0].line_number, 24629)<br>-        self.assertEqual(obj.locks[0].func, "handle_request_do")<br>-        self.assertEqual(obj.locks[0].name, "&netlock")<br>-        self.assertEqual(obj.locks[0].addr, "0x2aaabe671a40")<br>-        self.assertEqual(obj.locks[0].lock_count, 1)<br>-        self.assertTrue(obj.locks[0].held)<br>-        self.assertTrue(len(obj.locks[0].backtrace) == 0)<br>-<br>-<br>-class LockObjectUnitTest(unittest.TestCase):<br>-    """Unit tests for LockObject"""<br>-<br>-    def test_no_backtrace(self):<br>-        """Test creating a lock object with no thread backtrace"""<br>-<br>-        lock_line = "=== ---> Waiting for Lock #0 (sig_ss7.c): MUTEX 636 ss7_linkset &linkset->lock 0x2aaab8a6b588 (1)"<br>-        obj = LockObject()<br>-        obj.parse_lock_information(lock_line)<br>-        self.assertEqual(obj.id, 0)<br>-        self.assertEqual(obj.type, "MUTEX")<br>-        self.assertEqual(obj.file, "sig_ss7.c")<br>-        self.assertEqual(obj.line_number, 636)<br>-        self.assertEqual(obj.func, "ss7_linkset")<br>-        self.assertEqual(obj.name, "&linkset->lock")<br>-        self.assertEqual(obj.addr, "0x2aaab8a6b588")<br>-        self.assertEqual(obj.lock_count, 1)<br>-        self.assertFalse(obj.held)<br>-        self.assertTrue(len(obj.backtrace) == 0)<br>-<br>-    def test_with_backtrace(self):<br>-        """Test creating a lock object with a thread backtrace"""<br>-<br>-        lock_lines = "=== ---> Lock #1 (astobj2.c): MUTEX 657 internal_ao2_callback c 0x2aaaac491f50 (1)\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x4464be]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_callback+0x59) [0x446a4e]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_find+0x2b) [0x446ba7]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x46d3a7]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_get_by_name+0x24) [0x46d3e3]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/func_channel.so [0x2aaabfba2468]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_func_write+0x16a) [0x50aacd]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(pbx_builtin_setvar_helper+0x10e) [0x51fff4]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe422d09]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe4240a0]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe423cf1]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_io_wait+0x1ba) [0x4dc2e4]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe425722]\n"<br>-        lock_lines += "/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]\n"<br>-        lock_lines += "/lib64/libpthread.so.0 [0x3d1d80673d]\n"<br>-        lock_lines += "/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]\n"<br>-<br>-        obj = LockObject()<br>-        obj.parse_lock_information(lock_lines)<br>-        self.assertEqual(obj.id, 1)<br>-        self.assertEqual(obj.type, "MUTEX")<br>-        self.assertEqual(obj.file, "astobj2.c")<br>-        self.assertEqual(obj.line_number, 657)<br>-        self.assertEqual(obj.func, "internal_ao2_callback")<br>-        self.assertEqual(obj.name, "c")<br>-        self.assertEqual(obj.addr, "0x2aaaac491f50")<br>-        self.assertEqual(obj.lock_count, 1)<br>-        self.assertTrue(obj.held)<br>-        self.assertTrue(len(obj.backtrace) == 19)<br>-<br>-<br>-def main():<br>-    """Run the unit tests"""<br>-<br>-    logging.basicConfig(level=logging.DEBUG)<br>-    unittest.main()<br>-<br>-<br>-if __name__ == "__main__":<br>-    main()<br>diff --git a/lib/python/asterisk/matcher.py b/lib/python/asterisk/matcher.py<br>index 3ab9c24..8ef575e 100644<br>--- a/lib/python/asterisk/matcher.py<br>+++ b/lib/python/asterisk/matcher.py<br>@@ -9,13 +9,17 @@<br> """<br> <br> import logging<br>+import sys<br> <br>-from test_suite_utils import all_match<br>-from pluggable_registry import PLUGGABLE_EVENT_REGISTRY,\<br>-    PLUGGABLE_ACTION_REGISTRY<br>+from .test_runner import load_and_parse_module<br>+from .test_suite_utils import all_match<br>+from .pluggable_registry import PLUGGABLE_EVENT_REGISTRY<br> <br> <br> LOGGER = logging.getLogger(__name__)<br>+<br>+if sys.version_info[0] == 3:<br>+    unicode = str<br> <br> <br> class ConditionError(Exception):<br>@@ -217,7 +221,6 @@<br> <br>             matched.append(c)<br> <br>-<br>         if not matched:<br>             return False<br> <br>@@ -315,13 +318,9 @@<br> <br>         self.triggered_callback = triggered_callback<br> <br>-        module_name, _, obj_type = config['type'].partition('.')<br>-<br>-        module = __import__(module_name, fromlist=[obj_type])<br>-        if not module:<br>-            raise Exception("Unable to import module '{0}'.".format(module_name))<br>-<br>-        obj = getattr(module, obj_type)<br>+        obj = load_and_parse_module(config['type'])<br>+        if not obj:<br>+            raise Exception("Unable to import module '{0}'.".format(config['type']))<br> <br>         self.conditions = obj(config, test_object, self.__handle_match)<br> <br>diff --git a/lib/python/asterisk/matcher_listener.py b/lib/python/asterisk/matcher_listener.py<br>index d6b621d..cd2ca5a 100644<br>--- a/lib/python/asterisk/matcher_listener.py<br>+++ b/lib/python/asterisk/matcher_listener.py<br>@@ -13,7 +13,7 @@<br> from twisted.internet.protocol import DatagramProtocol<br> from twisted.internet import reactor<br> <br>-from matcher import PluggableConditions<br>+from .matcher import PluggableConditions<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/lib/python/asterisk/opensslversion.py b/lib/python/asterisk/opensslversion.py<br>index a7377ea..3fa5cd5 100644<br>--- a/lib/python/asterisk/opensslversion.py<br>+++ b/lib/python/asterisk/opensslversion.py<br>@@ -9,11 +9,10 @@<br> """<br> <br> import sys<br>-import unittest<br> import re<br> sys.path.append("lib/python")<br> <br>-import test_suite_utils<br>+from . import test_suite_utils<br> <br> class OpenSSLVersion:<br>     """An OpenSSL Version.<br>diff --git a/lib/python/asterisk/originate.py b/lib/python/asterisk/originate.py<br>index e6befb5..6c87a57 100644<br>--- a/lib/python/asterisk/originate.py<br>+++ b/lib/python/asterisk/originate.py<br>@@ -16,7 +16,7 @@<br> import json<br> import requests<br> <br>-import ari<br>+from . import ari<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/lib/python/asterisk/pcap.py b/lib/python/asterisk/pcap.py<br>index d39574a..cb5fa5c 100644<br>--- a/lib/python/asterisk/pcap.py<br>+++ b/lib/python/asterisk/pcap.py<br>@@ -739,13 +739,14 @@<br>         self.callbacks = {}<br>         self.traces = {}<br> <br>-    def process_packet(self, packet, (host, port)):<br>+    def process_packet(self, packet, addr):<br>         """Store a known packet in our traces and call our callbacks<br> <br>         Keyword Arguments:<br>         packet       A raw packet received from ... something.<br>-        (host, port) Tuple of received host and port<br>+        addr         Tuple of received host and port<br>         """<br>+        (host, port) = addr<br>         packet = self.packet_factory.interpret_packet(packet)<br>         if packet is None:<br>             return<br>@@ -812,13 +813,14 @@<br>             self.rules = rules<br>             self.cb = cb<br> <br>-        def datagramReceived(self, data, (host, port)):<br>+        def datagramReceived(self, data, addr):<br>             """Callback for when a datagram is received<br> <br>             Keyword Arguments:<br>             data         The actual packet<br>-            (host, port) Tuple of source host and port<br>+            addr         Tuple of received host and port<br>             """<br>+            (host, port) = addr<br>             LOGGER.debug('Proxy received from {0}:{1}\n{2}'.format(<br>                 host, port, data))<br> <br>diff --git a/lib/python/asterisk/phones.py b/lib/python/asterisk/phones.py<br>old mode 100755<br>new mode 100644<br>index 6a96b38..4987da5<br>--- a/lib/python/asterisk/phones.py<br>+++ b/lib/python/asterisk/phones.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Pluggable modules and classes to simulate phones.<br> <br> Copyright (C) 2015, Digium, Inc.<br>@@ -85,7 +84,7 @@<br> <br>     def __setup_pjsua_acc_cb(self):<br>         """Setup PJSUA account callbacks"""<br>-        for name, phone_obj in self.__pjsua_phones.iteritems():<br>+        for name, phone_obj in self.__pjsua_phones.items():<br>             acc_cb = AccCallback()<br>             phone_obj.account.set_callback(acc_cb)<br>             LOGGER.info("%s is ready to receive calls." % name)<br>@@ -106,7 +105,7 @@<br>         if name:<br>             return self.__pjsua_phones.get(name)<br>         if account:<br>-            for name, phone_obj in self.__pjsua_phones.iteritems():<br>+            for name, phone_obj in self.__pjsua_phones.items():<br>                 if account is phone_obj.account:<br>                     return phone_obj<br> <br>diff --git a/lib/python/asterisk/pjsua_mod.py b/lib/python/asterisk/pjsua_mod.py<br>index 63add6a..cdf3d18 100644<br>--- a/lib/python/asterisk/pjsua_mod.py<br>+++ b/lib/python/asterisk/pjsua_mod.py<br>@@ -17,6 +17,7 @@<br> sys.path.append("lib/python")<br> <br> from twisted.internet import reactor<br>+from .test_runner import load_and_parse_module<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>@@ -147,7 +148,7 @@<br>             self.lib.set_null_snd_dev()<br>             self.__create_accounts()<br>             self.lib.start()<br>-        except pj.Error, exception:<br>+        except pj.Error as exception:<br>             LOGGER.error("Exception: " + str(exception))<br>             self.lib.destroy()<br>             self.lib = None<br>@@ -306,6 +307,5 @@<br> <br>     def do_callback(self):<br>         """Call the configured callback module/method"""<br>-        callback_module = __import__(self.callback_module)<br>-        callback_method = getattr(callback_module, self.callback_method)<br>+        callback_method = load_and_parse_module(self.callback_module + '.' + self.callback_method)<br>         callback_method(self.test_object, self.pj_accounts)<br>diff --git a/lib/python/asterisk/pluggable_modules.py b/lib/python/asterisk/pluggable_modules.py<br>old mode 100755<br>new mode 100644<br>index 344f908..dddb036<br>--- a/lib/python/asterisk/pluggable_modules.py<br>+++ b/lib/python/asterisk/pluggable_modules.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Generic pluggable modules<br> <br> Copyright (C) 2012, Digium, Inc.<br>@@ -14,15 +13,15 @@<br> import re<br> <br> sys.path.append("lib/python")<br>-from ami import AMIEventInstance<br>+from .ami import AMIEventInstance<br> from twisted.internet import reactor<br> from starpy import fastagi<br>-from test_runner import load_and_parse_module<br>-from pluggable_registry import PLUGGABLE_ACTION_REGISTRY,\<br>+from .test_runner import load_and_parse_module<br>+from .pluggable_registry import PLUGGABLE_ACTION_REGISTRY,\<br>     PLUGGABLE_EVENT_REGISTRY,\<br>     PluggableRegistry<br> <br>-import matcher<br>+from . import matcher<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>@@ -576,7 +575,7 @@<br>             return<br> <br>         current_trigger = config['trigger']['match']<br>-        for key, value in current_trigger.iteritems():<br>+        for key, value in current_trigger.items():<br>             if key.lower() not in event:<br>                 LOGGER.debug("Condition %s not in event, returning", key)<br>                 return<br>@@ -701,8 +700,7 @@<br>         if self.commands:<br>             return self.execute_command(agi, 0)<br>         else:<br>-            callback_module = __import__(self.callback_module)<br>-            method = getattr(callback_module, self.callback_method)<br>+            method = load_and_parse_module(self.callback_module + '.' + self.callback_method)<br>             method(self.test_object, agi)<br> <br>     def on_command_failure(self, reason, agi, idx):<br>@@ -784,7 +782,7 @@<br> <br>         def register_modules(config, registry):<br>             """Register pluggable modules into the registry"""<br>-            for key, local_class_path in config.iteritems():<br>+            for key, local_class_path in config.items():<br>                 local_class = load_and_parse_module(local_class_path)<br>                 if not local_class:<br>                     raise Exception("Unable to load %s for module key %s"<br>@@ -804,7 +802,7 @@<br>         for e_a_set in config["mapping"]:<br>             plug_set = {"events": [], "actions": []}<br> <br>-            for plug_name, plug_config in e_a_set.iteritems():<br>+            for plug_name, plug_config in e_a_set.items():<br>                 self.parse_module_config(plug_set, plug_name, plug_config)<br> <br>             if 0 == len(plug_set["events"]):<br>@@ -923,8 +921,7 @@<br> <br>     def run(self, triggered_by, source, extra):<br>         """Call the callback."""<br>-        module = __import__(self.module)<br>-        method = getattr(module, self.method)<br>+        method = load_and_parse_module(self.module + '.' + self.method)<br>         self.test_object.set_passed(method(self.test_object, triggered_by,<br>                                            source, extra))<br> PLUGGABLE_ACTION_REGISTRY.register("callback", CallbackActionModule)<br>@@ -960,12 +957,12 @@<br>     def __init__(self, test_object, config):<br>         """Setup the test start observer"""<br>         self.test_object = test_object<br>-        self.module = __import__("phones")<br>+        self.module = "phones"<br>         self.method = config["action"]<br>         self.config = config<br> <br>     def run(self, triggered_by, source, extra):<br>         """Instruct phone to perform action"""<br>-        method = getattr(self.module, self.method)<br>+        method = load_and_parse_module(self.module + "." + self.method)<br>         method(self.test_object, triggered_by, source, extra, self.config)<br> PLUGGABLE_ACTION_REGISTRY.register("pjsua_phone", PjsuaPhoneActionModule)<br>diff --git a/lib/python/asterisk/pluggable_registry.py b/lib/python/asterisk/pluggable_registry.py<br>old mode 100755<br>new mode 100644<br>index 8169795..9a50a26<br>--- a/lib/python/asterisk/pluggable_registry.py<br>+++ b/lib/python/asterisk/pluggable_registry.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Pluggable module registries<br> <br> Copyright (C) 2014, Digium, Inc.<br>diff --git a/lib/python/asterisk/realtime_converter.py b/lib/python/asterisk/realtime_converter.py<br>index 3f50b5f..95ae579 100644<br>--- a/lib/python/asterisk/realtime_converter.py<br>+++ b/lib/python/asterisk/realtime_converter.py<br>@@ -9,7 +9,7 @@<br> import os<br> from sqlalchemy import create_engine, MetaData, Table<br> <br>-import astconfigparser<br>+from . import astconfigparser<br> import logging<br> <br> LOGGER = logging.getLogger(__name__)<br>@@ -83,8 +83,8 @@<br>         self.filename = filename<br>         self.sections = sections<br>         # All affected database tables in list form. Used for convenience<br>-        self.tables = [table for section in sections.itervalues() for table in<br>-                       section.itervalues()]<br>+        self.tables = [table for section in sections.values() for table in<br>+                       section.values()]<br>         self.sorcery = None<br>         self.extconfig = None<br> <br>@@ -111,9 +111,9 @@<br>         config_dir: The directory where Asterisk configuration can be found<br>         """<br>         with open(self.sorcery.file, 'a') as sorcery:<br>-            for section, items in self.sections.iteritems():<br>+            for section, items in self.sections.items():<br>                 sorcery.write('[{0}]\n'.format(section))<br>-                for obj, table in items.iteritems():<br>+                for obj, table in items.items():<br>                     sorcery.write('{0} = realtime,{1}\n'.format(obj, table))<br> <br>     def write_extconfig_conf(self):<br>@@ -141,7 +141,7 @@<br>         """<br>         conf = astconfigparser.MultiOrderedConfigParser()<br>         conf.read(os.path.join(config_dir, self.filename))<br>-        for title, sections in conf.sections().iteritems():<br>+        for title, sections in conf.sections().items():<br>             LOGGER.info("Inspecting objects with title {0}".format(title))<br>             for section in sections:<br>                 obj_type = section.get('type')[0]<br>@@ -165,7 +165,7 @@<br>         Keyword Arguments:<br>         obj_type: The object type to find the section for<br>         """<br>-        for section, contents in self.sections.iteritems():<br>+        for section, contents in self.sections.items():<br>             if obj_type in contents:<br>                 return section<br> <br>diff --git a/lib/python/asterisk/realtime_odbc_module.py b/lib/python/asterisk/realtime_odbc_module.py<br>index 3653553..bbd8a8e 100644<br>--- a/lib/python/asterisk/realtime_odbc_module.py<br>+++ b/lib/python/asterisk/realtime_odbc_module.py<br>@@ -53,7 +53,7 @@<br>         self.res_odbc = {}<br> <br>         # generate configuration for each dsn<br>-        for dsn, config in module_config.iteritems():<br>+        for dsn, config in module_config.items():<br>             self._configure(dsn, config)<br> <br>         # set the odbc and conf files<br>@@ -109,7 +109,7 @@<br>         with open(filepath, 'w') as filehandle:<br>             for section in contents:<br>                 filehandle.write('[' + section + ']\n')<br>-                for name, value in contents[section].iteritems():<br>+                for name, value in contents[section].items():<br>                     filehandle.write(name + '=' + value + '\n')<br> <br>     def _read_ini_file(self, filepath):<br>diff --git a/lib/python/asterisk/realtime_test_module.py b/lib/python/asterisk/realtime_test_module.py<br>index fd38aa9..da97422 100644<br>--- a/lib/python/asterisk/realtime_test_module.py<br>+++ b/lib/python/asterisk/realtime_test_module.py<br>@@ -91,7 +91,7 @@<br> <br>         return [row for row in table<br>                 if all(key in row and re.match(value, row[key])<br>-                       for key, value in where.iteritems())]<br>+                       for key, value in where.items())]<br> <br>     def retrieve_rows(self, table_name, where):<br>         """Retrieve multiple rows from a table.<br>@@ -239,7 +239,7 @@<br>         since we could use a dict comprehension.<br>         """<br>         filtered_args = {}<br>-        for key, values in args.iteritems():<br>+        for key, values in args.items():<br>             if " LIKE" in key:<br>                 # Strip away " LIKE" and % from values<br>                 filtered_args[key[:-5]] = [val.replace('%', '.*')<br>@@ -250,7 +250,7 @@<br>         LOGGER.debug('filtered args is %s' % filtered_args)<br> <br>         return dict((key, values[0] if values else '.*') for key, values in<br>-                    filtered_args.iteritems())<br>+                    filtered_args.items())<br> <br>     def encode_row(self, row):<br>         """Encode a retrieved row for an HTTP response.<br>@@ -261,7 +261,7 @@<br>         Example output: 'foo=cat&bar=dog&baz=donkey'<br>         """<br>         string = '&'.join(['{0}={1}'.format(cgi.escape(key), cgi.escape(val))<br>-                           for key, val in row.iteritems()])<br>+                           for key, val in row.items()])<br>         LOGGER.debug("Returning response %s" % string)<br>         return string<br> <br>@@ -481,7 +481,7 @@<br>         if not data:<br>             return<br> <br>-        for table_name, rows in data.iteritems():<br>+        for table_name, rows in data.items():<br>             self.rt_data.add_rows(table_name, rows)<br> <br>     def setup_http(self):<br>diff --git a/lib/python/asterisk/self_test/harness_shared.py b/lib/python/asterisk/self_test/harness_shared.py<br>new file mode 100644<br>index 0000000..91449ac<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/harness_shared.py<br>@@ -0,0 +1,54 @@<br>+"""Unit test harness<br>+<br>+This module provides the entry-point for tests<br>+<br>+Copyright (C) 2018, CFWare, LLC.<br>+Corey Farrell <git@cfware.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+import logging<br>+import os<br>+import sys<br>+from twisted.internet import defer<br>+import unittest<br>+<br>+# Add directory where the modules to test can be found<br>+sys.path.append('lib/python')<br>+<br>+<br>+def ReadTestFile(filename, basepath="lib/python/asterisk/self_test"):<br>+    fd = open(os.path.join(basepath, filename), "r")<br>+    output = fd.read()<br>+    fd.close()<br>+    return output<br>+<br>+<br>+class AstMockOutput(object):<br>+    """mock cli output base class"""<br>+<br>+    def __init__(self, host="127.0.0.1"):<br>+        """Constructor"""<br>+        self.host = host<br>+<br>+    def MockDeferFile(self, filename):<br>+        return self.MockDefer(ReadTestFile(filename))<br>+<br>+    def MockDefer(self, output):<br>+        """use real defer to mock deferred output"""<br>+        self.output = output<br>+        deferred = defer.Deferred()<br>+        deferred.callback(self)<br>+        return deferred<br>+<br>+<br>+def main():<br>+    """Run the unit tests"""<br>+<br>+    logging.basicConfig()<br>+    unittest.main()<br>+<br>+<br>+__all__ = ["main", "AstMockOutput", "ReadTestFile"]<br>diff --git a/lib/python/asterisk/self_test/locks-backtrace.txt b/lib/python/asterisk/self_test/locks-backtrace.txt<br>new file mode 100644<br>index 0000000..0de89a4<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-backtrace.txt<br>@@ -0,0 +1,20 @@<br>+=== ---> Lock #1 (astobj2.c): MUTEX 657 internal_ao2_callback c 0x2aaaac491f50 (1)<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x4464be]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_callback+0x59) [0x446a4e]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_find+0x2b) [0x446ba7]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x46d3a7]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_get_by_name+0x24) [0x46d3e3]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/func_channel.so [0x2aaabfba2468]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_func_write+0x16a) [0x50aacd]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(pbx_builtin_setvar_helper+0x10e) [0x51fff4]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe422d09]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe4240a0]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe423cf1]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_io_wait+0x1ba) [0x4dc2e4]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe425722]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]<br>+/lib64/libpthread.so.0 [0x3d1d80673d]<br>+/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]<br>diff --git a/lib/python/asterisk/self_test/locks-fail.txt b/lib/python/asterisk/self_test/locks-fail.txt<br>new file mode 100644<br>index 0000000..fb50ca8<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-fail.txt<br>@@ -0,0 +1,52 @@<br>+=======================================================================<br>+=== Currently Held Locks ==============================================<br>+=======================================================================<br>+===<br>+=== <pending> <lock#> (<file>): <lock type> <line num> <function> <lock name> <lock addr> (times locked)<br>+===<br>+=== Thread ID: 0x402c6940 (do_monitor           started at [25114] chan_sip.c restart_monitor())<br>+=== ---> Lock #0 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe423ee8]<br>+=== ---> Lock #1 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)<br>+=== --- ---> Locked Here: channel.c line 4304 (ast_indicate_data)<br>+=== -------------------------------------------------------------------<br>+===<br>+=== Thread ID: 0x449ec940 (netconsole           started at [ 1351] asterisk.c listener())<br>+=== ---> Waiting for Lock #0 (astobj2.c): MUTEX 842 internal_ao2_iterator_next a->c 0x2aaaac491f50 (1)<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x446cec]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_iterator_next+0x29) [0x447134]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_iterator_next+0x19) [0x46cf7d]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x489e43]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_full+0x222) [0x48eec4]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_multiple_full+0x92) [0x48f035]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x43d129]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]<br>+/lib64/libpthread.so.0 [0x3d1d80673d]<br>+/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]<br>+=== --- ---> Locked Here: astobj2.c line 657 (internal_ao2_callback)<br>+=== -------------------------------------------------------------------<br>+===<br>+=== Thread ID: 0x44a68940 (netconsole           started at [ 1351] asterisk.c listener())<br>+=== ---> Waiting for Lock #0 (astobj2.c): MUTEX 842 internal_ao2_iterator_next a->c 0x2aaaac491f50 (1)<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x446cec]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_iterator_next+0x29) [0x447134]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_iterator_next+0x19) [0x46cf7d]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x489e43]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_full+0x222) [0x48eec4]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_multiple_full+0x92) [0x48f035]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x43d129]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]<br>+/lib64/libpthread.so.0 [0x3d1d80673d]<br>+/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]<br>+=== --- ---> Locked Here: astobj2.c line 657 (internal_ao2_callback)<br>+=== -------------------------------------------------------------------<br>+===<br>+=======================================================================<br>diff --git a/lib/python/asterisk/self_test/locks-large-multiple-object.txt b/lib/python/asterisk/self_test/locks-large-multiple-object.txt<br>new file mode 100644<br>index 0000000..ea657b6<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-large-multiple-object.txt<br>@@ -0,0 +1,16 @@<br>+=== Thread ID: 0x449ec940 (netconsole           started at [ 1351] asterisk.c listener())<br>+=== ---> Waiting for Lock #0 (astobj2.c): MUTEX 842 internal_ao2_iterator_next a->c 0x2aaaac491f50 (1)<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_lock+0x53) [0x4456fc]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x446cec]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ao2_iterator_next+0x29) [0x447134]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_channel_iterator_next+0x19) [0x46cf7d]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x489e43]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_full+0x222) [0x48eec4]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_cli_command_multiple_full+0x92) [0x48f035]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x43d129]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk [0x5661c6]<br>+/lib64/libpthread.so.0 [0x3d1d80673d]<br>+/lib64/libc.so.6(clone+0x6d) [0x3d1ccd44bd]<br>+=== --- ---> Locked Here: astobj2.c line 657 (internal_ao2_callback)<br>diff --git a/lib/python/asterisk/self_test/locks-multiple-objects-no-backtrace.txt b/lib/python/asterisk/self_test/locks-multiple-objects-no-backtrace.txt<br>new file mode 100644<br>index 0000000..3ed0ca5<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-multiple-objects-no-backtrace.txt<br>@@ -0,0 +1,5 @@<br>+=== Thread ID: 0x402c6940 (do_monitor           started at [25114] chan_sip.c restart_monitor())<br>+=== ---> Lock #0 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)<br>+=== ---> Lock #1 (astobj2.c): MUTEX 657 internal_ao2_callback c 0x2aaaac491f50 (1)<br>+=== ---> Waiting for Lock #2 (channel.c): MUTEX 1691 ast_channel_cmp_cb chan 0x2aaaacd3a4e0 (1)<br>+=== --- ---> Locked Here: channel.c line 4304 (ast_indicate_data)<br>diff --git a/lib/python/asterisk/self_test/locks-pass.txt b/lib/python/asterisk/self_test/locks-pass.txt<br>new file mode 100644<br>index 0000000..bbb3b92<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-pass.txt<br>@@ -0,0 +1,8 @@<br>+=======================================================================<br>+=== Currently Held Locks ==============================================<br>+=======================================================================<br>+===<br>+=== <pending> <lock#> (<file>): <lock type> <line num> <function> <lock name> <lock addr> (times locked)<br>+===<br>+===<br>+=======================================================================<br>diff --git a/lib/python/asterisk/self_test/locks-single-object-no-held-info.txt b/lib/python/asterisk/self_test/locks-single-object-no-held-info.txt<br>new file mode 100644<br>index 0000000..e1bf47a<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-single-object-no-held-info.txt<br>@@ -0,0 +1,11 @@<br>+=== Thread ID: 0x7f668c142700 (do_monitor           started at [25915] chan_sip.c restart_monitor())<br>+=== ---> Lock #0 (chan_sip.c): MUTEX 25390 handle_request_do &netlock 0x7f6652193900 (1)<br>+main/logger.c:1302 ast_bt_get_addresses() (0x505e53+1D)<br>+main/lock.c:193 __ast_pthread_mutex_lock() (0x4fe55c+D9)<br>+channels/chan_sip.c:25393 handle_request_do()<br>+channels/chan_sip.c:25352 sipsock_read()<br>+main/io.c:288 ast_io_wait() (0x4f8228+19C)<br>+channels/chan_sip.c:25882 do_monitor()<br>+main/utils.c:1010 dummy_start()<br>+libpthread.so.0 <unknown>()<br>+libc.so.6 clone() (0x31be0e0bc0+6D)<br>diff --git a/lib/python/asterisk/self_test/locks-single-object.txt b/lib/python/asterisk/self_test/locks-single-object.txt<br>new file mode 100644<br>index 0000000..db356ed<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/locks-single-object.txt<br>@@ -0,0 +1,6 @@<br>+=== Thread ID: 0x402c6940 (do_monitor           started at [25114] chan_sip.c restart_monitor())<br>+=== ---> Lock #0 (chan_sip.c): MUTEX 24629 handle_request_do &netlock 0x2aaabe671a40 (1)<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(ast_bt_get_addresses+0x1a) [0x4e9679]<br>+/usr/local/asterisk-1.8.6.0/sbin/asterisk(__ast_pthread_mutex_lock+0xf6) [0x4e22d9]<br>+/usr/local/asterisk-1.8.6.0/lib/asterisk/modules/chan_sip.so [0x2aaabe423ee8]<br>+=== --- ---> Locked Here: channel.c line 4304 (ast_indicate_data)<br>diff --git a/lib/python/asterisk/self_test/test_buildoptions.py b/lib/python/asterisk/self_test/test_buildoptions.py<br>new file mode 100755<br>index 0000000..1f4ca77<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_buildoptions.py<br>@@ -0,0 +1,26 @@<br>+#!/usr/bin/env python<br>+"""Asterisk Build Options Handling Unit Test<br>+<br>+Copyright (C) 2011-2012, Digium, Inc.<br>+Matt Jordan <mjordan@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import main<br>+import unittest<br>+from asterisk.buildoptions import AsteriskBuildOptions<br>+<br>+<br>+class AsteriskBuildOptionsTests(unittest.TestCase):<br>+    """Unit tests for AsteriskBuildOptions"""<br>+<br>+    def test_1(self):<br>+        """Test the defaults paths"""<br>+        build_options = AsteriskBuildOptions()<br>+        self.assertTrue(build_options)<br>+<br>+<br>+if __name__ == "__main__":<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_cdr.py b/lib/python/asterisk/self_test/test_cdr.py<br>new file mode 100755<br>index 0000000..251530b<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_cdr.py<br>@@ -0,0 +1,44 @@<br>+#!/usr/bin/env python<br>+"""Asterisk call detail record unit tests<br>+<br>+This module implements an Asterisk CDR parser.<br>+<br>+Copyright (C) 2010, Digium, Inc.<br>+Terry Wilson<twilson@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import main<br>+import unittest<br>+from asterisk.cdr import AsteriskCSVCDR, AsteriskCSVCDRLine<br>+<br>+<br>+class AsteriskCSVCDRTests(unittest.TestCase):<br>+    """Unit tests for AsteriskCSVCDR"""<br>+<br>+    def test_cdr(self):<br>+        """Test the self_test/Master.csv record"""<br>+<br>+        cdr = AsteriskCSVCDR("lib/python/asterisk/self_test/Master.csv")<br>+        self.assertEqual(len(cdr), 2)<br>+        self.assertTrue(<br>+            AsteriskCSVCDRLine(duration=7, lastapp="hangup").match(<br>+                cdr[0],<br>+                exact=(True, True)))<br>+        self.assertTrue(cdr[0].match(<br>+            AsteriskCSVCDRLine(duration=7, lastapp="hangup"),<br>+            exact=(True, True)))<br>+<br>+        self.assertFalse(cdr[1].match(cdr[0], silent=True, exact=(True, True)))<br>+        self.assertFalse(cdr[0].match(cdr[1], silent=True, exact=(True, True)))<br>+        self.assertEqual(cdr[0].billsec, "7")<br>+<br>+        self.assertTrue(cdr.match(cdr))<br>+        cdr2 = AsteriskCSVCDR("lib/python/asterisk/self_test/Master2.csv")<br>+        self.assertFalse(cdr.match(cdr2))<br>+<br>+<br>+if __name__ == '__main__':<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_cel.py b/lib/python/asterisk/self_test/test_cel.py<br>new file mode 100755<br>index 0000000..3ea8aa9<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_cel.py<br>@@ -0,0 +1,47 @@<br>+#!/usr/bin/env python<br>+"""Asterisk call detail record unit tests<br>+<br>+This module implements an Asterisk CDR parser.<br>+<br>+Copyright (C) 2010, Digium, Inc.<br>+Terry Wilson<twilson@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import main<br>+import unittest<br>+from asterisk.cel import AsteriskCSVCEL, AsteriskCSVCELLine<br>+<br>+<br>+class AsteriskCSVCELTests(unittest.TestCase):<br>+    """Unit tests for AsteriskCSVCEL"""<br>+<br>+    def test_cel(self):<br>+        """Test CEL using self_test/CELMaster1.csv"""<br>+<br>+        cel = AsteriskCSVCEL("lib/python/asterisk/self_test/CELMaster1.csv")<br>+        self.assertEqual(len(cel), 16)<br>+        self.assertTrue(AsteriskCSVCELLine(<br>+            eventtype="LINKEDID_END",<br>+            channel="TinCan/string").match(cel[-1],<br>+                                           silent=True,<br>+                                           exact=(True, True)))<br>+        self.assertTrue(cel[-1].match(<br>+            AsteriskCSVCELLine(eventtype="LINKEDID_END",<br>+                               channel="TinCan/string"),<br>+            silent=True,<br>+            exact=(True, True)))<br>+<br>+        self.assertFalse(cel[1].match(cel[0], silent=True, exact=(True, True)))<br>+        self.assertFalse(cel[0].match(cel[1], silent=True, exact=(True, True)))<br>+        self.assertEqual(cel[-1].channel, "TinCan/string")<br>+<br>+        self.assertTrue(cel.match(cel))<br>+        cel2 = AsteriskCSVCEL("lib/python/asterisk/self_test/CELMaster2.csv")<br>+        self.assertFalse(cel.match(cel2))<br>+<br>+<br>+if __name__ == '__main__':<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_channel_test_condition.py b/lib/python/asterisk/self_test/test_channel_test_condition.py<br>new file mode 100755<br>index 0000000..e32d883<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_channel_test_condition.py<br>@@ -0,0 +1,144 @@<br>+#!/usr/bin/env python<br>+"""Tests for test condition for channels<br>+<br>+Copyright (C) 2011-2012, Digium, Inc.<br>+Matt Jordan <mjordan@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import AstMockOutput, main<br>+import unittest<br>+from asterisk.channel_test_condition import ChannelTestCondition<br>+<br>+<br>+class AstMockObjectInactive(AstMockOutput):<br>+    """mock cli output showing no active channels"""<br>+<br>+    def cli_exec(self, command):<br>+        """presume command is core show channels and generate output"""<br>+        output = ""<br>+        output += "Channel              Location             State   Application(Data)\n"<br>+        output += "0 active channels\n"<br>+        output += "0 active calls\n"<br>+        output += "2 calls processed\n"<br>+        output += "Asterisk ending (0).\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectSingle(AstMockOutput):<br>+    """mock cli output showing single active channel"""<br>+<br>+    def cli_exec(self, command):<br>+        """presume command is core show channels and generate output"""<br>+        output = ""<br>+        output += "Channel              Location             State   Application(Data)\n"<br>+        output += "Local/123@default-00 (None)               Down    ()\n"<br>+        output += "1 active channels\n"<br>+        output += "0 active calls\n"<br>+        output += "2 calls processed\n"<br>+        output += "Asterisk ending (0).\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectMultiple(AstMockOutput):<br>+    """mock cli output showing multiple active channels"""<br>+<br>+    def cli_exec(self, command):<br>+        """presume command is core show channels and generate output"""<br>+        output = ""<br>+        output += "Channel              Location             State   Application(Data)\n"<br>+        output += "PJSIP/123@default-00 (None)               Down    ()\n"<br>+        output += "Local/123@default-00 (None)               Down    ()\n"<br>+        output += "SIP/alice@default-00 (None)               Down    ()\n"<br>+        output += "3 active channels\n"<br>+        output += "0 active calls\n"<br>+        output += "2 calls processed\n"<br>+        output += "Asterisk ending (0).\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectLeaked(AstMockOutput):<br>+    """mock cli output showing leaked channel"""<br>+<br>+    def cli_exec(self, command):<br>+        """presume command is core show channels and generate output"""<br>+        output = ""<br>+        output += "Channel              Location             State   Application(Data)\n"<br>+        output += "Local/123@default-00 (None)               Down    ()\n"<br>+        output += "0 active channels\n"<br>+        output += "0 active calls\n"<br>+        output += "2 calls processed\n"<br>+        output += "Asterisk ending (0).\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class TestConfig(object):<br>+    """Fake TestConfig object for unittest"""<br>+<br>+    def __init__(self):<br>+        self.class_type_name = "bogus"<br>+        self.config = {}<br>+        self.enabled = True<br>+        self.pass_expected = True<br>+<br>+<br>+class ChannelTestConditionUnitTest(unittest.TestCase):<br>+    """Unit Tests for ChannelTestCondition"""<br>+<br>+    def test_evaluate_inactive(self):<br>+        """test inactive channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(AstMockObjectInactive())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_evaluate_multiple_fail(self):<br>+        """test multiple channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(AstMockObjectMultiple())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_evaluate_multiple_fail2(self):<br>+        """test multiple channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.allowed_channels = 2<br>+        obj.register_asterisk_instance(AstMockObjectMultiple())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_evaluate_multiple_pass(self):<br>+        """test multiple channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.allowed_channels = 3<br>+        obj.register_asterisk_instance(AstMockObjectMultiple())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_evaluate_single_fail(self):<br>+        """test single channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(AstMockObjectSingle())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_evaluate_single_pass(self):<br>+        """test single channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.allowed_channels = 1<br>+        obj.register_asterisk_instance(AstMockObjectSingle())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_evaluate_leaked(self):<br>+        """test leaked channel condition"""<br>+        obj = ChannelTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(AstMockObjectLeaked())<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+<br>+if __name__ == "__main__":<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_config.py b/lib/python/asterisk/self_test/test_config.py<br>new file mode 100755<br>index 0000000..cc88636<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_config.py<br>@@ -0,0 +1,83 @@<br>+#!/usr/bin/env python<br>+"""Asterisk Configuration File Handling Unit Tests.<br>+<br>+Copyright (C) 2010, Digium, Inc.<br>+Russell Bryant <russell@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import main<br>+import unittest<br>+from asterisk.config import ConfigFile<br>+<br>+<br>+class ConfigFileTests(unittest.TestCase):<br>+    """Unit tests for ConfigFile"""<br>+<br>+    def test_conf(self):<br>+        """Test parsing a blob of config data"""<br>+        test = \<br>+            "; stuff\n" \<br>+            "this line is invalid on purpose\n" \<br>+            "[this is] also invalid]\n" \<br>+            ";-- comment --;\n" \<br>+            ";--   \n" \<br>+            "[this is commented out]\n" \<br>+            "         --;\n" \<br>+            "[foo]\n" \<br>+            "a = b\n" \<br>+            "  b =   a  \n" \<br>+            "this line is invalid on purpose\n" \<br>+            ";moo\n" \<br>+            "c = d;asdadf;adfads;adsfasdf\n" \<br>+            "  [bar]   ;asdfasdf\n" \<br>+            "a-b=c-d\n" \<br>+            "xyz=x|y|z\n" \<br>+            "1234 => 4242,Example Mailbox,root@localhost,,var=val\n" \<br>+            "\n" \<br>+            "[template](!)\n" \<br>+            "foo=bar\n" \<br>+            "exten => _NXX.,n,Wait(1)\n" \<br>+            "astetcdir => /etc/asterisk\n"<br>+<br>+        conf = ConfigFile(filename=None, config_str=test)<br>+<br>+        self.assertEqual(len(conf.categories), 3)<br>+<br>+        self.assertEqual(conf.categories[0].name, "foo")<br>+        self.assertFalse(conf.categories[0].template)<br>+        self.assertEqual(len(conf.categories[0].options), 3)<br>+        self.assertEqual(conf.categories[0].options[0][0], "a")<br>+        self.assertEqual(conf.categories[0].options[0][1], "b")<br>+        self.assertEqual(conf.categories[0].options[1][0], "b")<br>+        self.assertEqual(conf.categories[0].options[1][1], "a")<br>+        self.assertEqual(conf.categories[0].options[2][0], "c")<br>+        self.assertEqual(conf.categories[0].options[2][1], "d")<br>+<br>+        self.assertEqual(conf.categories[1].name, "bar")<br>+        self.assertFalse(conf.categories[1].template)<br>+        self.assertEqual(len(conf.categories[1].options), 3)<br>+        self.assertEqual(conf.categories[1].options[0][0], "a-b")<br>+        self.assertEqual(conf.categories[1].options[0][1], "c-d")<br>+        self.assertEqual(conf.categories[1].options[1][0], "xyz")<br>+        self.assertEqual(conf.categories[1].options[1][1], "x|y|z")<br>+        self.assertEqual(conf.categories[1].options[2][0], "1234")<br>+        self.assertEqual(conf.categories[1].options[2][1],<br>+                         "4242,Example Mailbox,root@localhost,,var=val")<br>+<br>+        self.assertEqual(conf.categories[2].name, "template")<br>+        self.assertTrue(conf.categories[2].template)<br>+        self.assertEqual(len(conf.categories[2].options), 3)<br>+        self.assertEqual(conf.categories[2].options[0][0], "foo")<br>+        self.assertEqual(conf.categories[2].options[0][1], "bar")<br>+        self.assertEqual(conf.categories[2].options[1][0], "exten")<br>+        self.assertEqual(conf.categories[2].options[1][1],<br>+                         "_NXX.,n,Wait(1)")<br>+        self.assertEqual(conf.categories[2].options[2][0], "astetcdir")<br>+        self.assertEqual(conf.categories[2].options[2][1], "/etc/asterisk")<br>+<br>+<br>+if __name__ == "__main__":<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_lock_test_condition.py b/lib/python/asterisk/self_test/test_lock_test_condition.py<br>new file mode 100755<br>index 0000000..6cda06a<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_lock_test_condition.py<br>@@ -0,0 +1,193 @@<br>+#!/usr/bin/env python<br>+"""Held locks test condition unit tests<br>+<br>+Copyright (C) 2011-2012, Digium, Inc.<br>+Matt Jordan <mjordan@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import AstMockOutput, ReadTestFile, main<br>+import unittest<br>+from asterisk.lock_test_condition import LockSequence, LockObject, LockTestCondition<br>+<br>+<br>+class AstMockObjectPassed(AstMockOutput):<br>+    """A lock output that passed"""<br>+<br>+    def __init__(self):<br>+        """Constructor"""<br>+        self.host = "127.0.0.2"<br>+<br>+    def cli_exec(self, command):<br>+        """Fake out a CLI command execution"""<br>+        return self.MockDeferFile("locks-pass.txt")<br>+<br>+<br>+class AstMockObjectFailure(AstMockOutput):<br>+    """A lock object that failed"""<br>+<br>+    def cli_exec(self, command):<br>+        """Fake out a CLI command execution"""<br>+        return self.MockDeferFile("locks-fail.txt")<br>+<br>+<br>+class TestConfig(object):<br>+    """Fake TestConfig object"""<br>+<br>+    def __init__(self):<br>+        """ Values here don't matter much - we just need to have something """<br>+        self.class_type_name = "asterisk.LockTestCondition.LockTestCondition"<br>+        self.pass_expected = True<br>+        self.type = "Post"<br>+        self.related_condition = ""<br>+        self.config = {}<br>+        self.enabled = True<br>+<br>+<br>+class LockTestConditionUnitTest(unittest.TestCase):<br>+    """Unit tests for LockTestCondition"""<br>+<br>+    def test_evaluate_failed(self):<br>+        """Test a failed locking condition"""<br>+        ast = AstMockObjectFailure()<br>+        obj = LockTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_evaluate_pass(self):<br>+        """Test a passed locking condition"""<br>+        ast = AstMockObjectPassed()<br>+        obj = LockTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_evaluate_multiple(self):<br>+        """Test multiple results"""<br>+        ast1 = AstMockObjectPassed()<br>+        ast2 = AstMockObjectFailure()<br>+        obj = LockTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast1)<br>+        obj.register_asterisk_instance(ast2)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+<br>+class LockSequenceUnitTest(unittest.TestCase):<br>+    """Tests for parsing a lock sequence"""<br>+<br>+    def test_single_object_no_held_info(self):<br>+        """Test a lock sequence with no waiting lock"""<br>+<br>+        obj = LockSequence()<br>+        obj.parse_lock_sequence(ReadTestFile("locks-single-object-no-held-info.txt"))<br>+<br>+    def test_large_multiple_object(self):<br>+        """Test with a waiting lock"""<br>+<br>+        obj = LockSequence()<br>+        obj.parse_lock_sequence(ReadTestFile("locks-large-multiple-object.txt"))<br>+        self.assertEqual(obj.thread_id, "0x449ec940")<br>+        self.assertEqual(obj.thread_name, "netconsole")<br>+        self.assertEqual(obj.thread_line, 1351)<br>+        self.assertEqual(obj.thread_file, "asterisk.c")<br>+        self.assertEqual(obj.thread_func, "listener")<br>+        self.assertTrue(len(obj.locks) == 1)<br>+        self.assertEqual(obj.locks[0].locked_file, "astobj2.c")<br>+        self.assertEqual(obj.locks[0].locked_line, 657)<br>+        self.assertEqual(obj.locks[0].locked_func, "internal_ao2_callback")<br>+<br>+    def test_single_object(self):<br>+        """Test a lock held somewhere else"""<br>+<br>+        obj = LockSequence()<br>+        obj.parse_lock_sequence(ReadTestFile("locks-single-object.txt"))<br>+        self.assertEqual(obj.thread_id, "0x402c6940")<br>+        self.assertEqual(obj.thread_name, "do_monitor")<br>+        self.assertEqual(obj.thread_line, 25114)<br>+        self.assertEqual(obj.thread_file, "chan_sip.c")<br>+        self.assertEqual(obj.thread_func, "restart_monitor")<br>+        self.assertTrue(len(obj.locks) == 1)<br>+        self.assertEqual(obj.locks[0].locked_file, "channel.c")<br>+        self.assertEqual(obj.locks[0].locked_line, 4304)<br>+        self.assertEqual(obj.locks[0].locked_func, "ast_indicate_data")<br>+        self.assertEqual(obj.locks[0].id, 0)<br>+        self.assertEqual(obj.locks[0].type, "MUTEX")<br>+        self.assertEqual(obj.locks[0].file, "chan_sip.c")<br>+        self.assertEqual(obj.locks[0].line_number, 24629)<br>+        self.assertEqual(obj.locks[0].func, "handle_request_do")<br>+        self.assertEqual(obj.locks[0].name, "&netlock")<br>+        self.assertEqual(obj.locks[0].addr, "0x2aaabe671a40")<br>+        self.assertEqual(obj.locks[0].lock_count, 1)<br>+        self.assertTrue(obj.locks[0].held)<br>+        self.assertTrue(len(obj.locks[0].backtrace) == 3)<br>+<br>+    def test_multiple_objects_no_backtrace(self):<br>+        """Test multiple locks with no backtrace"""<br>+<br>+        obj = LockSequence()<br>+        obj.parse_lock_sequence(ReadTestFile("locks-multiple-objects-no-backtrace.txt"))<br>+        self.assertEqual(obj.thread_id, "0x402c6940")<br>+        self.assertEqual(obj.thread_name, "do_monitor")<br>+        self.assertEqual(obj.thread_line, 25114)<br>+        self.assertEqual(obj.thread_file, "chan_sip.c")<br>+        self.assertEqual(obj.thread_func, "restart_monitor")<br>+        self.assertTrue(len(obj.locks) == 3)<br>+        self.assertEqual(obj.locks[2].locked_file, "channel.c")<br>+        self.assertEqual(obj.locks[2].locked_line, 4304)<br>+        self.assertEqual(obj.locks[2].locked_func, "ast_indicate_data")<br>+        self.assertEqual(obj.locks[0].id, 0)<br>+        self.assertEqual(obj.locks[0].type, "MUTEX")<br>+        self.assertEqual(obj.locks[0].file, "chan_sip.c")<br>+        self.assertEqual(obj.locks[0].line_number, 24629)<br>+        self.assertEqual(obj.locks[0].func, "handle_request_do")<br>+        self.assertEqual(obj.locks[0].name, "&netlock")<br>+        self.assertEqual(obj.locks[0].addr, "0x2aaabe671a40")<br>+        self.assertEqual(obj.locks[0].lock_count, 1)<br>+        self.assertTrue(obj.locks[0].held)<br>+        self.assertTrue(len(obj.locks[0].backtrace) == 0)<br>+<br>+<br>+class LockObjectUnitTest(unittest.TestCase):<br>+    """Unit tests for LockObject"""<br>+<br>+    def test_no_backtrace(self):<br>+        """Test creating a lock object with no thread backtrace"""<br>+<br>+        lock_line = "=== ---> Waiting for Lock #0 (sig_ss7.c): " + \<br>+                    "MUTEX 636 ss7_linkset &linkset->lock 0x2aaab8a6b588 (1)"<br>+        obj = LockObject()<br>+        obj.parse_lock_information(lock_line)<br>+        self.assertEqual(obj.id, 0)<br>+        self.assertEqual(obj.type, "MUTEX")<br>+        self.assertEqual(obj.file, "sig_ss7.c")<br>+        self.assertEqual(obj.line_number, 636)<br>+        self.assertEqual(obj.func, "ss7_linkset")<br>+        self.assertEqual(obj.name, "&linkset->lock")<br>+        self.assertEqual(obj.addr, "0x2aaab8a6b588")<br>+        self.assertEqual(obj.lock_count, 1)<br>+        self.assertFalse(obj.held)<br>+        self.assertTrue(len(obj.backtrace) == 0)<br>+<br>+    def test_with_backtrace(self):<br>+        """Test creating a lock object with a thread backtrace"""<br>+<br>+        obj = LockObject()<br>+        obj.parse_lock_information(ReadTestFile("locks-backtrace.txt"))<br>+        self.assertEqual(obj.id, 1)<br>+        self.assertEqual(obj.type, "MUTEX")<br>+        self.assertEqual(obj.file, "astobj2.c")<br>+        self.assertEqual(obj.line_number, 657)<br>+        self.assertEqual(obj.func, "internal_ao2_callback")<br>+        self.assertEqual(obj.name, "c")<br>+        self.assertEqual(obj.addr, "0x2aaaac491f50")<br>+        self.assertEqual(obj.lock_count, 1)<br>+        self.assertTrue(obj.held)<br>+        self.assertTrue(len(obj.backtrace) == 19)<br>+<br>+<br>+if __name__ == "__main__":<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test2_matcher.py b/lib/python/asterisk/self_test/test_matcher.py<br>similarity index 100%<br>rename from lib/python/asterisk/self_test/test2_matcher.py<br>rename to lib/python/asterisk/self_test/test_matcher.py<br>diff --git a/lib/python/asterisk/self_test/test_sip_dialog_test_condition.py.txt b/lib/python/asterisk/self_test/test_sip_dialog_test_condition.py.txt<br>new file mode 100644<br>index 0000000..717e183<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_sip_dialog_test_condition.py.txt<br>@@ -0,0 +1,426 @@<br>+#!/usr/bin/env python<br>+"""Test condition for verifying SIP dialogs unit tests<br>+<br>+Copyright (C) 2011-2012, Digium, Inc.<br>+Matt Jordan <mjordan@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import AstMockOutput, main<br>+import unittest<br>+from asterisk.sip_dialog_test_condition import SipDialogPreTestCondition, \<br>+    SipDialogPostTestCondition<br>+<br>+<br>+class TestConfig(object):<br>+    """Mock TestConfig object"""<br>+<br>+    def __init__(self):<br>+        """Constructor<br>+<br>+        Values here don't matter much - we just need to have something"""<br>+        self.type_name = ("asterisk.sip_dialog_test_condition." +<br>+                          "SipDialogPostTestCondition")<br>+        self.pass_expected = True<br>+        self.type = "Post"<br>+        self.related_condition = ""<br>+        self.class_type_name = "sip_dialog_test_condition"<br>+        self.config = {}<br>+        self.enabled = True<br>+<br>+<br>+class TestConfigWithHistory(TestConfig):<br>+    """Mock TestConfig object with history requirements"""<br>+<br>+    def __init__(self):<br>+        """Constructor"""<br>+        super(TestConfigWithHistory, self).__init__()<br>+<br>+        self.config['history_requirements'] = []<br>+        self.config['history_requirements'].append('Hangup')<br>+        self.config['history_requirements'].append('NewChan')<br>+<br>+<br>+class AstMockObjectPostTestNoDestructionFail(AstMockOutput):<br>+    """Mock out CLI execution from Asterisk instance<br>+<br>+    This mock object makes it appear as if a dialog failed to be destroyed<br>+    """<br>+    def __init__(self):<br>+        """Constructor"""<br>+        self.host = "127.0.0.6"<br>+<br>+    def cli_exec(self, command):<br>+        """Mock CLI execution/response"""<br>+<br>+        output = ""<br>+        if command == "sip show objects":<br>+            output = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Peer objects by IP =-\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Registry objects: 0 =-\n\n"<br>+            output += "-= Dialog objects:\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. SchedDestroy    32000 ms\n"<br>+            output += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+            output += "13. Hangup          Cause Normal Clearing\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+            output += "12. Hangup          Cause Normal Clearing\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectPostTestNoHangupFail(AstMockOutput):<br>+    """Mock out CLI execution from Asterisk instance<br>+<br>+    This mock object makes it appear as if a channel failed to hangup<br>+    """<br>+<br>+    def __init__(self):<br>+        """Constructor"""<br>+        self.host = "127.0.0.5"<br>+<br>+    def cli_exec(self, command):<br>+        """Mock CLI execution/response"""<br>+<br>+        output = ""<br>+        if command == "sip show objects":<br>+            output = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Peer objects by IP =-\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Registry objects: 0 =-\n\n"<br>+            output += "-= Dialog objects:\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. SchedDestroy    32000 ms\n"<br>+            output += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. SchedDestroy    32000 ms\n"<br>+            output += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+            output += "13. Hangup          Cause Normal Clearing\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectPostTestNoDialogsPass(AstMockOutput):<br>+    """Mock out CLI execution from Asterisk instance<br>+<br>+    This mock object makes it appear as if there were no dialogs, which is okay<br>+    """<br>+    def __init__(self):<br>+        """Constructor"""<br>+        self.host = "127.0.0.4"<br>+<br>+    def cli_exec(self, command):<br>+        """Mock CLI execution/response"""<br>+<br>+        output = ""<br>+        if command == "sip show objects":<br>+            output = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Peer objects by IP =-\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Registry objects: 0 =-\n\n"<br>+            output += "-= Dialog objects:\n\n"<br>+<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectPostTestPass(AstMockOutput):<br>+    """Mock out CLI execution from Asterisk instance<br>+<br>+    This mock object provides two dialogs with acceptable history<br>+    """<br>+    def __init__(self):<br>+        """Constructor"""<br>+        self.host = "127.0.0.3"<br>+<br>+    def cli_exec(self, command):<br>+        """Mock CLI execution/response"""<br>+<br>+        output = ""<br>+        if command == "sip show objects":<br>+            output = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Peer objects by IP =-\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Registry objects: 0 =-\n\n"<br>+            output += "-= Dialog objects:\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. SchedDestroy    32000 ms\n"<br>+            output += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+            output += "13. Hangup          Cause Normal Clearing\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. SchedDestroy    32000 ms\n"<br>+            output += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+            output += "13. Hangup          Cause Normal Clearing\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectPreTestFail(AstMockOutput):<br>+    """Mock out CLI execution from Asterisk instance<br>+<br>+    This mock object provides history during a pre-test call, which is wrong<br>+    """<br>+    def __init__(self):<br>+        """Constructor"""<br>+        self.host = "127.0.0.2"<br>+<br>+    def cli_exec(self, command):<br>+        """Mock CLI execution/response"""<br>+<br>+        output = ""<br>+        if command == "sip show objects":<br>+            output = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Peer objects by IP =-\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Registry objects: 0 =-\n\n"<br>+            output += "-= Dialog objects:\n\n"<br>+            output += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>+            output += "type: dialog\n"<br>+            output += "objflags: 0\n"<br>+            output += "refcount: 2\n\n"<br>+        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>+            output = "* SIP Call\n"<br>+            output += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>+            output += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>+            output += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>+            output += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>+            output += "5. TxReq           ACK / 102 ACK - ACK\n"<br>+            output += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>+            output += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>+            output += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>+            output += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>+            output += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>+            output += "11. SchedDestroy    32000 ms\n"<br>+            output += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>+            output += "13. Hangup          Cause Normal Clearing\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+class AstMockObjectPreTestPass(AstMockOutput):<br>+    """Mock out CLI execution from Asterisk instance<br>+<br>+    This mock object provides no history during a pre-test call, which is the<br>+    expected state<br>+    """<br>+    def cli_exec(self, command):<br>+        """Mock CLI execution/response"""<br>+<br>+        output = ""<br>+        if command == "sip show objects":<br>+            output = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Peer objects by IP =-\n\n"<br>+            output += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>+            output += "-= Registry objects: 0 =-\n\n"<br>+            output += "-= Dialog objects:\n"<br>+        elif command == "sip show history":<br>+            output = "\n"<br>+        return self.MockDefer(output)<br>+<br>+<br>+@unittest.skip("sip_dialog_test_condition.py is broken")<br>+class SipDialogTestConditionUnitTest(unittest.TestCase):<br>+    """Unit tests for SipDialogTestCondition objects"""<br>+<br>+    def test_pre_test_pass(self):<br>+        """Verify that acceptable pre-test output passes"""<br>+        ast = AstMockObjectPreTestPass()<br>+        obj = SipDialogPreTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_pre_test_fail(self):<br>+        """Verify that unacceptable pre-test output fails"""<br>+        ast = AstMockObjectPreTestFail()<br>+        obj = SipDialogPreTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_pre_test_fail_multi_asterisk(self):<br>+        """Verify that pre-test output from multiple sources fails when one<br>+        of those sources is bad"""<br>+        ast1 = AstMockObjectPreTestFail()<br>+        ast2 = AstMockObjectPreTestPass()<br>+        obj = SipDialogPreTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast1)<br>+        obj.register_asterisk_instance(ast2)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_post_test_pass(self):<br>+        """Verify nominal post-test output"""<br>+        ast = AstMockObjectPostTestPass()<br>+        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_post_test_no_dialog_pass(self):<br>+        """Verify nominal post-test output with no dialogs passes"""<br>+        ast = AstMockObjectPostTestNoDialogsPass()<br>+        obj = SipDialogPostTestCondition(TestConfig())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Passed')<br>+<br>+    def test_post_test_no_hangup_fail(self):<br>+        """Verify no hangup detection is caught and results in a failure"""<br>+        ast = AstMockObjectPostTestNoHangupFail()<br>+        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_post_test_no_destruction_fail(self):<br>+        """Verify no destruction is caught and results in a failure"""<br>+        ast = AstMockObjectPostTestNoDestructionFail()<br>+        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>+        obj.register_asterisk_instance(ast)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+    def test_post_test_multi_asterisk_fail(self):<br>+        """Test multiple instances of Asterisk where a single failure causes<br>+        a failure in the overall result"""<br>+        ast1 = AstMockObjectPostTestNoHangupFail()<br>+        ast2 = AstMockObjectPostTestNoDestructionFail()<br>+        ast3 = AstMockObjectPostTestNoDialogsPass()<br>+        ast4 = AstMockObjectPostTestPass()<br>+        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>+        obj.register_asterisk_instance(ast1)<br>+        obj.register_asterisk_instance(ast2)<br>+        obj.register_asterisk_instance(ast3)<br>+        obj.register_asterisk_instance(ast4)<br>+        obj.evaluate()<br>+        self.assertEqual(obj.get_status(), 'Failed')<br>+<br>+<br>+if __name__ == "__main__":<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_sippversion.py b/lib/python/asterisk/self_test/test_sippversion.py<br>new file mode 100755<br>index 0000000..8c3d933<br>--- /dev/null<br>+++ b/lib/python/asterisk/self_test/test_sippversion.py<br>@@ -0,0 +1,221 @@<br>+#!/usr/bin/env python<br>+"""SIPp Version String Handling Tests<br>+<br>+Copyright (C) 2010, Digium, Inc.<br>+Paul Belanger <pabelanger@digium.com><br>+<br>+This program is free software, distributed under the terms of<br>+the GNU General Public License Version 2.<br>+"""<br>+<br>+from harness_shared import main<br>+import unittest<br>+from asterisk.sippversion import SIPpVersion<br>+<br>+<br>+class SIPpVersionTests(unittest.TestCase):<br>+    def test_version(self):<br>+        v = SIPpVersion("v3.2", None)<br>+        self.assertEqual(str(v), "v3.2")<br>+        self.assertEqual(v.concept, "v3")<br>+        self.assertEqual(v.major, "2")<br>+        self.assertEqual(v.minor, None)<br>+        self.assertFalse(v.tls)<br>+        self.assertFalse(v.pcap)<br>+<br>+    def test_version2(self):<br>+        v = SIPpVersion("v2.0.1", None)<br>+        self.assertEqual(str(v), "v2.0.1")<br>+        self.assertEqual(v.concept, "v2")<br>+        self.assertEqual(v.major, "0")<br>+        self.assertEqual(v.minor, "1")<br>+        self.assertFalse(v.tls)<br>+        self.assertFalse(v.pcap)<br>+<br>+    def test_version3(self):<br>+        v = SIPpVersion("v3.1", "TLS")<br>+        self.assertEqual(str(v), "v3.1-TLS")<br>+        self.assertEqual(v.concept, "v3")<br>+        self.assertEqual(v.major, "1")<br>+        self.assertEqual(v.minor, None)<br>+        self.assertTrue(v.tls)<br>+        self.assertFalse(v.pcap)<br>+<br>+    def test_version4(self):<br>+        v = SIPpVersion("v2.0.1", "TLS-PCAP")<br>+        self.assertEqual(str(v), "v2.0.1-TLS-PCAP")<br>+        self.assertEqual(v.concept, "v2")<br>+        self.assertEqual(v.major, "0")<br>+        self.assertEqual(v.minor, "1")<br>+        self.assertTrue(v.tls)<br>+        self.assertTrue(v.pcap)<br>+<br>+    def test_version5(self):<br>+        v = SIPpVersion("v3.2", "PCAP")<br>+        self.assertEqual(str(v), "v3.2-PCAP")<br>+        self.assertEqual(v.concept, "v3")<br>+        self.assertEqual(v.major, "2")<br>+        self.assertEqual(v.minor, None)<br>+        self.assertFalse(v.tls)<br>+        self.assertTrue(v.pcap)<br>+<br>+    def test_version6(self):<br>+        v = SIPpVersion(None, "PCAP")<br>+        self.assertEqual(str(v), "PCAP")<br>+        self.assertEqual(v.concept, None)<br>+        self.assertEqual(v.major, None)<br>+        self.assertEqual(v.minor, None)<br>+        self.assertFalse(v.tls)<br>+        self.assertTrue(v.pcap)<br>+<br>+    def test_version7(self):<br>+        v = SIPpVersion(None, "TLS")<br>+        self.assertEqual(str(v), "TLS")<br>+        self.assertEqual(v.concept, None)<br>+        self.assertEqual(v.major, None)<br>+        self.assertEqual(v.minor, None)<br>+        self.assertTrue(v.tls)<br>+        self.assertFalse(v.pcap)<br>+<br>+    def test_version8(self):<br>+        v = SIPpVersion(None, "TLS-PCAP")<br>+        self.assertEqual(str(v), "TLS-PCAP")<br>+        self.assertEqual(v.concept, None)<br>+        self.assertEqual(v.major, None)<br>+        self.assertEqual(v.minor, None)<br>+        self.assertTrue(v.tls)<br>+        self.assertTrue(v.pcap)<br>+<br>+    def test_cmp(self):<br>+        v1 = SIPpVersion("v3.2", None)<br>+        v2 = SIPpVersion("v3.1", None)<br>+        self.assertTrue(v1 > v2)<br>+<br>+    def test_cmp2(self):<br>+        v1 = SIPpVersion("v2.0.1", None)<br>+        v2 = SIPpVersion("v3.1", None)<br>+        self.assertTrue(v1 < v2)<br>+<br>+    def test_cmp3(self):<br>+        v1 = SIPpVersion("v3.1", None)<br>+        v2 = SIPpVersion("v3.1", None)<br>+        self.assertTrue(v1 == v2)<br>+<br>+    def test_cmp4(self):<br>+        v1 = SIPpVersion("v3.1", None)<br>+        v2 = SIPpVersion("v3.1", None)<br>+        self.assertFalse(v1 != v2)<br>+<br>+    def test_cmp5(self):<br>+        v1 = SIPpVersion("v3.1", "TLS")<br>+        v2 = SIPpVersion("v3.1", "TLS")<br>+        self.assertTrue(v1 == v2)<br>+<br>+    def test_cmp6(self):<br>+        v1 = SIPpVersion(None, "TLS")<br>+        v2 = SIPpVersion(None, "TLS")<br>+        self.assertTrue(v1 == v2)<br>+<br>+    def test_cmp7(self):<br>+        v1 = SIPpVersion("v2.0.1", None)<br>+        v2 = SIPpVersion("v2.0.1", None)<br>+        self.assertTrue(v1 == v2)<br>+<br>+    def test_cmp8(self):<br>+        v1 = SIPpVersion("v3.2", "TLS")<br>+        v2 = SIPpVersion("v3.2", "PCAP")<br>+        self.assertTrue(v1 != v2)<br>+<br>+    def test_cmp9(self):<br>+        v1 = SIPpVersion(None, "TLS")<br>+        v2 = SIPpVersion(None, "PCAP")<br>+        self.assertTrue(v1 != v2)<br>+<br>+    def test_cmp10(self):<br>+        v1 = SIPpVersion("v3.2", "TLS")<br>+        v2 = SIPpVersion("v3.2", "PCAP")<br>+        self.assertFalse(v1 == v2)<br>+<br>+    def test_cmp11(self):<br>+        v1 = SIPpVersion(None, "TLS")<br>+        v2 = SIPpVersion(None, "PCAP")<br>+        self.assertFalse(v1 == v2)<br>+<br>+    def test_cmp12(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion("v3.2", "TLS")<br>+        self.assertTrue(v1 >= v2)<br>+<br>+    def test_cmp13(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion(None, "TLS")<br>+        self.assertTrue(v1 >= v2)<br>+<br>+    def test_cmp14(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion("v3.2", "PCAP")<br>+        self.assertTrue(v1 >= v2)<br>+<br>+    def test_cmp15(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion(None, "PCAP")<br>+        self.assertTrue(v1 >= v2)<br>+<br>+    def test_cmp16(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion("v3.2", "TLS")<br>+        self.assertTrue(v1 != v2)<br>+<br>+    def test_cmp17(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion(None, "TLS")<br>+        self.assertTrue(v1 != v2)<br>+<br>+    def test_cmp18(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion("v3.2", "TLS")<br>+        self.assertFalse(v1 == v2)<br>+<br>+    def test_cmp19(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion(None, "TLS")<br>+        self.assertFalse(v1 == v2)<br>+<br>+    def test_cmp20(self):<br>+        v1 = SIPpVersion("v3.1", "PCAP")<br>+        v2 = SIPpVersion("v3.0", "TLS")<br>+        self.assertTrue(v1 > v2)<br>+<br>+    def test_cmp21(self):<br>+        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>+        v2 = SIPpVersion("v2.0.1", "PCAP")<br>+        self.assertTrue(v1 >= v2)<br>+<br>+    def test_cmp22(self):<br>+        v1 = SIPpVersion("v3.1", "TLS")<br>+        v2 = SIPpVersion("v3.1", "PCAP")<br>+        self.assertFalse(v1 > v2)<br>+<br>+    def test_cmp23(self):<br>+        v1 = SIPpVersion("v3.1", "TLS")<br>+        v2 = SIPpVersion("v3.1", "PCAP")<br>+        self.assertFalse(v1 < v2)<br>+<br>+    def test_cmp24(self):<br>+        v1 = SIPpVersion("v3.2", "TLS")<br>+        v2 = SIPpVersion("v3.1", "TLS-PCAP")<br>+        self.assertTrue(v1 >= v2)<br>+<br>+    def test_cmp25(self):<br>+        v1 = SIPpVersion("v3.1", "TLS-PCAP")<br>+        v2 = SIPpVersion("v3.2", "PCAP")<br>+        self.assertTrue(v1 <= v2)<br>+<br>+    def test_cmp26(self):<br>+        v1 = SIPpVersion("v2.0.1", "TLS-PCAP")<br>+        v2 = SIPpVersion("v3.0", "TLS")<br>+        self.assertFalse(v1 >= v2)<br>+<br>+<br>+if __name__ == "__main__":<br>+    main()<br>diff --git a/lib/python/asterisk/self_test/test_utils_socket.py b/lib/python/asterisk/self_test/test_utils_socket.py<br>index 3a0c33d..34672c6 100755<br>--- a/lib/python/asterisk/self_test/test_utils_socket.py<br>+++ b/lib/python/asterisk/self_test/test_utils_socket.py<br>@@ -10,17 +10,10 @@<br> the GNU General Public License Version 2.<br> """<br> <br>-import logging<br>-import sys<br>+from harness_shared import main<br> import unittest<br>-<br> from socket import SOCK_STREAM, SOCK_DGRAM, AF_INET, AF_INET6<br>-<br>-sys.path.append('lib/python')  # noqa<br> from asterisk.utils_socket import Ports, PortError, get_available_port, MIN_PORT<br>-<br>-<br>-LOGGER = logging.getLogger(__name__)<br> <br> <br> class PortTests(unittest.TestCase):<br>@@ -169,7 +162,4 @@<br> <br> if __name__ == "__main__":<br>     """Run the unit tests"""<br>-<br>-    logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,<br>-                        format="%(module)s:%(lineno)d - %(message)s")<br>-    unittest.main()<br>+    main()<br>diff --git a/lib/python/asterisk/sip_channel_test_condition.py b/lib/python/asterisk/sip_channel_test_condition.py<br>index 150d90f..0f265f0 100644<br>--- a/lib/python/asterisk/sip_channel_test_condition.py<br>+++ b/lib/python/asterisk/sip_channel_test_condition.py<br>@@ -10,7 +10,7 @@<br> """<br> <br> from twisted.internet import defer<br>-from test_conditions import TestCondition<br>+from .test_conditions import TestCondition<br> <br> <br> class SipChannelTestCondition(TestCondition):<br>diff --git a/lib/python/asterisk/sip_dialog_test_condition.py b/lib/python/asterisk/sip_dialog_test_condition.py<br>index 4c75710..619a388 100644<br>--- a/lib/python/asterisk/sip_dialog_test_condition.py<br>+++ b/lib/python/asterisk/sip_dialog_test_condition.py<br>@@ -10,9 +10,8 @@<br> <br> import logging<br> import logging.config<br>-import unittest<br> <br>-from test_conditions import TestCondition<br>+from .test_conditions import TestCondition<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>@@ -227,422 +226,3 @@<br>         self._counter = -1<br>         __get_dialogs()<br>         return self._finished_deferred<br>-<br>-<br>-class TestConfig(object):<br>-    """Mock TestConfig object"""<br>-<br>-    def __init__(self):<br>-        """Constructor<br>-<br>-        Values here don't matter much - we just need to have something"""<br>-        self.type_name = ("asterisk.sip_dialog_test_condition." +<br>-                          "SipDialogPostTestCondition")<br>-        self.pass_expected = True<br>-        self.type = "Post"<br>-        self.related_condition = ""<br>-        self.config = {}<br>-<br>-<br>-class TestConfigWithHistory(TestConfig):<br>-    """Mock TestConfig object with history requirements"""<br>-<br>-    def __init__(self):<br>-        """Constructor"""<br>-        super(TestConfigWithHistory, self).__init__()<br>-<br>-        self.config['history_requirements'] = []<br>-        self.config['history_requirements'].append('Hangup')<br>-        self.config['history_requirements'].append('NewChan')<br>-<br>-<br>-class AstMockObjectPostTestNoDestructionFail(object):<br>-    """Mock out CLI execution from Asterisk instance<br>-<br>-    This mock object makes it appear as if a dialog failed to be destroyed<br>-    """<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.6"<br>-<br>-    def cli_exec(self, command):<br>-        """Mock CLI execution/response"""<br>-<br>-        ret_string = ""<br>-        if command == "sip show objects":<br>-            ret_string = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Peer objects by IP =-\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Registry objects: 0 =-\n\n"<br>-            ret_string += "-= Dialog objects:\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. SchedDestroy    32000 ms\n"<br>-            ret_string += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-            ret_string += "13. Hangup          Cause Normal Clearing\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-            ret_string += "12. Hangup          Cause Normal Clearing\n"<br>-        return ret_string<br>-<br>-<br>-class AstMockObjectPostTestNoHangupFail(object):<br>-    """Mock out CLI execution from Asterisk instance<br>-<br>-    This mock object makes it appear as if a channel failed to hangup<br>-    """<br>-<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.5"<br>-<br>-    def cli_exec(self, command):<br>-        """Mock CLI execution/response"""<br>-<br>-        ret_string = ""<br>-        if command == "sip show objects":<br>-            ret_string = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Peer objects by IP =-\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Registry objects: 0 =-\n\n"<br>-            ret_string += "-= Dialog objects:\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. SchedDestroy    32000 ms\n"<br>-            ret_string += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. SchedDestroy    32000 ms\n"<br>-            ret_string += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-            ret_string += "13. Hangup          Cause Normal Clearing\n"<br>-        return ret_string<br>-<br>-<br>-class AstMockObjectPostTestNoDialogsPass(object):<br>-    """Mock out CLI execution from Asterisk instance<br>-<br>-    This mock object makes it appear as if there were no dialogs, which is okay<br>-    """<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.4"<br>-<br>-    def cli_exec(self, command):<br>-        """Mock CLI execution/response"""<br>-<br>-        ret_string = ""<br>-        if command == "sip show objects":<br>-            ret_string = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Peer objects by IP =-\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Registry objects: 0 =-\n\n"<br>-            ret_string += "-= Dialog objects:\n\n"<br>-<br>-        return ret_string<br>-<br>-<br>-class AstMockObjectPostTestPass(object):<br>-    """Mock out CLI execution from Asterisk instance<br>-<br>-    This mock object provides two dialogs with acceptable history<br>-    """<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.3"<br>-<br>-    def cli_exec(self, command):<br>-        """Mock CLI execution/response"""<br>-<br>-        ret_string = ""<br>-        if command == "sip show objects":<br>-            ret_string = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Peer objects by IP =-\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Registry objects: 0 =-\n\n"<br>-            ret_string += "-= Dialog objects:\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. SchedDestroy    32000 ms\n"<br>-            ret_string += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-            ret_string += "13. Hangup          Cause Normal Clearing\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c5@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. SchedDestroy    32000 ms\n"<br>-            ret_string += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-            ret_string += "13. Hangup          Cause Normal Clearing\n"<br>-        return ret_string<br>-<br>-<br>-class AstMockObjectPreTestFail(object):<br>-    """Mock out CLI execution from Asterisk instance<br>-<br>-    This mock object provides history during a pre-test call, which is wrong<br>-    """<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.2"<br>-<br>-    def cli_exec(self, command):<br>-        """Mock CLI execution/response"""<br>-<br>-        ret_string = ""<br>-        if command == "sip show objects":<br>-            ret_string = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Peer objects by IP =-\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Registry objects: 0 =-\n\n"<br>-            ret_string += "-= Dialog objects:\n\n"<br>-            ret_string += "name: 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060\n"<br>-            ret_string += "type: dialog\n"<br>-            ret_string += "objflags: 0\n"<br>-            ret_string += "refcount: 2\n\n"<br>-        elif command == "sip show history 2ec048aa4ed1239664f6408f0c5044c4@127.0.0.2:5060":<br>-            ret_string = "* SIP Call\n"<br>-            ret_string += "1. NewChan         Channel SIP/ast1-00000002 - from 2ec048aa4ed1239664f6408f0c5044\n"<br>-            ret_string += "2. TxReqRel        INVITE / 102 INVITE - INVITE\n"<br>-            ret_string += "3. Rx              SIP/2.0 / 102 INVITE / 100 Trying\n"<br>-            ret_string += "4. Rx              SIP/2.0 / 102 INVITE / 200 OK\n"<br>-            ret_string += "5. TxReq           ACK / 102 ACK - ACK\n"<br>-            ret_string += "6. Rx              BYE / 102 BYE / sip:ast2@127.0.0.2:5060\n"<br>-            ret_string += "7. RTCPaudio       Quality:ssrc=28249381;themssrc=485141946;lp=0;rxjitter=0.000029\n"<br>-            ret_string += "8. RTCPaudioJitter Quality:minrxjitter=0.000000;maxrxjitter=0.000000;avgrxjitter=0\n"<br>-            ret_string += "9. RTCPaudioLoss   Quality:minrxlost=0.000000;maxrxlost=0.000000;avgrxlost=0.00000\n"<br>-            ret_string += "10. RTCPaudioRTT    Quality:minrtt=0.000000;maxrtt=0.000000;avgrtt=0.000000;stdevrt\n"<br>-            ret_string += "11. SchedDestroy    32000 ms\n"<br>-            ret_string += "12. TxResp          SIP/2.0 / 102 BYE - 200 OK\n"<br>-            ret_string += "13. Hangup          Cause Normal Clearing\n"<br>-        return ret_string<br>-<br>-<br>-class AstMockObjectPreTestPass(object):<br>-    """Mock out CLI execution from Asterisk instance<br>-<br>-    This mock object provides no history during a pre-test call, which is the<br>-    expected state<br>-    """<br>-    def __init__(self):<br>-        """Constructor"""<br>-        self.host = "127.0.0.1"<br>-<br>-    def cli_exec(self, command):<br>-        """Mock CLI execution/response"""<br>-<br>-        ret_string = ""<br>-        if command == "sip show objects":<br>-            ret_string = "-= Peer objects: 4 static, 0 realtime, 0 autocreate =-\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7001\ntype: peer\nobjflags: 0\nrefcount: 1\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Peer objects by IP =-\n\n"<br>-            ret_string += "name: zoiper_01\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: audio\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "name: 7002\ntype: peer\nobjflags: 0\nrefcount: 3\n\n"<br>-            ret_string += "-= Registry objects: 0 =-\n\n"<br>-            ret_string += "-= Dialog objects:\n"<br>-        elif command == "sip show history":<br>-            return "\n"<br>-        return ret_string<br>-<br>-<br>-class SipDialogTestConditionUnitTest(unittest.TestCase):<br>-    """Unit tests for SipDialogTestCondition objects"""<br>-<br>-    def test_pre_test_pass(self):<br>-        """Verify that acceptable pre-test output passes"""<br>-        ast = AstMockObjectPreTestPass()<br>-        obj = SipDialogPreTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_pre_test_fail(self):<br>-        """Verify that unacceptable pre-test output fails"""<br>-        ast = AstMockObjectPreTestFail()<br>-        obj = SipDialogPreTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_pre_test_fail_multi_asterisk(self):<br>-        """Verify that pre-test output from multiple sources fails when one<br>-        of those sources is bad"""<br>-        ast1 = AstMockObjectPreTestFail()<br>-        ast2 = AstMockObjectPreTestPass()<br>-        obj = SipDialogPreTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast1)<br>-        obj.register_asterisk_instance(ast2)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_post_test_pass(self):<br>-        """Verify nominal post-test output"""<br>-        ast = AstMockObjectPostTestPass()<br>-        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_post_test_no_dialog_pass(self):<br>-        """Verify nominal post-test output with no dialogs passes"""<br>-        ast = AstMockObjectPostTestNoDialogsPass()<br>-        obj = SipDialogPostTestCondition(TestConfig())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Passed')<br>-<br>-    def test_post_test_no_hangup_fail(self):<br>-        """Verify no hangup detection is caught and results in a failure"""<br>-        ast = AstMockObjectPostTestNoHangupFail()<br>-        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_post_test_no_destruction_fail(self):<br>-        """Verify no destruction is caught and results in a failure"""<br>-        ast = AstMockObjectPostTestNoDestructionFail()<br>-        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>-        obj.register_asterisk_instance(ast)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-    def test_post_test_multi_asterisk_fail(self):<br>-        """Test multiple instances of Asterisk where a single failure causes<br>-        a failure in the overall result"""<br>-        ast1 = AstMockObjectPostTestNoHangupFail()<br>-        ast2 = AstMockObjectPostTestNoDestructionFail()<br>-        ast3 = AstMockObjectPostTestNoDialogsPass()<br>-        ast4 = AstMockObjectPostTestPass()<br>-        obj = SipDialogPostTestCondition(TestConfigWithHistory())<br>-        obj.register_asterisk_instance(ast1)<br>-        obj.register_asterisk_instance(ast2)<br>-        obj.register_asterisk_instance(ast3)<br>-        obj.register_asterisk_instance(ast4)<br>-        obj.evaluate()<br>-        self.assertEqual(obj.get_status(), 'Failed')<br>-<br>-<br>-def main():<br>-    """Execute the unit tests"""<br>-    logging.basicConfig(level=logging.DEBUG)<br>-    unittest.main()<br>-<br>-<br>-if __name__ == "__main__":<br>-    main()<br>diff --git a/lib/python/asterisk/sipp.py b/lib/python/asterisk/sipp.py<br>index b84965e..540a544 100644<br>--- a/lib/python/asterisk/sipp.py<br>+++ b/lib/python/asterisk/sipp.py<br>@@ -10,12 +10,12 @@<br> """<br> <br> import logging<br>-import test_suite_utils<br>+from . import test_suite_utils<br> <br> from abc import ABCMeta, abstractmethod<br> from twisted.internet import reactor, defer, protocol, error<br>-from test_case import TestCase<br>-from utils_socket import get_available_port<br>+from .test_case import TestCase<br>+from .utils_socket import get_available_port<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>@@ -495,7 +495,7 @@<br>     def outReceived(self, data):<br>         """Override of ProcessProtocol.outReceived"""<br>         LOGGER.debug("Received from SIPp scenario %s: %s" % (self._name, data))<br>-        self.output += data<br>+        self.output += data.decode('utf-8', 'ignore')<br> <br>     def connectionMade(self):<br>         """Override of ProcessProtocol.connectionMade"""<br>diff --git a/lib/python/asterisk/sippversion.py b/lib/python/asterisk/sippversion.py<br>index b1110fc..f942e9c 100644<br>--- a/lib/python/asterisk/sippversion.py<br>+++ b/lib/python/asterisk/sippversion.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """SIPp Version String Handling<br> <br> Copyright (C) 2010, Digium, Inc.<br>@@ -9,11 +8,7 @@<br> """<br> <br> import subprocess<br>-import sys<br>-import unittest<br>-sys.path.append("lib/python")<br>-<br>-import test_suite_utils<br>+from . import test_suite_utils<br> <br> <br> class SIPpVersion:<br>@@ -48,8 +43,9 @@<br>             except OSError:<br>                 return<br>             for line in sipp_process.stdout:<br>-                if line.strip().startswith('SIPp '):<br>-                    sipp = line.strip()[5:]<br>+                line = line.decode('utf-8').strip()<br>+                if line.startswith('SIPp '):<br>+                    sipp = line[5:]<br>                     sipp = sipp.split(',', 1)<br>                     sipp = sipp[0].split('-', 1)<br>                     version = sipp[0]<br>@@ -88,7 +84,7 @@<br> <br>     def __cmp__(self, other):<br>         """Compare two SIPpVersion instances against each other"""<br>-        return cmp(int(self), int(other))<br>+        return (int(self) > int(other)) - (int(self) < int(other))<br> <br>     def __ne__(self, other):<br>         """Determine if this SIPpVersion instance is not equal to another"""<br>@@ -105,6 +101,14 @@<br>         if ((res == 0) and (self.tls == other.tls and self.pcap == other.pcap)):<br>                 return True<br>         return False<br>+<br>+    def __le__(self, other):<br>+        """Determine if this SIPpVersion instance is less than or equal to another"""<br>+        return int(self) <= int(other)<br>+<br>+    def __lt__(self, other):<br>+        """Determine if this SIPpVersion instance is less than another"""<br>+        return int(self) < int(other)<br> <br>     def __parse_version(self, version):<br>         """Parse the version string returned from SIPp"""<br>@@ -124,215 +128,3 @@<br>             self.tls = True<br>         if value.find("PCAP") > -1:<br>             self.pcap = True<br>-<br>-<br>-class SIPpVersionTests(unittest.TestCase):<br>-    def test_version(self):<br>-        v = SIPpVersion("v3.2", None)<br>-        self.assertEqual(str(v), "v3.2")<br>-        self.assertEqual(v.concept, "v3")<br>-        self.assertEqual(v.major, "2")<br>-        self.assertEqual(v.minor, None)<br>-        self.assertFalse(v.tls)<br>-        self.assertFalse(v.pcap)<br>-<br>-    def test_version2(self):<br>-        v = SIPpVersion("v2.0.1", None)<br>-        self.assertEqual(str(v), "v2.0.1")<br>-        self.assertEqual(v.concept, "v2")<br>-        self.assertEqual(v.major, "0")<br>-        self.assertEqual(v.minor, "1")<br>-        self.assertFalse(v.tls)<br>-        self.assertFalse(v.pcap)<br>-<br>-    def test_version3(self):<br>-        v = SIPpVersion("v3.1", "TLS")<br>-        self.assertEqual(str(v), "v3.1-TLS")<br>-        self.assertEqual(v.concept, "v3")<br>-        self.assertEqual(v.major, "1")<br>-        self.assertEqual(v.minor, None)<br>-        self.assertTrue(v.tls)<br>-        self.assertFalse(v.pcap)<br>-<br>-    def test_version4(self):<br>-        v = SIPpVersion("v2.0.1", "TLS-PCAP")<br>-        self.assertEqual(str(v), "v2.0.1-TLS-PCAP")<br>-        self.assertEqual(v.concept, "v2")<br>-        self.assertEqual(v.major, "0")<br>-        self.assertEqual(v.minor, "1")<br>-        self.assertTrue(v.tls)<br>-        self.assertTrue(v.pcap)<br>-<br>-    def test_version5(self):<br>-        v = SIPpVersion("v3.2", "PCAP")<br>-        self.assertEqual(str(v), "v3.2-PCAP")<br>-        self.assertEqual(v.concept, "v3")<br>-        self.assertEqual(v.major, "2")<br>-        self.assertEqual(v.minor, None)<br>-        self.assertFalse(v.tls)<br>-        self.assertTrue(v.pcap)<br>-<br>-    def test_version6(self):<br>-        v = SIPpVersion(None, "PCAP")<br>-        self.assertEqual(str(v), "PCAP")<br>-        self.assertEqual(v.concept, None)<br>-        self.assertEqual(v.major, None)<br>-        self.assertEqual(v.minor, None)<br>-        self.assertFalse(v.tls)<br>-        self.assertTrue(v.pcap)<br>-<br>-    def test_version7(self):<br>-        v = SIPpVersion(None, "TLS")<br>-        self.assertEqual(str(v), "TLS")<br>-        self.assertEqual(v.concept, None)<br>-        self.assertEqual(v.major, None)<br>-        self.assertEqual(v.minor, None)<br>-        self.assertTrue(v.tls)<br>-        self.assertFalse(v.pcap)<br>-<br>-    def test_version8(self):<br>-        v = SIPpVersion(None, "TLS-PCAP")<br>-        self.assertEqual(str(v), "TLS-PCAP")<br>-        self.assertEqual(v.concept, None)<br>-        self.assertEqual(v.major, None)<br>-        self.assertEqual(v.minor, None)<br>-        self.assertTrue(v.tls)<br>-        self.assertTrue(v.pcap)<br>-<br>-    def test_cmp(self):<br>-        v1 = SIPpVersion("v3.2", None)<br>-        v2 = SIPpVersion("v3.1", None)<br>-        self.assertTrue(v1 > v2)<br>-<br>-    def test_cmp2(self):<br>-        v1 = SIPpVersion("v2.0.1", None)<br>-        v2 = SIPpVersion("v3.1", None)<br>-        self.assertTrue(v1 < v2)<br>-<br>-    def test_cmp3(self):<br>-        v1 = SIPpVersion("v3.1", None)<br>-        v2 = SIPpVersion("v3.1", None)<br>-        self.assertTrue(v1 == v2)<br>-<br>-    def test_cmp4(self):<br>-        v1 = SIPpVersion("v3.1", None)<br>-        v2 = SIPpVersion("v3.1", None)<br>-        self.assertFalse(v1 != v2)<br>-<br>-    def test_cmp5(self):<br>-        v1 = SIPpVersion("v3.1", "TLS")<br>-        v2 = SIPpVersion("v3.1", "TLS")<br>-        self.assertTrue(v1 == v2)<br>-<br>-    def test_cmp6(self):<br>-        v1 = SIPpVersion(None, "TLS")<br>-        v2 = SIPpVersion(None, "TLS")<br>-        self.assertTrue(v1 == v2)<br>-<br>-    def test_cmp7(self):<br>-        v1 = SIPpVersion("v2.0.1", None)<br>-        v2 = SIPpVersion("v2.0.1", None)<br>-        self.assertTrue(v1 == v2)<br>-<br>-    def test_cmp8(self):<br>-        v1 = SIPpVersion("v3.2", "TLS")<br>-        v2 = SIPpVersion("v3.2", "PCAP")<br>-        self.assertTrue(v1 != v2)<br>-<br>-    def test_cmp9(self):<br>-        v1 = SIPpVersion(None, "TLS")<br>-        v2 = SIPpVersion(None, "PCAP")<br>-        self.assertTrue(v1 != v2)<br>-<br>-    def test_cmp10(self):<br>-        v1 = SIPpVersion("v3.2", "TLS")<br>-        v2 = SIPpVersion("v3.2", "PCAP")<br>-        self.assertFalse(v1 == v2)<br>-<br>-    def test_cmp11(self):<br>-        v1 = SIPpVersion(None, "TLS")<br>-        v2 = SIPpVersion(None, "PCAP")<br>-        self.assertFalse(v1 == v2)<br>-<br>-    def test_cmp12(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion("v3.2", "TLS")<br>-        self.assertTrue(v1 >= v2)<br>-<br>-    def test_cmp13(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion(None, "TLS")<br>-        self.assertTrue(v1 >= v2)<br>-<br>-    def test_cmp14(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion("v3.2", "PCAP")<br>-        self.assertTrue(v1 >= v2)<br>-<br>-    def test_cmp15(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion(None, "PCAP")<br>-        self.assertTrue(v1 >= v2)<br>-<br>-    def test_cmp16(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion("v3.2", "TLS")<br>-        self.assertTrue(v1 != v2)<br>-<br>-    def test_cmp17(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion(None, "TLS")<br>-        self.assertTrue(v1 != v2)<br>-<br>-    def test_cmp18(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion("v3.2", "TLS")<br>-        self.assertFalse(v1 == v2)<br>-<br>-    def test_cmp19(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion(None, "TLS")<br>-        self.assertFalse(v1 == v2)<br>-<br>-    def test_cmp20(self):<br>-        v1 = SIPpVersion("v3.1", "PCAP")<br>-        v2 = SIPpVersion("v3.0", "TLS")<br>-        self.assertTrue(v1 > v2)<br>-<br>-    def test_cmp21(self):<br>-        v1 = SIPpVersion("v3.2", "TLS-PCAP")<br>-        v2 = SIPpVersion("v2.0.1", "PCAP")<br>-        self.assertTrue(v1 >= v2)<br>-<br>-    def test_cmp22(self):<br>-        v1 = SIPpVersion("v3.1", "TLS")<br>-        v2 = SIPpVersion("v3.1", "PCAP")<br>-        self.assertFalse(v1 > v2)<br>-<br>-    def test_cmp23(self):<br>-        v1 = SIPpVersion("v3.1", "TLS")<br>-        v2 = SIPpVersion("v3.1", "PCAP")<br>-        self.assertFalse(v1 < v2)<br>-<br>-    def test_cmp24(self):<br>-        v1 = SIPpVersion("v3.2", "TLS")<br>-        v2 = SIPpVersion("v3.1", "TLS-PCAP")<br>-        self.assertTrue(v1 >= v2)<br>-<br>-    def test_cmp25(self):<br>-        v1 = SIPpVersion("v3.1", "TLS-PCAP")<br>-        v2 = SIPpVersion("v3.2", "PCAP")<br>-        self.assertTrue(v1 <= v2)<br>-<br>-    def test_cmp26(self):<br>-        v1 = SIPpVersion("v2.0.1", "TLS-PCAP")<br>-        v2 = SIPpVersion("v3.0", "TLS")<br>-        self.assertFalse(v1 >= v2)<br>-<br>-<br>-def main():<br>-    unittest.main()<br>-<br>-<br>-if __name__ == "__main__":<br>-    main()<br>diff --git a/lib/python/asterisk/syncami.py b/lib/python/asterisk/syncami.py<br>index 031ae99..5482400 100644<br>--- a/lib/python/asterisk/syncami.py<br>+++ b/lib/python/asterisk/syncami.py<br>@@ -8,7 +8,13 @@<br> the GNU General Public License Version 2.<br> """<br> <br>-from urllib import urlencode<br>+try:<br>+    # python 2 import<br>+    from urllib import urlencode<br>+except:<br>+    # python 3 import<br>+    from urllib.parse import urlencode<br>+<br> from email.parser import HeaderParser<br> try:<br>     from httplib import *<br>@@ -87,7 +93,7 @@<br>         if res.status != 200:<br>             raise InvalidAMIResponse(res)<br>         self.cookie = res.getheader('set-cookie', None)<br>-        data = res.read()<br>+        data = res.read().decode('utf-8')<br>         parser = HeaderParser()<br> <br>         return parser.parsestr(data)<br>diff --git a/lib/python/asterisk/test_case.py b/lib/python/asterisk/test_case.py<br>index 80321f7..7bf6d16 100644<br>--- a/lib/python/asterisk/test_case.py<br>+++ b/lib/python/asterisk/test_case.py<br>@@ -19,9 +19,9 @@<br> from twisted.python import log<br> from starpy import manager, fastagi<br> <br>-from asterisk import Asterisk<br>-from test_config import TestConfig<br>-from test_conditions import TestConditionController<br>+from .asterisk import Asterisk<br>+from .test_config import TestConfig<br>+from .test_conditions import TestConditionController<br> <br> <br> try:<br>@@ -45,12 +45,12 @@<br>         except:<br>             msg = ("WARNING: failed to preserve existing loggers - some "<br>                    "logging statements may be missing")<br>-            print msg<br>+            print(msg)<br>             logging.config.fileConfig(config_file)<br>     else:<br>         msg = ("WARNING: no logging.conf file found; using default "<br>                "configuration")<br>-        print msg<br>+        print(msg)<br>         logging.basicConfig(level=logging.DEBUG)<br> <br>     root_logger = logging.getLogger()<br>@@ -102,7 +102,7 @@<br>         # for the rasterisk CLI connection. As a quick fix, we hash the path<br>         # using md5, to make it unique enough.<br>         self.realbase = self.test_name.replace("tests/", "", 1)<br>-        self.base = md5(self.realbase).hexdigest()<br>+        self.base = md5(self.realbase.encode()).hexdigest()<br>         # We provide a symlink to it from a named path.<br>         named_dir = os.path.join(Asterisk.test_suite_root, self.realbase)<br>         try:<br>diff --git a/lib/python/asterisk/test_conditions.py b/lib/python/asterisk/test_conditions.py<br>index dbf36a7..7053465 100644<br>--- a/lib/python/asterisk/test_conditions.py<br>+++ b/lib/python/asterisk/test_conditions.py<br>@@ -19,7 +19,7 @@<br> import logging<br> import logging.config<br> <br>-from buildoptions import AsteriskBuildOptions<br>+from .buildoptions import AsteriskBuildOptions<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/lib/python/asterisk/test_config.py b/lib/python/asterisk/test_config.py<br>index 8fdb884..6d2318d 100644<br>--- a/lib/python/asterisk/test_config.py<br>+++ b/lib/python/asterisk/test_config.py<br>@@ -18,12 +18,13 @@<br> <br> sys.path.append("lib/python")<br> <br>-import test_suite_utils<br>+from . import test_suite_utils<br>+from .test_runner import load_and_parse_module<br> <br>-from asterisk import Asterisk<br>-from buildoptions import AsteriskBuildOptions<br>-from sippversion import SIPpVersion<br>-from opensslversion import OpenSSLVersion<br>+from .asterisk import Asterisk<br>+from .buildoptions import AsteriskBuildOptions<br>+from .sippversion import SIPpVersion<br>+from .opensslversion import OpenSSLVersion<br> <br> <br> class TestConditionConfig(object):<br>@@ -70,14 +71,9 @@<br> <br>     def make_condition(self):<br>         """Build and return the condition object defined by this config"""<br>-        parts = self.class_type_name.split('.')<br>-        module = '.'.join(parts[:-1])<br>-        if module != '':<br>-            mod = __import__(module)<br>-            for comp in parts[1:]:<br>-                mod = getattr(mod, comp)<br>-            obj = mod(self)<br>-            return obj<br>+        mod = load_and_parse_module(self.class_type_name)<br>+        if mod is not None:<br>+            return mod(self)<br>         return None<br> <br> <br>@@ -147,7 +143,7 @@<br>                     self.met = getattr(self, dir_method)()<br>                     found = True<br>             if not found:<br>-                print "Unknown custom dependency - '%s'" % self.name<br>+                print("Unknown custom dependency - '%s'" % self.name)<br>         elif "asterisk" in dep:<br>             if self.ast:<br>                 self.name = dep["asterisk"]<br>@@ -167,9 +163,9 @@<br>             from test_case import PCAP_AVAILABLE<br>             self.met = PCAP_AVAILABLE<br>         else:<br>-            print "Unknown dependency type specified:"<br>+            print("Unknown dependency type specified:")<br>             for key in dep.keys():<br>-                print key<br>+                print(key)<br> <br>     def depend_remote(self):<br>         """Check to see if we run against a remote instance of Asterisk"""<br>@@ -256,13 +252,13 @@<br>         if self.asterisk_build_options:<br>             return (self.asterisk_build_options.check_option(name))<br>         else:<br>-            print "Unable to evaluate build options: no build options found"<br>+            print("Unable to evaluate build options: no build options found")<br>             return False<br> <br>     def _find_asterisk_module(self, name):<br>         """Determine if an Asterisk module exists"""<br>         if not Dependency.ast:<br>-            print "Unable to evaluate Asterisk modules: Asterisk not found"<br>+            print("Unable to evaluate Asterisk modules: Asterisk not found")<br>             return False<br> <br>         if Dependency.ast.original_astmoddir == "":<br>@@ -333,8 +329,8 @@<br>                 if self.config is not None and 'exclude-tests' in self.config:<br>                     self.excluded_tests = self.config['exclude-tests']<br>             else:<br>-                print ("WARNING - test configuration [%s] not found in "<br>-                       "config file" % self.test_configuration)<br>+                print("WARNING - test configuration [%s] not found in "<br>+                      "config file" % self.test_configuration)<br> <br>     def _process_testinfo(self):<br>         """Process the test information block"""<br>@@ -381,8 +377,8 @@<br>             self.config = yaml.load(config_file)<br> <br>         if not self.config:<br>-            print "ERROR: Failed to load configuration for test '%s'" % \<br>-                self.test_name<br>+            print("ERROR: Failed to load configuration for test '%s'" %<br>+                  self.test_name)<br>             return<br> <br>         self._process_global_settings()<br>@@ -408,8 +404,8 @@<br>             matches = [cond_def for cond_def in self.condition_definitions<br>                        if cond_def['name'] == conf['name']]<br>             if len(matches) != 1:<br>-                print ("Unknown or too many matches for condition: " +<br>-                       conf['name'])<br>+                print("Unknown or too many matches for condition: " +<br>+                      conf['name'])<br>             else:<br>                 pre_cond = TestConditionConfig(conf, matches[0], "Pre")<br>                 post_cond = TestConditionConfig(conf, matches[0], "Post")<br>diff --git a/lib/python/asterisk/test_runner.py b/lib/python/asterisk/test_runner.py<br>old mode 100755<br>new mode 100644<br>index 644d117..d278e02<br>--- a/lib/python/asterisk/test_runner.py<br>+++ b/lib/python/asterisk/test_runner.py<br>@@ -1,4 +1,3 @@<br>-#!/usr/bin/env python<br> """Module that spawns and manages running a test<br> <br> This module provides an entry point, loading, and teardown of test<br>@@ -22,8 +21,6 @@<br> <br> LOGGER = logging.getLogger('test_runner')<br> logging.basicConfig()<br>-<br>-sys.path.append('lib/python')<br> <br> <br> class TestModuleFinder(object):<br>@@ -144,9 +141,18 @@<br>     module_name = ".".join(parts[:-1])<br> <br>     if not len(module_name):<br>-        LOGGER.error("No module specified: %s" % module_name)<br>+        LOGGER.error("No module specified: %s" % typename)<br>         return None<br> <br>+    if os.path.exists('lib/python/asterisk/%s.py' % module_name):<br>+        # This is convoluted but required.  lib/python/asterisk packages<br>+        # must be loaded using absolute package names and 'asterisk' must<br>+        # be included in the list of parts.  We cannot simply prepend<br>+        # type_name from the start because this blocks load of modules<br>+        # that are local to the test (add-test-to-search-path: 'True').<br>+        module_name = 'asterisk.' + module_name<br>+        parts = ['asterisk'] + parts<br>+<br>     module = __import__(module_name)<br>     for comp in parts[1:]:<br>         module = getattr(module, comp)<br>diff --git a/lib/python/asterisk/test_suite_utils.py b/lib/python/asterisk/test_suite_utils.py<br>index d8ef541..84f54f5 100644<br>--- a/lib/python/asterisk/test_suite_utils.py<br>+++ b/lib/python/asterisk/test_suite_utils.py<br>@@ -1,4 +1,3 @@<br>-#! /usr/bin/env python<br> """Asterisk testsuite utils<br> <br> This module provides access to Asterisk testsuite utility<br>@@ -14,15 +13,19 @@<br> import os<br> import logging<br> import re<br>+import sys<br> <br> from os import close<br> from os import remove<br> from shutil import move<br> from tempfile import mkstemp<br>-from config import ConfigFile<br>+from .config import ConfigFile<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>+<br>+if sys.version_info[0] == 3:<br>+    unicode = str<br> <br> def which(program):<br>     """Find the executable for a specified program<br>@@ -100,7 +103,7 @@<br>     elif isinstance(pattern, dict):<br>         # Dict should match for every field in the pattern.<br>         # extra fields in the message are fine.<br>-        for key, value in pattern.iteritems():<br>+        for key, value in pattern.items():<br>             to_check = message.get(key)<br>             if to_check is None or not all_match(value, to_check):<br>                 return False<br>diff --git a/lib/python/asterisk/thread_test_condition.py b/lib/python/asterisk/thread_test_condition.py<br>index 595e3ce..c12321f 100644<br>--- a/lib/python/asterisk/thread_test_condition.py<br>+++ b/lib/python/asterisk/thread_test_condition.py<br>@@ -9,7 +9,7 @@<br> """<br> <br> import logging<br>-from test_conditions import TestCondition<br>+from .test_conditions import TestCondition<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/lib/python/asterisk/voicemail.py b/lib/python/asterisk/voicemail.py<br>index 283650b..3c4c7d5 100644<br>--- a/lib/python/asterisk/voicemail.py<br>+++ b/lib/python/asterisk/voicemail.py<br>@@ -20,9 +20,9 @@<br> import time<br> import random<br> <br>-from config import ConfigFile<br>-from test_case import TestCase<br>-from test_state import TestState, TestStateController, FailureTestState<br>+from .config import ConfigFile<br>+from .test_case import TestCase<br>+from .test_state import TestState, TestStateController, FailureTestState<br> <br> sys.path.append("lib/python")<br> <br>diff --git a/lib/python/rlmi.py b/lib/python/rlmi.py<br>index 5e662a0..2abf19e 100644<br>--- a/lib/python/rlmi.py<br>+++ b/lib/python/rlmi.py<br>@@ -343,7 +343,7 @@<br>             return None<br>         @classmethod<br>         def gds_reverse_node_mapping(cls, mapping):<br>-            return dict(((v, k) for k, v in mapping.iteritems()))<br>+            return dict(((v, k) for k, v in mapping.items()))<br> <br> <br> #<br>diff --git a/lib/python/sip_message.py b/lib/python/sip_message.py<br>index 85cd9fd..087c760 100644<br>--- a/lib/python/sip_message.py<br>+++ b/lib/python/sip_message.py<br>@@ -118,7 +118,7 @@<br> def main():<br>     msg = """INVITE sip:123@example.com SIP/2.0\r\nContact   : \tTerry Wilson\r\n   <terry@example.com>\r\nCall-ID:\r\n Whatever\r\nContact: New Contact\r\n\r\nData!!!!!"""<br>     sipmsg = SIPMessage(msg)<br>-    print sipmsg<br>+    print(sipmsg)<br>     if sipmsg.get_header('CoNtact') is None:<br>         return -1<br>     if len(sipmsg.get_header_all('contact')) != 2:<br>diff --git a/run-local b/run-local<br>index 026a063..326ac50 100755<br>--- a/run-local<br>+++ b/run-local<br>@@ -75,7 +75,12 @@<br>  LIBDIR=`dirname $HERE/astroot/usr/lib*/libasteriskssl.so`<br>     export LD_LIBRARY_PATH="${LIBDIR}${LD_LIBRARY_PATH}"<br>        set +e<br>-       ./runtests.py "$@"<br>+ if test -n "$PYTHON"; then<br>+         # Use specific interpreter<br>+           $PYTHON ./runtests.py "$@"<br>+ else<br>+         ./runtests.py "$@"<br>+ fi<br>    status=$?<br>     rm "$AST_TEST_ROOT"<br>         set -e<br>diff --git a/runtests.py b/runtests.py<br>index 1351a3c..eac88a1 100755<br>--- a/runtests.py<br>+++ b/runtests.py<br>@@ -111,7 +111,7 @@<br> <br>     def stdout_print(self, msg):<br>         self.stdout += msg + "\n"<br>-        print msg<br>+        print(msg)<br> <br>     def run(self):<br>         self.passed = False<br>@@ -124,13 +124,14 @@<br>         ]<br> <br>         if not os.path.exists(cmd[0]):<br>-            cmd = ["./lib/python/asterisk/test_runner.py",<br>+            cmd = [sys.executable,<br>+                   "-m", "asterisk.test_runner",<br>                    "%s" % self.test_name]<br>         if os.path.exists(cmd[0]) and os.access(cmd[0], os.X_OK):<br>             if self.options.pcap:<br>                 os.environ['PCAP'] = "yes"<br> <br>-            self.stdout_print("Running %s ..." % cmd)<br>+            self.stdout_print("Running %s ..." % self.test_name)<br>             p = subprocess.Popen(cmd, stdout=subprocess.PIPE,<br>                                  stderr=subprocess.STDOUT)<br>             self.pid = p.pid<br>@@ -139,6 +140,7 @@<br>             poll.register(p.stdout, select.POLLIN)<br> <br>             timedout = False<br>+            has_unicode_error = False<br>             try:<br>                 while (not abandon_test):<br>                     try:<br>@@ -148,20 +150,24 @@<br>                     except select.error as v:<br>                         if v[0] != errno.EINTR:<br>                             raise<br>-                    l = p.stdout.readline()<br>+                    l = p.stdout.readline().decode('ascii', 'ignore').strip()<br>                     if not l:<br>                         break<br>                     self.stdout_print(l)<br>+            except UnicodeEncodeError:<br>+                self.stdout_print('Unicode error reading output from test!')<br>+                has_unicode_error = True<br>+                pass<br>             except IOError:<br>                 pass<br>             p.wait()<br> <br>             # Sanitize p.returncode so it's always a boolean.<br>-            did_pass = (p.returncode == 0 and not abandon_test)<br>+            did_pass = (p.returncode == 0 and not abandon_test and not has_unicode_error)<br>             if did_pass and not self.test_config.expect_pass:<br>                 self.stdout_print("Test passed but was expected to fail.")<br>             if not did_pass and not self.test_config.expect_pass:<br>-                print "Test failed as expected."<br>+                print("Test failed as expected.")<br> <br>             self.passed = (did_pass == self.test_config.expect_pass)<br>             if abandon_test:<br>@@ -190,8 +196,8 @@<br>                     shutil.rmtree(absolute_dir)<br>                     os.remove(symlink_dir)<br>                 except:<br>-                    print "Unable to clean up directory for" \<br>-                          "test %s (non-fatal)" % self.test_name<br>+                    print("Unable to clean up directory for"<br>+                          "test %s (non-fatal)" % self.test_name)<br> <br>             self.__parse_run_output(self.stdout)<br>             if timedout:<br>@@ -202,13 +208,13 @@<br>                 status = 'passed'<br>             else:<br>                 status = 'failed'<br>-            pass_str = 'Test %s %s\n' % (cmd, status)<br>-            print pass_str<br>+            pass_str = 'Test %s %s\n' % (self.test_name, status)<br>+            print(pass_str)<br>             if self.options.syslog:<br>                 syslog.syslog(pass_str)<br> <br>         else:<br>-            print "FAILED TO EXECUTE %s, it must exist and be executable" % cmd<br>+            print("FAILED TO EXECUTE %s, it must exist and be executable" % cmd)<br>         self.time = time.time() - start_time<br> <br>     def _check_for_core(self):<br>@@ -237,7 +243,7 @@<br>         debug_level = email_config.get('debug', 0)<br> <br>         if not sender or len(recipients) == 0:<br>-            print "--email-on-crash requires sender and 1+ recipients"<br>+            print("--email-on-crash requires sender and 1+ recipients")<br>             return<br> <br>         with open(dest_file_name, 'r') as bt_file:<br>@@ -255,12 +261,12 @@<br>             send_email(smtp_server, sender, recipients, message,<br>                        debug=debug_level)<br>         except Exception as exception:<br>-            print "Failed to send email\nError: {0}".format(exception)<br>+            print("Failed to send email\nError: {0}".format(exception))<br> <br>     def _archive_core_dumps(self, core_dumps):<br>         for core in core_dumps:<br>             if not os.path.exists(core):<br>-                print "Unable to find core dump file %s, skipping" % core<br>+                print("Unable to find core dump file %s, skipping" % core)<br>                 continue<br>             random_num = random.randint(0, 16000)<br>             dest_dir = "./logs/%s" % self.test_relpath<br>@@ -275,36 +281,36 @@<br>                        "-ex", "thread apply all bt",<br>                        "--batch",<br>                        "-c", core]<br>-            print "Running %s" % (" ".join(gdb_cmd),)<br>+            print("Running %s" % (" ".join(gdb_cmd),))<br>             try:<br>                 res = subprocess.call(gdb_cmd, stdout=dest_file, stderr=subprocess.STDOUT)<br>                 if res != 0:<br>-                    print "error analyzing core dump; gdb exited with %d" % res<br>+                    print("error analyzing core dump; gdb exited with %d" % res)<br>                 # Copy the backtrace over to the logs<br>-                print "Archived backtrace: {0}".format(dest_file_name)<br>+                print("Archived backtrace: {0}".format(dest_file_name))<br> <br>                 if self.options.email_on_crash:<br>                     self._email_crash_report(dest_file_name)<br> <br>-            except OSError, ose:<br>-                print "OSError ([%d]: %s) occurred while executing %r" % \<br>-                    (ose.errno, ose.strerror, gdb_cmd)<br>+            except OSError as ose:<br>+                print("OSError ([%d]: %s) occurred while executing %r" %<br>+                    (ose.errno, ose.strerror, gdb_cmd))<br>             except:<br>-                print "Unknown exception occurred while executing %r" % (gdb_cmd,)<br>+                print("Unknown exception occurred while executing %r" % (gdb_cmd,))<br>             finally:<br>                 dest_file.close()<br>                 if self.options.keep_core:<br>                     try:<br>                         dst_core = os.path.join(dest_dir, "core_{0}".format(random_num))<br>                         shutil.copy(core, dst_core)<br>-                        print "Archived core file: {0}".format(dst_core)<br>+                        print("Archived core file: {0}".format(dst_core))<br>                     except Exception as e:<br>-                        print "Error occurred while copying core: {0}".format(e)<br>+                        print("Error occurred while copying core: {0}".format(e))<br>                 try:<br>                     os.unlink(core)<br>-                except OSError, e:<br>-                    print "Error removing core file: %s: " \<br>-                          "Beware of the stale core file in CWD!" % (e,)<br>+                except OSError as e:<br>+                    print("Error removing core file: %s: "<br>+                          "Beware of the stale core file in CWD!" % (e,))<br> <br>     def _find_run_dirs(self):<br>         test_run_dir = os.path.join(Asterisk.test_suite_root,<br>@@ -387,7 +393,7 @@<br>                     res = subprocess.call(refcounter,<br>                                           stdout=dest_file,<br>                                           stderr=subprocess.STDOUT)<br>-                except Exception, e:<br>+                except Exception as e:<br>                     self.stdout_print("Exception occurred while processing REF_DEBUG")<br>                 finally:<br>                     dest_file.close()<br>@@ -415,10 +421,10 @@<br>                 srcfile = os.path.join(src_dir, filename)<br>                 if os.path.exists(srcfile):<br>                     hardlink_or_copy(srcfile, os.path.join(dest_dir, filename))<br>-            except Exception, e:<br>-                print "Exception occurred while archiving file '%s' to %s: %s" % (<br>+            except Exception as e:<br>+                print("Exception occurred while archiving file '%s' to %s: %s" % (<br>                     srcfile, dest_dir, e<br>-                )<br>+                ))<br> <br>     def _archive_logs(self):<br>         (run_num, run_dir, archive_dir) = self._find_run_dirs()<br>@@ -519,35 +525,35 @@<br>         tags.sort(key=str.lower)<br>         maxwidth = max(len(t) for t in tags)<br> <br>-        print "Available test tags:"<br>+        print("Available test tags:")<br>         tags = chunks(tags, 3)<br>         for tag in tags:<br>-            print "\t%-*s     %-*s     %-*s" % (<br>+            print("\t%-*s     %-*s     %-*s" % (<br>                 maxwidth, tag[0],<br>                 maxwidth, len(tag) > 1 and tag[1] or '',<br>-                maxwidth, len(tag) > 2 and tag[2] or '')<br>+                maxwidth, len(tag) > 2 and tag[2] or ''))<br> <br>     def list_tests(self):<br>-        print "Configured tests:"<br>+        print("Configured tests:")<br>         i = 1<br>         for t in self.tests:<br>-            print "%.3d) %s" % (i, t.test_config.test_name)<br>-            print "      --> Summary: %s" % t.test_config.summary<br>+            print("%.3d) %s" % (i, t.test_config.test_name))<br>+            print("      --> Summary: %s" % t.test_config.summary)<br>             if t.test_config.skip is not None:<br>-                print "      --> Skip: %s" % t.test_config.skip<br>+                print("      --> Skip: %s" % t.test_config.skip)<br>             if t.test_config.features:<br>-                print "      --> Features:"<br>+                print("      --> Features:")<br>                 for feature_name in t.test_config.features:<br>-                    print "        --> %s: -- Met: %s" % \<br>-                        (feature_name, str(t.test_config.feature_check[feature_name]))<br>+                    print("        --> %s: -- Met: %s" %<br>+                        (feature_name, str(t.test_config.feature_check[feature_name])))<br>             if t.test_config.tags:<br>-                print "      --> Tags: %s" % str(t.test_config.tags)<br>+                print("      --> Tags: %s" % str(t.test_config.tags))<br>             for d in t.test_config.deps:<br>                 if d.version:<br>-                    print "      --> Dependency: %s" % (d.name)<br>-                    print "        --> Version: %s -- Met: %s" % (d.version, str(d.met))<br>+                    print("      --> Dependency: %s" % (d.name))<br>+                    print("        --> Version: %s -- Met: %s" % (d.version, str(d.met)))<br>                 else:<br>-                    print "      --> Dependency: %s -- Met: %s" % (d.name, str(d.met))<br>+                    print("      --> Dependency: %s -- Met: %s" % (d.name, str(d.met)))<br> <br>             i += 1<br> <br>@@ -571,7 +577,7 @@<br>                         else:<br>                             deps += ("%s" % d.name)<br> <br>-            print "%04d %s %s%s" % (i, flag, t.test_config.test_name, deps)<br>+            print("%04d %s %s%s" % (i, flag, t.test_config.test_name, deps))<br>             i += 1<br> <br>     def run(self):<br>@@ -587,8 +593,8 @@<br>                     if excluded in t.test_name:<br>                         continue<br>             i += 1<br>-        print "Tests to run: %d * %d time(s) = %d  Maximum test inactivity time: %d sec." % \<br>-            (i, self.options.number, i * self.options.number, (self.options.timeout / 1000))<br>+        print("Tests to run: %d * %d time(s) = %d  Maximum test inactivity time: %d sec." %<br>+            (i, self.options.number, i * self.options.number, (self.options.timeout / 1000)))<br> <br>         for t in self.tests:<br>             if abandon_test_suite:<br>@@ -596,18 +602,18 @@<br> <br>             if t.can_run is False:<br>                 if t.test_config.skip is not None:<br>-                    print "--> %s ... skipped '%s'" % (t.test_name, t.test_config.skip)<br>+                    print("--> %s ... skipped '%s'" % (t.test_name, t.test_config.skip))<br>                     t.skipped_reason = t.test_config.skip<br>                     self.total_skipped += 1<br>                     continue<br>-                print "--> Cannot run test '%s'" % t.test_name<br>+                print("--> Cannot run test '%s'" % t.test_name)<br>                 for f in t.test_config.features:<br>-                    print "--- --> Version Feature: %s - %s" % (<br>-                        f, str(t.test_config.feature_check[f]))<br>-                print "--- --> Tags: %s" % (t.test_config.tags)<br>+                    print("--- --> Version Feature: %s - %s" % (<br>+                        f, str(t.test_config.feature_check[f])))<br>+                print("--- --> Tags: %s" % (t.test_config.tags))<br>                 for d in t.test_config.deps:<br>-                    print "--- --> Dependency: %s - %s" % (d.name, str(d.met))<br>-                print<br>+                    print("--- --> Dependency: %s - %s" % (d.name, str(d.met)))<br>+                print("")<br>                 self.total_skipped += 1<br>                 t.skipped_reason = "Failed dependency"<br>                 continue<br>@@ -615,14 +621,14 @@<br>                 exclude = False<br>                 for excluded in self.global_config.excluded_tests:<br>                     if excluded in t.test_name:<br>-                        print "--- ---> Excluded test: %s" % excluded<br>+                        print("--- ---> Excluded test: %s" % excluded)<br>                         exclude = True<br>                 if exclude:<br>                     self.total_skipped += 1<br>                     continue<br> <br>             running_str = "--> Running test '%s' ..." % t.test_name<br>-            print running_str<br>+            print(running_str)<br>             if self.options.syslog:<br>                 syslog.syslog(running_str)<br> <br>@@ -630,16 +636,16 @@<br>                 t.passed = True<br>             else:<br>                 # Establish Preconditions<br>-                print "Making sure Asterisk isn't running ..."<br>+                print("Making sure Asterisk isn't running ...")<br>                 if os.system("if pidof asterisk >/dev/null; then "<br>                              "killall -9 asterisk >/dev/null 2>&1; "<br>                              "sleep 1; ! pidof asterisk >/dev/null; fi"):<br>-                    print "Could not kill asterisk."<br>-                print "Making sure SIPp isn't running..."<br>+                    print("Could not kill asterisk.")<br>+                print("Making sure SIPp isn't running...")<br>                 if os.system("if pidof sipp >/dev/null; then "<br>                              "killall -9 sipp >/dev/null 2>&1; "<br>                              "sleep 1; ! pidof sipp >/dev/null; fi"):<br>-                    print "Could not kill sipp."<br>+                    print("Could not kill sipp.")<br>                 # XXX TODO Hard coded path, gross.<br>                 os.system("rm -f /var/run/asterisk/asterisk.ctl")<br>                 os.system("rm -f /var/run/asterisk/asterisk.pid")<br>@@ -668,12 +674,12 @@<br>             (0x86, 0x9f),<br>         ]<br> <br>-        char_list = []<br>+        tbl = {}<br>         for r in bad_chars:<br>             # we do +1 here to include the last item<br>             for i in range(r[0], r[1] + 1):<br>-                char_list.append(chr(i))<br>-        return data.translate(None, ''.join(char_list))<br>+                tbl[chr(i)] = None<br>+        return data.translate(tbl)<br> <br>     def write_results_xml(self, doc, root):<br> <br>@@ -728,10 +734,10 @@<br>     except IOError:<br>         # Ignore errors for the optional tests/custom folder.<br>         if path != "tests/custom/tests.yaml":<br>-            print "Failed to open %s" % path<br>+            print("Failed to open %s" % path)<br>         return None<br>     except:<br>-        print "Unexpected error: %s" % sys.exc_info()[0]<br>+        print("Unexpected error: %s" % sys.exc_info()[0])<br>         return None<br> <br>     config = yaml.load(f)<br>@@ -747,7 +753,7 @@<br>     """<br>     global abandon_test_suite<br> <br>-    print "SIGUSR1 received; stopping test suite after current test..."<br>+    print("SIGUSR1 received; stopping test suite after current test...")<br>     abandon_test_suite = True<br> <br> <br>@@ -760,7 +766,7 @@<br>     global abandon_test<br>     global abandon_test_suite<br> <br>-    print "SIGTREM received; abandoning current test and stopping..."<br>+    print("SIGTREM received; abandoning current test and stopping...")<br>     abandon_test = True<br>     abandon_test_suite = True<br> <br>@@ -877,8 +883,8 @@<br> <br>     if options.valgrind:<br>         if not ET:<br>-            print "python lxml module not loaded, text summaries " \<br>-                  "from valgrind will not be produced.\n"<br>+            print("python lxml module not loaded, text summaries "<br>+                  "from valgrind will not be produced.\n")<br>         os.environ["VALGRIND_ENABLE"] = "true"<br> <br>     dom = xml.dom.getDOMImplementation()<br>@@ -894,7 +900,7 @@<br> <br>         running_str = "Running tests for Asterisk (run {0} of {1})...\n".format(<br>             iteration + 1, options.number)<br>-        print running_str<br>+        print(running_str)<br>         if options.syslog:<br>             syslog.syslog(running_str)<br> <br>@@ -903,22 +909,22 @@<br> <br>         # If exactly one test was requested, then skip the summary.<br>         if len(test_suite.tests) != 1:<br>-            print "\n=== TEST RESULTS ===\n"<br>-            print "PATH: %s\n" % os.getenv("PATH")<br>+            print("\n=== TEST RESULTS ===\n")<br>+            print("PATH: %s\n" % os.getenv("PATH"))<br>             for t in test_suite.tests:<br>                 sys.stdout.write("--> %s --- " % t.test_name)<br>                 if t.did_run is False:<br>-                    print "SKIPPED"<br>+                    print("SKIPPED")<br>                     for d in t.test_config.deps:<br>-                        print "      --> Dependency: %s -- Met: %s" % (d.name, str(d.met))<br>+                        print("      --> Dependency: %s -- Met: %s" % (d.name, str(d.met)))<br>                     if options.tags:<br>                         for t in t.test_config.tags:<br>-                            print "      --> Tag: %s -- Met: %s" % (t, str(t in options.tags))<br>+                            print("      --> Tag: %s -- Met: %s" % (t, str(t in options.tags)))<br>                     continue<br>                 if t.passed is True:<br>-                    print "PASSED"<br>+                    print("PASSED")<br>                 else:<br>-                    print "FAILED"<br>+                    print("FAILED")<br> <br>         iteration += 1<br> <br>@@ -929,11 +935,11 @@<br>         with open(TEST_RESULTS, "w") as f:<br>             doc.writexml(f, addindent="  ", newl="\n", encoding="utf-8")<br>     except IOError:<br>-        print "Failed to open test results output file: %s" % TEST_RESULTS<br>+        print("Failed to open test results output file: %s" % TEST_RESULTS)<br>     except:<br>-        print "Unexpected error: %s" % sys.exc_info()[0]<br>-    print "\n"<br>-    print doc.toprettyxml("  ", encoding="utf-8")<br>+        print("Unexpected error: %s" % sys.exc_info()[0])<br>+    print("\n")<br>+    print(doc.toprettyxml("  ", encoding="utf-8").decode('utf-8', 'ignore'))<br> <br>     if options.syslog:<br>         syslog.syslog("All tests concluded")<br>@@ -954,7 +960,7 @@<br> <br>     try:<br>         os.link(source, destination)<br>-    except OSError, e:<br>+    except OSError as e:<br>         # Different partitions can cause hard links to fail (error 18),<br>         # if there's a different error, bail out immediately.<br>         if e.args[0] != errno.EXDEV:<br>diff --git a/self_test b/self_test<br>index 1936014..045c090 100755<br>--- a/self_test<br>+++ b/self_test<br>@@ -28,17 +28,3 @@<br>   run_test $i python2 $PYTHON2<br>  [ "${i#test2}" = "${i}" ] && run_test $i python3 $PYTHON3<br> done<br>-<br>-# Temporary code for running unit tests that are not compatible with python3<br>-run_legacy_test() {<br>- # Arguments: test_name python_name python_bin<br>-        if test -n "$3"; then<br>-              echo " ==> Executing $1 ($2)"<br>-           $3 lib/python/asterisk/${1}.py<br>-       fi<br>-}<br>-<br>-for i in buildoptions channel_test_condition sippversion; do<br>-     run_legacy_test $i python $PYTHON<br>-    run_legacy_test $i python2 $PYTHON2<br>-done<br>diff --git a/tests/cdr/cdr-tests.py b/tests/cdr/cdr-tests.py<br>index a284673..9090045 100644<br>--- a/tests/cdr/cdr-tests.py<br>+++ b/tests/cdr/cdr-tests.py<br>@@ -13,8 +13,8 @@<br> import time<br> <br> sys.path.append("lib/python")<br>-from cdr import CDRModule<br>-from cdr import AsteriskCSVCDR<br>+from asterisk.cdr import CDRModule<br>+from asterisk.cdr import AsteriskCSVCDR<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/cdr/sqlite3/cdr_sqlite3.py b/tests/cdr/sqlite3/cdr_sqlite3.py<br>index 1def05e..32ed786 100644<br>--- a/tests/cdr/sqlite3/cdr_sqlite3.py<br>+++ b/tests/cdr/sqlite3/cdr_sqlite3.py<br>@@ -14,7 +14,7 @@<br> import re<br> <br> sys.path.append("lib/python")<br>-from config import ConfigFile<br>+from asterisk.config import ConfigFile<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/channels/SIP/tcpauthlimit/sipp_scenario.py b/tests/channels/SIP/tcpauthlimit/sipp_scenario.py<br>index 76a21aa..55a26f5 100644<br>--- a/tests/channels/SIP/tcpauthlimit/sipp_scenario.py<br>+++ b/tests/channels/SIP/tcpauthlimit/sipp_scenario.py<br>@@ -9,7 +9,7 @@<br> <br> import logging<br> <br>-from sipp import SIPpScenario<br>+from asterisk.sipp import SIPpScenario<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv4/scenario_generator.py b/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv4/scenario_generator.py<br>index 2e2357a..4fcb67a 100755<br>--- a/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv4/scenario_generator.py<br>+++ b/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv4/scenario_generator.py<br>@@ -12,8 +12,8 @@<br> <br> sys.path.append("lib/python")<br> <br>-import test_suite_utils<br>-from sipp import ScenarioGenerator<br>+from asterisk import test_suite_utils<br>+from asterisk.sipp import ScenarioGenerator<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv6/scenario_generator.py b/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv6/scenario_generator.py<br>index 2a52c96..541ba62 100755<br>--- a/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv6/scenario_generator.py<br>+++ b/tests/channels/pjsip/registration/inbound/nominal/contact_acl/ipv6/scenario_generator.py<br>@@ -12,8 +12,8 @@<br> <br> sys.path.append("lib/python")<br> <br>-import test_suite_utils<br>-from sipp import ScenarioGenerator<br>+from asterisk import test_suite_utils<br>+from asterisk.sipp import ScenarioGenerator<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py b/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py<br>index 143ea42..bbff8f8 100644<br>--- a/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py<br>+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py<br>@@ -15,7 +15,7 @@<br> <br> sys.path.append('lib/python')<br> <br>-from pcap import VOIPListener<br>+from asterisk.pcap import VOIPListener<br> from twisted.internet import reactor<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/tests/channels/pjsip/subscriptions/rls/rls_test.py b/tests/channels/pjsip/subscriptions/rls/rls_test.py<br>index 9c4051c..08d0567 100644<br>--- a/tests/channels/pjsip/subscriptions/rls/rls_test.py<br>+++ b/tests/channels/pjsip/subscriptions/rls/rls_test.py<br>@@ -13,7 +13,7 @@<br> sys.path.append("lib/python")<br> sys.path.append("tests/channels/pjsip/subscriptions/rls")<br> <br>-from pcap import VOIPProxy<br>+from asterisk.pcap import VOIPProxy<br> from rls_element import RLSPacket<br> from rls_validation import ValidationInfo<br> from twisted.internet import reactor<br>diff --git a/tests/channels/pjsip/transfers/attended_transfer/nominal/packet_sniffer.py b/tests/channels/pjsip/transfers/attended_transfer/nominal/packet_sniffer.py<br>index a96a661..b49f268 100755<br>--- a/tests/channels/pjsip/transfers/attended_transfer/nominal/packet_sniffer.py<br>+++ b/tests/channels/pjsip/transfers/attended_transfer/nominal/packet_sniffer.py<br>@@ -35,8 +35,8 @@<br> <br> sys.path.append('lib/python')<br> <br>-from phones import PjsuaPhoneController<br>-from pcap import VOIPListener<br>+from asterisk.phones import PjsuaPhoneController<br>+from asterisk.pcap import VOIPListener<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/codecs/audio_analyzer.py b/tests/codecs/audio_analyzer.py<br>index 9903c6d..e656777 100644<br>--- a/tests/codecs/audio_analyzer.py<br>+++ b/tests/codecs/audio_analyzer.py<br>@@ -13,8 +13,8 @@<br> import logging<br> from twisted.internet import reactor<br> <br>-import ami<br>-import ari<br>+from asterisk import ami<br>+from asterisk import ari<br> import tonetest<br> <br> sys.path.append("lib/python")<br>diff --git a/tests/codecs/rtp_analyzer.py b/tests/codecs/rtp_analyzer.py<br>index e20cde3..74f161a 100644<br>--- a/tests/codecs/rtp_analyzer.py<br>+++ b/tests/codecs/rtp_analyzer.py<br>@@ -13,7 +13,7 @@<br> <br> sys.path.append("lib/python")<br> <br>-import pcap<br>+from asterisk import pcap<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/hep/hep_capture_node.py b/tests/hep/hep_capture_node.py<br>index f663056..3e7feae 100644<br>--- a/tests/hep/hep_capture_node.py<br>+++ b/tests/hep/hep_capture_node.py<br>@@ -28,7 +28,7 @@<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>-from test_suite_utils import all_match<br>+from asterisk.test_suite_utils import all_match<br> <br> def enum(**enums):<br>     """Make an enumeration out of the passed in values"""<br>diff --git a/tests/manager/config/ManagerConfigTest.py b/tests/manager/config/ManagerConfigTest.py<br>index b7dbd8a..fe2e65f 100644<br>--- a/tests/manager/config/ManagerConfigTest.py<br>+++ b/tests/manager/config/ManagerConfigTest.py<br>@@ -13,8 +13,8 @@<br> <br> sys.path.append("lib/python")<br> <br>-from test_case import TestCase<br>-from syncami import SyncAMI<br>+from asterisk.test_case import TestCase<br>+from asterisk.syncami import SyncAMI<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/manager/device_state_changed/ami_device_state.py b/tests/manager/device_state_changed/ami_device_state.py<br>index a3ac5f3..96d8021 100644<br>--- a/tests/manager/device_state_changed/ami_device_state.py<br>+++ b/tests/manager/device_state_changed/ami_device_state.py<br>@@ -8,7 +8,7 @@<br> '''<br> <br> import logging<br>-from test_case import TestCase<br>+from asterisk.test_case import TestCase<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/manager/device_state_list/ami_device_state_list.py b/tests/manager/device_state_list/ami_device_state_list.py<br>index 5abd104..105780e 100755<br>--- a/tests/manager/device_state_list/ami_device_state_list.py<br>+++ b/tests/manager/device_state_list/ami_device_state_list.py<br>@@ -9,7 +9,7 @@<br> """<br> <br> import logging<br>-from test_case import TestCase<br>+from asterisk.test_case import TestCase<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/tests/manager/exten_state_list/ami_exten_state_list.py b/tests/manager/exten_state_list/ami_exten_state_list.py<br>index c9449f4..90bdc4f 100755<br>--- a/tests/manager/exten_state_list/ami_exten_state_list.py<br>+++ b/tests/manager/exten_state_list/ami_exten_state_list.py<br>@@ -9,7 +9,7 @@<br> """<br> <br> import logging<br>-from test_case import TestCase<br>+from asterisk.test_case import TestCase<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/tests/manager/presence_state_changed/ami_presence_state.py b/tests/manager/presence_state_changed/ami_presence_state.py<br>index e6861fc..d8ac551 100644<br>--- a/tests/manager/presence_state_changed/ami_presence_state.py<br>+++ b/tests/manager/presence_state_changed/ami_presence_state.py<br>@@ -8,7 +8,7 @@<br> '''<br> <br> import logging<br>-from test_case import TestCase<br>+from asterisk.test_case import TestCase<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/manager/presence_state_list/ami_presence_state_list.py b/tests/manager/presence_state_list/ami_presence_state_list.py<br>index 13c16c7..f67c386 100755<br>--- a/tests/manager/presence_state_list/ami_presence_state_list.py<br>+++ b/tests/manager/presence_state_list/ami_presence_state_list.py<br>@@ -9,7 +9,7 @@<br> """<br> <br> import logging<br>-from test_case import TestCase<br>+from asterisk.test_case import TestCase<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/tests/pbx/manager_extensions/ami_extension_control.py b/tests/pbx/manager_extensions/ami_extension_control.py<br>index d4ac26c..01e53af 100755<br>--- a/tests/pbx/manager_extensions/ami_extension_control.py<br>+++ b/tests/pbx/manager_extensions/ami_extension_control.py<br>@@ -8,7 +8,7 @@<br> '''<br> <br> import logging<br>-from test_case import TestCase<br>+from asterisk.test_case import TestCase<br> from twisted.internet import defer<br> <br> LOGGER = logging.getLogger(__name__)<br>diff --git a/tests/rest_api/external_interaction/attended_transfer/attended_transfer.py b/tests/rest_api/external_interaction/attended_transfer/attended_transfer.py<br>index d03fbfe..6b7790c 100644<br>--- a/tests/rest_api/external_interaction/attended_transfer/attended_transfer.py<br>+++ b/tests/rest_api/external_interaction/attended_transfer/attended_transfer.py<br>@@ -9,7 +9,7 @@<br> import logging<br> from sys import path<br> <br>-from sipp import SIPpScenario<br>+from asterisk.sipp import SIPpScenario<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/tests/rest_api/external_interaction/blind_transfer/call_transfer.py b/tests/rest_api/external_interaction/blind_transfer/call_transfer.py<br>index 23f1a7b..ddef074 100755<br>--- a/tests/rest_api/external_interaction/blind_transfer/call_transfer.py<br>+++ b/tests/rest_api/external_interaction/blind_transfer/call_transfer.py<br>@@ -16,7 +16,7 @@<br> import pjsua as pj<br> from twisted.internet import reactor<br> <br>-import pjsua_mod<br>+from asterisk import pjsua_mod<br> <br> LOGGER = logging.getLogger(__name__)<br> THIS_FILE = os.path.basename(__file__)<br>diff --git a/tests/rest_api/message/message_modules.py b/tests/rest_api/message/message_modules.py<br>index 2e4cdd4..c2d8f2e 100644<br>--- a/tests/rest_api/message/message_modules.py<br>+++ b/tests/rest_api/message/message_modules.py<br>@@ -14,7 +14,7 @@<br> <br> from twisted.internet import defer<br> <br>-from sipp import SIPpScenario<br>+from asterisk.sipp import SIPpScenario<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>diff --git a/usage.py b/usage.py<br>index 16dea94..9e7c33f 100755<br>--- a/usage.py<br>+++ b/usage.py<br>@@ -70,7 +70,7 @@<br>     def occurances(self, **kwargs):<br>         match = []<br>         for test in self.tests:<br>-            for key, value in kwargs.iteritems():<br>+            for key, value in kwargs.items():<br>                 if value in getattr(test, key):<br>                     match.append(test)<br>                     continue<br>@@ -78,14 +78,14 @@<br>         return len(match)<br> <br>     def results_for(self, key):<br>-        print key.title() + ":"<br>+        print(key.title() + ":")<br>         things = self.unique(key)<br>         width = max(len(t) for t in things)<br>         results = [(self.occurances(**{key: t}), t) for t in things]<br>         results.sort(key=lambda tup: tup[0], reverse=True)<br>         for (count, name) in results:<br>-            print "\t%-*s %5d" % (width, name, count)<br>-        print ""<br>+            print("\t%-*s %5d" % (width, name, count))<br>+        print("")<br> <br> <br> def load_yaml_config(path):<br>@@ -95,10 +95,10 @@<br>     except IOError:<br>         # Ignore errors for the optional tests/custom folder.<br>         if path != "tests/custom/tests.yaml":<br>-            print "Failed to open %s" % path<br>+            print("Failed to open %s" % path)<br>         return None<br>     except:<br>-        print "Unexpected error: %s" % sys.exc_info()[0]<br>+        print("Unexpected error: %s" % sys.exc_info()[0])<br>         return None<br> <br>     config = yaml.load(f)<br>@@ -108,11 +108,11 @@<br> <br> <br> def main(argv=None):<br>-    print "Testsuite Module Usage and Coverage Report"<br>-    print ""<br>+    print("Testsuite Module Usage and Coverage Report")<br>+    print("")<br>     test_suite = TestSuite()<br>-    print "Number of tests:", len(test_suite.tests)<br>-    print ""<br>+    print("Number of tests:", len(test_suite.tests))<br>+    print("")<br>     test_suite.results_for('tags')<br>     test_suite.results_for('test_objects')<br>     test_suite.results_for('test_modules')<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/9314">change 9314</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/9314"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: testsuite </div>
<div style="display:none"> Gerrit-Branch: 15 </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: If76c2d3e11e4ab4552d0df7841287c8bb2de7918 </div>
<div style="display:none"> Gerrit-Change-Number: 9314 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Corey Farrell <git@cfware.com> </div>