summaryrefslogtreecommitdiff
path: root/scripts/Expect.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/Expect.py')
-rwxr-xr-xscripts/Expect.py486
1 files changed, 260 insertions, 226 deletions
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 "<TIMEOUT>"
- 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 = "<infinite>"
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."""