[svn-commits] dlee: branch dlee/ari-tests r3842 - in /asterisk/team/dlee/ari-tests: lib/pyt...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Tue Jun 11 15:48:02 CDT 2013


Author: dlee
Date: Tue Jun 11 15:48:01 2013
New Revision: 3842

URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=3842
Log:
Test passes

Modified:
    asterisk/team/dlee/ari-tests/lib/python/asterisk/TestRunner.py
    asterisk/team/dlee/ari-tests/lib/python/asterisk/ari.py
    asterisk/team/dlee/ari-tests/tests/rest_api/continue/configs/ast1/extensions.conf
    asterisk/team/dlee/ari-tests/tests/rest_api/continue/rest_continue.py
    asterisk/team/dlee/ari-tests/tests/rest_api/continue/test-config.yaml

Modified: asterisk/team/dlee/ari-tests/lib/python/asterisk/TestRunner.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-tests/lib/python/asterisk/TestRunner.py?view=diff&rev=3842&r1=3841&r2=3842
==============================================================================
--- asterisk/team/dlee/ari-tests/lib/python/asterisk/TestRunner.py (original)
+++ asterisk/team/dlee/ari-tests/lib/python/asterisk/TestRunner.py Tue Jun 11 15:48:01 2013
@@ -48,7 +48,7 @@
         for path in search_paths:
             if os.path.exists('%s/%s.py' % (path, fullname)):
                 return TestModuleLoader(path)
-        LOGGER.warn("Unable to find module '%s'" % fullname)
+        LOGGER.debug("Unable to find module '%s'" % fullname)
         return None
 
 class TestModuleLoader(object):

Modified: asterisk/team/dlee/ari-tests/lib/python/asterisk/ari.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-tests/lib/python/asterisk/ari.py?view=diff&rev=3842&r1=3841&r2=3842
==============================================================================
--- asterisk/team/dlee/ari-tests/lib/python/asterisk/ari.py (original)
+++ asterisk/team/dlee/ari-tests/lib/python/asterisk/ari.py Tue Jun 11 15:48:01 2013
@@ -6,10 +6,18 @@
 the GNU General Public License Version 2.
 '''
 
+import datetime
+import json
 import logging
+import re
+import requests
 import time
+import traceback
 import urllib
-import websocket
+
+from twisted.internet import reactor
+from autobahn.websocket import WebSocketClientFactory, \
+    WebSocketClientProtocol, connectWS
 
 logger = logging.getLogger(__name__)
 
@@ -22,51 +30,186 @@
         return self.min <= v <= self.max
 
 def decode_range(v):
-    if isinstance(v, int):
+    if v is None:
+        # Unspecified; recieve at least one
+        return Range(1, float("inf"))
+    elif isinstance(v, int):
+        # Need exactly this many events
         return Range(v, v)
-    elif count[0] == '<':
+    elif v[0] == '<':
         # Need at most this many events
-        self.count_min = 0
-        self.count_max = int(count[1:])
-    elif count[0] == '>':
+        return Range(0, int(v[1:]))
+    elif v[0] == '>':
         # Need at least this many events
-        self.count_min = int(count[1:])
-        self.count_max = float("inf")
+        return Range(int(v[1:]), float("inf"))
     else:
         # Need exactly this many events
-        self.count_min = int(count)
-        self.count_max = int(count)
+        return Range(int(v), int(v))
 
 class EventMatcher(object):
-    def __init__(self, instance_config, test_object):
+    def __init__(self, ari, instance_config, test_object):
+        self.ari = ari
         self.instance_config = instance_config
         self.test_object = test_object
+        self.conditions = self.instance_config['conditions']
+        self.count_range = decode_range(self.instance_config.get('count'))
+        self.count = 0
+        self.passed = True
+        callback = self.instance_config.get('callback')
+        if callback:
+            module = __import__(callback['module'])
+            self.callback = getattr(module, callback['method'])
+        else:
+            # No callback; just use a no-op
+            self.callback = lambda **kwargs: None
+
+        test_object.register_stop_observer(self.on_stop)
+
+    def on_event(self, message):
+        if self.matches(message):
+            self.count += 1
+            # Split call and accumulation to always call the callback
+            try:
+                res = self.callback(self.ari, message)
+                if not res:
+                    logger.error("Callback failed: %r" %
+                                 self.instance_config)
+                self.passed = self.passed and res
+            except:
+                logger.error("Exception in callback: %s" % traceback.format_exc())
+                self.passed = False
+
+    def on_stop(self, *args):
+        if not self.count_range.contains(self.count):
+            logger.error("Expected %d <= count <= %d; was %d (%r)",
+                         self.count_range.min, self.count_range.max,
+                         self.count, self.conditions)
+            self.passed = False
+        self.test_object.set_passed(self.passed)
+
+    def matches(self, message):
+        # Validate the match
+        match = self.conditions.get('match')
+        res = all_match(match, message)
+
+        # Now validate the nomatch, if it's there
+        nomatch = self.conditions.get('nomatch')
+        if res and nomatch:
+            res = not all_match(nomatch, message)
+        return res
+
+def all_match(pattern, message):
+    #logger.debug("%r ?= %r" % (pattern, message))
+    if pattern is None:
+        return True
+    elif isinstance(pattern, list):
+        res = len(pattern) == len(message)
+        i = 0
+        while res and i < len(pattern):
+            res = all_match(pattern[i], message[i])
+        return res
+    elif isinstance(pattern, dict):
+        for key, value in pattern.iteritems():
+            to_check = message.get(key)
+            if to_check is None or not all_match(value, to_check):
+                return False
+        return True
+    elif isinstance(pattern, str):
+        return re.match(pattern, message) is not None
+    elif isinstance(pattern, int):
+        return pattern == message
+    else:
+        logger.error("Unhandled pattern type %s" % type(pattern)).__name__
+
+
+class StasisClientProtocol(WebSocketClientProtocol):
+    def __init__(self, on_event):
+        self.on_event = on_event
+
+    def onOpen(self):
+        logger.debug("onOpen()")
+
+    def onClose(self, wasClean, code, reason):
+        logger.debug("onClose(%r, %d, %s)" % (wasClean, code, reason))
+        reactor.callLater(1, self.factory.reconnect)
+
+    def onMessage(self, msg, binary):
+        self.on_event(json.loads(msg))
+
+
+class StasisClientFactory(WebSocketClientFactory):
+    def __init__(self, host, port, apps, on_event, timeout_secs=15):
+        url = "ws://%s:%d/ws?%s" % \
+              (host, port, urllib.urlencode({'app': apps}))
+        WebSocketClientFactory.__init__(self, url, protocols=["stasis"])
+        self.on_event = on_event
+        self.timeout_secs = timeout_secs
+        self.protocol = self.__build_protocol
+        self.attempts = 0
+        self.start = None
+
+        self.reconnect()
+
+    def __build_protocol(self):
+        return StasisClientProtocol(self.on_event)
+
+    def clientConnectionFailed(self, connector, reason):
+        logger.info("clientConnectionFailed(%s)" % (reason))
+        reactor.callLater(1, self.reconnect)
+
+    def reconnect(self):
+        self.attempts += 1
+        logger.debug("WebSocket attempt #%d" % self.attempts)
+        if not self.start:
+            self.start = datetime.datetime.now()
+        runtime = (datetime.datetime.now() - self.start).seconds
+        if runtime >= self.timeout_secs:
+            logger.error("  Giving up after %d seconds" % self.timeout_secs)
+        else:
+            connectWS(self)
+
+
+class ARI(object):
+    def __init__(self, host, port):
+        self.base_url = "http://%s:%d/stasis" % (host, port)
+
+    def build_url(self, *args, **kwargs):
+        url = '/'.join([self.base_url] + list(args))
+        params = urllib.urlencode(kwargs)
+        return "%s?%s" % (url, params)
+
+    def get(self, *args, **kwargs):
+        url = self.build_url(*args, **kwargs)
+        logger.info("GET %s" % url)
+        return requests.get()
+
+    def post(self, *args, **kwargs):
+        url = self.build_url(*args, **kwargs)
+        logger.info("POST %s" % url)
+        return requests.post(url)
+
+    def delete(self, *args, **kwargs):
+        url = self.build_url(*args, **kwargs)
+        logger.info("DELETE %s" % url)
+        return requests.delete(url)
+
 
 class WebSocketEventModule(object):
     def __init__(self, module_config, test_object):
+        logger.info("WebSocketEventModule ctor")
+        self.host = '127.0.0.1'
+        self.port = 8088
         self.test_object = test_object
-
-        self.events = [
-            EventMatcher(e, test_object) for e in module_config['events']]
-
+        self.ari = ARI(self.host, self.port)
+        self.event_matchers = [
+            EventMatcher(self.ari, e, test_object)
+            for e in module_config['events']]
         apps = module_config['apps']
         if isinstance(apps, list):
             apps = ','.join(apps)
-        path = "ws://127.0.0.1:8088/ws"
-        params = urllib.urlencode({'app': apps})
-        url = "%s?%s" % (path, params)
-
-        tries = 1
-        while True:
-            try:
-                self.ws = websocket.create_connection(
-                    url, header=["Sec-WebSocket-Protocol: stasis"])
-            except:
-                if tries > 10:
-                    logger.error("WebSocket connection failed.")
-                    break
-                # Wait a bit and try again
-                logger.info("WebSocket connection failed. Retrying...")
-                tries += 1
-                time.sleep(1)
-
+        self.factory = StasisClientFactory(host=self.host, port=8088, apps=apps,
+                                           on_event=self.on_event)
+
+    def on_event(self, event):
+        for matcher in self.event_matchers:
+            matcher.on_event(event)

Modified: asterisk/team/dlee/ari-tests/tests/rest_api/continue/configs/ast1/extensions.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-tests/tests/rest_api/continue/configs/ast1/extensions.conf?view=diff&rev=3842&r1=3841&r2=3842
==============================================================================
--- asterisk/team/dlee/ari-tests/tests/rest_api/continue/configs/ast1/extensions.conf (original)
+++ asterisk/team/dlee/ari-tests/tests/rest_api/continue/configs/ast1/extensions.conf Tue Jun 11 15:48:01 2013
@@ -2,6 +2,6 @@
 
 exten => s,1,NoOp()
 	same => n,Answer()
-#	same => n,Stasis(continue-test)
+	same => n,Stasis(continue-test)
 	same => n,UserEvent(TestResult,Result: pass,Status: successfully broke out of Stasis)
 	same => n,Hangup()

Modified: asterisk/team/dlee/ari-tests/tests/rest_api/continue/rest_continue.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-tests/tests/rest_api/continue/rest_continue.py?view=diff&rev=3842&r1=3841&r2=3842
==============================================================================
--- asterisk/team/dlee/ari-tests/tests/rest_api/continue/rest_continue.py (original)
+++ asterisk/team/dlee/ari-tests/tests/rest_api/continue/rest_continue.py Tue Jun 11 15:48:01 2013
@@ -8,16 +8,19 @@
 
 import logging
 
-LOGGER = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
 
 id = None
 
 def on_start(ari, event):
+    logger.debug("rest_continue.on_start(%r)" % event)
     global id
-    id = event['channel']['uniqueid']
-    ari.resource('channels', id, 'continue').post
+    id = event['stasis_start']['channel']['uniqueid']
+    resp = ari.post('channels', id, 'continue')
+    resp.raise_for_status()
     return True
 
 def on_end(ari, event):
+    logger.debug("rest_continue.on_end(%r)" % event)
     global id
-    return id == event['channel']['uniqueid']
+    return id == event['stasis_end']['channel']['uniqueid']

Modified: asterisk/team/dlee/ari-tests/tests/rest_api/continue/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/team/dlee/ari-tests/tests/rest_api/continue/test-config.yaml?view=diff&rev=3842&r1=3841&r2=3842
==============================================================================
--- asterisk/team/dlee/ari-tests/tests/rest_api/continue/test-config.yaml (original)
+++ asterisk/team/dlee/ari-tests/tests/rest_api/continue/test-config.yaml Tue Jun 11 15:48:01 2013
@@ -24,17 +24,19 @@
 ari-config:
     apps: continue-test
     events:
-        -   match:
-                application: continue-test
-                stasis_start:
-                    args: []
+        -   conditions:
+                match:
+                    application: continue-test
+                    stasis_start:
+                        args: []
             count: 1
             callback:
                 module: rest_continue
                 method: on_start
-        -   match:
-                application: continue-test
-                stasis_end:
+        -   conditions:
+                match:
+                    application: continue-test
+                    stasis_end:
             count: 1
             callback:
                 module: rest_continue
@@ -53,7 +55,7 @@
 properties:
     minversion: '12.0.0'
     dependencies:
-        - python : websocket
+        - python : autobahn.websocket
         - python : requests
         - python : twisted
         - python : starpy




More information about the svn-commits mailing list