#!/usr/bin/env python ############################################################################# # # Linux Desktop Testing Project http://ldtp.freedesktop.org # # Author: # S.Theyagarajan (theyaga@gmail.com) # K.Harishankaran (sp2hari@gmail.com) # # Copyright 2004 - 2006 Novell, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program; if not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # #Example Usage python record.py start gedit all #Example Usage pythin record.py stop #The filename.py will be the resultant Python script generated which can be used to play back # ############################################################################# from optparse import OptionParser #Defining the command line arguments usage = "Usage:\n %prog -c start [-a application] [-p prefs] [-t]\n %prog -c stop -f file" parser = OptionParser (usage) parser.add_option ("-c", "--command", dest="command", help="Command to start or stop the server", metavar="start/stop") parser.add_option ("-a", "--application", dest="application", default="all", help="Application to be recorded", metavar="all/application") parser.add_option ("-p", "--prefs", dest="prefs", help="Preferences while recording ", metavar="all/mouse/keybrd", default="all") parser.add_option ("-f", "--file", dest="filename", metavar="filepath", help="File where the recorded script is written") parser.add_option ("-t","--timer", action="store_true", help="Record time delays also") (options, args) = parser.parse_args () #Checking for the command line arguments if options.command == None : parser.error ("Command start or stop must be provided") if options.command != "start" and options.command != "stop": parser.error ("Invalid command . Please give start or stop") if options.command == "stop" and options.filename == None : parser.error ("File name must be given for stop command") #Assigning the variables to be used in the file if options.command == "start" : actionName = 'startrecord' elif options.command == "stop" : actionName = 'stoprecord' filename = options.filename arguments = options.prefs applicationName = options.application from ldtpcodegen import * from xml.dom.minidom import * from xml.parsers.expat import ExpatError import socket, os, sys, struct, time, threading, re, random, thread, traceback ldtpDebug = os.getenv ('LDTP_DEBUG') class ConnectionLost (Exception): def __init__(self, value): self.value = value def __str__(self): return repr (self.value) # generate XML content def generatexml (actionName, _requestId, application = None, argument = None): _xml = '' _xml += '' _xml += '' # Fill action name _xml = _xml + '' + _requestId + '' _xml = _xml + '' + actionName + '' if application != None: _xml = _xml + '' + application + '' _xml = _xml + '' _xml = _xml + 'ALL' _xml = _xml + '' _xml = _xml + '' _xml += '' return _xml # Send given packet to server def sendpacket (msg): try: # Get client socket fd based on thread id client = _sockFdPool.get (threading.currentThread ()) # Pack length (integer value) in network byte order msglen = struct.pack ('!i', len (msg)) #FIXME::msglen not taken using struct.pack as in ldtp.py # Send message length client.send (msglen) # Send message client.send (msg) # print len (msg), msg if ldtpDebug != None and ldtpDebug == '2': print 'send packet', msg except socket.error, msg: raise LdtpExecutionError ('Server aborted') def recvpacket (peek_flag = 0, sockfd = None): #print 'recv packet' flag = False try: flag = True client = None # Get client socket fd based on thread id if sockfd == None: client = _sockFdPool.get (threading.currentThread ()) else: client = sockfd _responsePacket = None client.settimeout (5.0) # Hardcoded 4 bytes, as the server sends 4 bytes as packet length data = client.recv (4, peek_flag) if data == '' or data == None: return None _packetSize, = struct.unpack('!i', data) if peek_flag == 0 and ldtpDebug != None and ldtpDebug == '2': print 'Received packet size', _packetSize # MSG_PEEK # This flag causes the receive operation to return data from the beginning # of the receive queue without removing that data from the queue. # Thus, a subsequent receive call will return the same data. if peek_flag != 0: # MSG_PEEK _responsePacket = client.recv (4 + _packetSize, peek_flag) else: _responsePacket = client.recv (_packetSize, peek_flag) if peek_flag != 0: _pattern = re.compile ('\<\?xml') _searchObj = re.search (_pattern, _responsePacket) _finalPacket = _responsePacket[_searchObj.start () :] _responsePacket = _finalPacket if peek_flag == 0 and ldtpDebug != None and ldtpDebug == '2': print 'Received response Packet', _responsePacket return _responsePacket except struct.error, msg: raise LdtpExecutionError ('Invalid packet length ' + str (msg)) except AttributeError, msg: raise LdtpExecutionError ('Error while receiving packet ' + str (msg)) except socket.timeout: if ldtpDebug != None and ldtpDebug == '2': print 'Timeout' return '' except socket.error, msg: raise LdtpExecutionError ('Error while receiving packet ' + str (msg)) except MemoryError, msg: raise LdtpExecutionError ('Error while receiving packet ' + str (msg)) except: raise LdtpExecutionError ('Error while receiving packet') def peekresponse (): return recvpacket (socket.MSG_PEEK) def getresponse (packetId): setflag = True while setflag: peekResponsePacket = peekresponse () if peekResponsePacket == None: continue _responseType, _responseStatus, _responseData , _serverResponseLen, _requestFilePath = parsexml (peekResponsePacket) # For precautions, we are just checking, whether the packet is notification or not setflag = False if _responseType == None: continue if _responseStatus[2] == packetId: if _responseStatus[0] != 0 and ldtpDebug != None: print '*** ' + _responseStatus[1] packet = recvpacket () # As we have already parsed the packet, # we are just returning the parsed packet return _responseStatus, _responseData socketpath = '/tmp/ldtp-record-' + os.getenv ('USER') + '-' + os.getenv ('DISPLAY') # Socket fd pool _sockFdPool = {} if socketpath == '': raise ConnectionLost ('Server not running') try: # Create a client socket mainsock = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM) except socket.error,msg: raise LdtpExecutionError ('Error while creating UNIX socket ' + str (msg)) # Let us retry connecting to the server for 3 times retryCount = 0 while True: try: try: # Connect to server socket mainsock.connect (socketpath) break except TypeError: raise ConnectionLost ('Environment LDTP_AUTH_SOCK variable not set') except socket.error, msg: if retryCount == 3: raise ConnectionLost ('Could not establish connection ' + str (msg)) retryCount += 1 _pid = os.fork () if _pid == 0: try: os.execvpe ('ldtp', (), os.environ) except OSError: raise LdtpExecutionError ('ldtp executable not in PATH') else: # Let us wait for 1 second, let the server starts time.sleep (1) _sockFdPool[threading.currentThread ()] = mainsock def shutdown (): if threading.activeCount () > 1: thread.exit () def getText (nodelist): rc = "" for node in nodelist: if node.nodeType == node.TEXT_NODE: rc = rc + node.data return rc def getCData (nodelist): rc = "" for node in nodelist: if node.nodeType == node.CDATA_SECTION_NODE: rc = rc + node.data return rc #This function generates the run.xml used as input to gldap.py def generaterunxml (filename): _runxml = '' _runxml += '' _runxml += ' 1 ' _runxml += '' _runxml += os.getcwd() + '/log.xml' _runxml += '' # Fill action name _runxml = _runxml + '' _runxml = _runxml + '' _runxml = _runxml + '' _runxml = _runxml + '' #print _runxml output_file = file("run.xml","w") output_file.write( _runxml) output_file.close() if ldtpDebug != None and ldtpDebug == '2': print "Run.xml created ..." def parsexml (xmldata): """Returns the value obtained from the server's return LDTP packet""" _statusMsg = None _statusCode = None _serverResponse = None _responseType = None _requestId = None _requestFilePath = None _serverResponse = None _responseObj = None _serverResponseLen = 0 try: if ldtpDebug != None and ldtpDebug == '2': print xmldata dom = parseString (xmldata) try: _responseObj = dom.getElementsByTagName ('RESPONSE')[0] _responseType = 'record' except IndexError: raise LdtpExecutionError ('Invalid response') try: _responseStatusObj = _responseObj.getElementsByTagName ('STATUS')[0] _statusCode = int (getText (_responseStatusObj.getElementsByTagName ('CODE')[0].childNodes)) _statusMsg = getText (_responseStatusObj.getElementsByTagName ('MESSAGE')[0].childNodes) except ValueError: raise LdtpExecutionError ('Invalid Status') except IndexError: raise LdtpExecutionError ('Invalid Status') try: _requestId = getText (_responseObj.getElementsByTagName ('ID')[0].childNodes) except IndexError: # On notification _requestId will be empty pass try: data = _responseObj.getElementsByTagName ('DATA')[0] _serverResponse = getCData (data.getElementsByTagName ('VALUE')[0].childNodes).encode ('utf-8') _serverResponseLen = int (getText (data.getElementsByTagName ('LENGTH')[0].childNodes)) except ValueError: raise LdtpExecutionError ('Invalid Data Length') except IndexError: _serverResponse = None # Data tag may not be present pass try: _requestFilePath = getText (_responseObj.getElementsByTagName ('FILEPATH')[0].childNodes) except IndexError: _requestFilePath = None # On notification _requestId will be empty pass except ExpatError, msg: if xml.parsers.expat.ErrorString (msg.code) == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS: return None raise LdtpExecutionError ('Parsing XML error: ' + str (msg)) # Return all the respective values, let the calling function decide what to do with the values if ldtpDebug != None and ldtpDebug == '2': print "Response Length:", _serverResponseLen print "Status", _statusCode, _statusMsg if _requestFilePath != None: print "XML File from server:", _requestFilePath #, "- Note: When filing bug attach this file, apart from steps to reproduce the bug" print "Created python file path ", filename + ".py" print "Created XML file path path ", filename + ".xml" if (_statusCode == 0): generate_python_code (_requestFilePath, filename) generate_data_xml (_requestFilePath, filename) return _responseType, (_statusCode, _statusMsg, _requestId), (_serverResponseLen, _serverResponse), _requestFilePath, _serverResponseLen def startrecord (applicationName = None, arguments = None): try: _requestId = str (random.randint (0, sys.maxint)) _message = generatexml ('startrecord', _requestId, applicationName, arguments) sendpacket (_message) except LdtpExecutionError, msg: pass except LdtpExecutionError, msg: raise LdtpExecutionError (str (msg)) def stoprecord (filename): try: _requestId = str (random.randint (0, sys.maxint)) _message = generatexml ('stoprecord', _requestId) sendpacket (_message) _responseStatus, _responseData = getresponse (_requestId) except LdtpExecutionError, msg: raise LdtpExecutionError (str (msg) + str (traceback.format_exc ())) if actionName == "startrecord" : startrecord (applicationName, arguments) else : stoprecord (filename)