Package Peach :: Package Engine :: Module state
[hide private]

Source Code for Module Peach.Engine.state

  1   
  2  ''' 
  3  State Machine Engine 
  4   
  5  Will try and run a state machine. 
  6   
  7  @author: Michael Eddington 
  8  @version: $Id: state.py 1099 2008-07-29 05:40:17Z meddingt $ 
  9  ''' 
 10   
 11  # 
 12  # Copyright (c) 2008 Michael Eddington 
 13  # 
 14  # Permission is hereby granted, free of charge, to any person obtaining a copy  
 15  # of this software and associated documentation files (the "Software"), to deal 
 16  # in the Software without restriction, including without limitation the rights  
 17  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
 18  # copies of the Software, and to permit persons to whom the Software is  
 19  # furnished to do so, subject to the following conditions: 
 20  # 
 21  # The above copyright notice and this permission notice shall be included in     
 22  # all copies or substantial portions of the Software. 
 23  # 
 24  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
 25  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
 26  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
 27  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 28  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 29  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 30  # SOFTWARE. 
 31  # 
 32   
 33  # Authors: 
 34  #   Michael Eddington (mike@phed.org) 
 35   
 36  # $Id: state.py 1099 2008-07-29 05:40:17Z meddingt $ 
 37   
 38  import sys, re, types, time 
 39  import traceback 
 40   
 41  import Ft.Xml.Domlette 
 42  from Ft.Xml.Domlette import Print, PrettyPrint 
 43   
 44  from Peach.Engine.dom import * 
 45  from Peach.Engine.parser import * 
 46  from Peach.Engine.incoming import * 
 47  from Peach.Engine.common import * 
 48  import Peach 
 49   
50 -def Debug(level, msg):
51 if Peach.Engine.engine.Engine.debug: 52 print msg
53
54 -def peachPrint(msg):
55 print "peachPrint: ", msg
56
57 -class StateEngine:
58 ''' 59 Runs a StateMachine instance. 60 ''' 61
62 - def __init__(self, engine, stateMachine, publisher):
63 ''' 64 engine - Engine 65 stateMachien - StateMachine to use 66 publisher - Publisher to use 67 ''' 68 69 self.engine = engine 70 self.stateMachine = stateMachine 71 self.publisher = publisher 72 self.f = peachPrint
73
74 - def run(self, mutator):
75 ''' 76 Perform a single run of a StateMachine using the provided 77 mutator. 78 ''' 79 80 Debug(1, "StateEngine.run: %s" % self.stateMachine.name) 81 82 self.publisher.hasBeenConnected = False 83 self.publisher.hasBeenStarted = False 84 85 self.actionValues = [] 86 87 mutator.onStateMachineStart(self) 88 89 try: 90 self._runState(self._getStateByName(self.stateMachine.initialState), mutator) 91 92 except SoftException: 93 94 # Soft exceptions are okay 95 pass 96 97 finally: 98 99 # At end of state machine make sure publisher is closed 100 if self.publisher.hasBeenConnected: 101 self.publisher.close() 102 self.publisher.hasBeenConnected = False 103 104 # At end of state machine make sure publisher is stopped 105 if self.publisher.hasBeenStarted: 106 self.publisher.stop() 107 self.publisher.hasBeenStarted = False 108 109 mutator.onStateMachineComplete(self) 110 111 return self.actionValues
112
113 - def _getStateByName(self, stateName):
114 ''' 115 Locate a State object by name in the StateMachine. 116 ''' 117 118 for child in self.stateMachine: 119 if child.elementType == 'state' and child.name == stateName: 120 return child 121 122 #else: 123 # Debug(1, "_getStateByName: No match on %s" % child.name) 124 125 return None
126
127 - def _runState(self, state, mutator):
128 ''' 129 Runs a specific State from a StateMachine. 130 ''' 131 132 Debug(1, "StateEngine._runState: %s" % state.name) 133 134 # First up we need to copy all the action's templates 135 # otherwise values can leak all over the place! 136 137 for action in state: 138 if not isinstance(action, Action): 139 continue 140 141 if action.template == None: 142 for c in action: 143 if c.elementType == 'actionparam' or c.elementType == 'actionresult': 144 # Copy template from origional first 145 if not hasattr(c, 'origionalTemplate'): 146 c.origionalTemplate = c.template 147 148 # Make a fresh copy of the template 149 c.__delitem__(c.template.name) 150 c.template = c.origionalTemplate.copy(c) 151 c.append(c.template) 152 153 continue 154 155 # Copy template from origional first 156 if not hasattr(action, 'origionalTemplate'): 157 action.origionalTemplate = action.template 158 159 # Make a fresh copy of the template 160 action.__delitem__(action.template.name) 161 action.template = action.origionalTemplate.copy(action) 162 action.append(action.template) 163 164 # Next setup a few things 165 166 self.actionValues.append( [ state.name, 'state' ] ) 167 mutator.onStateStart(state) 168 169 # EVENT: onEnter 170 if state.onEnter != None: 171 environment = { 172 'Peach' : self.engine.peach, 173 'State' : state, 174 'StateModel' : state.parent, 175 'peachPrint' : self.f, 176 'Mutator' : mutator, 177 'sleep' : time.sleep 178 } 179 180 evalEvent(state.onEnter, environment) 181 182 try: 183 184 try: 185 # Start with first action and continue along 186 for action in state: 187 if action.elementType != 'action': 188 continue 189 190 self._runAction(action, mutator) 191 192 except SoftException: 193 # SoftExceptions are fine 194 pass 195 196 # EVENT: onExit 197 if state.onExit != None: 198 environment = { 199 'Peach' : self.engine.peach, 200 'State' : state, 201 'StateModel' : state.parent, 202 'peachPrint' : self.f, 203 'Mutator' : mutator, 204 'sleep' : time.sleep 205 } 206 207 evalEvent(state.onExit, environment) 208 209 mutator.onStateComplete(state) 210 211 except StateChangeStateException, e: 212 213 # EVENT: onExit 214 if state.onExit != None: 215 environment = { 216 'Peach' : self.engine.peach, 217 'State' : state, 218 'StateModel' : state.parent, 219 'peachPrint' : self.f, 220 'Mutator' : mutator, 221 'sleep' : time.sleep 222 } 223 224 evalEvent(state.onExit, environment) 225 226 mutator.onStateComplete(state) 227 228 self._runState(e.state, mutator)
229 230 231 #def _execEvent(self, code, environment): 232 # ''' 233 # exec python code, no result. 234 # 235 # code - String 236 # environment - Dictionary, keys are variables to place in local scope 237 # ''' 238 # 239 # #print globals() 240 # scope = { '__builtins__' : globals()['__builtins__'] } 241 # for k in environment.keys(): 242 # scope[k] = environment[k] 243 # 244 # ret = exec(code, scope, scope) 245 # 246 # return ret 247
248 - def _runAction(self, action, mutator):
249 250 Debug(1, "StateEngine._runAction: %s" % action.name) 251 252 mutator.onActionStart(action) 253 254 # EVENT: when 255 if action.when != None: 256 environment = { 257 'Peach' : self.engine.peach, 258 'Action' : action, 259 'State' : action.parent, 260 'StateModel' : action.parent.parent, 261 'Mutator' : mutator, 262 'peachPrint' : self.f, 263 'sleep' : time.sleep 264 } 265 266 #print action.parent.parent 267 #for k in action.parent.parent._childrenHash.keys(): 268 # print "Key: ", k 269 270 if not evalEvent(action.when, environment): 271 return 272 273 # EVENT: onStart 274 if action.onStart != None: 275 environment = { 276 'Peach' : self.engine.peach, 277 'Action' : action, 278 'State' : action.parent, 279 'StateModel' : action.parent.parent, 280 'Mutator' : mutator, 281 'peachPrint' : self.f, 282 'sleep' : time.sleep 283 } 284 285 evalEvent(action.onStart, environment) 286 287 if action.type == 'input': 288 action.value = None 289 290 if self.publisher.hasBeenStarted == False: 291 self.publisher.start() 292 self.publisher.hasBeenStarted = True 293 if not self.publisher.hasBeenConnected: 294 self.publisher.connect() 295 self.publisher.hasBeenConnected = True 296 297 # Determine initial read size 298 cracker = DataCracker(self.engine.peach) 299 size = cracker.getInitialReadSize(action.template) 300 Debug(1, "StateEngine._runAction(input): Found initial read size of %s" % size) 301 302 data = "" # Data Buffer 303 timeout = False # Have we hit a timeout exception? 304 haveAllData = False # Do we have all the data? 305 306 while True: 307 try: 308 Debug(2, ">> STATE IS CALLING RECEIVE FOR %d BYTES" % size) 309 310 # Did we get asked for all the data? 311 if size == -1: 312 try: 313 # Read everything in 314 haveAllData = True 315 data += self.publisher.receive() 316 317 except Peach.publisher.Timeout, e: 318 # Timeout is okay here 319 pass 320 321 # Else try and read wanted size 322 else: 323 try: 324 data += self.publisher.receive(size) 325 timeout = False 326 327 except Peach.publisher.Timeout, e: 328 329 # Retry after a timeout 330 if timeout: 331 raise 332 333 timeout = True 334 haveAllData = True 335 336 # Make a fresh copy of the template 337 action.__delitem__(action.template.name) 338 action.template = action.origionalTemplate.copy(action) 339 action.append(action.template) 340 341 # Try and crack the data 342 Debug(1, "\n\n\n\n### cracker.crackData(%d) ##############################################" % len(data)) 343 if haveAllData: 344 Debug(1, "### HAVE ALL DATA!!!!!! ##############################################") 345 346 cracker = DataCracker(self.engine.peach) 347 cracker.haveAllData = haveAllData 348 (rating, pos) = cracker.crackData(action.template, data) 349 if rating > 2: 350 raise SoftException("Was unble to crack incoming data into %s data model." % action.template.name) 351 352 # If no exception, it worked 353 break 354 355 except NeedMoreData, e: 356 size = e.amount 357 Debug(2, ">> Going back for: %d" % size) 358 Debug(2, ">> Tab Level: %d" % DataCracker._tabLevel) 359 if DataCracker._tabLevel > 0: 360 size = 0 361 362 #Debug(2, ">> Currently Have:") 363 #Debug(2, "><><><><><><><><><><><><><><><><><><") 364 #Debug(2, data) 365 #Debug(2, "><><><><><><><><><><><><><><><><><><") 366 367 action.value = action.template.getValue() 368 369 #print "VALUE: %s" % repr(action.value) 370 371 #print ">>> action.template.gatValue(): " + action.template.getValue() 372 #print ">>> action.template", action.template 373 if Peach.Engine.engine.Engine.debug: 374 dict = {} 375 doc = Ft.Xml.Domlette.NonvalidatingReader.parseString("<Peach/>") 376 377 stateMachine = action.parent.parent 378 stateMachineNode = stateMachine.toXmlDom(doc.rootNode.firstChild, dict) 379 380 print "*****POST INPUT*************" 381 PrettyPrint(doc, asHtml=1) 382 print "******************" 383 384 elif action.type == 'output': 385 386 if not self.publisher.hasBeenStarted: 387 self.publisher.start() 388 self.publisher.hasBeenStarted = True 389 if not self.publisher.hasBeenConnected: 390 self.publisher.connect() 391 self.publisher.hasBeenConnected = True 392 393 action.value = mutator.getActionValue(action) 394 395 Debug(1, "Actiong output sending %d bytes" % len(action.value)) 396 self.publisher.send(action.value) 397 self.actionValues.append( [ action.name, 'output', action.value ] ) 398 399 obj = Element(action.name, None) 400 obj.elementType = 'dom' 401 obj.defaultValue = action.value 402 action.value = obj 403 404 elif action.type == 'call': 405 action.value = None 406 407 actionParams = [] 408 409 if not self.publisher.hasBeenStarted: 410 self.publisher.start() 411 self.publisher.hasBeenStarted = True 412 413 # build up our call 414 method = action.method 415 if method == None: 416 raise PeachException("StateEngine: Action of type \"call\" does not have method name!") 417 418 params = [] 419 for c in action: 420 if c.elementType == 'actionparam': 421 params.append(c) 422 423 argValues = [] 424 for p in params: 425 if p.type == 'out' or p.type == 'inout': 426 raise PeachException("StateEngine: Action of type \"call\" does not yet support out or inout parameters (bug in comtypes)!") 427 428 p.value = mutator.getActionParamValue(p) 429 argValues.append(p.value) 430 431 actionParams.append([p.name, 'param', p.value]) 432 433 ret = self.publisher.call(method, argValues) 434 435 # look for and set return 436 for c in action: 437 if c.elementType == 'actionresult': 438 cracker = DataCracker(self.engine.peach) 439 cracker.haveAllData = True 440 (rating, pos) = cracker.crackData(action.template, ret) 441 if rating > 2: 442 raise SoftException("Was unble to crack result data into %s data model." % action.template.name) 443 444 self.actionValues.append( [ action.name, 'call', method, actionParams ] ) 445 446 elif action.type == 'getprop': 447 action.value = None 448 449 if not self.publisher.hasBeenStarted: 450 self.publisher.start() 451 self.publisher.hasBeenStarted = True 452 453 # build up our call 454 property = action.property 455 if property == None: 456 raise Exception("StateEngine._runAction(): getprop type does not have property name!") 457 458 data = self.publisher.property(property) 459 460 self.actionValues.append( [ action.name, 'getprop', property, data ] ) 461 462 cracker = DataCracker(self.engine.peach) 463 cracker.haveAllData = True 464 (rating, pos) = cracker.crackData(action.template, data) 465 if rating > 2: 466 raise SoftException("Was unble to crack getprop data into %s data model." % action.template.name) 467 468 # If no exception, it worked 469 470 action.value = action.template.getValue() 471 472 if Peach.Engine.engine.Engine.debug: 473 ### Test CODE 474 dict = {} 475 doc = Ft.Xml.Domlette.NonvalidatingReader.parseString("<Peach/>") 476 477 stateMachine = action.parent.parent 478 stateMachineNode = stateMachine.toXmlDom(doc.rootNode.firstChild, dict) 479 480 print "*******POST GETPROP***********" 481 PrettyPrint(doc, asHtml=1) 482 print "******************" 483 484 elif action.type == 'setprop': 485 action.value = None 486 487 if not self.publisher.hasBeenStarted: 488 self.publisher.start() 489 self.publisher.hasBeenStarted = True 490 491 # build up our call 492 property = action.property 493 if property == None: 494 raise Exception("StateEngine: setprop type does not have property name!") 495 496 value = None 497 for c in action: 498 if c.elementType == 'actionparam' and c.type == "in": 499 value = c.value = mutator.getActionParamValue(c) 500 break 501 502 self.publisher.property(property, value) 503 self.actionValues.append( [ action.name, 'setprop', property, value ] ) 504 505 elif action.type == 'changeState': 506 action.value = None 507 mutator.onActionComplete(action) 508 raise StateChangeStateException(self._getStateByName(action.ref)) 509 510 self.actionValues.append( [ action.name, 'changeState', action.ref ] ) 511 512 elif action.type == 'slurp': 513 action.value = None 514 515 dict = {} 516 doc = Ft.Xml.Domlette.NonvalidatingReader.parseString("<Peach/>") 517 518 stateMachine = action.parent.parent 519 stateMachineNode = stateMachine.toXmlDom(doc.rootNode.firstChild, dict) 520 521 if Peach.Engine.engine.Engine.debug: 522 print "****** PRE SLURP ************" 523 PrettyPrint(doc, asHtml=1) 524 print "******************" 525 526 setNodes = doc.xpath(action.setXpath) 527 if len(setNodes) == 0: 528 raise Exception("StateEngine._runAction(xpath): setXpath did not return a node") 529 530 for node in setNodes: 531 532 if action.valueXpath != None: 533 valueNodes = doc.xpath(action.valueXpath) 534 if len(valueNodes) == 0: 535 raise Exception("StateEngine._runAction(xpath): valueXpath did not return a node") 536 537 valueNode = valueNodes[0] 538 539 if valueNode.hasAttributeNS(None, "currentValue"): 540 Debug(1, "Setting currentValue: [%s]" % str(valueNode.getAttributeNS(None, 'currentValue'))) 541 node.setAttributeNS(None, 'currentValue', valueNode.getAttributeNS(None, 'currentValue')) 542 543 if valueNode.hasAttributeNS(None, 'currentValue-Encoded'): 544 node.setAttributeNS(None, 'currentValue-Encoded', 545 valueNode.getAttributeNS(None, 'currentValue-Encoded')) 546 547 elif valueNode.hasAttributeNS(None, 'value'): 548 Debug(1, "Setting value: [%s]" % str(valueNode.getAttributeNS(None, 'value'))) 549 node.setAttributeNS(None, 'currentValue', valueNode.getAttributeNS(None, 'value')) 550 551 if valueNode.hasAttributeNS(None, 'value-Encoded'): 552 node.setAttributeNS(None, 'currentValue-Encoded', 553 valueNode.getAttributeNS(None, 'value-Encoded')) 554 555 else: 556 Debug(1, "Setting currentValue: [%s]" % str(action.valueLiteral)) 557 try: 558 node.setAttributeNS(None, 'currentValue', action.valueLiteral) 559 560 except UnicodeDecodeError: 561 node.setAttributeNS(None, "currentValue-Encoded", "base64") 562 node.setAttributeNS(None, 'currentValue', base64.b64encode(action.valueLiteral)) 563 564 565 if Peach.Engine.engine.Engine.debug: 566 print "****** POST SLURP ************" 567 PrettyPrint(doc, asHtml=1) 568 print "******************" 569 570 stateMachine.updateFromXmlDom(stateMachineNode, dict) 571 dict = None 572 573 elif action.type == 'connect': 574 if not self.publisher.hasBeenStarted: 575 self.publisher.start() 576 self.publisher.hasBeenStarted = True 577 578 self.publisher.connect() 579 self.publisher.hasBeenConnected = True 580 581 elif action.type == 'accept': 582 if not self.publisher.hasBeenStarted: 583 self.publisher.start() 584 self.publisher.hasBeenStarted = True 585 586 self.publisher.accept() 587 self.publisher.hasBeenConnected = True 588 589 elif action.type == 'close': 590 if not self.publisher.hasBeenConnected: 591 # If we haven't been opened lets ignore 592 # this close. 593 return 594 595 self.publisher.close() 596 self.publisher.hasBeenConnected = False 597 598 elif action.type == 'start': 599 self.publisher.start() 600 self.publisher.hasBeenStarted = True 601 602 elif action.type == 'stop': 603 if self.publisher.hasBeenStarted: 604 self.publisher.stop() 605 self.publisher.hasBeenStarted = False 606 607 elif action.type == 'wait': 608 time.sleep(float(action.valueLiteral)) 609 610 else: 611 raise Exception("StateEngine._runAction(): Unknown action.type of [%s]" % str(action.type)) 612 613 # EVENT: onComplete 614 if action.onComplete != None: 615 environment = { 616 'Peach' : self.engine.peach, 617 'Action' : action, 618 'State' : action.parent, 619 'Mutator' : mutator, 620 'StateModel' : action.parent.parent, 621 'sleep' : time.sleep 622 } 623 624 evalEvent(action.onComplete, environment) 625 626 mutator.onActionComplete(action)
627
628 - def _resetXmlNodes(self, node):
629 ''' 630 Reset XML node tree starting at 'node'. We will remove 631 any attribute calleed: 632 633 value 634 currentValue 635 defaultValue 636 value-Encoded 637 currentValue-Encoded 638 defaultValue-Encoded 639 640 ''' 641 642 att = ['value', 'currentValue', 'defaultValue', 643 'value-Encoded', 'currentValue-Encoded', 'defaultValue-Encoded'] 644 645 for a in att: 646 if node.hasAttributeNS(None, a): 647 node.removeAttributeNS(None, a) 648 649 for child in node.childNodes: 650 self._resetXmlNodes(child)
651
652 -class StateChangeStateException:
653 - def __init__(self, state):
654 self.state = state
655
656 - def __str__(self):
657 return "Exception: StateChangeStateException"
658
659 -class StateError:
660 - def __init__(self, msg):
661 self.msg = msg
662
663 - def __str__(self):
664 return self.msg
665 666 # End 667