From 943a48fc5c0a59b892eb746073c71b8dd88815e7 Mon Sep 17 00:00:00 2001 From: Mark Spruiell Date: Tue, 24 Apr 2012 14:16:15 -0700 Subject: python 3 support --- scripts/Expect.py | 486 +++++++++++++++++++++++++++++------------------------- 1 file changed, 260 insertions(+), 226 deletions(-) (limited to 'scripts/Expect.py') diff --git a/scripts/Expect.py b/scripts/Expect.py index f1d847d16ad..612099ec53c 100755 --- a/scripts/Expect.py +++ b/scripts/Expect.py @@ -9,7 +9,6 @@ import threading import subprocess -import StringIO import string import time import re @@ -39,7 +38,7 @@ class EOF: def __str__(self): return str(self.value) -class TIMEOUT: +class TIMEOUT(Exception): """Raised when a read time exceeds the timeout. """ def __init__(self, value): @@ -47,119 +46,132 @@ class TIMEOUT: def __str__(self): return str(self.value) +def getStringIO(): + if sys.version_info[0] == 2: + import StringIO + return StringIO.StringIO() + else: + import io + return io.StringIO() + def escape(s, escapeNewlines = True): if s == TIMEOUT: return "" - o = StringIO.StringIO() + o = getStringIO() for c in s: - if c == '\\': - o.write('\\\\') - elif c == '\'': - o.write("\\'") - elif c == '\"': - o.write('\\"') - elif c == '\b': - o.write('\\b') - elif c == '\f': - o.write('\\f') - elif c == '\n': + if c == '\\': + o.write('\\\\') + elif c == '\'': + o.write("\\'") + elif c == '\"': + o.write('\\"') + elif c == '\b': + o.write('\\b') + elif c == '\f': + o.write('\\f') + elif c == '\n': if escapeNewlines: o.write('\\n') else: o.write('\n') - elif c == '\r': - o.write('\\r') - elif c == '\t': - o.write('\\t') - else: - if c in string.printable: - o.write(c) - else: - o.write('\\%03o' % ord(c)) + elif c == '\r': + o.write('\\r') + elif c == '\t': + o.write('\\t') + else: + if c in string.printable: + o.write(c) + else: + o.write('\\%03o' % ord(c)) return o.getvalue() class reader(threading.Thread): def __init__(self, desc, p, logfile): - self.desc = desc - self.buf = StringIO.StringIO() - self.cv = threading.Condition() - self.p = p + self.desc = desc + self.buf = getStringIO() + self.cv = threading.Condition() + self.p = p self._trace = False - self._tbuf = StringIO.StringIO() - self._tracesupress = None - self.logfile = logfile + self._tbuf = getStringIO() + self._tracesuppress = None + self.logfile = logfile threading.Thread.__init__(self) def run(self): try: - while True: - c = self.p.stdout.read(1) - if not c: break + while True: + c = self.p.stdout.read(1) + if not c: break if c == '\r': continue - self.cv.acquire() - try: + self.cv.acquire() + try: + # Depending on Python version and platform, the value c could be a + # string or a bytes object. + if type(c) != str: + c = c.decode() self.trace(c) - self.buf.write(c) - self.cv.notify() - finally: - self.cv.release() - except IOError, e: - print e + self.buf.write(c) + self.cv.notify() + finally: + self.cv.release() + except IOError as e: + print(e) def trace(self, c): if self._trace: - if self._tracesupress: - self._tbuf.write(c) - if c == '\n': - content = self._tbuf.getvalue() - supress = False - for p in self._tracesupress: - if p.search(content): - supress = True - break - if not supress: - sys.stdout.write(content) - self._tbuf.truncate(0) - else: - sys.stdout.write(c) - sys.stdout.flush() - - def enabletrace(self, supress = None): - self.cv.acquire() - try: + if self._tracesuppress: + self._tbuf.write(c) + if c == '\n': + content = self._tbuf.getvalue() + suppress = False + for p in self._tracesuppress: + if p.search(content): + suppress = True + break + if not suppress: + sys.stdout.write(content) + self._tbuf.truncate(0) + self._tbuf.seek(0) + else: + sys.stdout.write(c) + sys.stdout.flush() + + def enabletrace(self, suppress = None): + self.cv.acquire() + try: if not self._trace: self._trace = True - self._tracesupress = supress + self._tracesuppress = suppress for c in self.buf.getvalue(): self.trace(c) - finally: - self.cv.release() + finally: + self.cv.release() def getbuf(self): - self.cv.acquire() - try: - buf = self.buf.getvalue() - finally: - self.cv.release() - return buf + self.cv.acquire() + try: + buf = self.buf.getvalue() + finally: + self.cv.release() + return buf def match(self, pattern, timeout, matchall = False): - """pattern is a list of string, regexp duples. - """ + """pattern is a list of string, regexp duples. + """ - if timeout is not None: - end = time.time() + timeout - start = time.time() + if timeout is not None: + end = time.time() + timeout + start = time.time() - # Trace the match - if self.logfile: + # Trace the match + if self.logfile: if timeout is None: tdesc = "" else: tdesc = "%.2fs" % timeout p = [ escape(s) for (s, r) in pattern ] - pdesc = StringIO.StringIO() + pdesc = getStringIO() if len(p) == 1: pdesc.write(escape(p[0])) else: @@ -169,81 +181,83 @@ class reader(threading.Thread): pdesc.write(','); pdesc.write(escape(pat)) pdesc.write(']'); - self.logfile.write('%s: expect: "%s" timeout: %s\n' % (self.desc, pdesc.getvalue(), tdesc)) - self.logfile.flush() + self.logfile.write('%s: expect: "%s" timeout: %s\n' % (self.desc, pdesc.getvalue(), tdesc)) + self.logfile.flush() maxend = None - self.cv.acquire() - try: - try: # This second try/except block is necessary because of python 2.3 - while True: - buf = self.buf.getvalue() - - # Try to match on the current buffer. - olen = len(pattern) - for index, p in enumerate(pattern): - s, regexp = p - if s == TIMEOUT: - continue - m = regexp.search(buf) - if m is not None: - before = buf[:m.start()] - matched = buf[m.start():m.end()] - after = buf[m.end():] - - if maxend is None or m.end() > maxend: - maxend = m.end() - - # Trace the match - if self.logfile: - if len(pattern) > 1: - self.logfile.write('%s: match found in %.2fs.\npattern: "%s"\nbuffer: "%s||%s||%s"\n'% - (self.desc, time.time() - start, escape(s), escape(before), - escape(matched), escape(after))) - else: - self.logfile.write('%s: match found in %.2fs.\nbuffer: "%s||%s||%s"\n' % - (self.desc, time.time() - start, escape(before), escape(matched), - escape(after))) - - if matchall: - del pattern[index] - # If all patterns have been found then - # truncate the buffer to the longest match, - # and then return. - if len(pattern) == 0: - self.buf.truncate(0) - self.buf.write(buf[maxend:]) - return buf - break - - # Consume matched portion of the buffer. - self.buf.truncate(0) - self.buf.write(after) - - return buf, before, after, m, index - - # If a single match was found then the match. - if len(pattern) != olen: - continue - - if timeout is None: - self.cv.wait() - else: - self.cv.wait(end - time.time()) - if time.time() >= end: - # Log the failure - if self.logfile: - self.logfile.write('%s: match failed.\npattern: "%s"\nbuffer: "%s"\n"' % - (self.desc, escape(s), escape(buf))) - self.logfile.flush() - raise TIMEOUT ('timeout exceeded in match\npattern: "%s"\nbuffer: "%s"\n' % - (escape(s), escape(buf, False))) - except TIMEOUT, e: - if (TIMEOUT, None) in pattern: - return buf, buf, TIMEOUT, None, pattern.index((TIMEOUT, None)) - raise e - finally: - self.cv.release() + self.cv.acquire() + try: + try: # This second try/except block is necessary because of python 2.3 + while True: + buf = self.buf.getvalue() + + # Try to match on the current buffer. + olen = len(pattern) + for index, p in enumerate(pattern): + s, regexp = p + if s == TIMEOUT: + continue + m = regexp.search(buf) + if m is not None: + before = buf[:m.start()] + matched = buf[m.start():m.end()] + after = buf[m.end():] + + if maxend is None or m.end() > maxend: + maxend = m.end() + + # Trace the match + if self.logfile: + if len(pattern) > 1: + self.logfile.write('%s: match found in %.2fs.\npattern: "%s"\nbuffer: "%s||%s||%s"\n'% + (self.desc, time.time() - start, escape(s), escape(before), + escape(matched), escape(after))) + else: + self.logfile.write('%s: match found in %.2fs.\nbuffer: "%s||%s||%s"\n' % + (self.desc, time.time() - start, escape(before), escape(matched), + escape(after))) + + if matchall: + del pattern[index] + # If all patterns have been found then + # truncate the buffer to the longest match, + # and then return. + if len(pattern) == 0: + self.buf.truncate(0) + self.buf.seek(0) + self.buf.write(buf[maxend:]) + return buf + break + + # Consume matched portion of the buffer. + self.buf.truncate(0) + self.buf.seek(0) + self.buf.write(after) + + return buf, before, after, m, index + + # If a single match was found then the match. + if len(pattern) != olen: + continue + + if timeout is None: + self.cv.wait() + else: + self.cv.wait(end - time.time()) + if time.time() >= end: + # Log the failure + if self.logfile: + self.logfile.write('%s: match failed.\npattern: "%s"\nbuffer: "%s"\n"' % + (self.desc, escape(s), escape(buf))) + self.logfile.flush() + raise TIMEOUT ('timeout exceeded in match\npattern: "%s"\nbuffer: "%s"\n' % + (escape(s), escape(buf, False))) + except TIMEOUT as e: + if (TIMEOUT, None) in pattern: + return buf, buf, TIMEOUT, None, pattern.index((TIMEOUT, None)) + raise e + finally: + self.cv.release() def splitCommand(command_line): arg_list = [] @@ -297,100 +311,103 @@ def splitCommand(command_line): class Expect (object): def __init__(self, command, startReader = True, timeout=30, logfile=None, mapping = None, desc = None, cwd = None, env = None): - self.buf = "" # The part before the match - self.before = "" # The part before the match - self.after = "" # The part after the match - self.matchindex = 0 # the index of the matched pattern - self.match = None # The last match + self.buf = "" # The part before the match + self.before = "" # The part before the match + self.after = "" # The part after the match + self.matchindex = 0 # the index of the matched pattern + self.match = None # The last match self.mapping = mapping # The mapping of the test. self.exitstatus = None # The exitstatus, either -signal or, if positive, the exit code. self.killed = None # If killed, the signal that was sent. - self.desc = desc - self.logfile = logfile - self.timeout = timeout - self.p = None + self.desc = desc + self.logfile = logfile + self.timeout = timeout + self.p = None - if self.logfile: - self.logfile.write('spawn: "%s"\n' % command) - self.logfile.flush() + if self.logfile: + self.logfile.write('spawn: "%s"\n' % command) + self.logfile.flush() if win32: # Don't rely on win32api #import win32process - #creationflags = win32process.CREATE_NEW_PROCESS_GROUP) + #creationflags = win32process.CREATE_NEW_PROCESS_GROUP) + # + # universal_newlines = True is necessary for Python 3 on Windows + # CREATE_NEW_PROCESS_GROUP = 512 self.p = subprocess.Popen(command, env = env, cwd = cwd, shell=False, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - creationflags = 512) # CREATE_NEW_PROCESS_GROUP + creationflags = CREATE_NEW_PROCESS_GROUP, universal_newlines=True) else: self.p = subprocess.Popen(splitCommand(command), env = env, cwd = cwd, shell=False, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - self.r = reader(desc, self.p, logfile) + self.r = reader(desc, self.p, logfile) - # The thread is marked as a daemon thread. This is done so that if - # an expect script runs off the end of main without kill/wait on each - # spawned process the script will not hang tring to join with the - # reader thread. Instead __del__ (below) will be called which - # terminates and joins with the reader thread. - self.r.setDaemon(True) + # The thread is marked as a daemon thread. This is done so that if + # an expect script runs off the end of main without kill/wait on each + # spawned process the script will not hang tring to join with the + # reader thread. Instead __del__ (below) will be called which + # terminates and joins with the reader thread. + self.r.setDaemon(True) - if startReader: - self.startReader() + if startReader: + self.startReader() def startReader(self): - self.r.start() + self.r.start() def __del__(self): - # Terminate and clean up. - if self.p is not None: - self.terminate() + # Terminate and clean up. + if self.p is not None: + self.terminate() def expect(self, pattern, timeout = 20): - """pattern is either a string, or a list of string regexp patterns. + """pattern is either a string, or a list of string regexp patterns. - timeout == None expect can block indefinitely. + timeout == None expect can block indefinitely. - timeout == -1 then the default is used. - """ - if timeout == -1: - timeout = self.timeout + timeout == -1 then the default is used. + """ + if timeout == -1: + timeout = self.timeout - if type(pattern) != list: - pattern = [ pattern ] + if type(pattern) != list: + pattern = [ pattern ] def compile(s): if type(s) == str: return re.compile(s, re.S) return None - pattern = [ ( p, compile(p) ) for p in pattern ] + pattern = [ ( p, compile(p) ) for p in pattern ] try: self.buf, self.before, self.after, self.match, self.matchindex = self.r.match(pattern, timeout) - except TIMEOUT, e: + except TIMEOUT as e: self.buf = "" self.before = "" self.after = "" self.match = None self.matchindex = 0 raise e - return self.matchindex + return self.matchindex def expectall(self, pattern, timeout = 10): - """pattern is a list of string regexp patterns. + """pattern is a list of string regexp patterns. - timeout == None expect can block indefinitely. + timeout == None expect can block indefinitely. - timeout == -1 then the default is used. - """ - if timeout == -1: - timeout = self.timeout + timeout == -1 then the default is used. + """ + if timeout == -1: + timeout = self.timeout - pattern = [ ( p, re.compile(p, re.S) ) for p in pattern ] + pattern = [ ( p, re.compile(p, re.S) ) for p in pattern ] try: self.buf = self.r.match(pattern, timeout, matchall = True) self.before = "" self.after = "" self.matchindex = 0 self.match = None - except TIMEOUT, e: + except TIMEOUT as e: self.buf = "" self.before = "" self.after = "" @@ -399,22 +416,27 @@ class Expect (object): raise e def sendline(self, data): - """send data to the application. - """ - if self.logfile: - self.logfile.write('%s: sendline: "%s"\n' % (self.desc, escape(data))) - self.logfile.flush() - self.p.stdin.write("%s\n" % data) + """send data to the application. + """ + if self.logfile: + self.logfile.write('%s: sendline: "%s"\n' % (self.desc, escape(data))) + self.logfile.flush() + if win32 or sys.version_info[0] == 2: + self.p.stdin.write(data) + self.p.stdin.write("\n") + else: + self.p.stdin.write(data.encode("utf-8")) + self.p.stdin.write("\n".encode("utf-8")) def wait(self, timeout = None): - """Wait for the application to terminate for up to timeout seconds, or + """Wait for the application to terminate for up to timeout seconds, or raises a TIMEOUT exception. If timeout is None, the wait is indefinite. The exit status is returned. A negative exit status means the application was killed by a signal. - """ - if self.p is not None: + """ + if self.p is not None: # Unfortunately, with the subprocess module there is no # better method of doing a timed wait. @@ -425,23 +447,23 @@ class Expect (object): if self.p.poll() is None: raise TIMEOUT ('timedwait exceeded timeout') - self.exitstatus = self.p.wait() + self.exitstatus = self.p.wait() # A Windows application with a negative exit status means # killed by CTRL_BREAK. Fudge the exit status. if win32 and self.exitstatus < 0 and self.killed is not None: self.exitstatus = -self.killed - self.p = None - self.r.join() - # Simulate a match on EOF - self.buf = self.r.getbuf() - self.before = self.buf - self.after = "" - self.r = None - return self.exitstatus + self.p = None + self.r.join() + # Simulate a match on EOF + self.buf = self.r.getbuf() + self.before = self.buf + self.after = "" + self.r = None + return self.exitstatus def terminate(self): - """Terminate the process.""" + """Terminate the process.""" # First try to break the app. Don't bother if this is win32 # and we're using java. It won't break (BREAK causes a stack # trace). @@ -468,19 +490,19 @@ class Expect (object): try: self.wait(timeout = 5) return - except TIMEOUT, e: + except TIMEOUT as e: pass - try: + try: if win32: # Next kill the app. if self.hasInterruptSupport(): - print "%s: did not respond to break. terminating: %d" % (self.desc, self.p.pid) - subprocess.TerminateProcess(self.p._handle, -1) + print("%s: did not respond to break. terminating: %d" % (self.desc, self.p.pid)) + self.p.terminate() else: os.kill(self.p.pid, signal.SIGKILL) self.wait() - except: + except: traceback.print_exc(file=sys.stdout) def kill(self, sig): @@ -506,7 +528,7 @@ class Expect (object): except: traceback.print_exc(file=sys.stdout) else: - subprocess.TerminateProcess(self.p._handle, sig) + self.p.terminate() else: os.kill(self.p.pid, sig) @@ -517,18 +539,21 @@ class Expect (object): # def waitTestSuccess(self, exitstatus = 0, timeout = None): """Wait for the process to terminate for up to timeout seconds, and - validate the exit status is as expected.""" + validate the exit status is as expected.""" def test(result, expected): if expected != result: - print "unexpected exit status: expected: %d, got %d" % (expected, result) + print("unexpected exit status: expected: %d, got %d" % (expected, result)) assert False self.wait(timeout) if self.mapping == "java": if self.killed is not None: if win32: - test(self.exitstatus, self.killed) + if self.killed == signal.SIGINT: + test(self.exitstatus, 1) + else: + test(self.exitstatus, self.killed) else: if self.killed == signal.SIGINT: test(130, self.exitstatus) @@ -539,8 +564,17 @@ class Expect (object): else: test(self.exitstatus, exitstatus) - def trace(self, supress = None): - self.r.enabletrace(supress) + def waitTestFail(self, timeout = None): + """Wait for the process to terminate for up to timeout seconds, and + validate the exit status is as expected.""" + + self.wait(timeout) + if self.exitstatus == 0: + print("unexpected non-zero exit status") + assert False + + def trace(self, suppress = None): + self.r.enabletrace(suppress) def hasInterruptSupport(self): """Return True if the application gracefully terminated, False otherwise.""" -- cgit v1.2.3