Scenario.py 8.24 KB
Newer Older
Maxime Perrotin's avatar
Maxime Perrotin committed
1
#!/usr/bin/env python
2 3 4
# -*- coding: utf-8 -*-
"""
    TASTE ASN.1 Value Editor
Maxime Perrotin's avatar
Maxime Perrotin committed
5

6 7 8 9 10 11 12 13 14 15
    Handle regression testing scenarios in the context of the MSC recordings

    Copyright (c) 2012-2015 European Space Agency

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int

    License is LGPLv3 - Check the LICENSE file
"""
Maxime Perrotin's avatar
Maxime Perrotin committed
16 17 18 19 20 21 22 23
from PySide.QtCore import QThread, Signal
import Queue
import datamodel
import sys
import os
import importlib
import time
import DV
Maxime Perrotin's avatar
Maxime Perrotin committed
24
import ctypes
Maxime Perrotin's avatar
Maxime Perrotin committed
25 26 27 28 29 30 31

try:
    from PythonController import(OpenMsgQueueForReading, GetMsgQueueBufferSize,
            RetrieveMessageFromQueue)
except ImportError:
    print 'ERROR importing PythonController'

Maxime Perrotin's avatar
Maxime Perrotin committed
32 33 34 35
from opengeode import Asn1scc as asn1scc

#TASTE_INST = os.popen('taste-config --prefix').readlines()[0].strip()
#sys.path.append(TASTE_INST + '/share/asn1-editor')
Maxime Perrotin's avatar
Maxime Perrotin committed
36 37 38 39 40 41 42 43 44


class Scenario(QThread, object):
    ''' Generic class handling the execution of an MSC scenario '''
    def __init__(self, func):
        QThread.__init__(self)
        self.msg_q = Queue.Queue()
        self.modules = {}
        self.scenario = func
Maxime Perrotin's avatar
Maxime Perrotin committed
45 46 47 48 49
        # Parse the dataview.asn to get the Python AST
        dataView = asn1scc.parse_asn1(['dataview-uniq.asn'],
                              rename_policy=asn1scc.ASN1.RenameOnlyConflicting,
                              ast_version=asn1scc.ASN1.UniqueEnumeratedNames,
                              flags=[asn1scc.ASN1.AstOnly])
Maxime Perrotin's avatar
Maxime Perrotin committed
50 51 52 53 54 55 56 57 58 59 60 61
        # Control the end of the scenario
        self.stop_requested = False
        # Load all TM and TC backends, once for all
        backends = datamodel.tm.keys()
        backends.extend(datamodel.tc.keys())
        for backend in backends:
            try:
                mod = reload(backend + '_backend')
            except (TypeError, NameError):
                try:
                    mod = importlib.import_module(backend + '_backend')
                except ImportError:
Maxime Perrotin's avatar
Maxime Perrotin committed
62 63 64 65 66 67 68
                    try:
                        self.log.put((self.name, 'ERROR',
                                      'Could not import {b}_backend.py'
                                      .format(b=backend)))
                    except AttributeError:
                        print("ERROR: could not import {}_backend.py"
                                .format(backend))
Maxime Perrotin's avatar
Maxime Perrotin committed
69 70
                    continue
            mod.setMsgQ()
Maxime Perrotin's avatar
Maxime Perrotin committed
71 72
            # set the ASN.AST into the backend module, needed to convert VN
            mod.ASN1_AST = dataView.types
Maxime Perrotin's avatar
Maxime Perrotin committed
73 74 75 76 77 78 79 80 81
            self.modules[backend] = mod

    def __call__(self, log, name='Scenario'):
        self.name = name
        self.log = log
        return self

    def run(self):
        ''' Thread starting point '''
Thanassis Tsiodras's avatar
Typo  
Thanassis Tsiodras committed
82
        self.log.put((self.name, 'INFO', 'Starting scenario'))
Maxime Perrotin's avatar
Maxime Perrotin committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
        try:
            self.scenario(self)
        except (IOError, TypeError) as err:
            self.log.put((self.name, 'ERROR', str(err)))
        else:
            self.log.put((self.name, 'INFO',
                          'Scenario completed with no errors'))
        self.log.put((self.name, 'INFO', 'END'))
        #self.done.emit(self.log)

    def stop(self):
        ''' Set the stop_requested flag to true '''
        self.stop_requested = True
        self.wait()

    def getNextMsg(self, timeout=None):
        ''' Wait for a message and return it (in native format) '''
        self.log.put((self.name, 'INFO', 'Waiting for the next message'))
        try:
            (msgId, p_data_from_mq) = self.msg_q.get(
                                             block=True, timeout=timeout)
        except Queue.Empty:
            # Timeout expired
            self.log.put((self.name, 'ERROR', 'Timeout expired'))
            raise IOError('Timeout expired')
        else:
            self.log.put((self.name, 'INFO', 'Received message'))
            self.msg_q.task_done()
            # Determine which message was received:
            for b, mod in self.modules.viewitems():
                if hasattr(mod, 'tmId') and mod.tmId == msgId:
                    nativeValue = mod.decode_TM(p_data_from_mq)
                    return (b, nativeValue)

    def expectMsg(self, msgId, value='', lineNo=0,
                  ignoreOther=False, timeout=None):
        '''
            Wait for a specific message, with optional explicit parameter
            If the message content shall not be checked, use value = '*'
            If you want to select fields to ignore, replace them with a '*'
            e.g. value = ' { name "John", age * }' will discard the 'age' field
        '''
        self.log.put((self.name, 'INFO',
                      'Waiting for {id}({val})'.format(id=msgId, val=value)))
        # Call the function from the backend
        for mod in self.modules.viewkeys():
            if(mod.lower() == msgId.lower()
                                  and hasattr(self.modules[mod], 'expect')):
                try:
                    self.modules[mod].expect(
                                    self.msg_q, value, ignoreOther, timeout)
                except (ValueError, TypeError) as err:
                    # Value error: good message but wrong params
                    # TypeError : wrong message received
                    self.log.put((self.name, 'ERROR', str(err)))
                    raise
                except IOError as err:
                    # Timeout
                    self.log.put((self.name, 'ERROR', str(err)))
                    raise
                else:
                    self.log.put((self.name, 'INFO',
                              'Received and verified message content, all OK'))
                break
        else:
            self.log.put((self.name, 'ERROR',
                          'Undefined message: ' + str(msgId)))
            raise TypeError('Undefined message: ' + str(msgId))

    def sendMsg(self, msgId, value='', lineNo=0):
        ''' Send a message to the running binary, use ASN.1 notation '''
        for mod in self.modules.viewkeys():
            if mod.lower() == msgId.lower():
                self.log.put((self.name, 'INFO', 'Sending ' + str(mod)))
                send_id = 'send_{id}_VN'.format(id=mod)
                if hasattr(self.modules[mod], send_id):
                    send_ptr = getattr(self.modules[mod], send_id)
                    # get error code from sending function
                    try:
                        send_ptr(value)
                    except IOError:
                        log_msg = 'Sending message error'
                        self.log.put((self.name, 'ERROR', log_msg))
                        raise IOError(log_msg)
                break
        else:
            log_msg = 'Undefined message: ' + str(msgId)
            self.log.put((self.name, 'ERROR', log_msg))
            raise TypeError(log_msg)


class PollerThread(QThread):
    ''' Class polling the msgQ and sending signals to running scenarii '''
    msgReceived = Signal(int)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.q_name = ("{uid}_{fvname}_PI_Python_queue"
                      .format(uid=str(os.geteuid()), fvname=datamodel.FVname))
        self.stop_requested = False
        self.slots = []

    def run(self):
        print('Opening msgQ: ' + self.q_name)
        msg_q = OpenMsgQueueForReading(self.q_name)
        if msg_q == -1:
            print 'Failed to open message queue ' + self.q_name
            return -1
        buffer_size = GetMsgQueueBufferSize(msg_q)
192 193
        # Configure a buffer to read data from the queue
        buff = ctypes.create_string_buffer(buffer_size)
Maxime Perrotin's avatar
Maxime Perrotin committed
194 195 196
        while True:
            if self.stop_requested:
                return 0
197 198 199
            # Create an actual buffer and get the pointer
            # (accessing .raw is doing Python magic)
            raw = buff.raw
Maxime Perrotin's avatar
Maxime Perrotin committed
200 201
            msg_received_type = RetrieveMessageFromQueue(msg_q,
                                                         buffer_size,
202
                                                         raw)
Maxime Perrotin's avatar
Maxime Perrotin committed
203 204 205 206 207
            if msg_received_type == -1:
                time.sleep(0.01)
                continue
            else:
                for slot in self.slots:
208
                    slot.put((msg_received_type, raw))
Maxime Perrotin's avatar
Maxime Perrotin committed
209 210 211 212 213 214 215 216 217

    def stop(self):
        ''' Request the poller to stop '''
        self.stop_requested = True
        self.wait()


if __name__ == "__main__":
    print 'This module cannot be run standalone. Check TASTE documentation'