[asterisk-commits] mmichelson: testsuite/asterisk/trunk r5145 - in /asterisk/trunk: lib/python/a...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Jun 19 16:34:56 CDT 2014


Author: mmichelson
Date: Thu Jun 19 16:34:48 2014
New Revision: 5145

URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=5145
Log:
Add realtime pluggable test module and baseline realtime tests.

Review: https://reviewboard.asterisk.org/r/3363


Added:
    asterisk/trunk/lib/python/asterisk/realtime_test_module.py   (with props)
    asterisk/trunk/tests/realtime/
    asterisk/trunk/tests/realtime/destroy/
    asterisk/trunk/tests/realtime/destroy/configs/
    asterisk/trunk/tests/realtime/destroy/configs/ast1/
    asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf   (with props)
    asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf   (with props)
    asterisk/trunk/tests/realtime/destroy/destroy.py   (with props)
    asterisk/trunk/tests/realtime/destroy/test-config.yaml   (with props)
    asterisk/trunk/tests/realtime/multi/
    asterisk/trunk/tests/realtime/multi/configs/
    asterisk/trunk/tests/realtime/multi/configs/ast1/
    asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf   (with props)
    asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf   (with props)
    asterisk/trunk/tests/realtime/multi/multi.py   (with props)
    asterisk/trunk/tests/realtime/multi/test-config.yaml   (with props)
    asterisk/trunk/tests/realtime/single/
    asterisk/trunk/tests/realtime/single/configs/
    asterisk/trunk/tests/realtime/single/configs/ast1/
    asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf   (with props)
    asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf   (with props)
    asterisk/trunk/tests/realtime/single/single.py   (with props)
    asterisk/trunk/tests/realtime/single/test-config.yaml   (with props)
    asterisk/trunk/tests/realtime/static/
    asterisk/trunk/tests/realtime/static/configs/
    asterisk/trunk/tests/realtime/static/configs/ast1/
    asterisk/trunk/tests/realtime/static/configs/ast1/extconfig.conf   (with props)
    asterisk/trunk/tests/realtime/static/configs/ast1/modules.conf   (with props)
    asterisk/trunk/tests/realtime/static/static.py   (with props)
    asterisk/trunk/tests/realtime/static/test-config.yaml   (with props)
    asterisk/trunk/tests/realtime/store/
    asterisk/trunk/tests/realtime/store/configs/
    asterisk/trunk/tests/realtime/store/configs/ast1/
    asterisk/trunk/tests/realtime/store/configs/ast1/extconfig.conf   (with props)
    asterisk/trunk/tests/realtime/store/configs/ast1/sorcery.conf   (with props)
    asterisk/trunk/tests/realtime/store/store.py   (with props)
    asterisk/trunk/tests/realtime/store/test-config.yaml   (with props)
    asterisk/trunk/tests/realtime/tests.yaml   (with props)
    asterisk/trunk/tests/realtime/update/
    asterisk/trunk/tests/realtime/update/configs/
    asterisk/trunk/tests/realtime/update/configs/ast1/
    asterisk/trunk/tests/realtime/update/configs/ast1/extconfig.conf   (with props)
    asterisk/trunk/tests/realtime/update/configs/ast1/sorcery.conf   (with props)
    asterisk/trunk/tests/realtime/update/test-config.yaml   (with props)
    asterisk/trunk/tests/realtime/update/update.py   (with props)
Modified:
    asterisk/trunk/tests/tests.yaml

Added: asterisk/trunk/lib/python/asterisk/realtime_test_module.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/lib/python/asterisk/realtime_test_module.py?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/lib/python/asterisk/realtime_test_module.py (added)
+++ asterisk/trunk/lib/python/asterisk/realtime_test_module.py Thu Jun 19 16:34:48 2014
@@ -1,0 +1,511 @@
+#!/usr/bin/env python
+"""
+Copyright (C) 2014, Digium, Inc.
+Mark Michelson <mmichelson at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+import logging
+import sys
+import cgi
+import re
+
+from twisted.internet import reactor
+from twisted.internet import error
+from twisted.web.server import Site
+from twisted.web.resource import Resource, NoResource
+
+LOGGER = logging.getLogger(__name__)
+
+THIS_MODULE = sys.modules[__name__]
+
+
+class RealtimeData(object):
+    """This class holds all of the data that is being stored in realtime. The
+    class cotains a dictionary of "tables" at the top level keyed on the
+    table's name. Each table consists of "rows" represented by a list of
+    dictionaries.
+
+    All variables are stored as strings since that is how they get passed in by
+    Asterisk and that is the format in which they are returned to Asterisk.
+    This means that type-checking on this sort of realtime backend is non-
+    existent, but since this is only being used in a test environment, I don't
+    care.
+    """
+    def __init__(self):
+        self.tables = {}
+
+    def add_rows(self, table_name, rows):
+        """Add multiple rows to a table.
+
+        :param table_name: String representing table to update.
+        :param rows: List of dictionaries to add to the table.
+        """
+        try:
+            self.tables[table_name].extend(rows)
+        except KeyError:
+            self.tables[table_name] = rows
+
+    def add_row(self, table_name, row):
+        """Add a single row to a table.
+
+        :param table_name: String representing table to update.
+        :param row: Dictionary to add to the table.
+        """
+        self.add_rows(table_name, [row])
+
+    def retrieve_row(self, table_name, where):
+        """Retrieve a single row from a table.
+
+        :param table_name: String representing table to update.
+        :param where: Dictionary used to determine which row to retrieve.
+
+        :returns: Row which matches the given input. If multiple rows match,
+        then only the first is returned. If no rows match, an empty row is
+        returned.
+
+        :raises: KeyError if a table does not exist with name table_name.
+        """
+        rows = self.retrieve_rows(table_name, where)
+        if rows:
+            LOGGER.debug("Retrieved rows %s" % rows)
+            return rows[0]
+        else:
+            return {}
+
+    def _filter_rows(self, table, where):
+        """Internal method used to determine rows affected by an operation
+
+        :param table: The table on which to apply the where clause (not the
+         table name).
+        :param where: A dictionary of key/value pairs used to filter rows from
+         the table.
+        :returns: A list of rows that match the given input.
+
+        If a value is empty in the where clause, then it matches all rows in
+        the table that have the specified key.
+        """
+        LOGGER.debug("Table has %s" % table)
+        LOGGER.debug("Searching table for %s" % where)
+
+        return [row for row in table
+                if all(key in row and re.match(value, row[key])
+                       for key, value in where.iteritems())]
+
+    def retrieve_rows(self, table_name, where):
+        """Retrieve multiple rows from a table.
+
+        :param table_name: String representing table to update.
+        :param where: Dictionary that determines which rows to retrieve.
+        :returns: List of rows that match the given input.
+        :raises: Keyerror if a table does not exist with name table_name
+        """
+        return self._filter_rows(self.tables[table_name], where)
+
+    def update_rows(self, table_name, where, update):
+        """Update row data in a table.
+
+        :param table_name: String representing table to update.
+        :param where: Dictionary that determines which rows to update.
+        :param update: Dictionary with new values to apply to affected rows.
+        :returns: number of rows that have been updated.
+        :raises: KeyError if a table does not exist with name table_name.
+        """
+        to_update = self._filter_rows(self.tables[table_name], where)
+
+        for item in to_update:
+            item.update(update)
+
+        LOGGER.debug("After update, table has %s" % self.tables[table_name])
+
+        return len(to_update)
+
+    def delete_rows(self, table_name, where):
+        """Delete rows from a table.
+        :param table_name: String representing table to delete from.
+        :param where: Dictionary that determines which rows to delete.
+        :returns: number of rows deleted.
+        :raises: KeyError if a table does not exist with name table_name.
+        """
+        LOGGER.debug("Being told to remove where %s" % where)
+        to_delete = self._filter_rows(self.tables[table_name], where)
+        LOGGER.debug("Items to delete are %s" % to_delete)
+        self.tables[table_name] = [row for row in self.tables[table_name]
+                                   if row not in to_delete]
+        return len(to_delete)
+
+
+class RootResource(Resource):
+    """Resource provided by the root of our HTTP server.
+    """
+    def __init__(self, rt_data):
+        Resource.__init__(self)
+        self.rt_data = rt_data
+        LOGGER.debug("Creating RootResource")
+
+    def getChild(self, name, request):
+        """Get child resource for site root.
+        :param name: Name of requested child resource.
+        :param request: incoming HTTP request.
+        :returns: Table resource for the given name.
+
+        When Asterisk makes an HTTP request, Twisted will initially call into
+        this resource in order to be given the proper resource given the path
+        in the URL. The name requested is expected to be one of the realtime
+        tables, so we create a TableResource representing that table. The
+        TableResource is then called into to get the resource relating to the
+        operation to perform on the table.
+        """
+        LOGGER.debug("Asking root for child %s", name)
+        return TableResource(name, self.rt_data)
+
+
+class TableResource(Resource):
+    """Resource for a specific table on our HTTP server.
+    """
+    def __init__(self, table_name, rt_data):
+        Resource.__init__(self)
+        self.table_name = table_name
+        self.rt_data = rt_data
+        LOGGER.debug("Creating TableResource for %s", table_name)
+
+    def getChild(self, name, request):
+        """Get child resource for a table
+        :param name: Name of requested child resource.
+        :param request: Incoming HTTP request.
+        :returns: LeafResource subclass.
+
+        Twisted automatically calls this when trying to get a requested
+        resource. The expected names of the resources are one of
+
+        * single
+        * multi
+        * update
+        * store
+        * destroy
+        * require
+        * static
+
+        Each of these corresponds to a resource class in this module. We use
+        reflection here to generate the requested resource dynamically. Each of
+        these resources represents an operation to perform on the table.
+        """
+        try:
+            return getattr(THIS_MODULE, "_" + name + "Resource")(
+                self.table_name, self.rt_data
+            )
+        except AttributeError as ex:
+            msg = "Error retrieving resource %s/%s: %s" % \
+                (self.table_name, name, ex)
+            LOGGER.error(msg)
+            return NoResource(message=msg)
+
+
+class LeafResource(Resource):
+    """Base class for leaf resources.
+
+    A leaf resource is one that has the isLeaf property set. This means that
+    there are no children for this resource in the hierarchy.
+
+    All resources representing table operations are subclasses of this
+    LeafResource since they all have no child resources. The LeafResource class
+    provides common operations that its children require, mostly pertaining to
+    manipulation of request data.
+    """
+    isLeaf = True
+
+    def __init__(self, table_name, rt_data):
+        Resource.__init__(self)
+        self.table_name = table_name
+        self.rt_data = rt_data
+        LOGGER.debug("Constructing LeafResource")
+
+    def unpack_args(self, args):
+        """Unpack lists used a args values.
+        :param args: Dictionary of key-list pairs to unpack
+        :returns: Same dictionary with values unpacked
+
+        A request object presents the URL and POST parameters as a dictionary
+        of the form {'param_name': [values]}. The values are placed in a list
+        to accommodate repeated parameter names that may appear in requests.
+        Since Asterisk does not repeat parameters, we are safe to unpack the
+        list.
+
+        Example input: {'foo': ['cat'], 'bar': ['dog']}
+        Example output: {'foo': 'cat', 'bar': 'dog'}
+
+        This method would be more efficient if we were using Python 2.7+
+        since we could use a dict comprehension.
+        """
+        filtered_args = {}
+        for key, values in args.iteritems():
+            if " LIKE" in key:
+                # Strip away " LIKE" and % from values
+                filtered_args[key[:-5]] = [val.replace('%', '.*')
+                                           for val in values]
+            else:
+                filtered_args[key] = values
+
+        LOGGER.debug('filtered args is %s' % filtered_args)
+
+        return dict((key, values[0] if values else '.*') for key, values in
+                    filtered_args.iteritems())
+
+    def encode_row(self, row):
+        """Encode a retrieved row for an HTTP response.
+        :param row: Dictionary to format for HTTP response body.
+        :returns: String of key=values.
+
+        Example input: {'foo': 'cat, 'bar': 'dog', 'baz: 'donkey'}
+        Example output: 'foo=cat&bar=dog&baz=donkey'
+        """
+        string = '&'.join(['{0}={1}'.format(cgi.escape(key), cgi.escape(val))
+                           for key, val in row.iteritems()])
+        LOGGER.debug("Returning response %s" % string)
+        return string
+
+    def encode_multi_row(self, rows):
+        """Encode multiple rows for an HTTP response.
+        :param rows: List of dictionaries to encode in HTTP response body.
+        :returns: Encoded rows, each row separated by a carriage return line
+         feed.
+        """
+        string = '\r\n'.join([self.encode_row(row) for row in rows])
+        LOGGER.debug("Returning response %s" % string)
+        return string
+
+    def return_404(self, request):
+        """Return a 404 HTTP response.
+        :param request: The request to which we are responding.
+        :returns: NoResource's rendering of the request.
+
+        Performed if a request tries to access a nonexistent table.
+        """
+        page = NoResource(message="Table %s could not be found" %
+                          self.table_name)
+        return page.render(request)
+
+
+class _singleResource(LeafResource):
+    """Resource for retrieving single record.
+
+    This retrieves a single row of data from the
+    realtime data.
+    """
+    def render_POST(self, request):
+        """Handle the POST method.
+        :param request: HTTP POST request.
+        :returns: Single encoded row of data.
+
+        POST parameters are used to determine which row to retrieve from the
+        database.
+        """
+        LOGGER.debug("Asked to render_POST in the single resource")
+        LOGGER.debug("request.args is %s" % request.args)
+        try:
+            return self.encode_row(self.rt_data.retrieve_row(self.table_name,
+                                   self.unpack_args(request.args)))
+        except KeyError:
+            return self.return_404(request)
+
+
+class _multiResource(LeafResource):
+    """Resource for retrieving multiple records.
+
+    This retrieves multiple rows of data from the realtime data.
+    """
+    def render_POST(self, request):
+        """Handle the POST method.
+        :param request: HTTP POST request.
+        :returns: Multiple encoded rows.
+
+        POST parameters are used to determine which rows to retrieve from the
+        realtime data.
+
+        This is an abstraction where Asterisk does a poor job of being
+        amenable to non-database sources. Sending parameters like
+        "id LIKE=%" means we have some work cut out for us in order to be able
+        to successfully retrieve values.
+        """
+        # If this were python 2.7+ we could use a dict comprehension here
+        LOGGER.debug("Asked to render POST on the multi resource")
+        LOGGER.debug("Multi URI: %s" % request.uri)
+        try:
+            return self.encode_multi_row(
+                self.rt_data.retrieve_rows(self.table_name,
+                                           self.unpack_args(request.args)))
+        except KeyError:
+            return self.return_404(request)
+
+
+class _updateResource(LeafResource):
+    """Resource for updating records.
+    """
+    def render_POST(self, request):
+        """Handle the POST method.
+        :param request: HTTP POST request.
+        :returns: The number of rows updated by the request.
+
+        The URL parameters determine which objects to update, and the POST
+        parameters determine which object fields to update.
+
+        Twisted makes this a bit difficult since it combines the URL and POST
+        parameters into a single dictionary. So what we have to do is parse the
+        URL parameters out of the URL and then remove those from the dictionary
+        that Twisted gives us. With the parameters separated out, the update
+        operation can proceed.
+        """
+        LOGGER.debug("Asked to render POST in the update resource")
+
+        _, _, uri_params = request.uri.rpartition('?')
+        # The odd slice notation gets the first and third items from the tuple
+        # returned from item.partition
+        uri_args = dict(item.partition('=')[::2] for item in
+                        uri_params.split('&'))
+
+        # The dict() created as the argument to unpack args is essentially the
+        # set difference between request.args and uri_args.
+        post_args = self.unpack_args(dict((key, request.args[key])
+                                     for key in request.args
+                                     if key not in uri_args))
+
+        LOGGER.debug("URI args: %s" % uri_args)
+        LOGGER.debug("POST args: %s" % post_args)
+        try:
+            affected = self.rt_data.update_rows(self.table_name, uri_args,
+                                                post_args)
+        except KeyError:
+            return self.return_404(request)
+        else:
+            return str(affected)
+
+
+class _storeResource(LeafResource):
+    """Resource for handling storing into realtime
+    """
+    def render_POST(self, request):
+        """Handle HTTP POST.
+        :param request: Incoming HTTP POST.
+        :returns: "1"
+
+        This is supposed to return the number of rows inserted, but since this
+        is only ever used to insert a single row, and insertion never fails,
+        this always returns "1".
+        """
+        LOGGER.debug("Asked to render POST in the store resource")
+        self.rt_data.add_row(
+            self.table_name, self.unpack_args(request.args)
+        )
+        return "1"
+
+
+class _destroyResource(LeafResource):
+    """Resource for handling destroying realtime data
+    """
+    def render_POST(self, request):
+        """Handle HTTP POST.
+        :param request: Incoming HTTP POST.
+        :returns: the number rows deleted.
+        """
+        LOGGER.debug("Asked to render POST in the destroy resource")
+        try:
+            affected = self.rt_data.delete_rows(
+                self.table_name, self.unpack_args(request.args)
+            )
+        except KeyError:
+            return self.return_404(request)
+        else:
+            return str(affected)
+
+
+class _requireResource(LeafResource):
+    """Resource for handling realtime requirements
+    """
+    def render_GET(self, request):
+        """Handle HTTP GET.
+        :param request: Incoming HTTP GET.
+        :returns: "0"
+
+        This is used to determine if the types being used for specific
+        realtime fields are correct. Since we store everything as a string, we
+        don't really care and rely on user input to store types that make sense
+        for the given tables. Returning a "0" body indicates we satisfy all
+        requirements.
+        """
+        return "0"
+
+
+class _staticResource(LeafResource):
+    """Resource used for static realtime.
+    """
+    def render_GET(self, request):
+        """Handle HTTP GET.
+        :param request: Incoming HTTP GET.
+        :returns: All matching records
+
+        Filename is passed in as URL parameter.
+        """
+        LOGGER.debug("Asked to render GET in the static resource")
+        # Forego a more complicated unpack since the file is the only thing we
+        # care about
+        where = {'filename': request.args['file'][0], 'commented': '0'}
+        try:
+            return self.encode_multi_row(
+                self.rt_data.retrieve_rows(self.table_name, where)
+            )
+        except KeyError:
+            return self.return_404(request)
+
+
+class RealtimeTestModule(object):
+    """Test module for realtime database tests.
+
+    This module uses an HTTP server to listen for requests in order to retrieve
+    or modify stored data. The actual data being retrieved is stored in an
+    instance of the RealtimeData class.
+    """
+    def __init__(self, module_config, test_object):
+        self.test_object = test_object
+        self.module_config = module_config
+        self.rt_data = RealtimeData()
+        self.test_object.register_ami_observer(self._ami_connect)
+
+        self.populate_rt_data(module_config.get('data'))
+        self.setup_http()
+
+    def populate_rt_data(self, data):
+        """Populate the database with an initial set of data.
+        :data: yaml dictionary that mimicks the RealtimeData format
+        """
+        if not data:
+            return
+
+        for table_name, rows in data.iteritems():
+            self.rt_data.add_rows(table_name, rows)
+
+    def setup_http(self):
+        """Create Twisted HTTP server.
+
+        We supply Twisted with a root resource to call into for all HTTP
+        requests made. The root resource is then responsible for dynamically
+        creating resources to handle specific requests
+        """
+        resource = RootResource(self.rt_data)
+        factory = Site(resource)
+        reactor.listenTCP(46821, factory)
+
+    def _ami_connect(self, ami):
+        """Callback for when AMI connects.
+
+        This is the launching point for the actual meat of each test. This
+        calls into a configuration-specified method, supplying the realtime
+        data.
+        """
+        if 'entry-method' not in self.module_config or \
+                'entry_module' not in self.module_config:
+            return
+
+        module = __import__(self.module_config['entry_module'])
+        method = getattr(module, self.module_config['entry_method'])
+        method(self.rt_data, self.test_object, ami)

Propchange: asterisk/trunk/lib/python/asterisk/realtime_test_module.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/lib/python/asterisk/realtime_test_module.py
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/lib/python/asterisk/realtime_test_module.py
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf (added)
+++ asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf Thu Jun 19 16:34:48 2014
@@ -1,0 +1,2 @@
+[settings]
+mailboxes => curl,http://localhost:8000/mwi

Propchange: asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/destroy/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf (added)
+++ asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf Thu Jun 19 16:34:48 2014
@@ -1,0 +1,2 @@
+[res_mwi_external]
+mailboxes=realtime,mailboxes

Propchange: asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/destroy/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/destroy/destroy.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/destroy/destroy.py?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/destroy/destroy.py (added)
+++ asterisk/trunk/tests/realtime/destroy/destroy.py Thu Jun 19 16:34:48 2014
@@ -1,0 +1,45 @@
+#!/usr/bin/env python
+
+import logging
+
+LOGGER = logging.getLogger(__name__)
+
+
+class DestroyTest(object):
+    def __init__(self, rt_data, test_object, ami):
+        self.rt_data = rt_data
+        self.test_object = test_object
+        self.ami = ami
+
+    def fail_test(self):
+        self.test_object.set_passed(False)
+        self.test_object.stop_reactor()
+
+    def pass_test(self):
+        self.test_object.set_passed(True)
+        self.test_object.stop_reactor()
+
+    def mwi_callback(self, ami, event):
+        LOGGER.debug("Received MWI event %s" % event)
+
+        event_mailbox = event.get('mailbox')
+
+        query = dict(id=event_mailbox)
+        rt_row = self.rt_data.retrieve_row('mwi', query)
+        if rt_row:
+            LOGGER.error("Mailbox %s in AMI event still exists in realtime" %
+                         event_mailbox)
+            self.fail_test()
+
+        self.pass_test()
+
+
+def check_it(rt_data, test_object, ami):
+    test = DestroyTest(rt_data, test_object, ami)
+
+    ami.registerEvent('MessageWaiting', test.mwi_callback)
+    message = {
+        'Action': 'MWIDelete',
+        'Mailbox': 'Dazed',
+    }
+    ami.sendMessage(message)

Propchange: asterisk/trunk/tests/realtime/destroy/destroy.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/destroy/destroy.py
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/destroy/destroy.py
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/destroy/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/destroy/test-config.yaml?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/destroy/test-config.yaml (added)
+++ asterisk/trunk/tests/realtime/destroy/test-config.yaml Thu Jun 19 16:34:48 2014
@@ -1,0 +1,43 @@
+testinfo:
+    summary:     'Realtime destroy test'
+    description: |
+        'This tests the ability of Asterisk to destroy an single entry from realtime.
+         An MWI mailbox is preloaded in realtime, and Asterisk is tasked with
+         deleting the mailbox when asked.'
+
+
+properties:
+    minversion: '12.0.0'
+    dependencies:
+        - python: 'twisted'
+        - python: 'starpy'
+        - asterisk: 'res_mwi_external'
+        - asterisk: 'res_mwi_external_ami'
+        - asterisk: 'res_config_curl'
+        - asterisk: 'func_curl'
+    tags:
+        - realtime
+        - mwi_external
+
+test-modules:
+    add-test-to-search-path: 'True'
+    test-object:
+        config-section: test-case-config
+        typename: 'test_case.TestCaseModule'
+    modules:
+        -
+            config-section: 'realtime-config'
+            typename: 'realtime_test_module.RealtimeTestModule'
+
+test-case-config:
+    connect-ami: 'True'
+
+realtime-config:
+    entry_module: 'destroy'
+    entry_method: 'check_it'
+    data:
+        mwi:
+            -
+                id: 'Dazed'
+                msgs_new: '5'
+                msgs_old: '4'

Propchange: asterisk/trunk/tests/realtime/destroy/test-config.yaml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/destroy/test-config.yaml
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/destroy/test-config.yaml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf (added)
+++ asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf Thu Jun 19 16:34:48 2014
@@ -1,0 +1,2 @@
+[settings]
+mailboxes => curl,http://localhost:8000/mwi

Propchange: asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/multi/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf (added)
+++ asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf Thu Jun 19 16:34:48 2014
@@ -1,0 +1,2 @@
+[res_mwi_external]
+mailboxes=realtime,mailboxes

Propchange: asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/multi/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/multi/multi.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/multi/multi.py?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/multi/multi.py (added)
+++ asterisk/trunk/tests/realtime/multi/multi.py Thu Jun 19 16:34:48 2014
@@ -1,0 +1,82 @@
+#!/usr/bin/env python
+
+import logging
+
+LOGGER = logging.getLogger(__name__)
+
+EXPECTED_MAILBOXES = 2
+# Values from test-config.yaml
+MAILBOXES = [
+    {'mailbox': 'Dazed', 'new': '5', 'old': '4'},
+    {'mailbox': 'Confused', 'new': '6', 'old': '8'}
+]
+
+
+class MultiTest(object):
+    def __init__(self, rt_data, test_object, ami):
+        self.rt_data = rt_data
+        self.test_object = test_object
+        self.ami = ami
+        self.retrieved_mailboxes = 0
+
+    def fail_test(self):
+        self.test_object.set_passed(False)
+        self.test_object.stop_reactor()
+
+    def pass_test(self):
+        self.test_object.set_passed(True)
+        self.test_object.stop_reactor()
+
+    def mwi_get_callback(self, ami, event):
+        LOGGER.debug("Received MWI event %s" % event)
+        self.retrieved_mailboxes += 1
+
+        event_mailbox = event.get('mailbox')
+        event_new = event.get('newmessages')
+        event_old = event.get('oldmessages')
+
+        check_mailbox = dict(mailbox=event_mailbox, new=event_new,
+                             old=event_old)
+
+        if check_mailbox not in MAILBOXES:
+            LOGGER.error("Unexpected values in MWIGet event")
+            self.fail_test()
+
+        query = dict(id=event_mailbox)
+        rt_row = self.rt_data.retrieve_row('mwi', query)
+        if not rt_row:
+            LOGGER.error("Mailbox %s in AMI event is not in realtime" %
+                         event_mailbox)
+            self.fail_test()
+        LOGGER.debug("Row retrieved from realtime: %s" % rt_row)
+
+        rt_mailbox = rt_row.get('id', '')
+        rt_new = rt_row.get('msgs_new', '')
+        rt_old = rt_row.get('msgs_old', '')
+
+        if event_mailbox != rt_mailbox or \
+                rt_new != event_new or \
+                rt_old != event_old:
+            LOGGER.error("AMI event and realtime data do not match")
+            self.fail_test()
+
+    def mwi_get_complete_callback(self, ami, event):
+        if self.retrieved_mailboxes != EXPECTED_MAILBOXES:
+            LOGGER.error("Did not retrieve the expected number of mailboxes,"
+                         " %d != %d" % (self.retrieved_mailboxes,
+                                       EXPECTED_MAILBOXES))
+            self.fail_test()
+        else:
+            self.pass_test()
+
+
+def check_it(rt_data, test_object, ami):
+    test = MultiTest(rt_data, test_object, ami)
+
+    ami.registerEvent('MWIGet', test.mwi_get_callback)
+    ami.registerEvent('MWIGetComplete', test.mwi_get_complete_callback)
+    message = {
+        'Action': 'MWIGet',
+        'Mailbox': '/^/',
+    }
+    ami.sendMessage(message)

Propchange: asterisk/trunk/tests/realtime/multi/multi.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/multi/multi.py
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/multi/multi.py
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/multi/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/multi/test-config.yaml?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/multi/test-config.yaml (added)
+++ asterisk/trunk/tests/realtime/multi/test-config.yaml Thu Jun 19 16:34:48 2014
@@ -1,0 +1,47 @@
+testinfo:
+    summary:     'Realtime multi test'
+    description: |
+        'This tests the ability of Asterisk to retrieve multiple entries from realtime.
+         Two MWI mailboxes are preloaded in realtime, and Asterisk is tasked with
+         retrieving them when asked.'
+
+
+properties:
+    minversion: '12.0.0'
+    dependencies:
+        - python: 'twisted'
+        - python: 'starpy'
+        - asterisk: 'res_mwi_external'
+        - asterisk: 'res_mwi_external_ami'
+        - asterisk: 'res_config_curl'
+        - asterisk: 'func_curl'
+    tags:
+        - realtime
+        - mwi_external
+
+test-modules:
+    add-test-to-search-path: 'True'
+    test-object:
+        config-section: test-case-config
+        typename: 'test_case.TestCaseModule'
+    modules:
+        -
+            config-section: 'realtime-config'
+            typename: 'realtime_test_module.RealtimeTestModule'
+
+test-case-config:
+    connect-ami: 'True'
+
+realtime-config:
+    entry_module: 'multi'
+    entry_method: 'check_it'
+    data:
+        mwi:
+            -
+                id: 'Dazed'
+                msgs_new: '5'
+                msgs_old: '4'
+            -
+                id: 'Confused'
+                msgs_new: '6'
+                msgs_old: '8'

Propchange: asterisk/trunk/tests/realtime/multi/test-config.yaml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/multi/test-config.yaml
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/multi/test-config.yaml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf (added)
+++ asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf Thu Jun 19 16:34:48 2014
@@ -1,0 +1,2 @@
+[settings]
+mailboxes => curl,http://localhost:8000/mwi

Propchange: asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: asterisk/trunk/tests/realtime/single/configs/ast1/extconfig.conf
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf?view=auto&rev=5145
==============================================================================
--- asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf (added)
+++ asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf Thu Jun 19 16:34:48 2014
@@ -1,0 +1,2 @@
+[res_mwi_external]
+mailboxes=realtime,mailboxes

Propchange: asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: asterisk/trunk/tests/realtime/single/configs/ast1/sorcery.conf

[... 788 lines stripped ...]



More information about the asterisk-commits mailing list